diff --git a/frontend/electron/main.ts b/frontend/electron/main.ts index 1109ece..9360e27 100644 --- a/frontend/electron/main.ts +++ b/frontend/electron/main.ts @@ -1,18 +1,28 @@ import { app, BrowserWindow, - ipcMain, dialog, - shell, - Tray, + ipcMain, Menu, nativeImage, screen, + shell, + Tray, } from 'electron' import * as path from 'path' import * as fs from 'fs' -import { spawn, exec } from 'child_process' -import { getAppRoot, checkEnvironment } from './services/environmentService' +import { exec, spawn } from 'child_process' +import { checkEnvironment, getAppRoot } from './services/environmentService' +import { setMainWindow as setDownloadMainWindow } from './services/downloadService' +import { + downloadPython, + installDependencies, + installPipPackage, + setMainWindow as setPythonMainWindow, + startBackend, +} from './services/pythonService' +import { cloneBackend, downloadGit, setMainWindow as setGitMainWindow } from './services/gitService' +import { cleanOldLogs, getLogFiles, getLogPath, log, setupLogger } from './services/logService' // 强制清理相关进程的函数 async function forceKillRelatedProcesses(): Promise { @@ -22,15 +32,15 @@ async function forceKillRelatedProcesses(): Promise { log.info('所有相关进程已清理') } catch (error) { log.error('清理进程时出错:', error) - + // 备用清理方法 if (process.platform === 'win32') { const appRoot = getAppRoot() const pythonExePath = path.join(appRoot, 'environment', 'python', 'python.exe') - - return new Promise((resolve) => { + + return new Promise(resolve => { // 使用更简单的命令强制结束相关进程 - exec(`taskkill /f /im python.exe`, (error) => { + exec(`taskkill /f /im python.exe`, error => { if (error) { log.warn('备用清理方法失败:', error.message) } else { @@ -42,16 +52,6 @@ async function forceKillRelatedProcesses(): Promise { } } } -import { setMainWindow as setDownloadMainWindow } from './services/downloadService' -import { - setMainWindow as setPythonMainWindow, - downloadPython, - installPipPackage, - installDependencies, - startBackend, -} from './services/pythonService' -import { setMainWindow as setGitMainWindow, downloadGit, cloneBackend } from './services/gitService' -import { setupLogger, log, getLogPath, getLogFiles, cleanOldLogs } from './services/logService' // 检查是否以管理员权限运行 function isRunningAsAdmin(): boolean { @@ -113,6 +113,7 @@ interface AppConfig { IfMinimizeDirectly: boolean IfSelfStart: boolean } + [key: string]: any } @@ -304,7 +305,9 @@ function updateTrayVisibility(config: AppConfig) { log.info('托盘图标已销毁') } } + let mainWindow: Electron.BrowserWindow | null = null + function createWindow() { log.info('开始创建主窗口') @@ -442,7 +445,7 @@ function createWindow() { screen.removeListener('display-metrics-changed', recomputeMinSize) // 置空模块级引用 mainWindow = null - + // 如果是正在退出,立即执行进程清理 if (isQuitting) { log.info('窗口关闭,执行最终清理') @@ -598,19 +601,19 @@ ipcMain.handle('kill-all-processes', async () => { ipcMain.handle('force-exit', async () => { log.info('收到强制退出命令') isQuitting = true - + // 立即清理进程 try { await forceKillRelatedProcesses() } catch (e) { log.error('强制清理失败:', e) } - + // 强制退出 setTimeout(() => { process.exit(0) }, 500) - + return { success: true } }) @@ -746,6 +749,137 @@ ipcMain.handle('stop-backend', async () => { return stopBackend() }) +// 全局存储对话框窗口引用和回调 +let dialogWindows = new Map() +let dialogCallbacks = new Map void>() + +// 创建对话框窗口 +function createQuestionDialog(questionData: any): Promise { + return new Promise((resolve) => { + const messageId = questionData.messageId || 'dialog_' + Date.now() + + // 存储回调函数 + dialogCallbacks.set(messageId, resolve) + + // 准备对话框数据 + const dialogData = { + title: questionData.title || '操作确认', + message: questionData.message || '是否要执行此操作?', + options: questionData.options || ['确定', '取消'], + messageId: messageId + } + + // 创建对话框窗口 + const dialogWindow = new BrowserWindow({ + width: 450, + height: 200, + minWidth: 350, + minHeight: 150, + maxWidth: 600, + maxHeight: 400, + resizable: true, + minimizable: false, + maximizable: false, + alwaysOnTop: true, + show: false, + frame: false, + modal: mainWindow ? true : false, + parent: mainWindow || undefined, + icon: path.join(__dirname, '../public/AUTO-MAS.ico'), + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, 'preload.js'), + }, + }) + + // 存储窗口引用 + dialogWindows.set(messageId, dialogWindow) + + // 编码对话框数据 + const encodedData = encodeURIComponent(JSON.stringify(dialogData)) + + // 加载对话框页面 + const dialogUrl = `file://${path.join(__dirname, '../public/dialog.html')}?data=${encodedData}` + dialogWindow.loadURL(dialogUrl) + + // 窗口准备好后显示并居中 + dialogWindow.once('ready-to-show', () => { + // 计算居中位置 + if (mainWindow && !mainWindow.isDestroyed()) { + const mainBounds = mainWindow.getBounds() + const dialogBounds = dialogWindow.getBounds() + const x = Math.round(mainBounds.x + (mainBounds.width - dialogBounds.width) / 2) + const y = Math.round(mainBounds.y + (mainBounds.height - dialogBounds.height) / 2) + dialogWindow.setPosition(x, y) + } else { + dialogWindow.center() + } + + dialogWindow.show() + dialogWindow.focus() + }) + + // 窗口关闭时清理 + dialogWindow.on('closed', () => { + dialogWindows.delete(messageId) + const callback = dialogCallbacks.get(messageId) + if (callback) { + dialogCallbacks.delete(messageId) + callback(false) // 默认返回 false (取消) + } + }) + + log.info(`对话框窗口已创建: ${messageId}`) + }) +} + +// 显示问题对话框 +ipcMain.handle('show-question-dialog', async (_event, questionData) => { + log.info('收到显示对话框请求:', questionData) + try { + const result = await createQuestionDialog(questionData) + log.info(`对话框结果: ${result}`) + return result + } catch (error) { + log.error('创建对话框失败:', error) + return false + } +}) + +// 处理对话框响应 +ipcMain.handle('dialog-response', async (_event, messageId: string, choice: boolean) => { + log.info(`收到对话框响应: ${messageId} = ${choice}`) + + const callback = dialogCallbacks.get(messageId) + if (callback) { + dialogCallbacks.delete(messageId) + callback(choice) + } + + // 关闭对话框窗口 + const dialogWindow = dialogWindows.get(messageId) + if (dialogWindow && !dialogWindow.isDestroyed()) { + dialogWindow.close() + } + dialogWindows.delete(messageId) + + return true +}) + +// 调整对话框窗口大小 +ipcMain.handle('resize-dialog-window', async (_event, height: number) => { + // 获取当前活动的对话框窗口(最后创建的) + const dialogWindow = Array.from(dialogWindows.values()).pop() + if (dialogWindow && !dialogWindow.isDestroyed()) { + const bounds = dialogWindow.getBounds() + dialogWindow.setBounds({ + ...bounds, + height: Math.max(150, Math.min(400, height)) + }) + } +}) + // Git相关 ipcMain.handle('download-git', async () => { const appRoot = getAppRoot() @@ -785,7 +919,7 @@ ipcMain.handle('check-git-update', async () => { // 不执行fetch,直接检查本地状态 // 这样避免了直接访问GitHub,而是在后续的pull操作中使用镜像站 - + // 获取当前HEAD的commit hash const currentCommit = await new Promise((resolve, reject) => { const revParseProc = spawn(gitPath, ['rev-parse', 'HEAD'], { @@ -811,14 +945,13 @@ ipcMain.handle('check-git-update', async () => { }) log.info(`当前本地commit: ${currentCommit}`) - + // 由于我们跳过了fetch步骤(避免直接访问GitHub), // 我们无法准确知道远程是否有更新 // 因此返回true,让后续的pull操作通过镜像站来检查和获取更新 // 如果没有更新,pull操作会很快完成且不会有实际变化 log.info('跳过远程检查,返回hasUpdate=true以触发镜像站更新流程') return { hasUpdate: true, skipReason: 'avoided_github_access' } - } catch (error) { log.error('检查Git更新失败:', error) // 如果检查失败,返回true以触发更新流程,确保代码是最新的 @@ -1084,23 +1217,23 @@ app.on('before-quit', async event => { // 立即开始强制清理,不等待优雅关闭 log.info('开始强制清理所有相关进程') - + try { // 并行执行多种清理方法 const cleanupPromises = [ // 方法1: 使用我们的进程管理器 forceKillRelatedProcesses(), - + // 方法2: 直接使用 taskkill 命令 - new Promise((resolve) => { + new Promise(resolve => { if (process.platform === 'win32') { const appRoot = getAppRoot() const commands = [ `taskkill /f /im python.exe`, `wmic process where "CommandLine like '%main.py%'" delete`, - `wmic process where "CommandLine like '%${appRoot.replace(/\\/g, '\\\\')}%'" delete` + `wmic process where "CommandLine like '%${appRoot.replace(/\\/g, '\\\\')}%'" delete`, ] - + let completed = 0 commands.forEach(cmd => { exec(cmd, () => { @@ -1110,26 +1243,26 @@ app.on('before-quit', async event => { } }) }) - + // 2秒超时 setTimeout(resolve, 2000) } else { resolve() } - }) + }), ] - + // 最多等待3秒 const timeoutPromise = new Promise(resolve => setTimeout(resolve, 3000)) await Promise.race([Promise.all(cleanupPromises), timeoutPromise]) - + log.info('进程清理完成') } catch (e) { log.error('进程清理时出错:', e) } log.info('应用强制退出') - + // 使用 process.exit 而不是 app.exit,更加强制 setTimeout(() => { process.exit(0) diff --git a/frontend/electron/preload.ts b/frontend/electron/preload.ts index 1f927bd..0a31b5c 100644 --- a/frontend/electron/preload.ts +++ b/frontend/electron/preload.ts @@ -63,6 +63,11 @@ contextBridge.exposeInMainWorld('electronAPI', { openFile: (filePath: string) => ipcRenderer.invoke('open-file', filePath), showItemInFolder: (filePath: string) => ipcRenderer.invoke('show-item-in-folder', filePath), + // 对话框相关 + showQuestionDialog: (questionData: any) => ipcRenderer.invoke('show-question-dialog', questionData), + dialogResponse: (messageId: string, choice: boolean) => ipcRenderer.invoke('dialog-response', messageId, choice), + resizeDialogWindow: (height: number) => ipcRenderer.invoke('resize-dialog-window', height), + // 监听下载进度 onDownloadProgress: (callback: (progress: any) => void) => { ipcRenderer.on('download-progress', (_, progress) => callback(progress)) diff --git a/frontend/public/dialog.html b/frontend/public/dialog.html new file mode 100644 index 0000000..6a451a5 --- /dev/null +++ b/frontend/public/dialog.html @@ -0,0 +1,276 @@ + + + + + + 操作确认 + + + +
+
+

操作确认

+
+
+

是否要执行此操作?

+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/frontend/src/components/WebSocketMessageListener.vue b/frontend/src/components/WebSocketMessageListener.vue index dfaafa8..3ed6f33 100644 --- a/frontend/src/components/WebSocketMessageListener.vue +++ b/frontend/src/components/WebSocketMessageListener.vue @@ -1,51 +1,26 @@ - diff --git a/frontend/src/types/electron.d.ts b/frontend/src/types/electron.d.ts index bfc195e..394161f 100644 --- a/frontend/src/types/electron.d.ts +++ b/frontend/src/types/electron.d.ts @@ -1,64 +1,74 @@ -export interface ElectronAPI { - openDevTools: () => Promise - selectFolder: () => Promise - selectFile: (filters?: any[]) => Promise - openUrl: (url: string) => Promise<{ success: boolean; error?: string }> - - // 窗口控制 - windowMinimize: () => Promise - windowMaximize: () => Promise - windowClose: () => Promise - windowIsMaximized: () => Promise - appQuit: () => Promise - - // 进程管理 - getRelatedProcesses: () => Promise - killAllProcesses: () => Promise<{ success: boolean; error?: string }> - forceExit: () => Promise<{ success: boolean }> - - // 初始化相关API - checkEnvironment: () => Promise - checkCriticalFiles: () => Promise<{ pythonExists: boolean; gitExists: boolean; mainPyExists: boolean }> - checkGitUpdate: () => Promise<{ hasUpdate: boolean; error?: string }> - downloadPython: (mirror?: string) => Promise - installPip: () => Promise - downloadGit: () => Promise - installDependencies: (mirror?: string) => Promise - cloneBackend: (repoUrl?: string) => Promise - updateBackend: (repoUrl?: string) => Promise - startBackend: () => Promise<{ success: boolean; error?: string }> - stopBackend?: () => Promise<{ success: boolean; error?: string }> - - // 管理员权限相关 - checkAdmin: () => Promise - restartAsAdmin: () => Promise - - // 配置文件操作 - saveConfig: (config: any) => Promise - loadConfig: () => Promise - resetConfig: () => Promise - - // 日志文件操作 - getLogPath: () => Promise - getLogFiles: () => Promise - getLogs: (lines?: number, fileName?: string) => Promise - clearLogs: (fileName?: string) => Promise - cleanOldLogs: (daysToKeep?: number) => Promise - - // 保留原有方法以兼容现有代码 - saveLogsToFile: (logs: string) => Promise - loadLogsFromFile: () => Promise - - // 文件系统操作 - openFile: (filePath: string) => Promise - showItemInFolder: (filePath: string) => Promise - - // 监听下载进度 - onDownloadProgress: (callback: (progress: any) => void) => void - removeDownloadProgressListener: () => void -} - declare global { + interface ElectronAPI { + openDevTools: () => Promise + selectFolder: () => Promise + selectFile: (filters?: any[]) => Promise + openUrl: (url: string) => Promise<{ success: boolean; error?: string }> + + // 窗口控制 + windowMinimize: () => Promise + windowMaximize: () => Promise + windowClose: () => Promise + windowIsMaximized: () => Promise + appQuit: () => Promise + + // 进程管理 + getRelatedProcesses: () => Promise + killAllProcesses: () => Promise<{ success: boolean; error?: string }> + forceExit: () => Promise<{ success: boolean }> + + // 初始化相关API + checkEnvironment: () => Promise + checkCriticalFiles: () => Promise<{ pythonExists: boolean; gitExists: boolean; mainPyExists: boolean }> + checkGitUpdate: () => Promise<{ hasUpdate: boolean; error?: string }> + downloadPython: (mirror?: string) => Promise + installPip: () => Promise + downloadGit: () => Promise + installDependencies: (mirror?: string) => Promise + cloneBackend: (repoUrl?: string) => Promise + updateBackend: (repoUrl?: string) => Promise + startBackend: () => Promise<{ success: boolean; error?: string }> + stopBackend?: () => Promise<{ success: boolean; error?: string }> + + // 管理员权限相关 + checkAdmin: () => Promise + restartAsAdmin: () => Promise + + // 配置文件操作 + saveConfig: (config: any) => Promise + loadConfig: () => Promise + resetConfig: () => Promise + + // 日志文件操作 + getLogPath: () => Promise + getLogFiles: () => Promise + getLogs: (lines?: number, fileName?: string) => Promise + clearLogs: (fileName?: string) => Promise + cleanOldLogs: (daysToKeep?: number) => Promise + + // 保留原有方法以兼容现有代码 + saveLogsToFile: (logs: string) => Promise + loadLogsFromFile: () => Promise + + // 文件系统操作 + openFile: (filePath: string) => Promise + showItemInFolder: (filePath: string) => Promise + + // 对话框相关 + showQuestionDialog: (questionData: { + title?: string + message?: string + options?: string[] + messageId?: string + }) => Promise + dialogResponse: (messageId: string, choice: boolean) => Promise + resizeDialogWindow: (height: number) => Promise + + // 监听下载进度 + onDownloadProgress: (callback: (progress: any) => void) => void + removeDownloadProgressListener: () => void + } + interface Window { electronAPI: ElectronAPI } diff --git a/frontend/src/types/electron.ts b/frontend/src/types/electron.ts deleted file mode 100644 index 5679ce0..0000000 --- a/frontend/src/types/electron.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Electron API 类型定义 -export interface ElectronAPI { - // 开发工具 - openDevTools: () => Promise - selectFolder: () => Promise - selectFile: (filters?: Array<{ name: string; extensions: string[] }>) => Promise - - // 窗口控制 - windowMinimize: () => Promise - windowMaximize: () => Promise - windowClose: () => Promise - windowIsMaximized: () => Promise - - // 管理员权限检查 - checkAdmin: () => Promise - - // 重启为管理员 - restartAsAdmin: () => Promise - appQuit: () => Promise - - // 进程管理 - getRelatedProcesses: () => Promise - killAllProcesses: () => Promise<{ success: boolean; error?: string }> - forceExit: () => Promise<{ success: boolean }> - // 环境检查 - checkEnvironment: () => Promise<{ - pythonExists: boolean - gitExists: boolean - backendExists: boolean - dependenciesInstalled: boolean - isInitialized: boolean - }> - - // 关键文件检查 - checkCriticalFiles: () => Promise<{ - pythonExists: boolean - pipExists: boolean - gitExists: boolean - mainPyExists: boolean - }> - - // Python相关 - downloadPython: (mirror: string) => Promise<{ success: boolean; error?: string }> - deletePython: () => Promise<{ success: boolean; error?: string }> - - // pip相关 - installPip: () => Promise<{ success: boolean; error?: string }> - deletePip: () => Promise<{ success: boolean; error?: string }> - - // Git相关 - downloadGit: () => Promise<{ success: boolean; error?: string }> - deleteGit: () => Promise<{ success: boolean; error?: string }> - checkGitUpdate: () => Promise<{ hasUpdate: boolean; error?: string }> - - // 后端代码相关 - cloneBackend: (gitUrl: string) => Promise<{ success: boolean; error?: string }> - updateBackend: (gitUrl: string) => Promise<{ success: boolean; error?: string }> - - // 依赖安装 - installDependencies: (mirror: string) => Promise<{ success: boolean; error?: string }> - - // 后端服务 - startBackend: () => Promise<{ success: boolean; error?: string }> - - // 下载进度监听 - onDownloadProgress: ( - callback: (progress: { progress: number; status: string; message: string }) => void - ) => void - removeDownloadProgressListener: () => void -} - -declare global { - interface Window { - electronAPI: ElectronAPI - } -} - -export {} diff --git a/frontend/src/utils/logger.ts b/frontend/src/utils/logger.ts index f9ded36..03c8f4c 100644 --- a/frontend/src/utils/logger.ts +++ b/frontend/src/utils/logger.ts @@ -1,24 +1,13 @@ // 渲染进程日志工具 -interface ElectronAPI { - getLogPath: () => Promise - getLogFiles: () => Promise - getLogs: (lines?: number, fileName?: string) => Promise - clearLogs: (fileName?: string) => Promise - cleanOldLogs: (daysToKeep?: number) => Promise -} +const LogLevel = { + DEBUG: 'DEBUG', + INFO: 'INFO', + WARN: 'WARN', + ERROR: 'ERROR' +} as const -declare global { - interface Window { - electronAPI: ElectronAPI - } -} - -export enum LogLevel { - DEBUG = 'DEBUG', - INFO = 'INFO', - WARN = 'WARN', - ERROR = 'ERROR' -} +export type LogLevel = typeof LogLevel[keyof typeof LogLevel] +export { LogLevel } class Logger { // 直接使用原生console,主进程会自动处理日志记录 @@ -40,32 +29,32 @@ class Logger { // 获取日志文件路径 async getLogPath(): Promise { - if (window.electronAPI) { - return await window.electronAPI.getLogPath() + if ((window as any).electronAPI) { + return await (window as any).electronAPI.getLogPath() } throw new Error('Electron API not available') } // 获取日志文件列表 async getLogFiles(): Promise { - if (window.electronAPI) { - return await window.electronAPI.getLogFiles() + if ((window as any).electronAPI) { + return await (window as any).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) + if ((window as any).electronAPI) { + return await (window as any).electronAPI.getLogs(lines, fileName) } throw new Error('Electron API not available') } // 清空日志 async clearLogs(fileName?: string): Promise { - if (window.electronAPI) { - await window.electronAPI.clearLogs(fileName) + if ((window as any).electronAPI) { + await (window as any).electronAPI.clearLogs(fileName) console.info(`日志已清空: ${fileName || '当前文件'}`) } else { throw new Error('Electron API not available') @@ -74,8 +63,8 @@ class Logger { // 清理旧日志 async cleanOldLogs(daysToKeep: number = 7): Promise { - if (window.electronAPI) { - await window.electronAPI.cleanOldLogs(daysToKeep) + if ((window as any).electronAPI) { + await (window as any).electronAPI.cleanOldLogs(daysToKeep) console.info(`已清理${daysToKeep}天前的旧日志`) } else { throw new Error('Electron API not available') diff --git a/websocket_test.html b/websocket_test.html new file mode 100644 index 0000000..650d68f --- /dev/null +++ b/websocket_test.html @@ -0,0 +1,225 @@ + + + + + + WebSocket消息测试 + + + +
+

WebSocket消息测试工具

+ +
+

连接状态

+ + +
未连接
+
+ +
+

测试消息

+ + + + +
+ +
+

消息日志

+ +
+
+
+ + + + \ No newline at end of file