From baf8a642b845fba79d95d7599c3e2ddccbcc31db Mon Sep 17 00:00:00 2001 From: AoXuan Date: Tue, 2 Sep 2025 19:26:04 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=97=A5=E5=BF=97=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=8A=9F=E8=83=BD&=E4=BC=98=E5=8C=96=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/electron/main.ts | 39 +- frontend/electron/preload.ts | 5 +- frontend/electron/services/logService.ts | 86 ++-- frontend/src/components/LogViewer.vue | 560 +++++++++++++++++++---- frontend/src/router/index.ts | 6 + frontend/src/types/electron.d.ts | 5 +- frontend/src/utils/logger.ts | 25 +- frontend/src/views/Logs.vue | 51 +++ frontend/src/views/Settings.vue | 14 + 9 files changed, 662 insertions(+), 129 deletions(-) create mode 100644 frontend/src/views/Logs.vue diff --git a/frontend/electron/main.ts b/frontend/electron/main.ts index 78ebef3..68e5557 100644 --- a/frontend/electron/main.ts +++ b/frontend/electron/main.ts @@ -13,7 +13,7 @@ import { stopBackend, } from './services/pythonService' import { setMainWindow as setGitMainWindow, downloadGit, cloneBackend } from './services/gitService' -import { setupLogger, log, getLogPath, cleanOldLogs } from './services/logService' +import { setupLogger, log, getLogPath, getLogFiles, cleanOldLogs } from './services/logService' // 检查是否以管理员权限运行 function isRunningAsAdmin(): boolean { @@ -256,9 +256,27 @@ ipcMain.handle('get-log-path', async () => { } }) -ipcMain.handle('get-logs', async (event, lines?: number) => { +ipcMain.handle('get-log-files', async () => { try { - const logFilePath = getLogPath() + return getLogFiles() + } catch (error) { + log.error('获取日志文件列表失败:', error) + throw error + } +}) + +ipcMain.handle('get-logs', async (event, lines?: number, fileName?: string) => { + try { + let logFilePath: string + + if (fileName) { + // 如果指定了文件名,使用指定的文件 + const appRoot = getAppRoot() + logFilePath = path.join(appRoot, 'logs', fileName) + } else { + // 否则使用当前日志文件 + logFilePath = getLogPath() + } if (!fs.existsSync(logFilePath)) { return '' @@ -278,13 +296,22 @@ ipcMain.handle('get-logs', async (event, lines?: number) => { } }) -ipcMain.handle('clear-logs', async () => { +ipcMain.handle('clear-logs', async (event, fileName?: string) => { try { - const logFilePath = getLogPath() + let logFilePath: string + + if (fileName) { + // 如果指定了文件名,清空指定的文件 + const appRoot = getAppRoot() + logFilePath = path.join(appRoot, 'logs', fileName) + } else { + // 否则清空当前日志文件 + logFilePath = getLogPath() + } if (fs.existsSync(logFilePath)) { fs.writeFileSync(logFilePath, '', 'utf8') - log.info('日志文件已清空') + log.info(`日志文件已清空: ${fileName || '当前文件'}`) } } catch (error) { log.error('清空日志文件失败:', error) diff --git a/frontend/electron/preload.ts b/frontend/electron/preload.ts index 2134795..7e136fd 100644 --- a/frontend/electron/preload.ts +++ b/frontend/electron/preload.ts @@ -32,8 +32,9 @@ contextBridge.exposeInMainWorld('electronAPI', { // 日志文件操作 getLogPath: () => ipcRenderer.invoke('get-log-path'), - getLogs: (lines?: number) => ipcRenderer.invoke('get-logs', lines), - clearLogs: () => ipcRenderer.invoke('clear-logs'), + getLogFiles: () => ipcRenderer.invoke('get-log-files'), + getLogs: (lines?: number, fileName?: string) => ipcRenderer.invoke('get-logs', lines, fileName), + clearLogs: (fileName?: string) => ipcRenderer.invoke('clear-logs', fileName), cleanOldLogs: (daysToKeep?: number) => ipcRenderer.invoke('clean-old-logs', daysToKeep), // 保留原有方法以兼容现有代码 diff --git a/frontend/electron/services/logService.ts b/frontend/electron/services/logService.ts index c2196b3..8efc88c 100644 --- a/frontend/electron/services/logService.ts +++ b/frontend/electron/services/logService.ts @@ -15,6 +15,15 @@ function getLogDirectory(): string { return path.join(appRoot, 'logs') } +// 获取当前日期的日志文件名 - 使用ISO 8601格式 +function getTodayLogFileName(): string { + const today = new Date() + const year = today.getFullYear() + const month = String(today.getMonth() + 1).padStart(2, '0') + const day = String(today.getDate()).padStart(2, '0') + return `frontendlog-${year}-${month}-${day}.log` +} + // 配置日志系统 export function setupLogger() { // 设置日志文件路径到软件安装目录 @@ -30,28 +39,21 @@ export function setupLogger() { log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}' log.transports.console.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}' - // 设置主进程日志文件路径和名称 - log.transports.file.resolvePathFn = () => path.join(logPath, 'app.log') + // 设置主进程日志文件路径和名称 - 按日期分文件 + log.transports.file.resolvePathFn = () => { + const fileName = getTodayLogFileName() + return path.join(logPath, fileName) + } // 设置日志级别 log.transports.file.level = 'debug' log.transports.console.level = 'debug' - // 设置文件大小限制 (10MB) - log.transports.file.maxSize = 10 * 1024 * 1024 + // 设置文件大小限制 (50MB,因为按日期分文件,可以设置更大) + log.transports.file.maxSize = 50 * 1024 * 1024 - // 保留最近的5个日志文件 - log.transports.file.archiveLog = (file: any) => { - const filePath = file.toString() - const info = path.parse(filePath) - - try { - return path.join(info.dir, `${info.name}.old${info.ext}`) - } catch (e) { - console.warn('Could not archive log file', e) - return null - } - } + // 禁用自动归档,因为我们按日期分文件 + log.transports.file.archiveLog = null // 捕获未处理的异常和Promise拒绝 log.catchErrors({ @@ -98,7 +100,7 @@ export function setupLogger() { } log.info('日志系统初始化完成') - log.info(`日志文件路径: ${path.join(logPath, 'main.log')}`) + log.info(`日志文件路径: ${path.join(logPath, getTodayLogFileName())}`) return log } @@ -106,9 +108,25 @@ export function setupLogger() { // 导出日志实例和工具函数 export { log, stripAnsiColors } -// 获取日志文件路径 +// 获取当前日志文件路径 export function getLogPath(): string { - return path.join(getLogDirectory(), 'main.log') + return path.join(getLogDirectory(), getTodayLogFileName()) +} + +// 获取所有日志文件列表 +export function getLogFiles(): string[] { + const fs = require('fs') + const logDir = getLogDirectory() + + if (!fs.existsSync(logDir)) { + return [] + } + + const files = fs.readdirSync(logDir) + return files + .filter((file: string) => file.match(/^frontendlog-\d{4}-\d{2}-\d{2}\.log$/)) + .sort() + .reverse() // 最新的在前面 } // 清理旧日志文件 @@ -121,19 +139,27 @@ export function cleanOldLogs(daysToKeep: number = 7) { } const files = fs.readdirSync(logDir) - const now = Date.now() - const maxAge = daysToKeep * 24 * 60 * 60 * 1000 // 转换为毫秒 + const now = new Date() + const cutoffDate = new Date(now.getTime() - daysToKeep * 24 * 60 * 60 * 1000) + + // 格式化截止日期为YYYY-MM-DD + const cutoffDateStr = cutoffDate.getFullYear() + '-' + + String(cutoffDate.getMonth() + 1).padStart(2, '0') + '-' + + String(cutoffDate.getDate()).padStart(2, '0') files.forEach((file: string) => { - const filePath = path.join(logDir, file) - const stats = fs.statSync(filePath) - - if (now - stats.mtime.getTime() > maxAge) { - try { - fs.unlinkSync(filePath) - log.info(`已删除旧日志文件: ${file}`) - } catch (error) { - log.error(`删除旧日志文件失败: ${file}`, error) + // 匹配日志文件名格式 frontendlog-YYYY-MM-DD.log + const match = file.match(/^frontendlog-(\d{4}-\d{2}-\d{2})\.log$/) + if (match) { + const fileDateStr = match[1] + if (fileDateStr < cutoffDateStr) { + const filePath = path.join(logDir, file) + try { + fs.unlinkSync(filePath) + log.info(`已删除旧日志文件: ${file}`) + } catch (error) { + log.error(`删除旧日志文件失败: ${file}`, error) + } } } }) diff --git a/frontend/src/components/LogViewer.vue b/frontend/src/components/LogViewer.vue index 94cdc05..7e11f71 100644 --- a/frontend/src/components/LogViewer.vue +++ b/frontend/src/components/LogViewer.vue @@ -1,94 +1,218 @@ \ No newline at end of file + + + + + +/* 滚动条样式 - 适配深色模式 */ +.log-container::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.log-container::-webkit-scrollbar-track { + background: var(--ant-color-bg-container); + border-radius: 4px; +} + +.log-container::-webkit-scrollbar-thumb { + background: var(--ant-color-border); + border-radius: 4px; +} + +.log-container::-webkit-scrollbar-thumb:hover { + background: var(--ant-color-border-secondary); +} + +/* 空状态样式 */ +:deep(.ant-empty) { + padding: 40px 20px; +} + +:deep(.ant-empty-description) { + color: var(--ant-color-text-secondary); +} + +/* 加载状态 */ +:deep(.ant-spin-container) { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; +} + +:deep(.ant-spin-nested-loading) { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; +} + +/* 深色模式特定样式 */ +[data-theme='dark'] .log-content { + border-color: #434343; + background: #1f1f1f; +} + +[data-theme='dark'] .log-container { + background: #141414; +} + +[data-theme='dark'] .log-text { + color: #e6e6e6; +} + +[data-theme='dark'] .log-container::-webkit-scrollbar-track { + background: #262626; +} + +[data-theme='dark'] .log-container::-webkit-scrollbar-thumb { + background: #434343; +} + +[data-theme='dark'] .log-container::-webkit-scrollbar-thumb:hover { + background: #595959; +} + +/* 响应式调整 */ +@media (max-width: 768px) { + .log-viewer { + padding: 8px; + gap: 8px; + } + + .log-text { + font-size: 11px; + } +} + +/* 日志级别颜色 - 适配深色模式 */ +.log-text { + /* ERROR 级别 - 红色 */ + --log-error-color: #ff4d4f; + --log-error-bg: rgba(255, 77, 79, 0.1); + + /* WARN 级别 - 橙色 */ + --log-warn-color: #fa8c16; + --log-warn-bg: rgba(250, 140, 22, 0.1); + + /* INFO 级别 - 蓝色 */ + --log-info-color: #1890ff; + --log-info-bg: rgba(24, 144, 255, 0.1); + + /* DEBUG 级别 - 绿色 */ + --log-debug-color: #52c41a; + --log-debug-bg: rgba(82, 196, 26, 0.1); +} + +[data-theme='dark'] .log-text { + --log-error-color: #ff7875; + --log-error-bg: rgba(255, 120, 117, 0.15); + + --log-warn-color: #ffa940; + --log-warn-bg: rgba(255, 169, 64, 0.15); + + --log-info-color: #40a9ff; + --log-info-bg: rgba(64, 169, 255, 0.15); + + --log-debug-color: #73d13d; + --log-debug-bg: rgba(115, 209, 61, 0.15); +} + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 63689a3..4f223ec 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -76,6 +76,12 @@ const routes: RouteRecordRaw[] = [ component: () => import('../views/Settings.vue'), meta: { title: '设置' }, }, + { + path: '/logs', + name: 'Logs', + component: () => import('../views/Logs.vue'), + meta: { title: '日志查看' }, + }, ] const router = createRouter({ diff --git a/frontend/src/types/electron.d.ts b/frontend/src/types/electron.d.ts index 1a8ef81..7f2d1b3 100644 --- a/frontend/src/types/electron.d.ts +++ b/frontend/src/types/electron.d.ts @@ -25,8 +25,9 @@ export interface ElectronAPI { // 日志文件操作 getLogPath: () => Promise - getLogs: (lines?: number) => Promise - clearLogs: () => Promise + getLogFiles: () => Promise + getLogs: (lines?: number, fileName?: string) => Promise + clearLogs: (fileName?: string) => Promise cleanOldLogs: (daysToKeep?: number) => Promise // 保留原有方法以兼容现有代码 diff --git a/frontend/src/utils/logger.ts b/frontend/src/utils/logger.ts index 200e4e3..f9ded36 100644 --- a/frontend/src/utils/logger.ts +++ b/frontend/src/utils/logger.ts @@ -1,8 +1,9 @@ // 渲染进程日志工具 interface ElectronAPI { getLogPath: () => Promise - getLogs: (lines?: number) => Promise - clearLogs: () => Promise + getLogFiles: () => Promise + getLogs: (lines?: number, fileName?: string) => Promise + clearLogs: (fileName?: string) => Promise cleanOldLogs: (daysToKeep?: number) => Promise } @@ -45,19 +46,27 @@ class Logger { throw new Error('Electron API not available') } - // 获取日志内容 - async getLogs(lines?: number): Promise { + // 获取日志文件列表 + async getLogFiles(): Promise { if (window.electronAPI) { - return await window.electronAPI.getLogs(lines) + return await window.electronAPI.getLogFiles() + } + throw new Error('Electron API not available') + } + + // 获取日志内容 + async getLogs(lines?: number, fileName?: string): Promise { + if (window.electronAPI) { + return await window.electronAPI.getLogs(lines, fileName) } throw new Error('Electron API not available') } // 清空日志 - async clearLogs(): Promise { + async clearLogs(fileName?: string): Promise { if (window.electronAPI) { - await window.electronAPI.clearLogs() - console.info('日志已清空') + await window.electronAPI.clearLogs(fileName) + console.info(`日志已清空: ${fileName || '当前文件'}`) } else { throw new Error('Electron API not available') } diff --git a/frontend/src/views/Logs.vue b/frontend/src/views/Logs.vue new file mode 100644 index 0000000..d3e5125 --- /dev/null +++ b/frontend/src/views/Logs.vue @@ -0,0 +1,51 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index e2ca012..6ec44f1 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -1,5 +1,6 @@