feat: 优化代码结构(在Claude的帮助下)
This commit is contained in:
@@ -386,16 +386,7 @@ interface InputStatus {
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
code: number
|
||||
message?: string
|
||||
detail?: {
|
||||
code: string
|
||||
name: string
|
||||
text: string
|
||||
size: number
|
||||
}
|
||||
}
|
||||
|
||||
import {
|
||||
BoxIcon,
|
||||
EyeIcon,
|
||||
@@ -415,6 +406,14 @@ import QRCode from 'qrcode.vue'
|
||||
import { useFileDataStore } from '@/stores/fileData'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import api from '@/utils/api'
|
||||
import type { ApiResponse } from '@/types'
|
||||
|
||||
interface RetrieveResponse {
|
||||
code: string
|
||||
name: string
|
||||
text: string
|
||||
size: number
|
||||
}
|
||||
import { saveAs } from 'file-saver'
|
||||
import { marked } from 'marked'
|
||||
import DOMPurify from 'dompurify'
|
||||
@@ -491,7 +490,7 @@ const handleSubmit = async () => {
|
||||
const response = await api.post('/share/select/', {
|
||||
code: code.value
|
||||
})
|
||||
const res = (response.data || response) as ApiResponse
|
||||
const res = (response.data || response) as ApiResponse<RetrieveResponse>
|
||||
|
||||
if (res && res.code === 200) {
|
||||
if (res.detail) {
|
||||
@@ -574,8 +573,6 @@ const getQRCodeValue = (record: FileRecord) => {
|
||||
}
|
||||
|
||||
const downloadRecord = (record: FileRecord) => {
|
||||
console.log(record)
|
||||
|
||||
if (record.downloadUrl) {
|
||||
// 如果是文件,直接下载
|
||||
window.open(
|
||||
|
||||
+17
-20
@@ -594,6 +594,7 @@ import QRCode from 'qrcode.vue'
|
||||
import { useFileDataStore } from '@/stores/fileData'
|
||||
import { useAlertStore } from '@/stores/alertStore'
|
||||
import api from '@/utils/api'
|
||||
import type { ApiResponse } from '@/types'
|
||||
import { copyRetrieveLink, copyRetrieveCode, copyWgetCommand } from '@/utils/clipboard'
|
||||
import { getStorageUnit } from '@/utils/convert'
|
||||
|
||||
@@ -615,15 +616,7 @@ interface ShareRecord {
|
||||
retrieveCode: string
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
code: number
|
||||
detail: {
|
||||
code?: string
|
||||
name?: string
|
||||
upload_id?: string
|
||||
existed?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const config: Config = JSON.parse(localStorage.getItem('config') || '{}') as Config
|
||||
|
||||
@@ -635,7 +628,7 @@ const sendType = ref('file')
|
||||
const selectedFile = ref<File | null>(null)
|
||||
const textContent = ref('')
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
const expirationMethod = ref('day')
|
||||
const expirationMethod = ref(config.expireStyle?.[0] || 'day')
|
||||
const expirationValue = ref('1')
|
||||
const uploadProgress = ref(0)
|
||||
const showDrawer = ref(false)
|
||||
@@ -880,7 +873,12 @@ const handleChunkUpload = async (file: File) => {
|
||||
const chunkSize = 5 * 1024 * 1024
|
||||
const chunks = Math.ceil(file.size / chunkSize)
|
||||
// 1. 初始化切片上传
|
||||
const initResponse: ApiResponse = await api.post('chunk/upload/init/', {
|
||||
const initResponse: ApiResponse<{
|
||||
code?: string
|
||||
name?: string
|
||||
upload_id?: string
|
||||
existed?: boolean
|
||||
}> = await api.post('chunk/upload/init/', {
|
||||
file_name: file.name,
|
||||
file_size: file.size,
|
||||
chunk_size: chunkSize,
|
||||
@@ -890,10 +888,10 @@ const handleChunkUpload = async (file: File) => {
|
||||
if (initResponse.code !== 200) {
|
||||
throw new Error('初始化切片上传失败')
|
||||
}
|
||||
if (initResponse.detail.existed) {
|
||||
if (initResponse.detail?.existed) {
|
||||
return initResponse
|
||||
}
|
||||
const uploadId = initResponse.detail.upload_id
|
||||
const uploadId = initResponse.detail?.upload_id
|
||||
|
||||
// 2. 上传切片
|
||||
for (let i = 0; i < chunks; i++) {
|
||||
@@ -905,7 +903,7 @@ const handleChunkUpload = async (file: File) => {
|
||||
chunkFormData.append('chunk', new Blob([chunk], { type: file.type })) // 确保以Blob形式添加
|
||||
|
||||
// 使用 application/x-www-form-urlencoded 格式
|
||||
const chunkResponse: ApiResponse = await api.post(
|
||||
const chunkResponse: ApiResponse<unknown> = await api.post(
|
||||
`chunk/upload/chunk/${uploadId}/${i}`,
|
||||
chunkFormData,
|
||||
{
|
||||
@@ -927,7 +925,7 @@ const handleChunkUpload = async (file: File) => {
|
||||
}
|
||||
|
||||
// 3. 完成上传
|
||||
const completeResponse: ApiResponse = await api.post(`chunk/upload/complete/${uploadId}`, {
|
||||
const completeResponse: ApiResponse<{ code?: string; name?: string }> = await api.post(`chunk/upload/complete/${uploadId}`, {
|
||||
expire_value: expirationValue.value ? parseInt(expirationValue.value) : 1,
|
||||
expire_style: expirationMethod.value
|
||||
})
|
||||
@@ -965,7 +963,7 @@ const handleDefaultFileUpload = async (file: File) => {
|
||||
formData.append('file', file)
|
||||
formData.append('expire_value', expirationValue.value)
|
||||
formData.append('expire_style', expirationMethod.value)
|
||||
const response: ApiResponse = await api.post('share/file/', formData, config)
|
||||
const response: ApiResponse<{ code?: string; name?: string }> = await api.post('share/file/', formData, config)
|
||||
return response
|
||||
}
|
||||
const checkOpenUpload = () => {
|
||||
@@ -1060,8 +1058,8 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
if (response && response.code === 200) {
|
||||
const retrieveCode = response.detail.code || ''
|
||||
const fileName = response.detail.name || ''
|
||||
const retrieveCode = (response.detail as unknown as { code?: string })?.code || ''
|
||||
const fileName = (response.detail as unknown as { name?: string })?.name || ''
|
||||
// 添加新的发送记录
|
||||
const newRecord: ShareRecord = {
|
||||
id: Date.now(),
|
||||
@@ -1077,7 +1075,7 @@ const handleSubmit = async () => {
|
||||
: getExpirationTime(expirationMethod.value, expirationValue.value),
|
||||
retrieveCode: retrieveCode
|
||||
}
|
||||
fileDataStore.addShareData(newRecord)
|
||||
fileDataStore.addShareDataRecord(newRecord)
|
||||
|
||||
// 显示发送成功消息
|
||||
alertStore.showAlert(`文件发送成功!取件码:${retrieveCode}`, 'success')
|
||||
@@ -1093,7 +1091,6 @@ const handleSubmit = async () => {
|
||||
throw new Error('服务器响应异常')
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error('发送失败:', error)
|
||||
if (error && typeof error === 'object' && 'response' in error) {
|
||||
const axiosError = error as { response?: { data?: { detail?: string } } }
|
||||
if (axiosError.response?.data?.detail) {
|
||||
|
||||
@@ -113,14 +113,12 @@ import {
|
||||
HardDriveIcon,
|
||||
UsersIcon,
|
||||
ActivityIcon,
|
||||
UploadIcon,
|
||||
TrashIcon,
|
||||
UserIcon
|
||||
} from 'lucide-vue-next'
|
||||
import { StatsService } from '@/services'
|
||||
import type { DashboardData } from '@/types'
|
||||
const isDarkMode = inject('isDarkMode')
|
||||
import api from '@/utils/api'
|
||||
|
||||
const dashboardData: any = reactive({
|
||||
const dashboardData = reactive<DashboardData>({
|
||||
totalFiles: 0,
|
||||
storageUsed: 0,
|
||||
yesterdayCount: 0,
|
||||
@@ -130,30 +128,6 @@ const dashboardData: any = reactive({
|
||||
sysUptime: 0
|
||||
})
|
||||
|
||||
// 添加最近活动数据
|
||||
const recentActivities = [
|
||||
{
|
||||
icon: UploadIcon,
|
||||
description: '张三上传了文件 "项目计划.pdf"',
|
||||
time: '10分钟前'
|
||||
},
|
||||
{
|
||||
icon: UserIcon,
|
||||
description: '新用户李四加入了系统',
|
||||
time: '30分钟前'
|
||||
},
|
||||
{
|
||||
icon: TrashIcon,
|
||||
description: '王五删除了文件 "旧文档.doc"',
|
||||
time: '1小时前'
|
||||
},
|
||||
{
|
||||
icon: FileIcon,
|
||||
description: '系统自动备份完成',
|
||||
time: '2小时前'
|
||||
}
|
||||
]
|
||||
|
||||
const getSysUptime = (startTimestamp: number) => {
|
||||
const now = new Date().getTime()
|
||||
const uptime = now - startTimestamp
|
||||
@@ -181,14 +155,16 @@ const getLocalstorageUsed = (nowUsedBit: string) => {
|
||||
}
|
||||
}
|
||||
const getDashboardData = async () => {
|
||||
const response: any = await api.get('admin/dashboard')
|
||||
dashboardData.totalFiles = response.detail.totalFiles
|
||||
dashboardData.storageUsed = getLocalstorageUsed(response.detail.storageUsed)
|
||||
dashboardData.yesterdaySize = getLocalstorageUsed(response.detail.yesterdaySize)
|
||||
dashboardData.todaySize = getLocalstorageUsed(response.detail.todaySize)
|
||||
dashboardData.yesterdayCount = response.detail.yesterdayCount
|
||||
dashboardData.todayCount = response.detail.todayCount
|
||||
dashboardData.sysUptime = getSysUptime(response.detail.sysUptime)
|
||||
const response = await StatsService.getDashboard()
|
||||
if (response.detail) {
|
||||
dashboardData.totalFiles = response.detail.totalFiles
|
||||
dashboardData.storageUsed = getLocalstorageUsed(response.detail.storageUsed.toString())
|
||||
dashboardData.yesterdaySize = getLocalstorageUsed(response.detail.yesterdaySize.toString())
|
||||
dashboardData.todaySize = getLocalstorageUsed(response.detail.todaySize.toString())
|
||||
dashboardData.yesterdayCount = response.detail.yesterdayCount
|
||||
dashboardData.todayCount = response.detail.todayCount
|
||||
dashboardData.sysUptime = getSysUptime(Number(response.detail.sysUptime))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -381,7 +381,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { inject, ref, computed } from 'vue'
|
||||
import api from '@/utils/api'
|
||||
import { FileService } from '@/services'
|
||||
import type { FileListItem, FileEditForm } from '@/types'
|
||||
import {
|
||||
FileIcon,
|
||||
SearchIcon,
|
||||
@@ -405,7 +406,7 @@ function formatTimestamp(timestamp: string): string {
|
||||
}
|
||||
|
||||
const isDarkMode = inject('isDarkMode')
|
||||
const tableData: any = ref([])
|
||||
const tableData = ref<FileListItem[]>([])
|
||||
const alertStore = useAlertStore()
|
||||
// 修改文件表头
|
||||
const fileTableHeaders = ['取件码', '名称', '大小', '描述', '过期时间', '操作']
|
||||
@@ -420,7 +421,7 @@ const params = ref({
|
||||
|
||||
// 添加编辑相关的状态
|
||||
const showEditModal = ref(false)
|
||||
const editForm = ref({
|
||||
const editForm = ref<FileEditForm>({
|
||||
id: null,
|
||||
code: '',
|
||||
prefix: '',
|
||||
@@ -430,7 +431,7 @@ const editForm = ref({
|
||||
})
|
||||
|
||||
// 打开编辑模态框
|
||||
const openEditModal = (file: any) => {
|
||||
const openEditModal = (file: FileListItem) => {
|
||||
editForm.value = {
|
||||
id: file.id,
|
||||
code: file.code,
|
||||
@@ -458,99 +459,86 @@ const closeEditModal = () => {
|
||||
// 处理更新
|
||||
const handleUpdate = async () => {
|
||||
try {
|
||||
await api({
|
||||
url: 'admin/file/update',
|
||||
method: 'patch',
|
||||
data: editForm.value
|
||||
})
|
||||
await FileService.updateFile(editForm.value)
|
||||
await loadFiles()
|
||||
closeEditModal()
|
||||
} catch (error: any) {
|
||||
alertStore.showAlert(error.response.data.detail, 'error')
|
||||
} catch (error: unknown) {
|
||||
const err = error as { response?: { data?: { detail?: string } } }
|
||||
alertStore.showAlert(err.response?.data?.detail || '更新失败', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 下载文件处理
|
||||
const downloadFile = async (id: number) => {
|
||||
try {
|
||||
const response = await api({
|
||||
url: 'admin/file/download',
|
||||
method: 'get',
|
||||
params: { id },
|
||||
responseType: 'blob'
|
||||
})
|
||||
// 下载文件处理 - 暂时移除未使用的函数
|
||||
// const downloadFile = async (id: number) => {
|
||||
// try {
|
||||
// const response = await FileService.downloadAdminFile(id)
|
||||
|
||||
const contentDisposition = response.headers['content-disposition']
|
||||
let filename = 'file'
|
||||
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
|
||||
if (filenameMatch != null && filenameMatch[1]) {
|
||||
filename = filenameMatch[1].replace(/['"]/g, '')
|
||||
}
|
||||
// const contentDisposition = response.headers['content-disposition']
|
||||
// let filename = 'file'
|
||||
// const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
|
||||
// if (filenameMatch != null && filenameMatch[1]) {
|
||||
// filename = filenameMatch[1].replace(/['"]/g, '')
|
||||
// }
|
||||
|
||||
// @ts-ignore
|
||||
if (window.showSaveFilePicker) {
|
||||
await saveFileByWebApi(response.data, filename)
|
||||
} else {
|
||||
await saveFileByElementA(response.data, filename)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('下载失败:', error)
|
||||
}
|
||||
}
|
||||
// // @ts-expect-error - showSaveFilePicker is not in standard Window interface
|
||||
// if (window.showSaveFilePicker) {
|
||||
// await saveFileByWebApi(response.data, filename)
|
||||
// } else {
|
||||
// await saveFileByElementA(response.data, filename)
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error('下载失败:', error)
|
||||
// }
|
||||
// }
|
||||
|
||||
// 删除文件处理
|
||||
const deleteFile = async (id: number) => {
|
||||
try {
|
||||
await api({
|
||||
url: 'admin/file/delete',
|
||||
method: 'delete',
|
||||
data: { id }
|
||||
})
|
||||
await FileService.deleteAdminFile(id)
|
||||
await loadFiles()
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 文件保存辅助函数
|
||||
async function saveFileByElementA(fileBlob: Blob, filename: string) {
|
||||
const downloadUrl = window.URL.createObjectURL(fileBlob)
|
||||
const link = document.createElement('a')
|
||||
link.href = downloadUrl
|
||||
link.download = filename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(downloadUrl)
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
// 文件保存辅助函数 - 暂时移除未使用的函数
|
||||
// async function saveFileByElementA(fileBlob: Blob, filename: string) {
|
||||
// const downloadUrl = window.URL.createObjectURL(fileBlob)
|
||||
// const link = document.createElement('a')
|
||||
// link.href = downloadUrl
|
||||
// link.download = filename
|
||||
// document.body.appendChild(link)
|
||||
// link.click()
|
||||
// window.URL.revokeObjectURL(downloadUrl)
|
||||
// document.body.removeChild(link)
|
||||
// }
|
||||
|
||||
async function saveFileByWebApi(fileBlob: Blob, filename: string) {
|
||||
// @ts-ignore
|
||||
const newHandle = await window.showSaveFilePicker({
|
||||
suggestedName: filename
|
||||
})
|
||||
const writableStream = await newHandle.createWritable()
|
||||
await writableStream.write(fileBlob)
|
||||
await writableStream.close()
|
||||
}
|
||||
// async function saveFileByWebApi(fileBlob: Blob, filename: string) {
|
||||
// // @ts-expect-error - showSaveFilePicker is not in standard Window interface
|
||||
// const newHandle = await window.showSaveFilePicker({
|
||||
// suggestedName: filename
|
||||
// })
|
||||
// const writableStream = await newHandle.createWritable()
|
||||
// await writableStream.write(fileBlob)
|
||||
// await writableStream.close()
|
||||
// }
|
||||
|
||||
// 加载文件列表
|
||||
const loadFiles = async () => {
|
||||
try {
|
||||
const res: any = await api({
|
||||
url: '/admin/file/list',
|
||||
method: 'get',
|
||||
params: params.value
|
||||
})
|
||||
tableData.value = res.detail.data
|
||||
params.value.total = res.detail.total
|
||||
const res = await FileService.getAdminFileList(params.value)
|
||||
if (res.detail) {
|
||||
tableData.value = res.detail.data
|
||||
params.value.total = res.detail.total
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载文件列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 页码改变处理函数
|
||||
const handlePageChange = async (page: any) => {
|
||||
const handlePageChange = async (page: number | string) => {
|
||||
if (typeof page === 'string') return // 忽略省略号
|
||||
if (page < 1 || page > totalPages.value) return
|
||||
params.value.page = page
|
||||
await loadFiles()
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
<template>
|
||||
<div :class="[
|
||||
'min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8 transition-colors duration-200 relative overflow-hidden',
|
||||
isDarkMode ? 'bg-gray-900' : 'bg-gray-50'
|
||||
]">
|
||||
<div
|
||||
:class="[
|
||||
'min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8 transition-colors duration-200 relative overflow-hidden',
|
||||
isDarkMode ? 'bg-gray-900' : 'bg-gray-50'
|
||||
]"
|
||||
>
|
||||
<div class="absolute inset-0 z-0">
|
||||
<div class="cyber-grid"></div>
|
||||
<div class="floating-particles"></div>
|
||||
</div>
|
||||
<div class="max-w-md w-full space-y-8 backdrop-blur-lg bg-opacity-20 p-8 rounded-xl border border-opacity-20"
|
||||
:class="[isDarkMode ? 'bg-gray-800 border-gray-600' : 'bg-white/70 border-gray-200']">
|
||||
<div
|
||||
class="max-w-md w-full space-y-8 backdrop-blur-lg bg-opacity-20 p-8 rounded-xl border border-opacity-20"
|
||||
:class="[isDarkMode ? 'bg-gray-800 border-gray-600' : 'bg-white/70 border-gray-200']"
|
||||
>
|
||||
<div>
|
||||
<div class="mx-auto h-16 w-16 relative">
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-r from-cyan-500 via-purple-500 to-pink-500 rounded-full animate-spin-slow">
|
||||
</div>
|
||||
class="absolute inset-0 bg-gradient-to-r from-cyan-500 via-purple-500 to-pink-500 rounded-full animate-spin-slow"
|
||||
></div>
|
||||
<div
|
||||
class="absolute -inset-2 bg-gradient-to-r from-cyan-500 via-purple-500 to-pink-500 rounded-full opacity-50 blur-md animate-pulse">
|
||||
</div>
|
||||
<div :class="[
|
||||
'absolute inset-1 rounded-full flex items-center justify-center',
|
||||
isDarkMode ? 'bg-gray-800' : 'bg-white'
|
||||
]">
|
||||
class="absolute -inset-2 bg-gradient-to-r from-cyan-500 via-purple-500 to-pink-500 rounded-full opacity-50 blur-md animate-pulse"
|
||||
></div>
|
||||
<div
|
||||
:class="[
|
||||
'absolute inset-1 rounded-full flex items-center justify-center',
|
||||
isDarkMode ? 'bg-gray-800' : 'bg-white'
|
||||
]"
|
||||
>
|
||||
<BoxIcon :class="['h-8 w-8', isDarkMode ? 'text-cyan-400' : 'text-cyan-600']" />
|
||||
</div>
|
||||
</div>
|
||||
<h2 :class="[
|
||||
'mt-6 text-center text-3xl font-extrabold',
|
||||
isDarkMode ? 'text-white' : 'text-gray-900'
|
||||
]">
|
||||
<h2
|
||||
:class="[
|
||||
'mt-6 text-center text-3xl font-extrabold',
|
||||
isDarkMode ? 'text-white' : 'text-gray-900'
|
||||
]"
|
||||
>
|
||||
登录
|
||||
</h2>
|
||||
</div>
|
||||
@@ -36,23 +44,35 @@
|
||||
<div class="rounded-md shadow-sm -space-y-px">
|
||||
<div>
|
||||
<label for="password" class="sr-only">密码</label>
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" required
|
||||
v-model="password" :class="[
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
v-model="password"
|
||||
:class="[
|
||||
'appearance-none rounded-t-md relative block w-full px-4 py-3 border transition-all duration-200 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:border-cyan-500 focus:z-10 sm:text-sm backdrop-blur-sm',
|
||||
isDarkMode
|
||||
? 'bg-gray-800/50 border-gray-600 text-white placeholder-gray-400 hover:border-gray-500'
|
||||
: 'bg-white/50 border-gray-300 text-gray-900 hover:border-gray-400'
|
||||
]" placeholder="密码" />
|
||||
]"
|
||||
placeholder="密码"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" :class="[
|
||||
'group relative w-full flex justify-center py-3 px-4 border border-transparent text-sm font-medium rounded-md text-white transition-all duration-300 transform hover:scale-[1.02] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyan-500 shadow-lg hover:shadow-cyan-500/50',
|
||||
isDarkMode
|
||||
? 'bg-gradient-to-r from-cyan-500 to-purple-500 hover:from-cyan-600 hover:to-purple-600'
|
||||
: 'bg-gradient-to-r from-cyan-600 to-purple-600 hover:from-cyan-700 hover:to-purple-700',
|
||||
isLoading ? 'opacity-75 cursor-not-allowed' : ''
|
||||
]" :disabled="isLoading">
|
||||
<button
|
||||
type="submit"
|
||||
:class="[
|
||||
'group relative w-full flex justify-center py-3 px-4 border border-transparent text-sm font-medium rounded-md text-white transition-all duration-300 transform hover:scale-[1.02] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyan-500 shadow-lg hover:shadow-cyan-500/50',
|
||||
isDarkMode
|
||||
? 'bg-gradient-to-r from-cyan-500 to-purple-500 hover:from-cyan-600 hover:to-purple-600'
|
||||
: 'bg-gradient-to-r from-cyan-600 to-purple-600 hover:from-cyan-700 hover:to-purple-700',
|
||||
isLoading ? 'opacity-75 cursor-not-allowed' : ''
|
||||
]"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
<span class="absolute left-0 inset-y-0 flex items-center pl-3"> </span>
|
||||
{{ isLoading ? '登录中...' : '登录' }}
|
||||
</button>
|
||||
@@ -66,9 +86,15 @@
|
||||
import { ref, inject } from 'vue'
|
||||
import { BoxIcon } from 'lucide-vue-next'
|
||||
import api from '@/utils/api'
|
||||
import type { ApiResponse } from '@/types'
|
||||
import { useAlertStore } from '@/stores/alertStore'
|
||||
import { useAdminData } from '@/stores/adminStore'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// 登录响应类型定义
|
||||
interface LoginResponse {
|
||||
token: string
|
||||
}
|
||||
const alertStore = useAlertStore()
|
||||
const password = ref('')
|
||||
const isLoading = ref(false)
|
||||
@@ -91,19 +117,20 @@ const router = useRouter()
|
||||
const handleSubmit = async () => {
|
||||
if (!validateForm()) return
|
||||
api
|
||||
.post('/admin/login', { password: password.value })
|
||||
.then((res: any) => {
|
||||
adminStore.updateAdminPwd(res.detail.token)
|
||||
.post<ApiResponse<LoginResponse>>('/admin/login', { password: password.value })
|
||||
.then((res) => {
|
||||
adminStore.setToken(res.data.detail?.token || '')
|
||||
router.push('/admin')
|
||||
})
|
||||
.catch((error: any) => {
|
||||
alertStore.showAlert(error.response.data.detail, 'error')
|
||||
.catch((error) => {
|
||||
alertStore.showAlert(error.response?.data?.detail || '登录失败', 'error')
|
||||
})
|
||||
isLoading.value = true
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
// 处理登录成功
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
// 处理错误
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -145,7 +172,8 @@ button:active:not(:disabled) {
|
||||
}
|
||||
|
||||
.cyber-grid {
|
||||
background-image: linear-gradient(transparent 95%, rgba(99, 102, 241, 0.1) 50%),
|
||||
background-image:
|
||||
linear-gradient(transparent 95%, rgba(99, 102, 241, 0.1) 50%),
|
||||
linear-gradient(90deg, transparent 95%, rgba(99, 102, 241, 0.1) 50%);
|
||||
background-size: 30px 30px;
|
||||
width: 100%;
|
||||
|
||||
@@ -1,47 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { inject, ref } from 'vue'
|
||||
import api from '@/utils/api'
|
||||
import { ConfigService } from '@/services'
|
||||
import type { ConfigState } from '@/types'
|
||||
import { useAlertStore } from '@/stores/alertStore'
|
||||
|
||||
const isDarkMode = inject('isDarkMode')
|
||||
interface ConfigState {
|
||||
name: string
|
||||
description: string
|
||||
file_storage: string
|
||||
themesChoices: any[]
|
||||
expireStyle: string[]
|
||||
admin_token: string
|
||||
robotsText: string
|
||||
keywords: string
|
||||
notify_title: string
|
||||
notify_content: string
|
||||
openUpload: number
|
||||
uploadSize: number
|
||||
storage_path: string
|
||||
uploadMinute: number
|
||||
max_save_seconds: number
|
||||
opacity: number
|
||||
enableChunk: number
|
||||
s3_access_key_id: string
|
||||
background: string
|
||||
showAdminAddr: number
|
||||
page_explain: string
|
||||
s3_secret_access_key: string
|
||||
aws_session_token: string
|
||||
s3_signature_version: string
|
||||
s3_region_name: string
|
||||
s3_bucket_name: string
|
||||
s3_endpoint_url: string
|
||||
s3_hostname: string
|
||||
uploadCount: number
|
||||
errorMinute: number
|
||||
errorCount: number
|
||||
s3_proxy: number
|
||||
themesSelect: string
|
||||
webdav_url: string
|
||||
webdav_username: string
|
||||
webdav_password: string
|
||||
}
|
||||
|
||||
const config = ref<ConfigState>({
|
||||
name: '',
|
||||
@@ -100,48 +63,54 @@ const convertToSeconds = (time: number, unit: string): number => {
|
||||
return time * units[unit as keyof typeof units]
|
||||
}
|
||||
|
||||
const refreshData = () => {
|
||||
api({
|
||||
url: 'admin/config/get',
|
||||
method: 'get'
|
||||
}).then((res: any) => {
|
||||
config.value = res.detail
|
||||
|
||||
// 将字节转换为合适的单位
|
||||
const size = config.value.uploadSize
|
||||
if (size >= 1024 * 1024 * 1024) {
|
||||
fileSize.value = Math.round(size / (1024 * 1024 * 1024))
|
||||
sizeUnit.value = 'GB'
|
||||
} else if (size >= 1024 * 1024) {
|
||||
fileSize.value = Math.round(size / (1024 * 1024))
|
||||
sizeUnit.value = 'MB'
|
||||
} else {
|
||||
fileSize.value = Math.round(size / 1024)
|
||||
sizeUnit.value = 'KB'
|
||||
}
|
||||
|
||||
// 时间单位转换逻辑
|
||||
const seconds = config.value.max_save_seconds
|
||||
if (seconds === 0) {
|
||||
// 如果是0,显示为7天
|
||||
saveTime.value = 7
|
||||
saveTimeUnit.value = '天'
|
||||
} else if (seconds % 86400 === 0 && seconds >= 86400) {
|
||||
saveTime.value = seconds / 86400
|
||||
saveTimeUnit.value = '天'
|
||||
} else if (seconds % 3600 === 0 && seconds >= 3600) {
|
||||
saveTime.value = seconds / 3600
|
||||
saveTimeUnit.value = '时'
|
||||
} else if (seconds % 60 === 0 && seconds >= 60) {
|
||||
saveTime.value = seconds / 60
|
||||
saveTimeUnit.value = '分'
|
||||
} else {
|
||||
saveTime.value = seconds
|
||||
saveTimeUnit.value = '秒'
|
||||
}
|
||||
})
|
||||
}
|
||||
const alertStore = useAlertStore()
|
||||
|
||||
const refreshData = async () => {
|
||||
try {
|
||||
const res = await ConfigService.getConfig()
|
||||
if (res.code === 200 && res.detail) {
|
||||
// 直接使用ConfigState类型的响应数据
|
||||
config.value = res.detail
|
||||
|
||||
// 将字节转换为合适的单位
|
||||
const size = config.value.uploadSize
|
||||
if (size >= 1024 * 1024 * 1024) {
|
||||
fileSize.value = Math.round(size / (1024 * 1024 * 1024))
|
||||
sizeUnit.value = 'GB'
|
||||
} else if (size >= 1024 * 1024) {
|
||||
fileSize.value = Math.round(size / (1024 * 1024))
|
||||
sizeUnit.value = 'MB'
|
||||
} else {
|
||||
fileSize.value = Math.round(size / 1024)
|
||||
sizeUnit.value = 'KB'
|
||||
}
|
||||
|
||||
// 时间单位转换逻辑
|
||||
const seconds = config.value.max_save_seconds
|
||||
if (seconds === 0) {
|
||||
// 如果是0,显示为7天
|
||||
saveTime.value = 7
|
||||
saveTimeUnit.value = '天'
|
||||
} else if (seconds % 86400 === 0 && seconds >= 86400) {
|
||||
saveTime.value = seconds / 86400
|
||||
saveTimeUnit.value = '天'
|
||||
} else if (seconds % 3600 === 0 && seconds >= 3600) {
|
||||
saveTime.value = seconds / 3600
|
||||
saveTimeUnit.value = '时'
|
||||
} else if (seconds % 60 === 0 && seconds >= 60) {
|
||||
saveTime.value = seconds / 60
|
||||
saveTimeUnit.value = '分'
|
||||
} else {
|
||||
saveTime.value = seconds
|
||||
saveTimeUnit.value = '秒'
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '获取配置失败'
|
||||
alertStore.showAlert(errorMessage, 'error')
|
||||
console.error('获取系统配置失败:', error)
|
||||
}
|
||||
}
|
||||
// 转换文件大小为字节
|
||||
const convertToBytes = (size: number, unit: string): number => {
|
||||
const units = {
|
||||
@@ -163,16 +132,15 @@ const submitSave = () => {
|
||||
formData.max_save_seconds = convertToSeconds(saveTime.value, saveTimeUnit.value)
|
||||
}
|
||||
|
||||
api({
|
||||
url: 'admin/config/update',
|
||||
method: 'patch',
|
||||
data: formData
|
||||
}).then((res: any) => {
|
||||
ConfigService.updateConfig(formData).then((res) => {
|
||||
if (res.code == 200) {
|
||||
alertStore.showAlert('保存成功', 'success')
|
||||
} else {
|
||||
alertStore.showAlert(res.message, 'error')
|
||||
alertStore.showAlert(res.message || '保存失败', 'error')
|
||||
}
|
||||
}).catch((error) => {
|
||||
const errorMessage = error instanceof Error ? error.message : '保存失败'
|
||||
alertStore.showAlert(errorMessage, 'error')
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user