feat: 优化代码结构(在Claude的帮助下)
This commit is contained in:
@@ -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<FileInfo | null>(null)
|
||||
const downloadCode = ref('')
|
||||
|
||||
// 计算属性
|
||||
const hasFileInfo = computed(() => fileInfo.value !== null)
|
||||
const canDownload = computed(() => hasFileInfo.value && !isLoading.value)
|
||||
|
||||
// 获取文件信息
|
||||
const getFileInfo = async (code: string): Promise<FileInfo | null> => {
|
||||
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<boolean> => {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<UploadStatus>(UPLOAD_STATUS.IDLE)
|
||||
const uploadProgress = ref<UploadProgress>({
|
||||
loaded: 0,
|
||||
total: 0,
|
||||
percentage: 0
|
||||
})
|
||||
const uploadedCode = ref<string>('')
|
||||
const currentFile = ref<File | null>(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<string | null> => {
|
||||
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<string | null> => {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<SystemConfig>({ ...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<SystemConfig | null> => {
|
||||
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<SystemConfig>): Promise<boolean> => {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<ThemeMode>(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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user