feat: 优化代码结构(在Claude的帮助下)

This commit is contained in:
Lan
2025-09-04 12:01:26 +08:00
parent ca7f576b36
commit 97595f2a00
28 changed files with 2504 additions and 318 deletions
+13 -37
View File
@@ -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(() => {
+57 -69
View File
@@ -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()
+62 -34
View File
@@ -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%;
+54 -86
View File
@@ -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')
})
}