Merge branch 'feature/refactor' of github.com:DLmaster361/AUTO_MAA into feature/refactor
This commit is contained in:
@@ -10,8 +10,10 @@ import {
|
||||
installPipPackage,
|
||||
installDependencies,
|
||||
startBackend,
|
||||
stopBackend,
|
||||
} from './services/pythonService'
|
||||
import { setMainWindow as setGitMainWindow, downloadGit, cloneBackend } from './services/gitService'
|
||||
import { setupLogger, log, getLogPath, cleanOldLogs } from './services/logService'
|
||||
|
||||
// 检查是否以管理员权限运行
|
||||
function isRunningAsAdmin(): boolean {
|
||||
@@ -59,6 +61,8 @@ function restartAsAdmin(): void {
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
function createWindow() {
|
||||
log.info('开始创建主窗口')
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1600,
|
||||
height: 1000,
|
||||
@@ -76,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
|
||||
})
|
||||
|
||||
@@ -91,6 +98,7 @@ function createWindow() {
|
||||
setDownloadMainWindow(mainWindow)
|
||||
setPythonMainWindow(mainWindow)
|
||||
setGitMainWindow(mainWindow)
|
||||
log.info('主窗口创建完成,服务引用已设置')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,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()
|
||||
@@ -251,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
|
||||
}
|
||||
})
|
||||
@@ -265,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
|
||||
}
|
||||
})
|
||||
@@ -301,13 +365,45 @@ 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -4,6 +4,9 @@ import { spawn } from 'child_process'
|
||||
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
|
||||
|
||||
@@ -100,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 => {
|
||||
@@ -134,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 => {
|
||||
@@ -366,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', {
|
||||
@@ -380,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 => {
|
||||
@@ -470,73 +473,139 @@ export async function installPipPackage(
|
||||
}
|
||||
|
||||
// 启动后端
|
||||
export async function startBackend(appRoot: string): Promise<{ success: boolean; error?: string }> {
|
||||
let backendProc: ChildProcessWithoutNullStreams | null = null
|
||||
|
||||
/**
|
||||
* 启动后端
|
||||
* @param appRoot 项目根目录
|
||||
* @param timeoutMs 等待启动超时(默认 30 秒)
|
||||
*/
|
||||
export async function startBackend(appRoot: string, timeoutMs = 30_000) {
|
||||
try {
|
||||
const pythonPath = path.join(appRoot, 'environment', 'python', 'python.exe')
|
||||
const backendPath = path.join(appRoot)
|
||||
const mainPyPath = path.join(backendPath, 'main.py')
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!fs.existsSync(pythonPath)) {
|
||||
throw new Error('Python可执行文件不存在')
|
||||
}
|
||||
if (!fs.existsSync(mainPyPath)) {
|
||||
throw new Error('后端主文件不存在')
|
||||
}
|
||||
|
||||
console.log(`启动后端指令: "${pythonPath}" "${mainPyPath}"(cwd: ${appRoot})`)
|
||||
|
||||
// 启动后端进程
|
||||
const backendProcess = spawn(pythonPath, [mainPyPath], {
|
||||
cwd: appRoot,
|
||||
stdio: 'pipe',
|
||||
env: {
|
||||
...process.env,
|
||||
PYTHONIOENCODING: 'utf-8', // 设置Python输出编码为UTF-8
|
||||
},
|
||||
})
|
||||
|
||||
// 等待后端启动
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('后端启动超时'))
|
||||
}, 30000) // 30秒超时
|
||||
|
||||
backendProcess.stdout?.on('data', data => {
|
||||
const output = data.toString()
|
||||
console.log('Backend output:', output)
|
||||
|
||||
// 检查是否包含启动成功的标志
|
||||
if (output.includes('Uvicorn running') || output.includes('36163')) {
|
||||
clearTimeout(timeout)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
||||
// ✅ 重要:也要监听 stderr
|
||||
backendProcess.stderr?.on('data', data => {
|
||||
const output = data.toString()
|
||||
console.error('Backend error:', output) // 保留原有日志
|
||||
|
||||
// ✅ 在 stderr 中也检查启动标志
|
||||
if (output.includes('Uvicorn running') || output.includes('36163')) {
|
||||
clearTimeout(timeout)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
||||
backendProcess.stderr?.on('data', data => {
|
||||
console.error('Backend error:', data.toString())
|
||||
})
|
||||
|
||||
backendProcess.on('error', error => {
|
||||
clearTimeout(timeout)
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
|
||||
// 如果已经在运行,直接返回
|
||||
if (backendProc && !backendProc.killed && backendProc.exitCode == null) {
|
||||
console.log('[Backend] 已在运行, PID =', backendProc.pid)
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : String(error) }
|
||||
}
|
||||
|
||||
const pythonExe = path.join(appRoot, 'environment', 'python', 'python.exe')
|
||||
const mainPy = path.join(appRoot, 'main.py')
|
||||
|
||||
if (!fs.existsSync(pythonExe)) {
|
||||
throw new Error(`Python可执行文件不存在: ${pythonExe}`)
|
||||
}
|
||||
if (!fs.existsSync(mainPy)) {
|
||||
throw new Error(`后端主文件不存在: ${mainPy}`)
|
||||
}
|
||||
|
||||
console.log(`[Backend] spawn "${pythonExe}" "${mainPy}" (cwd=${appRoot})`)
|
||||
|
||||
backendProc = spawn(pythonExe, [mainPy], {
|
||||
cwd: appRoot,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: { ...process.env, PYTHONIOENCODING: 'utf-8' },
|
||||
})
|
||||
|
||||
backendProc.stdout.setEncoding('utf8')
|
||||
backendProc.stderr.setEncoding('utf8')
|
||||
|
||||
backendProc.stdout.on('data', d => {
|
||||
const line = stripAnsiColors(d.toString().trim())
|
||||
if (line) log.info('[Backend]', line)
|
||||
})
|
||||
backendProc.stderr.on('data', d => {
|
||||
const line = stripAnsiColors(d.toString().trim())
|
||||
if (line) log.info('[Backend]', line)
|
||||
})
|
||||
|
||||
backendProc.once('exit', (code, signal) => {
|
||||
console.log('[Backend] 退出', { code, signal })
|
||||
backendProc = null
|
||||
})
|
||||
backendProc.once('error', e => {
|
||||
console.error('[Backend] 进程错误:', e)
|
||||
})
|
||||
|
||||
// 等待启动成功(匹配 Uvicorn 的输出)
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
let settled = false
|
||||
const timer = setTimeout(() => {
|
||||
if (!settled) {
|
||||
settled = true
|
||||
reject(new Error('后端启动超时'))
|
||||
}
|
||||
}, timeoutMs)
|
||||
|
||||
const checkReady = (buf: Buffer | string) => {
|
||||
if (settled) return
|
||||
const s = buf.toString()
|
||||
if (/Uvicorn running|http:\/\/0\.0\.0\.0:\d+/.test(s)) {
|
||||
settled = true
|
||||
clearTimeout(timer)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
|
||||
backendProc!.stdout.on('data', checkReady)
|
||||
backendProc!.stderr.on('data', checkReady)
|
||||
|
||||
backendProc!.once('exit', (code, sig) => {
|
||||
if (!settled) {
|
||||
settled = true
|
||||
clearTimeout(timer)
|
||||
reject(new Error(`后端提前退出: code=${code}, signal=${sig ?? ''}`))
|
||||
}
|
||||
})
|
||||
backendProc!.once('error', err => {
|
||||
if (!settled) {
|
||||
settled = true
|
||||
clearTimeout(timer)
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
console.log('[Backend] 启动成功, PID =', backendProc.pid)
|
||||
return { success: true }
|
||||
} catch (e) {
|
||||
console.error('[Backend] 启动失败:', e)
|
||||
return { success: false, error: e instanceof Error ? e.message : String(e) }
|
||||
}
|
||||
}
|
||||
|
||||
/** 停止后端进程(如果没启动就直接返回成功) */
|
||||
export async function stopBackend() {
|
||||
if (!backendProc || backendProc.killed) {
|
||||
console.log('[Backend] 未运行,无需停止')
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
const pid = backendProc.pid
|
||||
console.log('[Backend] 正在停止后端服务, PID =', pid)
|
||||
|
||||
return new Promise<{ success: boolean; error?: string }>(resolve => {
|
||||
// 清监听,避免重复日志
|
||||
backendProc?.stdout?.removeAllListeners('data')
|
||||
backendProc?.stderr?.removeAllListeners('data')
|
||||
|
||||
backendProc!.once('exit', (code, signal) => {
|
||||
console.log('[Backend] 已退出', { code, signal })
|
||||
backendProc = null
|
||||
resolve({ success: true })
|
||||
})
|
||||
|
||||
backendProc!.once('error', err => {
|
||||
console.error('[Backend] 停止时出错:', err)
|
||||
backendProc = null
|
||||
resolve({ success: false, error: err instanceof Error ? err.message : String(err) })
|
||||
})
|
||||
|
||||
try {
|
||||
backendProc!.kill() // 默认 SIGTERM,Windows 下等价于结束进程
|
||||
} catch (e) {
|
||||
console.error('[Backend] kill 调用失败:', e)
|
||||
backendProc = null
|
||||
resolve({ success: false, error: e instanceof Error ? e.message : String(e) })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -6,54 +6,53 @@ export type GeneralConfig_Script = {
|
||||
/**
|
||||
* 脚本可执行文件路径
|
||||
*/
|
||||
ScriptPath?: (string | null);
|
||||
ScriptPath?: string | null
|
||||
/**
|
||||
* 脚本启动附加命令参数
|
||||
*/
|
||||
Arguments?: (string | null);
|
||||
Arguments?: string | null
|
||||
/**
|
||||
* 是否追踪脚本子进程
|
||||
*/
|
||||
IfTrackProcess?: (boolean | null);
|
||||
IfTrackProcess?: boolean | null
|
||||
/**
|
||||
* 配置文件路径
|
||||
*/
|
||||
ConfigPath?: (string | null);
|
||||
ConfigPath?: string | null
|
||||
/**
|
||||
* 配置文件类型: 单个文件, 文件夹
|
||||
*/
|
||||
ConfigPathMode?: ('File' | 'Folder' | null);
|
||||
ConfigPathMode?: 'File' | 'Folder' | null
|
||||
/**
|
||||
* 更新配置时机, 从不, 仅成功时, 仅失败时, 任务结束时
|
||||
*/
|
||||
UpdateConfigMode?: ('Never' | 'Success' | 'Failure' | 'Always' | null);
|
||||
UpdateConfigMode?: 'Never' | 'Success' | 'Failure' | 'Always' | null
|
||||
/**
|
||||
* 日志文件路径
|
||||
*/
|
||||
LogPath?: (string | null);
|
||||
LogPath?: string | null
|
||||
/**
|
||||
* 日志文件名格式
|
||||
*/
|
||||
LogPathFormat?: (string | null);
|
||||
LogPathFormat?: string | null
|
||||
/**
|
||||
* 日志时间戳开始位置
|
||||
*/
|
||||
LogTimeStart?: (number | null);
|
||||
LogTimeStart?: number | null
|
||||
/**
|
||||
* 日志时间戳结束位置
|
||||
*/
|
||||
LogTimeEnd?: (number | null);
|
||||
LogTimeEnd?: number | null
|
||||
/**
|
||||
* 日志时间戳格式
|
||||
*/
|
||||
LogTimeFormat?: (string | null);
|
||||
LogTimeFormat?: string | null
|
||||
/**
|
||||
* 成功时日志
|
||||
*/
|
||||
SuccessLog?: (string | null);
|
||||
SuccessLog?: string | null
|
||||
/**
|
||||
* 错误时日志
|
||||
*/
|
||||
ErrorLog?: (string | null);
|
||||
};
|
||||
|
||||
ErrorLog?: string | null
|
||||
}
|
||||
|
||||
@@ -1,344 +1,189 @@
|
||||
<template>
|
||||
<div class="log-viewer">
|
||||
<div class="log-header">
|
||||
<div class="log-controls">
|
||||
<a-select v-model:value="selectedLevel" style="width: 120px" @change="filterLogs">
|
||||
<a-select-option value="all">所有级别</a-select-option>
|
||||
<a-select-option value="debug">Debug</a-select-option>
|
||||
<a-select-option value="info">Info</a-select-option>
|
||||
<a-select-option value="warn">Warn</a-select-option>
|
||||
<a-select-option value="error">Error</a-select-option>
|
||||
<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-input-search
|
||||
v-model:value="searchText"
|
||||
placeholder="搜索日志..."
|
||||
style="width: 200px"
|
||||
@search="filterLogs"
|
||||
@change="filterLogs"
|
||||
/>
|
||||
|
||||
<a-button @click="clearLogs" danger>
|
||||
<a-button @click="clearLogs" :loading="clearing" type="primary" danger>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
清空日志
|
||||
</a-button>
|
||||
|
||||
<a-button @click="downloadLogs" type="primary">
|
||||
<a-button @click="cleanOldLogs" :loading="cleaning">
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
<ClearOutlined />
|
||||
</template>
|
||||
导出日志
|
||||
清理旧日志
|
||||
</a-button>
|
||||
|
||||
<a-button @click="toggleAutoScroll" :type="autoScroll ? 'primary' : 'default'">
|
||||
<a-button @click="openLogDirectory">
|
||||
<template #icon>
|
||||
<VerticalAlignBottomOutlined />
|
||||
<FolderOpenOutlined />
|
||||
</template>
|
||||
自动滚动
|
||||
打开日志目录
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div class="log-stats">总计: {{ filteredLogs.length }} 条日志</div>
|
||||
<div class="log-info">
|
||||
<a-space>
|
||||
<span>日志文件: {{ logPath }}</span>
|
||||
<span>总行数: {{ totalLines }}</span>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<div ref="logContainer" class="log-container" @scroll="handleScroll">
|
||||
<div
|
||||
v-for="(log, index) in filteredLogs"
|
||||
:key="index"
|
||||
class="log-entry"
|
||||
:class="[`log-${log.level}`, { 'log-highlight': highlightedIndex === index }]"
|
||||
>
|
||||
<div class="log-timestamp">{{ log.timestamp }}</div>
|
||||
<div class="log-level">{{ log.level.toUpperCase() }}</div>
|
||||
<div v-if="log.component" class="log-component">[{{ log.component }}]</div>
|
||||
<div class="log-message">{{ log.message }}</div>
|
||||
<div v-if="log.data" class="log-data">
|
||||
<a-button size="small" type="link" @click="toggleDataVisibility(index)">
|
||||
{{ expandedData.has(index) ? '隐藏数据' : '显示数据' }}
|
||||
</a-button>
|
||||
<pre v-if="expandedData.has(index)" class="log-data-content">{{
|
||||
JSON.stringify(log.data, null, 2)
|
||||
}}</pre>
|
||||
</div>
|
||||
</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, computed, nextTick, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import {
|
||||
ReloadOutlined,
|
||||
DeleteOutlined,
|
||||
DownloadOutlined,
|
||||
VerticalAlignBottomOutlined,
|
||||
ClearOutlined,
|
||||
FolderOpenOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { logger, type LogEntry, type LogLevel } from '@/utils/logger'
|
||||
import { logger } from '@/utils/logger'
|
||||
|
||||
const logContainer = ref<HTMLElement>()
|
||||
const selectedLevel = ref<LogLevel | 'all'>('all')
|
||||
const searchText = ref('')
|
||||
const autoScroll = ref(true)
|
||||
const expandedData = ref(new Set<number>())
|
||||
const highlightedIndex = ref(-1)
|
||||
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 logs = logger.getLogs()
|
||||
// 刷新日志
|
||||
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
|
||||
|
||||
const filteredLogs = computed(() => {
|
||||
let filtered = logs.value
|
||||
|
||||
// 按级别过滤
|
||||
if (selectedLevel.value !== 'all') {
|
||||
filtered = filtered.filter(log => log.level === selectedLevel.value)
|
||||
// 自动滚动到底部
|
||||
setTimeout(() => {
|
||||
const textarea = document.querySelector('.log-textarea textarea') as HTMLTextAreaElement
|
||||
if (textarea) {
|
||||
textarea.scrollTop = textarea.scrollHeight
|
||||
}
|
||||
|
||||
// 按搜索文本过滤
|
||||
if (searchText.value) {
|
||||
const search = searchText.value.toLowerCase()
|
||||
filtered = filtered.filter(
|
||||
log =>
|
||||
log.message.toLowerCase().includes(search) ||
|
||||
log.component?.toLowerCase().includes(search) ||
|
||||
(log.data && JSON.stringify(log.data).toLowerCase().includes(search))
|
||||
)
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
function filterLogs() {
|
||||
// 过滤逻辑已在computed中处理
|
||||
nextTick(() => {
|
||||
if (autoScroll.value) {
|
||||
scrollToBottom()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function clearLogs() {
|
||||
logger.clearLogs()
|
||||
expandedData.value.clear()
|
||||
}
|
||||
|
||||
function downloadLogs() {
|
||||
logger.downloadLogs()
|
||||
}
|
||||
|
||||
function toggleAutoScroll() {
|
||||
autoScroll.value = !autoScroll.value
|
||||
if (autoScroll.value) {
|
||||
scrollToBottom()
|
||||
}, 100)
|
||||
} catch (error) {
|
||||
message.error('获取日志失败: ' + error)
|
||||
logger.error('获取日志失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDataVisibility(index: number) {
|
||||
if (expandedData.value.has(index)) {
|
||||
expandedData.value.delete(index)
|
||||
} else {
|
||||
expandedData.value.add(index)
|
||||
// 清空日志
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
if (logContainer.value) {
|
||||
logContainer.value.scrollTop = logContainer.value.scrollHeight
|
||||
// 清理旧日志
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function handleScroll() {
|
||||
if (!logContainer.value) return
|
||||
// 打开日志目录
|
||||
const openLogDirectory = async () => {
|
||||
try {
|
||||
const path = await logger.getLogPath()
|
||||
// 获取日志目录路径
|
||||
const logDir = path.substring(0, path.lastIndexOf('\\') || path.lastIndexOf('/'))
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = logContainer.value
|
||||
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 10
|
||||
|
||||
if (!isAtBottom) {
|
||||
autoScroll.value = false
|
||||
if (window.electronAPI?.openUrl) {
|
||||
await window.electronAPI.openUrl(`file://${logDir}`)
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('打开日志目录失败: ' + error)
|
||||
logger.error('打开日志目录失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听新日志添加
|
||||
let unwatchLogs: (() => void) | null = null
|
||||
// 获取日志文件路径
|
||||
const getLogPath = async () => {
|
||||
try {
|
||||
logPath.value = await logger.getLogPath()
|
||||
} catch (error) {
|
||||
logger.error('获取日志路径失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始滚动到底部
|
||||
nextTick(() => {
|
||||
scrollToBottom()
|
||||
})
|
||||
|
||||
// 监听日志变化
|
||||
unwatchLogs =
|
||||
logs.value && typeof logs.value === 'object' && 'length' in logs.value
|
||||
? () => {} // 如果logs是响应式的,Vue会自动处理
|
||||
: null
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (unwatchLogs) {
|
||||
unwatchLogs()
|
||||
}
|
||||
})
|
||||
|
||||
// 监听日志变化,自动滚动
|
||||
const prevLogsLength = ref(logs.value.length)
|
||||
const checkForNewLogs = () => {
|
||||
if (logs.value.length > prevLogsLength.value) {
|
||||
prevLogsLength.value = logs.value.length
|
||||
if (autoScroll.value) {
|
||||
nextTick(() => {
|
||||
scrollToBottom()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 定期检查新日志
|
||||
const logCheckInterval = setInterval(checkForNewLogs, 100)
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(logCheckInterval)
|
||||
getLogPath()
|
||||
refreshLogs()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.log-viewer {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--ant-color-bg-container);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--ant-color-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.log-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.log-stats {
|
||||
font-size: 14px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
.log-info {
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.log-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
.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-entry {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 2px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.log-entry:hover {
|
||||
|
||||
}
|
||||
|
||||
.log-highlight {
|
||||
background: var(--ant-color-primary-bg) !important;
|
||||
}
|
||||
|
||||
.log-timestamp {
|
||||
color: var(--ant-color-text-tertiary);
|
||||
white-space: nowrap;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
font-weight: bold;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.log-debug .log-level {
|
||||
background: var(--ant-color-fill-secondary);
|
||||
color: var(--ant-color-text-secondary);
|
||||
}
|
||||
|
||||
.log-info .log-level {
|
||||
background: var(--ant-color-info-bg);
|
||||
color: var(--ant-color-info);
|
||||
}
|
||||
|
||||
.log-warn .log-level {
|
||||
background: var(--ant-color-warning-bg);
|
||||
color: var(--ant-color-warning);
|
||||
}
|
||||
|
||||
.log-error .log-level {
|
||||
background: var(--ant-color-error-bg);
|
||||
color: var(--ant-color-error);
|
||||
}
|
||||
|
||||
.log-component {
|
||||
color: var(--ant-color-primary);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
flex: 1;
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.log-data {
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.log-data-content {
|
||||
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.log-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.log-controls {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.log-timestamp,
|
||||
.log-level,
|
||||
.log-component {
|
||||
min-width: auto;
|
||||
}
|
||||
.log-textarea :deep(.ant-input:focus) {
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
||||
@@ -13,15 +13,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { createComponentLogger } from '@/utils/logger'
|
||||
|
||||
const logger = createComponentLogger('AdminCheck')
|
||||
|
||||
async function handleRestartAsAdmin() {
|
||||
try {
|
||||
await window.electronAPI.restartAsAdmin()
|
||||
} catch (error) {
|
||||
logger.error('重启为管理员失败', error)
|
||||
console.error('重启为管理员失败', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -39,12 +39,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { createComponentLogger } from '@/utils/logger'
|
||||
import { getConfig } from '@/utils/config'
|
||||
import { getMirrorUrl } from '@/config/mirrors'
|
||||
import router from '@/router'
|
||||
|
||||
const logger = createComponentLogger('AutoMode')
|
||||
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
@@ -120,7 +119,7 @@ async function startAutoProcess() {
|
||||
|
||||
// 如果初始化时的镜像源不通,让用户重新选择
|
||||
if (!pipResult.success) {
|
||||
logger.warn(`使用镜像源 ${pipMirror} 安装依赖失败,需要重新选择镜像源`)
|
||||
console.warn(`使用镜像源 ${pipMirror} 安装依赖失败,需要重新选择镜像源`)
|
||||
|
||||
// 切换到手动模式让用户重新选择镜像源
|
||||
progressText.value = '依赖安装失败,需要重新配置镜像源'
|
||||
@@ -142,14 +141,14 @@ async function startAutoProcess() {
|
||||
progress.value = 100
|
||||
progressStatus.value = 'success'
|
||||
|
||||
logger.info('自动启动流程完成,即将进入应用')
|
||||
console.log('自动启动流程完成,即将进入应用')
|
||||
|
||||
// 延迟0.5秒后自动进入应用
|
||||
setTimeout(() => {
|
||||
props.onAutoComplete()
|
||||
}, 500)
|
||||
} catch (error) {
|
||||
logger.error('自动启动流程失败', error)
|
||||
console.error('自动启动流程失败', error)
|
||||
progressText.value = `自动启动失败: ${error instanceof Error ? error.message : String(error)}`
|
||||
progressStatus.value = 'exception'
|
||||
|
||||
@@ -169,7 +168,7 @@ async function checkGitUpdate(): Promise<boolean> {
|
||||
const result = await window.electronAPI.checkGitUpdate()
|
||||
return result.hasUpdate || false
|
||||
} catch (error) {
|
||||
logger.warn('检查Git更新失败:', error)
|
||||
console.warn('检查Git更新失败:', error)
|
||||
// 如果检查失败,假设有更新,这样会触发代码拉取和依赖安装
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { notification } from 'ant-design-vue'
|
||||
import { createComponentLogger } from '@/utils/logger'
|
||||
import { saveConfig } from '@/utils/config'
|
||||
import ThemeStep from './ThemeStep.vue'
|
||||
import PythonStep from './PythonStep.vue'
|
||||
@@ -121,7 +120,7 @@ import BackendStep from './BackendStep.vue'
|
||||
import DependenciesStep from './DependenciesStep.vue'
|
||||
import ServiceStep from './ServiceStep.vue'
|
||||
|
||||
const logger = createComponentLogger('ManualMode')
|
||||
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
@@ -284,26 +283,26 @@ async function autoStartSpeedTest() {
|
||||
|
||||
// 安装函数
|
||||
async function installPython() {
|
||||
logger.info('开始安装Python')
|
||||
console.log('开始安装Python')
|
||||
const mirror = pythonStepRef.value?.selectedPythonMirror || 'tsinghua'
|
||||
const result = await window.electronAPI.downloadPython(mirror)
|
||||
if (result.success) {
|
||||
logger.info('Python安装成功')
|
||||
console.log('Python安装成功')
|
||||
await saveConfig({ pythonInstalled: true })
|
||||
} else {
|
||||
logger.error('Python安装失败', result.error)
|
||||
console.error('Python安装失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
async function installGit() {
|
||||
logger.info('开始安装Git工具')
|
||||
console.log('开始安装Git工具')
|
||||
const result = await window.electronAPI.downloadGit()
|
||||
if (result.success) {
|
||||
logger.info('Git工具安装成功')
|
||||
console.log('Git工具安装成功')
|
||||
await saveConfig({ gitInstalled: true })
|
||||
} else {
|
||||
logger.error('Git工具安装失败', result.error)
|
||||
console.error('Git工具安装失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
}
|
||||
@@ -311,13 +310,13 @@ async function installGit() {
|
||||
async function cloneBackend() {
|
||||
const selectedMirror = backendStepRef.value?.selectedGitMirror || 'github'
|
||||
const mirror = backendStepRef.value?.gitMirrors?.find((m: any) => m.key === selectedMirror)
|
||||
logger.info('开始克隆后端代码', { mirror: mirror?.name, url: mirror?.url })
|
||||
console.log('开始克隆后端代码', { mirror: mirror?.name, url: mirror?.url })
|
||||
const result = await window.electronAPI.cloneBackend(mirror?.url)
|
||||
if (result.success) {
|
||||
logger.info('后端代码克隆成功')
|
||||
console.log('后端代码克隆成功')
|
||||
await saveConfig({ backendExists: true })
|
||||
} else {
|
||||
logger.error('后端代码克隆失败', result.error)
|
||||
console.error('后端代码克隆失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
}
|
||||
@@ -325,31 +324,31 @@ async function cloneBackend() {
|
||||
async function updateBackend() {
|
||||
const selectedMirror = backendStepRef.value?.selectedGitMirror || 'github'
|
||||
const mirror = backendStepRef.value?.gitMirrors?.find((m: any) => m.key === selectedMirror)
|
||||
logger.info('开始更新后端代码', { mirror: mirror?.name, url: mirror?.url })
|
||||
console.log('开始更新后端代码', { mirror: mirror?.name, url: mirror?.url })
|
||||
const result = await window.electronAPI.updateBackend(mirror?.url)
|
||||
if (!result.success) {
|
||||
logger.error('后端代码更新失败', result.error)
|
||||
console.error('后端代码更新失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
logger.info('后端代码更新成功')
|
||||
console.log('后端代码更新成功')
|
||||
}
|
||||
|
||||
async function installDependencies() {
|
||||
logger.info('开始安装Python依赖')
|
||||
console.log('开始安装Python依赖')
|
||||
const mirror = dependenciesStepRef.value?.selectedPipMirror || 'tsinghua'
|
||||
const result = await window.electronAPI.installDependencies(mirror)
|
||||
if (result.success) {
|
||||
logger.info('Python依赖安装成功')
|
||||
console.log('Python依赖安装成功')
|
||||
await saveConfig({ dependenciesInstalled: true })
|
||||
} else {
|
||||
logger.error('Python依赖安装失败', result.error)
|
||||
console.error('Python依赖安装失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
// 自动启动后端服务(进入第七步时调用)
|
||||
async function autoStartBackendService() {
|
||||
logger.info('自动启动后端服务')
|
||||
console.log('自动启动后端服务')
|
||||
isProcessing.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
@@ -367,14 +366,14 @@ async function autoStartBackendService() {
|
||||
serviceStepRef.value.serviceStatus = '后端服务启动成功,即将进入主页...'
|
||||
}
|
||||
stepStatus.value = 'finish'
|
||||
logger.info('后端服务自动启动成功,延迟1秒后自动进入主页')
|
||||
console.log('后端服务自动启动成功,延迟1秒后自动进入主页')
|
||||
|
||||
// 延迟1秒后自动进入主页
|
||||
setTimeout(() => {
|
||||
handleEnterApp()
|
||||
}, 1000)
|
||||
} else {
|
||||
logger.error('后端服务自动启动失败', result.error)
|
||||
console.error('后端服务自动启动失败', result.error)
|
||||
if (serviceStepRef.value) {
|
||||
serviceStepRef.value.serviceStatus = '后端服务启动失败,请点击重新启动'
|
||||
}
|
||||
@@ -384,7 +383,7 @@ async function autoStartBackendService() {
|
||||
if (serviceStepRef.value) {
|
||||
serviceStepRef.value.serviceStatus = '后端服务启动失败,请点击重新启动'
|
||||
}
|
||||
logger.error('后端服务自动启动异常', error)
|
||||
console.error('后端服务自动启动异常', error)
|
||||
errorMessage.value = error instanceof Error ? error.message : String(error)
|
||||
} finally {
|
||||
if (serviceStepRef.value) {
|
||||
@@ -396,7 +395,7 @@ async function autoStartBackendService() {
|
||||
|
||||
// 手动启动后端服务(用户点击按钮时调用)
|
||||
async function startBackendService() {
|
||||
logger.info('手动重新启动后端服务')
|
||||
console.log('手动重新启动后端服务')
|
||||
|
||||
if (serviceStepRef.value) {
|
||||
serviceStepRef.value.startingService = true
|
||||
@@ -412,21 +411,21 @@ async function startBackendService() {
|
||||
serviceStepRef.value.serviceStatus = '后端服务启动成功,即将进入主页...'
|
||||
}
|
||||
stepStatus.value = 'finish'
|
||||
logger.info('后端服务手动启动成功,延迟1秒后自动进入主页')
|
||||
console.log('后端服务手动启动成功,延迟1秒后自动进入主页')
|
||||
|
||||
// 延迟1秒后自动进入主页
|
||||
setTimeout(() => {
|
||||
handleEnterApp()
|
||||
}, 1000)
|
||||
} else {
|
||||
logger.error('后端服务手动启动失败', result.error)
|
||||
console.error('后端服务手动启动失败', result.error)
|
||||
throw new Error(result.error)
|
||||
}
|
||||
} catch (error) {
|
||||
if (serviceStepRef.value) {
|
||||
serviceStepRef.value.serviceStatus = '后端服务启动失败'
|
||||
}
|
||||
logger.error('后端服务手动启动异常', error)
|
||||
console.error('后端服务手动启动异常', error)
|
||||
throw error
|
||||
} finally {
|
||||
if (serviceStepRef.value) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router/index.ts'
|
||||
import { OpenAPI } from '@/api'
|
||||
import LoggerPlugin, { logger } from '@/utils/logger'
|
||||
|
||||
import Antd from 'ant-design-vue'
|
||||
import 'ant-design-vue/dist/reset.css'
|
||||
@@ -10,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')
|
||||
|
||||
@@ -18,16 +20,23 @@ 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)
|
||||
|
||||
// 注册插件
|
||||
app.use(Antd)
|
||||
app.use(router)
|
||||
app.use(LoggerPlugin)
|
||||
|
||||
// 全局错误处理
|
||||
app.config.errorHandler = (err, instance, info) => {
|
||||
logger.error('Vue应用错误:', err, '组件信息:', info)
|
||||
}
|
||||
|
||||
// 挂载应用
|
||||
app.mount('#app')
|
||||
|
||||
// 记录应用启动日志
|
||||
logger.info('应用启动', { version: '1.0.0', environment: process.env.NODE_ENV })
|
||||
logger.info('前端应用初始化完成')
|
||||
|
||||
@@ -76,12 +76,6 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('../views/Settings.vue'),
|
||||
meta: { title: '设置' },
|
||||
},
|
||||
{
|
||||
path: '/logs',
|
||||
name: 'Logs',
|
||||
component: () => import('../views/Logs.vue'),
|
||||
meta: { title: '系统日志' },
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
38
frontend/src/types/electron.d.ts
vendored
38
frontend/src/types/electron.d.ts
vendored
@@ -1,25 +1,35 @@
|
||||
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>
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { createComponentLogger } from './logger'
|
||||
import type { ThemeMode, ThemeColor } from '@/composables/useTheme'
|
||||
|
||||
const logger = createComponentLogger('Config')
|
||||
|
||||
export interface FrontendConfig {
|
||||
// 基础配置
|
||||
isFirstLaunch: boolean
|
||||
@@ -71,7 +68,6 @@ async function getConfigInternal(): Promise<FrontendConfig> {
|
||||
return config
|
||||
} catch (error) {
|
||||
console.error('读取配置失败:', error)
|
||||
logger.error('读取配置失败', error)
|
||||
return { ...DEFAULT_CONFIG }
|
||||
}
|
||||
}
|
||||
@@ -107,10 +103,8 @@ export async function saveConfig(config: Partial<FrontendConfig>): Promise<void>
|
||||
console.log('合并后的配置:', newConfig)
|
||||
await window.electronAPI.saveConfig(newConfig)
|
||||
console.log('配置保存成功')
|
||||
logger.info('配置已保存', newConfig)
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error)
|
||||
logger.error('保存配置失败', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -122,9 +116,8 @@ export async function resetConfig(): Promise<void> {
|
||||
localStorage.removeItem('app-config')
|
||||
localStorage.removeItem('theme-settings')
|
||||
localStorage.removeItem('app-initialized')
|
||||
logger.info('配置已重置')
|
||||
} catch (error) {
|
||||
logger.error('重置配置失败', error)
|
||||
console.error('重置配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,171 +1,75 @@
|
||||
import { ref } from 'vue'
|
||||
// 渲染进程日志工具
|
||||
interface ElectronAPI {
|
||||
getLogPath: () => Promise<string>
|
||||
getLogs: (lines?: number) => Promise<string>
|
||||
clearLogs: () => Promise<void>
|
||||
cleanOldLogs: (daysToKeep?: number) => Promise<void>
|
||||
}
|
||||
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error'
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: ElectronAPI
|
||||
}
|
||||
}
|
||||
|
||||
export interface LogEntry {
|
||||
timestamp: string
|
||||
level: LogLevel
|
||||
message: string
|
||||
data?: any
|
||||
component?: string
|
||||
export enum LogLevel {
|
||||
DEBUG = 'DEBUG',
|
||||
INFO = 'INFO',
|
||||
WARN = 'WARN',
|
||||
ERROR = 'ERROR'
|
||||
}
|
||||
|
||||
class Logger {
|
||||
private logs = ref<LogEntry[]>([])
|
||||
private maxLogs = 1000 // 最大日志条数
|
||||
private logToConsole = true
|
||||
private logToStorage = true
|
||||
|
||||
constructor() {
|
||||
// 延迟加载日志,等待electron API准备就绪
|
||||
setTimeout(() => {
|
||||
this.loadLogsFromStorage()
|
||||
}, 100)
|
||||
// 直接使用原生console,主进程会自动处理日志记录
|
||||
debug(message: string, ...args: any[]) {
|
||||
console.debug(message, ...args)
|
||||
}
|
||||
|
||||
private formatTimestamp(): string {
|
||||
const now = new Date()
|
||||
return now.toISOString().replace('T', ' ').substring(0, 19)
|
||||
info(message: string, ...args: any[]) {
|
||||
console.info(message, ...args)
|
||||
}
|
||||
|
||||
private addLog(level: LogLevel, message: string, data?: any, component?: string) {
|
||||
const logEntry: LogEntry = {
|
||||
timestamp: this.formatTimestamp(),
|
||||
level,
|
||||
message,
|
||||
data,
|
||||
component,
|
||||
warn(message: string, ...args: any[]) {
|
||||
console.warn(message, ...args)
|
||||
}
|
||||
|
||||
// 添加到内存日志
|
||||
this.logs.value.push(logEntry)
|
||||
|
||||
// 限制日志数量
|
||||
if (this.logs.value.length > this.maxLogs) {
|
||||
this.logs.value.shift()
|
||||
error(message: string, ...args: any[]) {
|
||||
console.error(message, ...args)
|
||||
}
|
||||
|
||||
// 输出到控制台
|
||||
if (this.logToConsole) {
|
||||
const consoleMessage = `[${logEntry.timestamp}] [${level.toUpperCase()}] ${component ? `[${component}] ` : ''}${message}`
|
||||
|
||||
switch (level) {
|
||||
case 'debug':
|
||||
console.debug(consoleMessage, data)
|
||||
break
|
||||
case 'info':
|
||||
console.info(consoleMessage, data)
|
||||
break
|
||||
case 'warn':
|
||||
console.warn(consoleMessage, data)
|
||||
break
|
||||
case 'error':
|
||||
console.error(consoleMessage, data)
|
||||
break
|
||||
// 获取日志文件路径
|
||||
async getLogPath(): Promise<string> {
|
||||
if (window.electronAPI) {
|
||||
return await window.electronAPI.getLogPath()
|
||||
}
|
||||
throw new Error('Electron API not available')
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
if (this.logToStorage) {
|
||||
this.saveLogsToStorage()
|
||||
// 获取日志内容
|
||||
async getLogs(lines?: number): Promise<string> {
|
||||
if (window.electronAPI) {
|
||||
return await window.electronAPI.getLogs(lines)
|
||||
}
|
||||
}
|
||||
|
||||
private async saveLogsToStorage() {
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.saveLogsToFile) {
|
||||
const logsToSave = this.logs.value.slice(-500) // 只保存最近500条日志
|
||||
await window.electronAPI.saveLogsToFile(JSON.stringify(logsToSave, null, 2))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存日志到本地文件失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private async loadLogsFromStorage() {
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.loadLogsFromFile) {
|
||||
const savedLogs = await window.electronAPI.loadLogsFromFile()
|
||||
if (savedLogs) {
|
||||
const parsedLogs = JSON.parse(savedLogs) as LogEntry[]
|
||||
this.logs.value = parsedLogs
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('从本地文件加载日志失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 公共方法
|
||||
debug(message: string, data?: any, component?: string) {
|
||||
this.addLog('debug', message, data, component)
|
||||
}
|
||||
|
||||
info(message: string, data?: any, component?: string) {
|
||||
this.addLog('info', message, data, component)
|
||||
}
|
||||
|
||||
warn(message: string, data?: any, component?: string) {
|
||||
this.addLog('warn', message, data, component)
|
||||
}
|
||||
|
||||
error(message: string, data?: any, component?: string) {
|
||||
this.addLog('error', message, data, component)
|
||||
}
|
||||
|
||||
// 获取日志
|
||||
getLogs() {
|
||||
return this.logs
|
||||
throw new Error('Electron API not available')
|
||||
}
|
||||
|
||||
// 清空日志
|
||||
clearLogs() {
|
||||
this.logs.value = []
|
||||
localStorage.removeItem('app-logs')
|
||||
async clearLogs(): Promise<void> {
|
||||
if (window.electronAPI) {
|
||||
await window.electronAPI.clearLogs()
|
||||
console.info('日志已清空')
|
||||
} else {
|
||||
throw new Error('Electron API not available')
|
||||
}
|
||||
}
|
||||
|
||||
// 导出日志到文件
|
||||
exportLogs(): string {
|
||||
const logText = this.logs.value
|
||||
.map(log => {
|
||||
const dataStr = log.data ? ` | Data: ${JSON.stringify(log.data)}` : ''
|
||||
const componentStr = log.component ? ` | Component: ${log.component}` : ''
|
||||
return `[${log.timestamp}] [${log.level.toUpperCase()}]${componentStr} ${log.message}${dataStr}`
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
return logText
|
||||
}
|
||||
|
||||
// 下载日志文件
|
||||
downloadLogs() {
|
||||
const logText = this.exportLogs()
|
||||
const blob = new Blob([logText], { type: 'text/plain;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `auto-maa-logs-${new Date().toISOString().split('T')[0]}.txt`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
// 配置选项
|
||||
setLogToConsole(enabled: boolean) {
|
||||
this.logToConsole = enabled
|
||||
}
|
||||
|
||||
setLogToStorage(enabled: boolean) {
|
||||
this.logToStorage = enabled
|
||||
}
|
||||
|
||||
setMaxLogs(max: number) {
|
||||
this.maxLogs = max
|
||||
if (this.logs.value.length > max) {
|
||||
this.logs.value = this.logs.value.slice(-max)
|
||||
// 清理旧日志
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,20 +77,13 @@ class Logger {
|
||||
// 创建全局日志实例
|
||||
export const logger = new Logger()
|
||||
|
||||
// 创建组件专用的日志器
|
||||
export function createComponentLogger(componentName: string) {
|
||||
return {
|
||||
debug: (message: string, data?: any) => logger.debug(message, data, componentName),
|
||||
info: (message: string, data?: any) => logger.info(message, data, componentName),
|
||||
warn: (message: string, data?: any) => logger.warn(message, data, componentName),
|
||||
error: (message: string, data?: any) => logger.error(message, data, componentName),
|
||||
}
|
||||
}
|
||||
// 捕获未处理的错误(直接使用console,主进程会处理日志记录)
|
||||
window.addEventListener('error', (event) => {
|
||||
console.error('未处理的错误:', event.error?.message || event.message, event.error?.stack)
|
||||
})
|
||||
|
||||
// Vue插件
|
||||
export default {
|
||||
install(app: any) {
|
||||
app.config.globalProperties.$logger = logger
|
||||
app.provide('logger', logger)
|
||||
},
|
||||
}
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
console.error('未处理的Promise拒绝:', event.reason)
|
||||
})
|
||||
|
||||
export default logger
|
||||
@@ -29,7 +29,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { createComponentLogger } from '@/utils/logger'
|
||||
import { getConfig, saveConfig, setInitialized } from '@/utils/config'
|
||||
import AdminCheck from '@/components/initialization/AdminCheck.vue'
|
||||
import AutoMode from '@/components/initialization/AutoMode.vue'
|
||||
@@ -37,7 +36,6 @@ import ManualMode from '@/components/initialization/ManualMode.vue'
|
||||
import type { DownloadProgress } from '@/types/initialization'
|
||||
|
||||
const router = useRouter()
|
||||
const logger = createComponentLogger('Initialization')
|
||||
|
||||
// 基础状态
|
||||
const isAdmin = ref(true)
|
||||
@@ -77,7 +75,6 @@ async function enterApp() {
|
||||
// 检查关键文件是否存在
|
||||
async function checkCriticalFiles() {
|
||||
try {
|
||||
logger.info('开始检查关键文件存在性')
|
||||
console.log('🔍 正在调用 window.electronAPI.checkCriticalFiles()...')
|
||||
|
||||
// 检查API是否存在
|
||||
@@ -110,7 +107,6 @@ async function checkCriticalFiles() {
|
||||
console.log('🔍 最终返回结果:', result)
|
||||
return result
|
||||
} catch (error) {
|
||||
logger.error('检查关键文件失败', error)
|
||||
console.error('❌ 检查关键文件失败,使用配置文件状态:', error)
|
||||
|
||||
// 如果检查失败,从配置文件读取状态
|
||||
@@ -140,7 +136,6 @@ async function checkCriticalFiles() {
|
||||
// 检查环境状态
|
||||
async function checkEnvironment() {
|
||||
try {
|
||||
logger.info('开始检查环境状态')
|
||||
|
||||
// 只检查关键exe文件是否存在
|
||||
const criticalFiles = await checkCriticalFiles()
|
||||
@@ -186,11 +181,9 @@ async function checkEnvironment() {
|
||||
|
||||
// 只有在非首次启动、配置显示已初始化、且所有关键exe文件都存在时才进入自动模式
|
||||
if (!isFirst && config.init && allExeFilesExist) {
|
||||
logger.info('非首次启动、配置显示已初始化且所有关键文件存在,进入自动模式')
|
||||
console.log('进入自动模式,开始自动启动流程')
|
||||
autoMode.value = true
|
||||
} else {
|
||||
logger.info('需要进入手动模式进行配置')
|
||||
console.log('进入手动模式')
|
||||
console.log(
|
||||
'原因: isFirst =',
|
||||
@@ -209,7 +202,6 @@ async function checkEnvironment() {
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = `环境检查失败: ${error instanceof Error ? error.message : String(error)}`
|
||||
logger.error('环境检查失败', error)
|
||||
console.error('环境检查失败:', error)
|
||||
|
||||
// 检查失败时强制进入手动模式
|
||||
@@ -224,7 +216,7 @@ async function checkAdminPermission() {
|
||||
isAdmin.value = adminStatus
|
||||
console.log('管理员权限检查结果:', adminStatus)
|
||||
} catch (error) {
|
||||
logger.error('检查管理员权限失败', error)
|
||||
console.error('检查管理员权限失败:', error)
|
||||
isAdmin.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
<template>
|
||||
<div class="logs-page">
|
||||
<div class="page-header">
|
||||
<h2>系统日志</h2>
|
||||
<p>查看应用运行日志,支持搜索、过滤和导出功能</p>
|
||||
</div>
|
||||
|
||||
<div class="logs-content">
|
||||
<LogViewer />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import LogViewer from '@/components/LogViewer.vue'
|
||||
import { createComponentLogger } from '@/utils/logger'
|
||||
|
||||
const logger = createComponentLogger('LogsPage')
|
||||
|
||||
onMounted(() => {
|
||||
logger.info('进入日志查看页面')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.logs-page {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--ant-color-text);
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-header p {
|
||||
margin: 0;
|
||||
color: var(--ant-color-text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.logs-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -687,11 +687,7 @@ onMounted(() => {
|
||||
|
||||
<Divider />
|
||||
|
||||
<div class="setting-item">
|
||||
<h4>系统日志</h4>
|
||||
<p class="setting-description">查看应用运行日志,用于问题排查和调试</p>
|
||||
<Button type="default" @click="$router.push('/logs')">查看系统日志</Button>
|
||||
</div>
|
||||
|
||||
</Space>
|
||||
</Card>
|
||||
</Tabs.TabPane>
|
||||
|
||||
@@ -1775,21 +1775,33 @@ electron-builder@^26.0.12:
|
||||
resolved "https://registry.npmmirror.com/electron-builder/-/electron-builder-26.0.12.tgz#797af2e70efdd96c9ea5d8a8164b8728c90d65ff"
|
||||
integrity sha512-cD1kz5g2sgPTMFHjLxfMjUK5JABq3//J4jPswi93tOPFz6btzXYtK5NrDt717NRbukCUDOrrvmYVOWERlqoiXA==
|
||||
dependencies:
|
||||
app-builder-lib "26.0.12"
|
||||
builder-util "26.0.11"
|
||||
builder-util-runtime "9.3.1"
|
||||
chalk "^4.1.2"
|
||||
dmg-builder "26.0.12"
|
||||
fs-extra "^10.1.0"
|
||||
is-ci "^3.0.0"
|
||||
lazy-val "^1.0.5"
|
||||
simple-update-notifier "2.0.0"
|
||||
yargs "^17.6.2"
|
||||
app-builder-lib: "npm:26.0.12"
|
||||
builder-util: "npm:26.0.11"
|
||||
builder-util-runtime: "npm:9.3.1"
|
||||
chalk: "npm:^4.1.2"
|
||||
dmg-builder: "npm:26.0.12"
|
||||
fs-extra: "npm:^10.1.0"
|
||||
is-ci: "npm:^3.0.0"
|
||||
lazy-val: "npm:^1.0.5"
|
||||
simple-update-notifier: "npm:2.0.0"
|
||||
yargs: "npm:^17.6.2"
|
||||
bin:
|
||||
electron-builder: cli.js
|
||||
install-app-deps: install-app-deps.js
|
||||
checksum: 10c0/88911a812c37bc8c154aed876af26f07206ee3734b6b0b011beffd2c5788fbbdaf41e99ac1663c23627add5963fbc31678de859f946462fca05eb0e24027625b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
electron-publish@26.0.11:
|
||||
version "26.0.11"
|
||||
resolved "https://registry.npmmirror.com/electron-publish/-/electron-publish-26.0.11.tgz#92c9329a101af2836d9d228c82966eca1eee9a7b"
|
||||
integrity sha512-a8QRH0rAPIWH9WyyS5LbNvW9Ark6qe63/LqDB7vu2JXYpi0Gma5Q60Dh4tmTqhOBQt0xsrzD8qE7C+D7j+B24A==
|
||||
"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"
|
||||
dependencies:
|
||||
"@types/fs-extra" "^9.0.11"
|
||||
builder-util "26.0.11"
|
||||
@@ -2186,14 +2198,45 @@ fs-extra@^10.0.0, fs-extra@^10.1.0:
|
||||
resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
||||
integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^2.0.0"
|
||||
"@ant-design/icons-vue": "npm:^7.0.1"
|
||||
"@types/adm-zip": "npm:^0.5.7"
|
||||
"@types/markdown-it": "npm:^14.1.2"
|
||||
"@types/node": "npm:22.17.1"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^8.38.0"
|
||||
"@typescript-eslint/parser": "npm:^8.38.0"
|
||||
"@vitejs/plugin-vue": "npm:^6.0.1"
|
||||
"@vue/tsconfig": "npm:^0.7.0"
|
||||
adm-zip: "npm:^0.5.16"
|
||||
ant-design-vue: "npm:4.x"
|
||||
axios: "npm:^1.11.0"
|
||||
concurrently: "npm:^9.2.0"
|
||||
cross-env: "npm:^10.0.0"
|
||||
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"
|
||||
eslint-plugin-vue: "npm:^10.4.0"
|
||||
form-data: "npm:^4.0.4"
|
||||
markdown-it: "npm:^14.1.0"
|
||||
openapi-typescript-codegen: "npm:^0.29.0"
|
||||
prettier: "npm:^3.6.2"
|
||||
typescript: "npm:^5.9.2"
|
||||
vite: "npm:^7.0.4"
|
||||
vite-plugin-eslint: "npm:^1.8.1"
|
||||
vue: "npm:^3.5.17"
|
||||
vue-eslint-parser: "npm:^10.2.0"
|
||||
vue-router: "npm:4"
|
||||
vue-tsc: "npm:^2.2.12"
|
||||
wait-on: "npm:^8.0.4"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
fs-extra@^11.1.1, fs-extra@^11.2.0:
|
||||
version "11.3.1"
|
||||
resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.1.tgz#ba7a1f97a85f94c6db2e52ff69570db3671d5a74"
|
||||
integrity sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==
|
||||
"fs-extra@npm:^10.0.0, fs-extra@npm:^10.1.0":
|
||||
version: 10.1.0
|
||||
resolution: "fs-extra@npm:10.1.0"
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
|
||||
Reference in New Issue
Block a user