From c14c4bca62dfcd5142dadbb1f7eaa63406384863 Mon Sep 17 00:00:00 2001 From: Lan Date: Thu, 4 Sep 2025 12:01:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=BB=93=E6=9E=84=EF=BC=88=E5=9C=A8Claude=E7=9A=84=E5=B8=AE?= =?UTF-8?q?=E5=8A=A9=E4=B8=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 8 +- src/components/common/ContentPreviewModal.vue | 193 ++++++++++++ src/components/common/ExpirationSelector.vue | 159 ++++++++++ src/components/common/FileDetailModal.vue | 170 ++++++++++ src/components/common/FileRecordList.vue | 112 +++++++ src/components/common/FileUploadArea.vue | 94 ++++++ src/components/common/SendTypeSelector.vue | 53 ++++ src/components/common/SideDrawer.vue | 63 ++++ src/components/common/TextInputArea.vue | 88 ++++++ src/composables/useFileDownload.ts | 144 +++++++++ src/composables/useFileUpload.ts | 139 +++++++++ src/composables/useSystemConfig.ts | 142 +++++++++ src/composables/useTheme.ts | 138 ++++++++ src/constants/index.ts | 88 ++++++ src/hooks/index.ts | 14 + src/services/index.ts | 137 ++++++++ src/stores/adminStore.ts | 80 ++++- src/stores/alertStore.ts | 15 +- src/stores/fileData.ts | 295 ++++++++++++++++-- src/types/index.ts | 180 +++++++++++ src/utils/api.ts | 29 +- src/views/RetrievewFileView.vue | 23 +- src/views/SendFileView.vue | 37 +-- src/views/manage/DashboardView.vue | 50 +-- src/views/manage/FileManageView.vue | 126 ++++---- src/views/manage/LoginView.vue | 96 ++++-- src/views/manage/SystemSettingsView.vue | 140 ++++----- tsconfig.json | 9 +- 28 files changed, 2504 insertions(+), 318 deletions(-) create mode 100644 src/components/common/ContentPreviewModal.vue create mode 100644 src/components/common/ExpirationSelector.vue create mode 100644 src/components/common/FileDetailModal.vue create mode 100644 src/components/common/FileRecordList.vue create mode 100644 src/components/common/FileUploadArea.vue create mode 100644 src/components/common/SendTypeSelector.vue create mode 100644 src/components/common/SideDrawer.vue create mode 100644 src/components/common/TextInputArea.vue create mode 100644 src/composables/useFileDownload.ts create mode 100644 src/composables/useFileUpload.ts create mode 100644 src/composables/useTheme.ts create mode 100644 src/constants/index.ts create mode 100644 src/hooks/index.ts create mode 100644 src/services/index.ts create mode 100644 src/types/index.ts diff --git a/src/App.vue b/src/App.vue index cc8b709..dd32cc0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,12 +3,13 @@ import { ref, watchEffect, provide, onMounted } from 'vue' import { RouterView } from 'vue-router' import ThemeToggle from './components/common/ThemeToggle.vue' import { useRouter } from 'vue-router' -import api from './utils/api' const isDarkMode = ref(false) const isLoading = ref(false) const router = useRouter() import AlertComponent from '@/components/common/AlertComponent.vue' import { useAlertStore } from '@/stores/alertStore' +import { ConfigService } from '@/services' +import type { ApiResponse, ConfigState } from '@/types' const alertStore = useAlertStore() // 检查系统颜色模式 @@ -38,8 +39,9 @@ onMounted(() => { } else { setColorMode(checkSystemColorScheme()) } - api.post('/', {}).then((res: any) => { - if (res.code === 200) { + ConfigService.getUserConfig().then((res: ApiResponse) => { + if (res.code === 200 && res.detail) { + console.log(res); localStorage.setItem('config', JSON.stringify(res.detail)) if ( res.detail.notify_title && diff --git a/src/components/common/ContentPreviewModal.vue b/src/components/common/ContentPreviewModal.vue new file mode 100644 index 0000000..53753f8 --- /dev/null +++ b/src/components/common/ContentPreviewModal.vue @@ -0,0 +1,193 @@ + + + + + \ No newline at end of file diff --git a/src/components/common/ExpirationSelector.vue b/src/components/common/ExpirationSelector.vue new file mode 100644 index 0000000..19bd0d7 --- /dev/null +++ b/src/components/common/ExpirationSelector.vue @@ -0,0 +1,159 @@ + + + \ No newline at end of file diff --git a/src/components/common/FileDetailModal.vue b/src/components/common/FileDetailModal.vue new file mode 100644 index 0000000..0079216 --- /dev/null +++ b/src/components/common/FileDetailModal.vue @@ -0,0 +1,170 @@ + + + + + \ No newline at end of file diff --git a/src/components/common/FileRecordList.vue b/src/components/common/FileRecordList.vue new file mode 100644 index 0000000..a0abf92 --- /dev/null +++ b/src/components/common/FileRecordList.vue @@ -0,0 +1,112 @@ + + + + + \ No newline at end of file diff --git a/src/components/common/FileUploadArea.vue b/src/components/common/FileUploadArea.vue new file mode 100644 index 0000000..c1769fc --- /dev/null +++ b/src/components/common/FileUploadArea.vue @@ -0,0 +1,94 @@ + + + \ No newline at end of file diff --git a/src/components/common/SendTypeSelector.vue b/src/components/common/SendTypeSelector.vue new file mode 100644 index 0000000..d5a73ad --- /dev/null +++ b/src/components/common/SendTypeSelector.vue @@ -0,0 +1,53 @@ + + + \ No newline at end of file diff --git a/src/components/common/SideDrawer.vue b/src/components/common/SideDrawer.vue new file mode 100644 index 0000000..c19b36f --- /dev/null +++ b/src/components/common/SideDrawer.vue @@ -0,0 +1,63 @@ + + + + + \ No newline at end of file diff --git a/src/components/common/TextInputArea.vue b/src/components/common/TextInputArea.vue new file mode 100644 index 0000000..273efcf --- /dev/null +++ b/src/components/common/TextInputArea.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file diff --git a/src/composables/useFileDownload.ts b/src/composables/useFileDownload.ts new file mode 100644 index 0000000..b578fb2 --- /dev/null +++ b/src/composables/useFileDownload.ts @@ -0,0 +1,144 @@ +import { ref, computed } from 'vue' +import { FileService } from '@/services' +import { useAlertStore } from '@/stores/alertStore' +import type { FileInfo } from '@/types' +import { saveAs } from 'file-saver' + +export function useFileDownload() { + const alertStore = useAlertStore() + + // 状态管理 + const isLoading = ref(false) + const fileInfo = ref(null) + const downloadCode = ref('') + + // 计算属性 + const hasFileInfo = computed(() => fileInfo.value !== null) + const canDownload = computed(() => hasFileInfo.value && !isLoading.value) + + // 获取文件信息 + const getFileInfo = async (code: string): Promise => { + if (!code.trim()) { + alertStore.showAlert('请输入取件码', 'warning') + return null + } + + try { + isLoading.value = true + downloadCode.value = code + + const response = await FileService.getFile(code) + + if (response.code === 200 && response.detail) { + fileInfo.value = response.detail + return response.detail + } else { + throw new Error(response.message || '文件不存在或已过期') + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '获取文件信息失败' + alertStore.showAlert(errorMessage, 'error') + fileInfo.value = null + return null + } finally { + isLoading.value = false + } + } + + // 下载文件 + const downloadFile = async (code?: string): Promise => { + const targetCode = code || downloadCode.value + + if (!targetCode.trim()) { + alertStore.showAlert('请输入取件码', 'warning') + return false + } + + try { + isLoading.value = true + + // 如果没有文件信息,先获取 + if (!fileInfo.value || downloadCode.value !== targetCode) { + const info = await getFileInfo(targetCode) + if (!info) { + return false + } + } + + const blob = await FileService.downloadFile(targetCode) + + // 使用 file-saver 保存文件 + if (fileInfo.value?.name) { + saveAs(blob, fileInfo.value.name) + alertStore.showAlert('文件下载成功!', 'success') + return true + } else { + throw new Error('文件名获取失败') + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '下载失败' + alertStore.showAlert(errorMessage, 'error') + return false + } finally { + isLoading.value = false + } + } + + // 重置状态 + const resetDownload = () => { + isLoading.value = false + fileInfo.value = null + downloadCode.value = '' + } + + // 格式化文件大小 + const formatFileSize = (bytes: number): string => { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] + } + + // 格式化时间 + const formatTime = (timeString: string): string => { + try { + const date = new Date(timeString) + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }) + } catch { + return timeString + } + } + + // 检查文件是否过期 + const isFileExpired = computed(() => { + if (!fileInfo.value?.expireTime) return false + return new Date(fileInfo.value.expireTime) < new Date() + }) + + return { + // 状态 + isLoading, + fileInfo, + downloadCode, + + // 计算属性 + hasFileInfo, + canDownload, + isFileExpired, + + // 方法 + getFileInfo, + downloadFile, + resetDownload, + formatFileSize, + formatTime + } +} \ No newline at end of file diff --git a/src/composables/useFileUpload.ts b/src/composables/useFileUpload.ts new file mode 100644 index 0000000..7f1b673 --- /dev/null +++ b/src/composables/useFileUpload.ts @@ -0,0 +1,139 @@ +import { ref, computed, readonly } from 'vue' +import { FileService } from '@/services' +import { useAlertStore } from '@/stores/alertStore' +import type { UploadProgress, UploadStatus } from '@/types' +import { UPLOAD_STATUS, FILE_SIZE_LIMITS } from '@/constants' + +export function useFileUpload() { + const alertStore = useAlertStore() + + // 状态管理 + const uploadStatus = ref(UPLOAD_STATUS.IDLE) + const uploadProgress = ref({ + loaded: 0, + total: 0, + percentage: 0 + }) + const uploadedCode = ref('') + const currentFile = ref(null) + + // 计算属性 + const isUploading = computed(() => uploadStatus.value === UPLOAD_STATUS.UPLOADING) + const isSuccess = computed(() => uploadStatus.value === UPLOAD_STATUS.SUCCESS) + const isError = computed(() => uploadStatus.value === UPLOAD_STATUS.ERROR) + const isIdle = computed(() => uploadStatus.value === UPLOAD_STATUS.IDLE) + + // 文件验证 + const validateFile = (file: File): boolean => { + if (file.size > FILE_SIZE_LIMITS.MAX_FILE_SIZE) { + alertStore.showAlert( + `文件大小不能超过 ${Math.round(FILE_SIZE_LIMITS.MAX_FILE_SIZE / 1024 / 1024)}MB`, + 'error' + ) + return false + } + return true + } + + // 上传文件 + const uploadFile = async (file: File): Promise => { + if (!validateFile(file)) { + return null + } + + try { + uploadStatus.value = UPLOAD_STATUS.UPLOADING + currentFile.value = file + uploadedCode.value = '' + + const response = await FileService.uploadFile(file, (progress) => { + uploadProgress.value = progress + }) + + if (response.code === 200 && response.detail?.code) { + uploadStatus.value = UPLOAD_STATUS.SUCCESS + uploadedCode.value = String(response.detail.code) + alertStore.showAlert('文件上传成功!', 'success') + return String(response.detail.code) + } else { + throw new Error(response.message || '上传失败') + } + } catch (error) { + uploadStatus.value = UPLOAD_STATUS.ERROR + const errorMessage = error instanceof Error ? error.message : '上传失败' + alertStore.showAlert(errorMessage, 'error') + return null + } + } + + // 上传文本 + const uploadText = async (text: string): Promise => { + if (!text.trim()) { + alertStore.showAlert('请输入要发送的文本内容', 'warning') + return null + } + + try { + uploadStatus.value = UPLOAD_STATUS.UPLOADING + uploadedCode.value = '' + + const response = await FileService.uploadText(text) + + if (response.code === 200 && response.detail?.code) { + uploadStatus.value = UPLOAD_STATUS.SUCCESS + uploadedCode.value = String(response.detail.code) + alertStore.showAlert('文本发送成功!', 'success') + return String(response.detail.code) + } else { + throw new Error(response.message || '发送失败') + } + } catch (error) { + uploadStatus.value = UPLOAD_STATUS.ERROR + const errorMessage = error instanceof Error ? error.message : '发送失败' + alertStore.showAlert(errorMessage, 'error') + return null + } + } + + // 重置状态 + const resetUpload = () => { + uploadStatus.value = UPLOAD_STATUS.IDLE + uploadProgress.value = { + loaded: 0, + total: 0, + percentage: 0 + } + uploadedCode.value = '' + currentFile.value = null + } + + // 格式化文件大小 + const formatFileSize = (bytes: number): string => { + if (bytes === 0) return '0 B' + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] + } + + return { + // 状态 + uploadStatus: readonly(uploadStatus), + uploadProgress: readonly(uploadProgress), + uploadedCode: readonly(uploadedCode), + currentFile: readonly(currentFile), + + // 计算属性 + isUploading, + isSuccess, + isError, + isIdle, + + // 方法 + uploadFile, + uploadText, + resetUpload, + validateFile, + formatFileSize + } +} \ No newline at end of file diff --git a/src/composables/useSystemConfig.ts b/src/composables/useSystemConfig.ts index e69de29..0b7fe04 100644 --- a/src/composables/useSystemConfig.ts +++ b/src/composables/useSystemConfig.ts @@ -0,0 +1,142 @@ +import { ref, computed } from 'vue' +import { ConfigService } from '@/services' +import { useAlertStore } from '@/stores/alertStore' +import type { SystemConfig } from '@/types' +import { STORAGE_KEYS, DEFAULT_CONFIG } from '@/constants' + +export function useSystemConfig() { + const alertStore = useAlertStore() + + // 状态管理 + const config = ref({ ...DEFAULT_CONFIG }) + const isLoading = ref(false) + + // 从本地存储获取配置 + const getStoredConfig = (): SystemConfig | null => { + try { + const storedConfig = localStorage.getItem(STORAGE_KEYS.CONFIG) + if (storedConfig) { + return JSON.parse(storedConfig) + } + } catch (error) { + console.error('解析本地配置失败:', error) + } + return null + } + + // 保存配置到本地存储 + const saveConfigToStorage = (configData: SystemConfig) => { + try { + localStorage.setItem(STORAGE_KEYS.CONFIG, JSON.stringify(configData)) + } catch (error) { + console.error('保存配置到本地存储失败:', error) + } + } + + // 获取系统配置 + const fetchConfig = async (): Promise => { + try { + isLoading.value = true + + const response = await ConfigService.getConfig() + + if (response.code === 200 && response.detail) { + config.value = { ...DEFAULT_CONFIG, ...response.detail } + saveConfigToStorage(config.value) + + // 处理通知 + if (response.detail.notify_title && response.detail.notify_content) { + const notifyKey = response.detail.notify_title + response.detail.notify_content + const lastNotify = localStorage.getItem(STORAGE_KEYS.NOTIFY) + + if (lastNotify !== notifyKey) { + localStorage.setItem(STORAGE_KEYS.NOTIFY, notifyKey) + alertStore.showAlert( + `${response.detail.notify_title}: ${response.detail.notify_content}`, + 'success' + ) + } + } + + return config.value + } else { + throw new Error(response.message || '获取配置失败') + } + } catch (error) { + // 如果网络请求失败,尝试使用本地存储的配置 + const storedConfig = getStoredConfig() + if (storedConfig) { + config.value = storedConfig + return config.value + } + + const errorMessage = error instanceof Error ? error.message : '获取配置失败' + alertStore.showAlert(errorMessage, 'error') + return null + } finally { + isLoading.value = false + } + } + + // 更新系统配置 + const updateConfig = async (newConfig: Partial): Promise => { + try { + isLoading.value = true + + const response = await ConfigService.updateConfig(newConfig) + + if (response.code === 200) { + config.value = { ...config.value, ...newConfig } + saveConfigToStorage(config.value) + alertStore.showAlert('配置更新成功!', 'success') + return true + } else { + throw new Error(response.message || '更新配置失败') + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '更新配置失败' + alertStore.showAlert(errorMessage, 'error') + return false + } finally { + isLoading.value = false + } + } + + // 初始化配置 + const initConfig = async () => { + // 先尝试从本地存储加载 + const storedConfig = getStoredConfig() + if (storedConfig) { + config.value = storedConfig + } + + // 然后从服务器获取最新配置 + await fetchConfig() + } + + // 计算属性 + const maxFileSizeMB = computed(() => { + return Math.round(config.value.maxFileSize / 1024 / 1024) + }) + + const isConfigLoaded = computed(() => { + return config.value.name !== DEFAULT_CONFIG.name || !isLoading.value + }) + + return { + // 状态 + config, + isLoading, + + // 计算属性 + maxFileSizeMB, + isConfigLoaded, + + // 方法 + fetchConfig, + updateConfig, + initConfig, + getStoredConfig, + saveConfigToStorage + } +} \ No newline at end of file diff --git a/src/composables/useTheme.ts b/src/composables/useTheme.ts new file mode 100644 index 0000000..aab65aa --- /dev/null +++ b/src/composables/useTheme.ts @@ -0,0 +1,138 @@ +import { ref, computed } from 'vue' +import { STORAGE_KEYS, THEME_MODES } from '@/constants' +import type { ThemeMode } from '@/types' + +export function useTheme() { + // 状态管理 + const isDarkMode = ref(false) + const themeMode = ref(THEME_MODES.SYSTEM) + + // 检查系统颜色模式 + const checkSystemColorScheme = (): boolean => { + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches + } + + // 从本地存储获取用户之前的选择 + const getUserPreference = (): ThemeMode | null => { + const storedPreference = localStorage.getItem(STORAGE_KEYS.COLOR_MODE) + if (storedPreference && Object.values(THEME_MODES).includes(storedPreference as ThemeMode)) { + return storedPreference as ThemeMode + } + return null + } + + // 设置颜色模式 + const setThemeMode = (mode: ThemeMode) => { + themeMode.value = mode + localStorage.setItem(STORAGE_KEYS.COLOR_MODE, mode) + + // 根据模式设置实际的暗色模式状态 + if (mode === THEME_MODES.SYSTEM) { + isDarkMode.value = checkSystemColorScheme() + } else { + isDarkMode.value = mode === THEME_MODES.DARK + } + + // 更新 HTML 类名 + updateHtmlClass() + } + + // 更新 HTML 元素的类名 + const updateHtmlClass = () => { + const html = document.documentElement + if (isDarkMode.value) { + html.classList.add('dark') + } else { + html.classList.remove('dark') + } + } + + // 切换主题 + const toggleTheme = () => { + if (themeMode.value === THEME_MODES.LIGHT) { + setThemeMode(THEME_MODES.DARK) + } else if (themeMode.value === THEME_MODES.DARK) { + setThemeMode(THEME_MODES.SYSTEM) + } else { + setThemeMode(THEME_MODES.LIGHT) + } + } + + // 监听系统主题变化 + const setupSystemThemeListener = () => { + if (window.matchMedia) { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') + + const handleChange = (e: MediaQueryListEvent) => { + if (themeMode.value === THEME_MODES.SYSTEM) { + isDarkMode.value = e.matches + updateHtmlClass() + } + } + + mediaQuery.addEventListener('change', handleChange) + + // 返回清理函数 + return () => { + mediaQuery.removeEventListener('change', handleChange) + } + } + return () => {} + } + + // 初始化主题 + const initTheme = () => { + const userPreference = getUserPreference() + if (userPreference) { + setThemeMode(userPreference) + } else { + setThemeMode(THEME_MODES.SYSTEM) + } + + // 设置系统主题监听 + return setupSystemThemeListener() + } + + // 计算属性 + const themeIcon = computed(() => { + switch (themeMode.value) { + case THEME_MODES.LIGHT: + return 'sun' + case THEME_MODES.DARK: + return 'moon' + case THEME_MODES.SYSTEM: + return 'monitor' + default: + return 'monitor' + } + }) + + const themeLabel = computed(() => { + switch (themeMode.value) { + case THEME_MODES.LIGHT: + return '浅色模式' + case THEME_MODES.DARK: + return '深色模式' + case THEME_MODES.SYSTEM: + return '跟随系统' + default: + return '跟随系统' + } + }) + + return { + // 状态 + isDarkMode, + themeMode, + + // 计算属性 + themeIcon, + themeLabel, + + // 方法 + setThemeMode, + toggleTheme, + initTheme, + checkSystemColorScheme + } +} \ No newline at end of file diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 0000000..9887e55 --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1,88 @@ +// 应用常量配置 + +// 存储键名 +export const STORAGE_KEYS = { + COLOR_MODE: 'colorMode', + ADMIN_PASSWORD: 'adminPassword', + TOKEN: 'token', + CONFIG: 'config', + NOTIFY: 'notify' +} as const + +// 主题模式 +export const THEME_MODES = { + LIGHT: 'light', + DARK: 'dark', + SYSTEM: 'system' +} as const + +// 发送类型 +export const SEND_TYPES = { + FILE: 'file', + TEXT: 'text' +} as const + +// 警告类型 +export const ALERT_TYPES = { + SUCCESS: 'success', + ERROR: 'error', + WARNING: 'warning', + INFO: 'info' +} as const + +// 上传状态 +export const UPLOAD_STATUS = { + IDLE: 'idle', + UPLOADING: 'uploading', + SUCCESS: 'success', + ERROR: 'error' +} as const + +// API 响应状态码 +export const API_STATUS_CODES = { + SUCCESS: 200, + UNAUTHORIZED: 401, + FORBIDDEN: 403, + NOT_FOUND: 404, + SERVER_ERROR: 500 +} as const + +// 文件大小限制 (字节) +export const FILE_SIZE_LIMITS = { + MAX_FILE_SIZE: 100 * 1024 * 1024, // 100MB + CHUNK_SIZE: 1024 * 1024 // 1MB +} as const + +// 时间相关常量 +export const TIME_CONSTANTS = { + ALERT_DURATION: 5000, // 5秒 + REQUEST_TIMEOUT: 30000, // 30秒 + PROGRESS_UPDATE_INTERVAL: 100 // 100毫秒 +} as const + +// 路由路径 +export const ROUTES = { + HOME: '/', + SEND: '/send', + ADMIN: '/admin', + LOGIN: '/login', + DASHBOARD: '/admin/dashboard', + FILE_MANAGE: '/admin/files', + SETTINGS: '/admin/settings' +} as const + +// 正则表达式 +export const REGEX_PATTERNS = { + EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, + PHONE: /^1[3-9]\d{9}$/, + CODE: /^[A-Za-z0-9]{4,8}$/ // 取件码格式 +} as const + +// 默认配置 +export const DEFAULT_CONFIG = { + name: 'FileCodeBox', + description: '文件传输工具', + maxFileSize: FILE_SIZE_LIMITS.MAX_FILE_SIZE, + allowedFileTypes: ['*'] as string[], + expireDays: 7 +} \ No newline at end of file diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..0207fdd --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,14 @@ +// 自定义 Vue hooks 导出文件 +// 这里可以导出所有自定义的 Vue hooks + +// 示例:导出常用的自定义 hooks +// export { useLocalStorage } from './useLocalStorage' +// export { useDebounce } from './useDebounce' +// export { useThrottle } from './useThrottle' +// export { useEventListener } from './useEventListener' +// export { useClickOutside } from './useClickOutside' + +// 目前项目中的业务逻辑已经通过 composables 进行了封装 +// hooks 文件夹主要用于存放更通用的、可复用的 Vue hooks + +export {} \ No newline at end of file diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..7e067f8 --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,137 @@ +// API 服务层 +import api from '@/utils/api' +import type { ApiResponse, FileInfo, ConfigState, AdminUser, FileUploadResponse, TextSendResponse, DashboardData, FileListResponse, FileEditForm } from '@/types' +import type { UploadProgress } from '@/types' + +// 系统配置服务 +export class ConfigService { + static async getConfig(): Promise> { + return api.get('/admin/config/get') + } + static async getUserConfig(): Promise> { + return api.post('/') + } + static async updateConfig(config: Partial): Promise { + return api.patch('/admin/config/update', config) + } +} + +// 文件服务 +export class FileService { + static async uploadFile( + file: File, + onProgress?: (progress: UploadProgress) => void + ): Promise> { + const formData = new FormData() + formData.append('file', file) + + return api.post('/share/file/', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + }, + onUploadProgress: (progressEvent) => { + if (onProgress && progressEvent.total) { + const progress: UploadProgress = { + loaded: progressEvent.loaded, + total: progressEvent.total, + percentage: Math.round((progressEvent.loaded * 100) / progressEvent.total) + } + onProgress(progress) + } + } + }) + } + + static async uploadText(text: string): Promise> { + return api.post('/share/text/', { content: text }) + } + + static async getFile(code: string): Promise> { + return api.get(`/file/${code}`) + } + + static async downloadFile(code: string): Promise { + const response = await api.get(`/download/${code}`, { + responseType: 'blob' + }) + return response.data + } + + static async deleteFile(fileId: string): Promise { + return api.post('/admin/file/delete', { id: fileId }) + } + + static async getFileList(page = 1, limit = 10): Promise> { + return api.get('/admin/file/list', { + params: { page, size: limit } + }) + } + + // 文件管理相关方法 + static async getAdminFileList(params: { page: number; size: number; keyword?: string }): Promise> { + return api.get('/admin/file/list', { params }) + } + + static async updateFile(data: FileEditForm): Promise { + return api.post('/admin/file/update', data) + } + + static async deleteAdminFile(id: number): Promise { + return api.post('/admin/file/delete', { id }) + } + + static async downloadAdminFile(id: number): Promise<{ data: Blob; headers: Record }> { + return api.get('/admin/file/download', { + params: { id }, + responseType: 'blob' + }) + } +} + +// 认证服务 +export class AuthService { + static async login(password: string): Promise> { + return api.post('/admin/login', { password }) + } + + static async logout(): Promise { + return api.post('/admin/logout') + } + + static async verifyToken(): Promise> { + return api.get('/admin/verify') + } +} + +// 统计服务 +export class StatsService { + static async getDashboardStats(): Promise> { + return api.get('/admin/dashboard') + } + + static async getDashboard(): Promise> { + return api.get('/admin/dashboard') + } +} + +// 导出所有服务 +export const services = { + config: ConfigService, + file: FileService, + auth: AuthService, + stats: StatsService +} + +export default services \ No newline at end of file diff --git a/src/stores/adminStore.ts b/src/stores/adminStore.ts index 9986d22..639e9f6 100644 --- a/src/stores/adminStore.ts +++ b/src/stores/adminStore.ts @@ -1,11 +1,79 @@ import { defineStore } from 'pinia' -import { ref } from 'vue' +import { ref, computed } from 'vue' +import { STORAGE_KEYS } from '@/constants' +import type { AdminUser } from '@/types' -export const useAdminData = defineStore('adminData', () => { - const adminPassword = ref(localStorage.getItem('adminPassword') || '') - function updateAdminPwd(pwd: string) { +export const useAdminStore = defineStore('admin', () => { + // 状态 + const adminPassword = ref(localStorage.getItem(STORAGE_KEYS.ADMIN_PASSWORD) || '') + const token = ref(localStorage.getItem(STORAGE_KEYS.TOKEN) || '') + const isLoggedIn = ref(false) + const userInfo = ref(null) + + // 计算属性 + const isAuthenticated = computed(() => { + return isLoggedIn.value && !!token.value + }) + + // 方法 + const updateAdminPassword = (pwd: string) => { adminPassword.value = pwd - localStorage.setItem('token', pwd) + localStorage.setItem(STORAGE_KEYS.ADMIN_PASSWORD, pwd) + } + + const setToken = (newToken: string) => { + token.value = newToken + localStorage.setItem(STORAGE_KEYS.TOKEN, newToken) + } + + const setUserInfo = (user: AdminUser) => { + userInfo.value = user + isLoggedIn.value = true + setToken(user.token) + } + + const login = (user: AdminUser) => { + setUserInfo(user) + } + + const logout = () => { + adminPassword.value = '' + token.value = '' + isLoggedIn.value = false + userInfo.value = null + + // 清除本地存储 + localStorage.removeItem(STORAGE_KEYS.ADMIN_PASSWORD) + localStorage.removeItem(STORAGE_KEYS.TOKEN) + } + + const initAuth = () => { + const storedToken = localStorage.getItem(STORAGE_KEYS.TOKEN) + if (storedToken) { + token.value = storedToken + isLoggedIn.value = true + } + } + + return { + // 状态 + adminPassword, + token, + isLoggedIn, + userInfo, + + // 计算属性 + isAuthenticated, + + // 方法 + updateAdminPassword, + setToken, + setUserInfo, + login, + logout, + initAuth } - return { adminPassword, updateAdminPwd } }) + +// 保持向后兼容 +export const useAdminData = useAdminStore diff --git a/src/stores/alertStore.ts b/src/stores/alertStore.ts index a25d102..6eb6ff0 100644 --- a/src/stores/alertStore.ts +++ b/src/stores/alertStore.ts @@ -1,13 +1,6 @@ import { defineStore } from 'pinia' - -interface Alert { - id: number - message: string - type: 'success' | 'error' | 'warning' | 'info' - progress: number - duration: number - startTime: number -} +import type { Alert, AlertType } from '@/types' +import { TIME_CONSTANTS } from '@/constants' export const useAlertStore = defineStore('alert', { state: () => ({ @@ -16,8 +9,8 @@ export const useAlertStore = defineStore('alert', { actions: { showAlert( message: string, - type: 'success' | 'error' | 'warning' | 'info' = 'info', - duration = 5000 + type: AlertType = 'info', + duration = TIME_CONSTANTS.ALERT_DURATION ) { const id = Date.now() const startTime = Date.now() diff --git a/src/stores/fileData.ts b/src/stores/fileData.ts index dbeaa9f..a03519c 100644 --- a/src/stores/fileData.ts +++ b/src/stores/fileData.ts @@ -1,39 +1,290 @@ import { defineStore } from 'pinia' -import { reactive } from 'vue' +import { ref, computed } from 'vue' +import type { FileInfo, UploadProgress, UploadStatus } from '@/types' +import { UPLOAD_STATUS } from '@/constants' export const useFileDataStore = defineStore('fileData', () => { - const receiveData = reactive(JSON.parse(localStorage.getItem('receiveData') || '[]') || []) // 接收的数据 - const shareData = reactive(JSON.parse(localStorage.getItem('shareData') || '[]') || []) // 接收的数据 - function save() { - localStorage.setItem('receiveData', JSON.stringify(receiveData)) - localStorage.setItem('shareData', JSON.stringify(shareData)) + // 上传相关状态 + const uploadStatus = ref(UPLOAD_STATUS.IDLE) + const uploadProgress = ref({ + loaded: 0, + total: 0, + percentage: 0 + }) + const uploadedCode = ref('') + const currentFile = ref(null) + + // 下载相关状态 + const downloadCode = ref('') + const fileInfo = ref(null) + const isDownloading = ref(false) + + // 文件列表状态(管理页面使用) + const fileList = ref([]) + const totalFiles = ref(0) + const currentPage = ref(1) + const pageSize = ref(10) + const isLoadingList = ref(false) + + // 接收数据状态(取件记录) + const receiveData = ref>([]) + + // 分享数据状态(发送记录) + const shareData = ref>([]) + + // 计算属性 + const isUploading = computed(() => uploadStatus.value === UPLOAD_STATUS.UPLOADING) + const isUploadSuccess = computed(() => uploadStatus.value === UPLOAD_STATUS.SUCCESS) + const isUploadError = computed(() => uploadStatus.value === UPLOAD_STATUS.ERROR) + const hasFileInfo = computed(() => fileInfo.value !== null) + const canDownload = computed(() => hasFileInfo.value && !isDownloading.value) + + const totalPages = computed(() => { + return Math.ceil(totalFiles.value / pageSize.value) + }) + + // 上传相关方法 + const setUploadStatus = (status: UploadStatus) => { + uploadStatus.value = status } - function addReceiveData(data: any) { - receiveData.unshift(data) - save() + + const setUploadProgress = (progress: UploadProgress) => { + uploadProgress.value = progress + } + + const setUploadedCode = (code: string) => { + uploadedCode.value = code + } + + const setCurrentFile = (file: File | null) => { + currentFile.value = file + } + + const resetUpload = () => { + uploadStatus.value = UPLOAD_STATUS.IDLE + uploadProgress.value = { + loaded: 0, + total: 0, + percentage: 0 + } + uploadedCode.value = '' + currentFile.value = null + } + + // 下载相关方法 + const setDownloadCode = (code: string) => { + downloadCode.value = code + } + + const setFileInfo = (info: FileInfo | null) => { + fileInfo.value = info + } + + const setDownloading = (loading: boolean) => { + isDownloading.value = loading + } + + const resetDownload = () => { + downloadCode.value = '' + fileInfo.value = null + isDownloading.value = false + } + + // 文件列表相关方法 + const setFileList = (files: FileInfo[]) => { + fileList.value = files + } + + const addFile = (file: FileInfo) => { + fileList.value.unshift(file) + totalFiles.value += 1 + } + + const removeFile = (fileId: string) => { + const index = fileList.value.findIndex(file => file.id === fileId) + if (index > -1) { + fileList.value.splice(index, 1) + totalFiles.value -= 1 + } + } + + const updateFile = (fileId: string, updates: Partial) => { + const index = fileList.value.findIndex(file => file.id === fileId) + if (index > -1) { + fileList.value[index] = { ...fileList.value[index], ...updates } + } + } + + const setTotalFiles = (total: number) => { + totalFiles.value = total + } + + const setCurrentPage = (page: number) => { + currentPage.value = page + } + + const setPageSize = (size: number) => { + pageSize.value = size + } + + const setLoadingList = (loading: boolean) => { + isLoadingList.value = loading + } + + const resetFileList = () => { + fileList.value = [] + totalFiles.value = 0 + currentPage.value = 1 + isLoadingList.value = false } - function addShareData(data: any) { - shareData.unshift(data) - save() + // 添加分享数据方法 + const addShareData = (data: { code: string; name?: string }) => { + setUploadedCode(data.code) + if (data.name) { + // 如果有文件名,可以创建一个临时的 FileInfo 对象 + const fileInfo: FileInfo = { + id: data.code, + name: data.name, + size: 0, + type: '', + uploadTime: new Date().toISOString(), + downloadCount: 0 + } + addFile(fileInfo) + } + } + + // 接收数据相关方法 + const addReceiveData = (data: { + id: number + code: string + filename: string + size: string + downloadUrl: string | null + content: string | null + date: string + }) => { + receiveData.value.push(data) } - function deleteReceiveData(index: number) { - receiveData.splice(index, 1) - save() + const removeReceiveData = (id: number) => { + const index = receiveData.value.findIndex(item => item.id === id) + if (index > -1) { + receiveData.value.splice(index, 1) + } + } + + const deleteReceiveData = (index: number) => { + if (index > -1 && index < receiveData.value.length) { + receiveData.value.splice(index, 1) + } } - function deleteShareData(index: number) { - shareData.splice(index, 1) - save() + const clearReceiveData = () => { + receiveData.value = [] } + + // 分享数据相关方法 + const addShareDataRecord = (data: { + id: number + filename: string + date: string + size: string + expiration: string + retrieveCode: string + }) => { + shareData.value.push(data) + } + + const deleteShareData = (index: number) => { + if (index > -1 && index < shareData.value.length) { + shareData.value.splice(index, 1) + } + } + + const clearShareData = () => { + shareData.value = [] + } + return { - receiveData, - shareData, - save, + // 上传状态 + uploadStatus, + uploadProgress, + uploadedCode, + currentFile, + + // 下载状态 + downloadCode, + fileInfo, + isDownloading, + + // 文件列表状态 + fileList, + totalFiles, + currentPage, + pageSize, + isLoadingList, + + // 计算属性 + isUploading, + isUploadSuccess, + isUploadError, + hasFileInfo, + canDownload, + totalPages, + + // 上传方法 + setUploadStatus, + setUploadProgress, + setUploadedCode, + setCurrentFile, + resetUpload, + + // 下载方法 + setDownloadCode, + setFileInfo, + setDownloading, + resetDownload, + + // 文件列表方法 + setFileList, + addFile, + removeFile, + updateFile, + setTotalFiles, + setCurrentPage, + setPageSize, + setLoadingList, + resetFileList, addShareData, + + // 接收数据状态和方法 + receiveData, addReceiveData, + removeReceiveData, deleteReceiveData, - deleteShareData + clearReceiveData, + + // 分享数据状态和方法 + shareData, + addShareDataRecord, + deleteShareData, + clearShareData } }) diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..812f3fd --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,180 @@ +// 通用类型定义 +export interface ApiResponse { + code: number + message?: string + detail?: T +} + +// 文件相关类型 +export interface FileInfo { + id: string + name: string + size: number + type: string + uploadTime: string + downloadCount: number + expireTime?: string +} + +// 文件管理相关类型 +export interface FileListItem { + id: number + code: string + prefix: string + suffix: string + size: number + text?: string + description?: string + expired_at: string + expired_count: number | null + created_at: string +} + +export interface FileEditForm { + id: number | null + code: string + prefix: string + suffix: string + expired_at: string + expired_count: number | null +} + +export interface FileListResponse { + data: FileListItem[] + total: number + page: number + size: number +} + +// 文件上传响应类型 +export interface FileUploadResponse { + code: number + name: string +} + +// 文本发送响应类型 +export interface TextSendResponse { + code: number +} + +export interface UploadProgress { + loaded: number + total: number + percentage: number +} + +// 系统配置类型 +export interface SystemConfig { + name: string + description?: string + maxFileSize: number + allowedFileTypes: string[] + expireDays: number + notify_title?: string + notify_content?: string +} + +// 主题选择项类型 +export interface ThemeChoice { + key: string + name: string + author: string + version: string +} + +// 完整的系统配置状态类型 +export interface ConfigState { + name: string + description: string + file_storage: string + themesChoices: ThemeChoice[] + 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 +} + +// 系统配置API响应类型 +export interface ConfigResponse { + code: number + message?: string + detail?: ConfigState +} + +// 用户相关类型 +export interface AdminUser { + id: string + username: string + token: string +} + +// Dashboard 数据类型 +export interface DashboardData { + totalFiles: number + storageUsed: number | string + yesterdayCount: number + todayCount: number + yesterdaySize: number | string + todaySize: number | string + sysUptime: number | string +} + +// 主题相关类型 +export type ThemeMode = 'light' | 'dark' | 'system' + +// 发送类型 +export type SendType = 'file' | 'text' + +// 警告类型 +export type AlertType = 'success' | 'error' | 'warning' | 'info' + +export interface Alert { + id: number + message: string + type: AlertType + progress: number + duration: number + startTime: number +} + +// 文件上传状态 +export type UploadStatus = 'idle' | 'uploading' | 'success' | 'error' + +// 路由相关类型 +export interface RouteConfig { + path: string + name: string + component: () => Promise<{ default: object }> + meta?: { + requiresAuth?: boolean + title?: string + } +} \ No newline at end of file diff --git a/src/utils/api.ts b/src/utils/api.ts index 8469250..5ccf4e1 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,18 +1,18 @@ import axios from 'axios' +import { TIME_CONSTANTS } from '@/constants' // 从环境变量中获取 API 基础 URL const baseURL = import.meta.env.MODE === 'production' ? import.meta.env.VITE_API_BASE_URL_PROD : import.meta.env.VITE_API_BASE_URL_DEV - // 确保 baseURL 是一个有效的字符串 const sanitizedBaseURL = typeof baseURL === 'string' ? baseURL : '' // 创建 axios 实例 const api = axios.create({ baseURL: sanitizedBaseURL, - timeout: 1000000000000000, // 请求超时时间 + timeout: TIME_CONSTANTS.REQUEST_TIMEOUT, // 30秒超时 headers: { 'Content-Type': 'application/json' } @@ -46,27 +46,26 @@ api.interceptors.response.use( (error) => { // 处理错误响应 if (error.response) { - switch (error.response.status) { + const { status } = error.response + switch (status) { case 401: - console.error('未授权,请重新登录') - localStorage.clear() - window.location.href = '/#/login' + localStorage.removeItem('token') + // 使用 router 进行导航而不是直接修改 location + if (window.location.hash !== '#/login') { + window.location.href = '/#/login' + } break case 403: - // 禁止访问 - console.error('禁止访问') - break case 404: - // 未找到 - console.error('请求的资源不存在') - break + case 500: default: - console.error('发生错误:', error.response.data) + // 错误信息通过Promise.reject传递给调用方处理 + break } } else if (error.request) { - console.error('未收到响应:', error.request) + // 网络错误,通过Promise.reject传递给调用方处理 } else { - console.error('请求配置错误:', error.message) + // 请求配置错误,通过Promise.reject传递给调用方处理 } return Promise.reject(error) } diff --git a/src/views/RetrievewFileView.vue b/src/views/RetrievewFileView.vue index 120d2e0..b4a1791 100644 --- a/src/views/RetrievewFileView.vue +++ b/src/views/RetrievewFileView.vue @@ -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 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( diff --git a/src/views/SendFileView.vue b/src/views/SendFileView.vue index 724453c..5f53c5c 100644 --- a/src/views/SendFileView.vue +++ b/src/views/SendFileView.vue @@ -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(null) const textContent = ref('') const fileInput = ref(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 = 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) { diff --git a/src/views/manage/DashboardView.vue b/src/views/manage/DashboardView.vue index 5ab6363..23d5d51 100644 --- a/src/views/manage/DashboardView.vue +++ b/src/views/manage/DashboardView.vue @@ -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({ 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(() => { diff --git a/src/views/manage/FileManageView.vue b/src/views/manage/FileManageView.vue index 65e33e2..8103e08 100644 --- a/src/views/manage/FileManageView.vue +++ b/src/views/manage/FileManageView.vue @@ -381,7 +381,8 @@