"use strict"; import { app, protocol, BrowserWindow, ipcMain, dialog } from "electron"; import { createProtocol } from "vue-cli-plugin-electron-builder/lib"; // 替换argon2为bcryptjs const bcrypt = require("bcryptjs"); const isDevelopment = process.env.NODE_ENV !== "production"; // 导入fs模块用于文件操作 const fs = require("fs"); const path = require("path"); // 导入数据库相关函数 const { checkDatabaseInitialized, initializeDatabase, initializeUserDatabase, } = require("./db/index.js"); // 导入数据库路径函数 const { getUserDbPath } = require("./db/path.js"); // 导入配置服务 const { initConfigIpc } = require("./service/configService.js"); // 导入字典服务 const { initDictIpc } = require("./service/dictService.js"); // 导入试题服务 const { initQuestionIpc } = require("./service/questionService.js"); // 导入考试服务 const { initExamIpc } = require("./service/examService.js"); // 导入考生服务 const { initExamineeIpc } = require("./service/examineeService.js"); // 导入考生考试服务 const { initExamingIpc } = require("./service/examingService.js"); // 导入文件服务 const { initFileIpc } = require("./service/fileService.js"); // Scheme must be registered before the app is ready protocol.registerSchemesAsPrivileged([ { scheme: "app", privileges: { secure: true, standard: true } }, ]); // 添加全局变量保存窗口引用 let mainWindow = null; async function createWindow() { // Create the browser window. mainWindow = new BrowserWindow({ width: 800, // 默认宽度(实际会被最大化覆盖) height: 600, // 默认高度(实际会被最大化覆盖) show: false, // 先隐藏窗口,避免闪烁 webPreferences: { // 修改:使用更可靠的方式获取preload路径,避免依赖process.cwd() preload: path.join(__dirname, 'preload.js'), nodeIntegration: false, contextIsolation: true, }, }); // 设置隐藏菜单栏 mainWindow.setMenu(null); // 在窗口显示前设置最大化 mainWindow.maximize(); // 然后显示窗口 mainWindow.show(); // 移除之前的便携模式检测逻辑,直接打开开发者工具 // 这样在任何模式下都能查看控制台报错信息 try { console.log('打开开发者工具'); mainWindow.webContents.openDevTools(); } catch (error) { console.error('打开开发者工具时出错:', error); } // 添加窗口关闭事件监听 mainWindow.on("close", (event) => { console.log("检测到窗口关闭事件"); // 阻止默认的关闭行为 event.preventDefault(); console.log("已阻止默认关闭行为"); // 使用同步方式显示对话框(Electron 11.5支持的方式) try { // 直接显示对话框并获取结果 const response = dialog.showMessageBoxSync(mainWindow, { type: "warning", title: "确认关闭", message: "确认要退出软件吗?退出后,未完成的考试将不会被保存。", buttons: ["取消", "确认关闭"], defaultId: 0, cancelId: 0, }); console.log("用户选择了response:", response); // 检查用户选择的按钮索引 if (response === 1) { console.log("用户确认关闭,准备退出应用"); // 移除所有事件监听器以防止任何干扰 mainWindow.removeAllListeners(); // 强制关闭窗口 mainWindow.destroy(); // 然后退出应用 setTimeout(() => { console.log("强制退出应用"); app.quit(); }, 100); } else { console.log("用户取消关闭"); } } catch (error) { console.error("显示对话框时出错:", error); } }); if (process.env.WEBPACK_DEV_SERVER_URL) { // Load the url of the dev server if in development mode await mainWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL); if (!process.env.IS_TEST) mainWindow.webContents.openDevTools(); } else { createProtocol("app"); // Load the index.html when not in development mainWindow.loadURL("app://./index.html"); } } // Quit when all windows are closed. app.on("window-all-closed", () => { // On macOS it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== "darwin") { app.quit(); } }); app.on("activate", () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); // 添加退出确认逻辑 app.on("before-quit", (event) => { console.log("检测到应用退出事件"); // 注释掉这部分代码,因为我们已经在窗口的close事件中处理了确认逻辑 /* // 如果是MacOS系统,让系统默认处理 if (process.platform === 'darwin') { return } // 如果没有窗口,直接退出 if (!mainWindow) { return } // 阻止默认的退出行为 event.preventDefault() // 显示确认对话框 dialog.showMessageBox(mainWindow, { type: 'warning', title: '确认关闭', message: '确认要退出软件吗?退出后,未完成的考试将不会被保存。', buttons: ['取消', '确认关闭'], defaultId: 0, cancelId: 0 }).then((result) => { // 如果用户确认关闭,则退出应用 if (result.response === 1) { app.exit(0) } }).catch((err) => { console.error('显示退出确认对话框失败:', err) }) */ }); // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. // 在文件顶部添加 const sqlite3 = require("sqlite3").verbose(); // 在app.on('ready')事件中添加 app.on("ready", async () => { // 禁用Vue DevTools扩展 /* if (isDevelopment && !process.env.IS_TEST) { // Install Vue Devtools try { await installExtension(VUEJS_DEVTOOLS) } catch (e) { console.error('Vue Devtools failed to install:', e.toString()) } } */ // 初始化配置相关的IPC处理程序 initConfigIpc(ipcMain); // 初始化字典相关的IPC处理程序 initDictIpc(ipcMain); // 初始化试题相关的IPC处理程序 initQuestionIpc(ipcMain); // 初始化考试相关的IPC处理程序 initExamIpc(ipcMain); // 初始化考生相关的IPC处理程序 initExamineeIpc(ipcMain); // 初始化考生考试相关的IPC处理程序 initExamingIpc(ipcMain); // 初始化文件相关的IPC处理程序 initFileIpc(ipcMain); // 检查数据库是否初始化 try { const isInitialized = await checkDatabaseInitialized(); if (!isInitialized) { await initializeDatabase(); } } catch (error) { console.error("数据库初始化失败:", error); } createWindow(); }); // Exit cleanly on request from parent process in development mode. if (isDevelopment) { if (process.platform === "win32") { process.on("message", (data) => { if (data === "graceful-exit") { app.quit(); } }); } else { process.on("SIGTERM", () => { app.quit(); }); } } // 实现hashTest接口(替换原来的argon2Test) ipcMain.handle("hashTest", async (event, inputString) => { try { // 使用bcrypt.hash方法计算哈希值 const salt = await bcrypt.genSalt(10); const hash = await bcrypt.hash(inputString, salt); return hash; } catch (error) { console.error("Error in hashTest calculation:", error); throw error; } }); // 数据库相关IPC接口 ipcMain.handle("check-database-initialized", async () => { try { return await checkDatabaseInitialized(); } catch (error) { console.error("Failed to check database initialization:", error); return false; } }); ipcMain.handle("initialize-database", async () => { try { return await initializeDatabase(); } catch (error) { console.error("Failed to initialize database:", error); return false; } }); // 检查user.db是否存在 ipcMain.handle("checkUserDbExists", async () => { try { const userDbPath = getUserDbPath(); return fs.existsSync(userDbPath); } catch (error) { console.error("检查user.db文件是否存在失败:", error); return false; } }); // 静默初始化用户数据库 ipcMain.handle("initializeUserDatabaseSilently", async () => { try { console.log("开始静默初始化用户数据库..."); const result = await initializeUserDatabase(); console.log("静默初始化用户数据库完成:", result); return { success: true, result }; } catch (error) { console.error("静默初始化用户数据库失败:", error); return { success: false, error: error.message }; } }); // 检测是否为便携模式运行 let isPortableMode = false; let appDataPath = ""; // 初始化应用路径 - 兼容Node.js 12和Windows 7 function initializeAppPaths() { console.log("开始初始化应用路径..."); // 获取应用相关路径 const appRoot = process.cwd(); const exePath = app.getPath("exe"); const appDir = path.dirname(exePath); const userDataPath = app.getPath("userData"); console.log("应用路径信息:"); console.log("- 当前工作目录:", appRoot); console.log("- 可执行文件路径:", exePath); console.log("- 应用目录:", appDir); console.log("- 用户数据目录:", userDataPath); // 检查多个可能的便携模式数据目录位置 const possibleDataPaths = [ // 1. 当前工作目录下的data path.join(appRoot, "data"), // 2. 可执行文件同级目录下的data path.join(appDir, "data"), // 3. 如果在portable-app目录中,检查其父目录下的data path.basename(appDir) === "portable-app" ? path.join(path.dirname(appDir), "data") : null, // 4. 可执行文件同级目录下的portable-app/data path.join(appDir, "portable-app", "data") ].filter(Boolean); // 过滤掉null值 // 检查便携模式标记文件 const possibleFlagPaths = [ path.join(appRoot, "portable.txt"), path.join(appDir, "portable.txt"), path.basename(appDir) === "portable-app" ? path.join(path.dirname(appDir), "portable.txt") : null ].filter(Boolean); // 记录检测结果 console.log("便携模式检测:"); possibleDataPaths.forEach((p, i) => { console.log(`- 数据路径 ${i+1} 存在:`, fs.existsSync(p) ? "是" : "否", p); }); possibleFlagPaths.forEach((p, i) => { console.log(`- 标记文件 ${i+1} 存在:`, fs.existsSync(p) ? "是" : "否", p); }); // 确定是否为便携模式 // 1. 如果存在便携模式标记文件,则为便携模式 const flagFileExists = possibleFlagPaths.some(p => fs.existsSync(p)); // 2. 如果存在data目录,也视为便携模式 const dataPathExists = possibleDataPaths.some(p => fs.existsSync(p)); isPortableMode = flagFileExists || dataPathExists; // 确定数据目录路径 if (isPortableMode) { // 按优先级选择第一个存在的数据目录 appDataPath = possibleDataPaths.find(p => fs.existsSync(p)) || possibleDataPaths[0]; console.log("检测到便携模式,使用数据文件夹:", appDataPath); // 确保数据目录存在 if (!fs.existsSync(appDataPath)) { try { fs.mkdirSync(appDataPath, { recursive: true }); console.log("已创建便携模式数据目录:", appDataPath); } catch (error) { console.error("创建便携模式数据目录失败:", error); } } } else { // 非便携模式使用默认用户数据目录 appDataPath = path.join(userDataPath, "data"); console.log("使用默认数据目录:", appDataPath); // 确保默认数据目录存在 if (!fs.existsSync(appDataPath)) { try { fs.mkdirSync(appDataPath, { recursive: true }); console.log("已创建默认数据目录:", appDataPath); } catch (error) { console.error("创建默认数据目录失败:", error); } } } } // 添加全局函数获取数据目录 global.getAppDataPath = function () { return appDataPath; }; global.isPortableMode = function () { return isPortableMode; }; // 在应用ready事件前初始化路径 initializeAppPaths();