refactor: 添加回log功能~这次好用了~
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
stopBackend,
|
||||
} from './services/pythonService'
|
||||
import { setMainWindow as setGitMainWindow, downloadGit, cloneBackend } from './services/gitService'
|
||||
import { setupLogger, log, getLogPath, cleanOldLogs } from './services/logService'
|
||||
|
||||
// 检查是否以管理员权限运行
|
||||
function isRunningAsAdmin(): boolean {
|
||||
@@ -60,6 +61,8 @@ function restartAsAdmin(): void {
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
function createWindow() {
|
||||
log.info('开始创建主窗口')
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1600,
|
||||
height: 1000,
|
||||
@@ -77,13 +80,16 @@ function createWindow() {
|
||||
mainWindow.setMenuBarVisibility(false)
|
||||
const devServer = process.env.VITE_DEV_SERVER_URL
|
||||
if (devServer) {
|
||||
log.info(`加载开发服务器: ${devServer}`)
|
||||
mainWindow.loadURL(devServer)
|
||||
} else {
|
||||
const indexHtmlPath = path.join(app.getAppPath(), 'dist', 'index.html')
|
||||
log.info(`加载生产环境页面: ${indexHtmlPath}`)
|
||||
mainWindow.loadFile(indexHtmlPath)
|
||||
}
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
log.info('主窗口已关闭')
|
||||
mainWindow = null
|
||||
})
|
||||
|
||||
@@ -92,6 +98,7 @@ function createWindow() {
|
||||
setDownloadMainWindow(mainWindow)
|
||||
setPythonMainWindow(mainWindow)
|
||||
setGitMainWindow(mainWindow)
|
||||
log.info('主窗口创建完成,服务引用已设置')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +247,62 @@ ipcMain.handle('reset-config', async () => {
|
||||
})
|
||||
|
||||
// 日志文件操作
|
||||
ipcMain.handle('get-log-path', async () => {
|
||||
try {
|
||||
return getLogPath()
|
||||
} catch (error) {
|
||||
log.error('获取日志路径失败:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('get-logs', async (event, lines?: number) => {
|
||||
try {
|
||||
const logFilePath = getLogPath()
|
||||
|
||||
if (!fs.existsSync(logFilePath)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const logs = fs.readFileSync(logFilePath, 'utf8')
|
||||
|
||||
if (lines && lines > 0) {
|
||||
const logLines = logs.split('\n')
|
||||
return logLines.slice(-lines).join('\n')
|
||||
}
|
||||
|
||||
return logs
|
||||
} catch (error) {
|
||||
log.error('读取日志文件失败:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('clear-logs', async () => {
|
||||
try {
|
||||
const logFilePath = getLogPath()
|
||||
|
||||
if (fs.existsSync(logFilePath)) {
|
||||
fs.writeFileSync(logFilePath, '', 'utf8')
|
||||
log.info('日志文件已清空')
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('清空日志文件失败:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('clean-old-logs', async (event, daysToKeep = 7) => {
|
||||
try {
|
||||
cleanOldLogs(daysToKeep)
|
||||
log.info(`已清理${daysToKeep}天前的旧日志文件`)
|
||||
} catch (error) {
|
||||
log.error('清理旧日志文件失败:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
// 保留原有的日志操作方法以兼容现有代码
|
||||
ipcMain.handle('save-logs-to-file', async (event, logs: string) => {
|
||||
try {
|
||||
const appRoot = getAppRoot()
|
||||
@@ -252,9 +315,9 @@ ipcMain.handle('save-logs-to-file', async (event, logs: string) => {
|
||||
|
||||
const logFilePath = path.join(logsDir, 'app.log')
|
||||
fs.writeFileSync(logFilePath, logs, 'utf8')
|
||||
console.log(`日志已保存到: ${logFilePath}`)
|
||||
log.info(`日志已保存到: ${logFilePath}`)
|
||||
} catch (error) {
|
||||
console.error('保存日志文件失败:', error)
|
||||
log.error('保存日志文件失败:', error)
|
||||
throw error
|
||||
}
|
||||
})
|
||||
@@ -266,13 +329,13 @@ ipcMain.handle('load-logs-from-file', async () => {
|
||||
|
||||
if (fs.existsSync(logFilePath)) {
|
||||
const logs = fs.readFileSync(logFilePath, 'utf8')
|
||||
console.log(`从文件加载日志: ${logFilePath}`)
|
||||
log.info(`从文件加载日志: ${logFilePath}`)
|
||||
return logs
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error('加载日志文件失败:', error)
|
||||
log.error('加载日志文件失败:', error)
|
||||
return null
|
||||
}
|
||||
})
|
||||
@@ -305,22 +368,42 @@ app.on('second-instance', () => {
|
||||
app.on('before-quit', async event => {
|
||||
// 只处理一次,避免多重触发
|
||||
event.preventDefault()
|
||||
log.info('应用准备退出')
|
||||
try {
|
||||
await stopBackend()
|
||||
log.info('后端服务已停止')
|
||||
} catch (e) {
|
||||
log.error('停止后端时出错:', e)
|
||||
console.error('停止后端时出错:', e)
|
||||
} finally {
|
||||
log.info('应用退出')
|
||||
app.exit(0)
|
||||
}
|
||||
})
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// 初始化日志系统
|
||||
setupLogger()
|
||||
|
||||
// 清理7天前的旧日志
|
||||
cleanOldLogs(7)
|
||||
|
||||
log.info('应用启动')
|
||||
log.info(`应用版本: ${app.getVersion()}`)
|
||||
log.info(`Electron版本: ${process.versions.electron}`)
|
||||
log.info(`Node版本: ${process.versions.node}`)
|
||||
log.info(`平台: ${process.platform}`)
|
||||
|
||||
// 检查管理员权限
|
||||
if (!isRunningAsAdmin()) {
|
||||
log.warn('应用未以管理员权限运行')
|
||||
console.log('应用未以管理员权限运行')
|
||||
// 在生产环境中,可以选择是否强制要求管理员权限
|
||||
// 这里先创建窗口,让用户选择是否重新启动
|
||||
} else {
|
||||
log.info('应用以管理员权限运行')
|
||||
}
|
||||
|
||||
createWindow()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('Preload loaded')
|
||||
console.log('预加载脚本已加载')
|
||||
})
|
||||
|
||||
// 暴露安全的 API 给渲染进程
|
||||
@@ -31,6 +31,12 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
resetConfig: () => ipcRenderer.invoke('reset-config'),
|
||||
|
||||
// 日志文件操作
|
||||
getLogPath: () => ipcRenderer.invoke('get-log-path'),
|
||||
getLogs: (lines?: number) => ipcRenderer.invoke('get-logs', lines),
|
||||
clearLogs: () => ipcRenderer.invoke('clear-logs'),
|
||||
cleanOldLogs: (daysToKeep?: number) => ipcRenderer.invoke('clean-old-logs', daysToKeep),
|
||||
|
||||
// 保留原有方法以兼容现有代码
|
||||
saveLogsToFile: (logs: string) => ipcRenderer.invoke('save-logs-to-file', logs),
|
||||
loadLogsFromFile: () => ipcRenderer.invoke('load-logs-from-file'),
|
||||
|
||||
|
||||
140
frontend/electron/services/logService.ts
Normal file
140
frontend/electron/services/logService.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import log from 'electron-log'
|
||||
import * as path from 'path'
|
||||
import { getAppRoot } from './environmentService'
|
||||
|
||||
// 移除ANSI颜色转义字符的函数
|
||||
function stripAnsiColors(text: string): string {
|
||||
// 匹配ANSI转义序列的正则表达式 - 更完整的模式
|
||||
const ansiRegex = /\x1b\[[0-9;]*[mGKHF]|\x1b\[[\d;]*[A-Za-z]/g
|
||||
return text.replace(ansiRegex, '')
|
||||
}
|
||||
|
||||
// 获取应用安装目录下的日志路径
|
||||
function getLogDirectory(): string {
|
||||
const appRoot = getAppRoot()
|
||||
return path.join(appRoot, 'logs')
|
||||
}
|
||||
|
||||
// 配置日志系统
|
||||
export function setupLogger() {
|
||||
// 设置日志文件路径到软件安装目录
|
||||
const logPath = getLogDirectory()
|
||||
|
||||
// 确保日志目录存在
|
||||
const fs = require('fs')
|
||||
if (!fs.existsSync(logPath)) {
|
||||
fs.mkdirSync(logPath, { recursive: true })
|
||||
}
|
||||
|
||||
// 配置日志格式
|
||||
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.level = 'debug'
|
||||
log.transports.console.level = 'debug'
|
||||
|
||||
// 设置文件大小限制 (10MB)
|
||||
log.transports.file.maxSize = 10 * 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
|
||||
}
|
||||
}
|
||||
|
||||
// 捕获未处理的异常和Promise拒绝
|
||||
log.catchErrors({
|
||||
showDialog: false,
|
||||
onError: (options: any) => {
|
||||
log.error('未处理的错误:', options.error)
|
||||
log.error('版本信息:', options.versions)
|
||||
log.error('进程类型:', options.processType)
|
||||
},
|
||||
})
|
||||
|
||||
// 重写console方法,将所有控制台输出重定向到日志
|
||||
const originalConsole = {
|
||||
log: console.log,
|
||||
error: console.error,
|
||||
warn: console.warn,
|
||||
info: console.info,
|
||||
debug: console.debug,
|
||||
}
|
||||
|
||||
console.log = (...args) => {
|
||||
log.info(...args)
|
||||
originalConsole.log(...args)
|
||||
}
|
||||
|
||||
console.error = (...args) => {
|
||||
log.error(...args)
|
||||
originalConsole.error(...args)
|
||||
}
|
||||
|
||||
console.warn = (...args) => {
|
||||
log.warn(...args)
|
||||
originalConsole.warn(...args)
|
||||
}
|
||||
|
||||
console.info = (...args) => {
|
||||
log.info(...args)
|
||||
originalConsole.info(...args)
|
||||
}
|
||||
|
||||
console.debug = (...args) => {
|
||||
log.debug(...args)
|
||||
originalConsole.debug(...args)
|
||||
}
|
||||
|
||||
log.info('日志系统初始化完成')
|
||||
log.info(`日志文件路径: ${path.join(logPath, 'main.log')}`)
|
||||
|
||||
return log
|
||||
}
|
||||
|
||||
// 导出日志实例和工具函数
|
||||
export { log, stripAnsiColors }
|
||||
|
||||
// 获取日志文件路径
|
||||
export function getLogPath(): string {
|
||||
return path.join(getLogDirectory(), 'main.log')
|
||||
}
|
||||
|
||||
// 清理旧日志文件
|
||||
export function cleanOldLogs(daysToKeep: number = 7) {
|
||||
const fs = require('fs')
|
||||
const logDir = getLogDirectory()
|
||||
|
||||
if (!fs.existsSync(logDir)) {
|
||||
return
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(logDir)
|
||||
const now = Date.now()
|
||||
const maxAge = daysToKeep * 24 * 60 * 60 * 1000 // 转换为毫秒
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import { BrowserWindow } from 'electron'
|
||||
import AdmZip from 'adm-zip'
|
||||
import { downloadFile } from './downloadService'
|
||||
import { ChildProcessWithoutNullStreams } from 'node:child_process'
|
||||
import { log, stripAnsiColors } from './logService'
|
||||
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
@@ -101,13 +103,13 @@ async function installPip(pythonPath: string, appRoot: string): Promise<void> {
|
||||
})
|
||||
|
||||
process.stdout?.on('data', data => {
|
||||
const output = data.toString()
|
||||
console.log('pip安装输出:', output)
|
||||
const output = stripAnsiColors(data.toString())
|
||||
log.info('pip安装输出:', output)
|
||||
})
|
||||
|
||||
process.stderr?.on('data', data => {
|
||||
const errorOutput = data.toString()
|
||||
console.log('pip安装错误输出:', errorOutput)
|
||||
const errorOutput = stripAnsiColors(data.toString())
|
||||
log.warn('pip安装错误输出:', errorOutput)
|
||||
})
|
||||
|
||||
process.on('close', code => {
|
||||
@@ -135,13 +137,13 @@ async function installPip(pythonPath: string, appRoot: string): Promise<void> {
|
||||
})
|
||||
|
||||
verifyProcess.stdout?.on('data', data => {
|
||||
const output = data.toString()
|
||||
console.log('pip版本信息:', output)
|
||||
const output = stripAnsiColors(data.toString())
|
||||
log.info('pip版本信息:', output)
|
||||
})
|
||||
|
||||
verifyProcess.stderr?.on('data', data => {
|
||||
const errorOutput = data.toString()
|
||||
console.log('pip版本检查错误:', errorOutput)
|
||||
const errorOutput = stripAnsiColors(data.toString())
|
||||
log.warn('pip版本检查错误:', errorOutput)
|
||||
})
|
||||
|
||||
verifyProcess.on('close', code => {
|
||||
@@ -367,8 +369,8 @@ export async function installDependencies(
|
||||
)
|
||||
|
||||
process.stdout?.on('data', data => {
|
||||
const output = data.toString()
|
||||
console.log('Pip output:', output)
|
||||
const output = stripAnsiColors(data.toString())
|
||||
log.info('Pip output:', output)
|
||||
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('download-progress', {
|
||||
@@ -381,8 +383,8 @@ export async function installDependencies(
|
||||
})
|
||||
|
||||
process.stderr?.on('data', data => {
|
||||
const errorOutput = data.toString()
|
||||
console.error('Pip error:', errorOutput)
|
||||
const errorOutput = stripAnsiColors(data.toString())
|
||||
log.error('Pip error:', errorOutput)
|
||||
})
|
||||
|
||||
process.on('close', code => {
|
||||
@@ -508,12 +510,12 @@ export async function startBackend(appRoot: string, timeoutMs = 30_000) {
|
||||
backendProc.stderr.setEncoding('utf8')
|
||||
|
||||
backendProc.stdout.on('data', d => {
|
||||
const line = d.toString().trim()
|
||||
if (line) console.log('[Backend]', line)
|
||||
const line = stripAnsiColors(d.toString().trim())
|
||||
if (line) log.info('[Backend]', line)
|
||||
})
|
||||
backendProc.stderr.on('data', d => {
|
||||
const line = d.toString().trim()
|
||||
if (line) console.log('[Backend]', line)
|
||||
const line = stripAnsiColors(d.toString().trim())
|
||||
if (line) log.info('[Backend]', line)
|
||||
})
|
||||
|
||||
backendProc.once('exit', (code, signal) => {
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"ant-design-vue": "4.x",
|
||||
"axios": "^1.11.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"electron-log": "^5.4.3",
|
||||
"form-data": "^4.0.4",
|
||||
"markdown-it": "^14.1.0",
|
||||
"vue": "^3.5.17",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ConfigProvider } from 'ant-design-vue'
|
||||
import { useTheme } from './composables/useTheme.ts'
|
||||
import AppLayout from './components/AppLayout.vue'
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||
import { logger } from '@/utils/logger'
|
||||
|
||||
const route = useRoute()
|
||||
const { antdTheme, initTheme } = useTheme()
|
||||
@@ -13,7 +14,9 @@ const { antdTheme, initTheme } = useTheme()
|
||||
const isInitializationPage = computed(() => route.name === 'Initialization')
|
||||
|
||||
onMounted(() => {
|
||||
logger.info('App组件已挂载')
|
||||
initTheme()
|
||||
logger.info('主题初始化完成')
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
189
frontend/src/components/LogViewer.vue
Normal file
189
frontend/src/components/LogViewer.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="log-viewer">
|
||||
<div class="log-controls">
|
||||
<a-space wrap>
|
||||
<a-button @click="refreshLogs" :loading="loading">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
刷新日志
|
||||
</a-button>
|
||||
|
||||
<a-select v-model:value="logLines" @change="refreshLogs" style="width: 120px">
|
||||
<a-select-option :value="100">最近100行</a-select-option>
|
||||
<a-select-option :value="500">最近500行</a-select-option>
|
||||
<a-select-option :value="1000">最近1000行</a-select-option>
|
||||
<a-select-option :value="0">全部日志</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-button @click="clearLogs" :loading="clearing" type="primary" danger>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
清空日志
|
||||
</a-button>
|
||||
|
||||
<a-button @click="cleanOldLogs" :loading="cleaning">
|
||||
<template #icon>
|
||||
<ClearOutlined />
|
||||
</template>
|
||||
清理旧日志
|
||||
</a-button>
|
||||
|
||||
<a-button @click="openLogDirectory">
|
||||
<template #icon>
|
||||
<FolderOpenOutlined />
|
||||
</template>
|
||||
打开日志目录
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="log-info">
|
||||
<a-space>
|
||||
<span>日志文件: {{ logPath }}</span>
|
||||
<span>总行数: {{ totalLines }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="log-content">
|
||||
<a-textarea v-model:value="logs" :rows="25" readonly class="log-textarea" placeholder="暂无日志内容" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
DeleteOutlined,
|
||||
ClearOutlined,
|
||||
FolderOpenOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { logger } from '@/utils/logger'
|
||||
|
||||
const logs = ref('')
|
||||
const logPath = ref('')
|
||||
const logLines = ref(500)
|
||||
const totalLines = ref(0)
|
||||
const loading = ref(false)
|
||||
const clearing = ref(false)
|
||||
const cleaning = ref(false)
|
||||
|
||||
// 刷新日志
|
||||
const refreshLogs = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const logContent = await logger.getLogs(logLines.value || undefined)
|
||||
logs.value = logContent
|
||||
totalLines.value = logContent.split('\n').filter(line => line.trim()).length
|
||||
|
||||
// 自动滚动到底部
|
||||
setTimeout(() => {
|
||||
const textarea = document.querySelector('.log-textarea textarea') as HTMLTextAreaElement
|
||||
if (textarea) {
|
||||
textarea.scrollTop = textarea.scrollHeight
|
||||
}
|
||||
}, 100)
|
||||
} catch (error) {
|
||||
message.error('获取日志失败: ' + error)
|
||||
logger.error('获取日志失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 清空日志
|
||||
const clearLogs = async () => {
|
||||
clearing.value = true
|
||||
try {
|
||||
await logger.clearLogs()
|
||||
logs.value = ''
|
||||
totalLines.value = 0
|
||||
message.success('日志已清空')
|
||||
} catch (error) {
|
||||
message.error('清空日志失败: ' + error)
|
||||
logger.error('清空日志失败:', error)
|
||||
} finally {
|
||||
clearing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 清理旧日志
|
||||
const cleanOldLogs = async () => {
|
||||
cleaning.value = true
|
||||
try {
|
||||
await logger.cleanOldLogs(7)
|
||||
message.success('已清理7天前的旧日志文件')
|
||||
} catch (error) {
|
||||
message.error('清理旧日志失败: ' + error)
|
||||
logger.error('清理旧日志失败:', error)
|
||||
} finally {
|
||||
cleaning.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 打开日志目录
|
||||
const openLogDirectory = async () => {
|
||||
try {
|
||||
const path = await logger.getLogPath()
|
||||
// 获取日志目录路径
|
||||
const logDir = path.substring(0, path.lastIndexOf('\\') || path.lastIndexOf('/'))
|
||||
|
||||
if (window.electronAPI?.openUrl) {
|
||||
await window.electronAPI.openUrl(`file://${logDir}`)
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('打开日志目录失败: ' + error)
|
||||
logger.error('打开日志目录失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取日志文件路径
|
||||
const getLogPath = async () => {
|
||||
try {
|
||||
logPath.value = await logger.getLogPath()
|
||||
} catch (error) {
|
||||
logger.error('获取日志路径失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLogPath()
|
||||
refreshLogs()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.log-viewer {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.log-controls {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.log-info {
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.log-textarea :deep(.ant-input) {
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
border: none;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.log-textarea :deep(.ant-input:focus) {
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
@@ -9,6 +9,9 @@ import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
|
||||
// 导入日志系统
|
||||
import { logger } from '@/utils/logger'
|
||||
|
||||
// 配置dayjs中文本地化
|
||||
dayjs.locale('zh-cn')
|
||||
|
||||
@@ -17,6 +20,10 @@ import { API_ENDPOINTS } from '@/config/mirrors'
|
||||
// 配置API基础URL
|
||||
OpenAPI.BASE = API_ENDPOINTS.local
|
||||
|
||||
// 记录应用启动
|
||||
logger.info('前端应用开始初始化')
|
||||
logger.info(`API基础URL: ${OpenAPI.BASE}`)
|
||||
|
||||
// 创建应用实例
|
||||
const app = createApp(App)
|
||||
|
||||
@@ -24,5 +31,12 @@ const app = createApp(App)
|
||||
app.use(Antd)
|
||||
app.use(router)
|
||||
|
||||
// 全局错误处理
|
||||
app.config.errorHandler = (err, instance, info) => {
|
||||
logger.error('Vue应用错误:', err, '组件信息:', info)
|
||||
}
|
||||
|
||||
// 挂载应用
|
||||
app.mount('#app')
|
||||
|
||||
logger.info('前端应用初始化完成')
|
||||
|
||||
42
frontend/src/types/electron.d.ts
vendored
42
frontend/src/types/electron.d.ts
vendored
@@ -1,28 +1,38 @@
|
||||
export interface ElectronAPI {
|
||||
openDevTools: () => Promise<void>
|
||||
selectFolder: () => Promise<string | null>
|
||||
selectFile: (filters?: any[]) => Promise<string | null>
|
||||
selectFile: (filters?: any[]) => Promise<string[]>
|
||||
openUrl: (url: string) => Promise<{ success: boolean; error?: string }>
|
||||
|
||||
// 初始化相关API
|
||||
checkEnvironment: () => Promise<{
|
||||
pythonExists: boolean
|
||||
gitExists: boolean
|
||||
backendExists: boolean
|
||||
dependenciesInstalled: boolean
|
||||
isInitialized: boolean
|
||||
}>
|
||||
downloadPython: (mirror?: string) => Promise<{ success: boolean; error?: string }>
|
||||
downloadGit: () => Promise<{ success: boolean; error?: string }>
|
||||
installDependencies: (mirror?: string) => Promise<{ success: boolean; error?: string }>
|
||||
cloneBackend: (repoUrl?: string) => Promise<{ success: boolean; error?: string }>
|
||||
updateBackend: (repoUrl?: string) => Promise<{ success: boolean; error?: string }>
|
||||
startBackend: () => Promise<{ success: boolean; error?: string }>
|
||||
|
||||
checkEnvironment: () => Promise<any>
|
||||
downloadPython: (mirror?: string) => Promise<any>
|
||||
installPip: () => Promise<any>
|
||||
downloadGit: () => Promise<any>
|
||||
installDependencies: (mirror?: string) => Promise<any>
|
||||
cloneBackend: (repoUrl?: string) => Promise<any>
|
||||
updateBackend: (repoUrl?: string) => Promise<any>
|
||||
startBackend: () => Promise<any>
|
||||
|
||||
// 管理员权限相关
|
||||
checkAdmin: () => Promise<boolean>
|
||||
restartAsAdmin: () => Promise<void>
|
||||
|
||||
// 配置文件操作
|
||||
saveConfig: (config: any) => Promise<void>
|
||||
loadConfig: () => Promise<any>
|
||||
resetConfig: () => Promise<void>
|
||||
|
||||
// 日志文件操作
|
||||
getLogPath: () => Promise<string>
|
||||
getLogs: (lines?: number) => Promise<string>
|
||||
clearLogs: () => Promise<void>
|
||||
cleanOldLogs: (daysToKeep?: number) => Promise<void>
|
||||
|
||||
// 保留原有方法以兼容现有代码
|
||||
saveLogsToFile: (logs: string) => Promise<void>
|
||||
loadLogsFromFile: () => Promise<string | null>
|
||||
|
||||
|
||||
// 监听下载进度
|
||||
onDownloadProgress: (callback: (progress: any) => void) => void
|
||||
removeDownloadProgressListener: () => void
|
||||
|
||||
89
frontend/src/utils/logger.ts
Normal file
89
frontend/src/utils/logger.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
// 渲染进程日志工具
|
||||
interface ElectronAPI {
|
||||
getLogPath: () => Promise<string>
|
||||
getLogs: (lines?: number) => Promise<string>
|
||||
clearLogs: () => Promise<void>
|
||||
cleanOldLogs: (daysToKeep?: number) => Promise<void>
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: ElectronAPI
|
||||
}
|
||||
}
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG = 'DEBUG',
|
||||
INFO = 'INFO',
|
||||
WARN = 'WARN',
|
||||
ERROR = 'ERROR'
|
||||
}
|
||||
|
||||
class Logger {
|
||||
// 直接使用原生console,主进程会自动处理日志记录
|
||||
debug(message: string, ...args: any[]) {
|
||||
console.debug(message, ...args)
|
||||
}
|
||||
|
||||
info(message: string, ...args: any[]) {
|
||||
console.info(message, ...args)
|
||||
}
|
||||
|
||||
warn(message: string, ...args: any[]) {
|
||||
console.warn(message, ...args)
|
||||
}
|
||||
|
||||
error(message: string, ...args: any[]) {
|
||||
console.error(message, ...args)
|
||||
}
|
||||
|
||||
// 获取日志文件路径
|
||||
async getLogPath(): Promise<string> {
|
||||
if (window.electronAPI) {
|
||||
return await window.electronAPI.getLogPath()
|
||||
}
|
||||
throw new Error('Electron API not available')
|
||||
}
|
||||
|
||||
// 获取日志内容
|
||||
async getLogs(lines?: number): Promise<string> {
|
||||
if (window.electronAPI) {
|
||||
return await window.electronAPI.getLogs(lines)
|
||||
}
|
||||
throw new Error('Electron API not available')
|
||||
}
|
||||
|
||||
// 清空日志
|
||||
async clearLogs(): Promise<void> {
|
||||
if (window.electronAPI) {
|
||||
await window.electronAPI.clearLogs()
|
||||
console.info('日志已清空')
|
||||
} else {
|
||||
throw new Error('Electron API not available')
|
||||
}
|
||||
}
|
||||
|
||||
// 清理旧日志
|
||||
async cleanOldLogs(daysToKeep: number = 7): Promise<void> {
|
||||
if (window.electronAPI) {
|
||||
await window.electronAPI.cleanOldLogs(daysToKeep)
|
||||
console.info(`已清理${daysToKeep}天前的旧日志`)
|
||||
} else {
|
||||
throw new Error('Electron API not available')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建全局日志实例
|
||||
export const logger = new Logger()
|
||||
|
||||
// 捕获未处理的错误(直接使用console,主进程会处理日志记录)
|
||||
window.addEventListener('error', (event) => {
|
||||
console.error('未处理的错误:', event.error?.message || event.message, event.error?.stack)
|
||||
})
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
console.error('未处理的Promise拒绝:', event.reason)
|
||||
})
|
||||
|
||||
export default logger
|
||||
@@ -2449,6 +2449,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"electron-log@npm:^5.4.3":
|
||||
version: 5.4.3
|
||||
resolution: "electron-log@npm:5.4.3"
|
||||
checksum: 10c0/84d398d2a53cd73fdd1118f7b376b08d74938c79cb4aa3454aa176b61ebf1522f5cc92179355e69e0053301df7a19fbe6cf302319e5fbe5fc6a1600f282a2646
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"electron-publish@npm:26.0.11":
|
||||
version: 26.0.11
|
||||
resolution: "electron-publish@npm:26.0.11"
|
||||
@@ -3061,6 +3068,7 @@ __metadata:
|
||||
dayjs: "npm:^1.11.13"
|
||||
electron: "npm:^37.2.5"
|
||||
electron-builder: "npm:^26.0.12"
|
||||
electron-log: "npm:^5.4.3"
|
||||
eslint: "npm:^9.32.0"
|
||||
eslint-config-prettier: "npm:^10.1.8"
|
||||
eslint-plugin-prettier: "npm:^5.5.3"
|
||||
|
||||
Reference in New Issue
Block a user