419 lines
13 KiB
JavaScript
419 lines
13 KiB
JavaScript
"use strict";
|
||
|
||
const {
|
||
app,
|
||
protocol,
|
||
BrowserWindow,
|
||
ipcMain,
|
||
dialog,
|
||
shell,
|
||
} = require("electron");
|
||
const { createProtocol } = require("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 { getSystemDbPath, 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");
|
||
// 试卷
|
||
const { initPaperIpc } = require("./service/paperService.js");
|
||
// 导入 package.json 获取系统信息
|
||
const packageInfo = require("../package.json");
|
||
|
||
// 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')事件中添加获取package.json的IPC处理程序
|
||
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())
|
||
}
|
||
}
|
||
*/
|
||
|
||
// 添加获取package.json的IPC处理程序
|
||
ipcMain.handle("get-package-info", async () => {
|
||
try {
|
||
return {
|
||
success: true,
|
||
data: packageInfo,
|
||
};
|
||
} catch (error) {
|
||
console.error("获取package.json信息失败:", error);
|
||
return {
|
||
success: false,
|
||
message: "获取package.json信息失败",
|
||
data: null,
|
||
};
|
||
}
|
||
});
|
||
|
||
// 实现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() {
|
||
// 检查应用根目录是否存在data文件夹,如果存在则认为是便携模式
|
||
// Windows路径兼容处理
|
||
const appRoot = process.cwd();
|
||
const portableDataPath = path.join(appRoot, "data");
|
||
const defaultDataPath = path.join(app.getPath("userData"), "data");
|
||
|
||
// 检查是否为便携模式
|
||
if (fs.existsSync(portableDataPath)) {
|
||
isPortableMode = true;
|
||
appDataPath = portableDataPath;
|
||
console.log("检测到便携模式,使用当前目录的data文件夹:", appDataPath);
|
||
} else {
|
||
isPortableMode = false;
|
||
appDataPath = defaultDataPath;
|
||
console.log("使用默认数据目录:", appDataPath);
|
||
|
||
// 确保默认数据目录存在
|
||
if (!fs.existsSync(appDataPath)) {
|
||
// 递归创建目录,兼容Windows路径
|
||
try {
|
||
fs.mkdirSync(appDataPath, { recursive: true });
|
||
} catch (error) {
|
||
console.error("创建默认数据目录失败:", error);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加全局函数获取数据目录
|
||
global.getAppDataPath = function () {
|
||
return appDataPath;
|
||
};
|
||
|
||
global.isPortableMode = function () {
|
||
return isPortableMode;
|
||
};
|
||
|
||
// 在应用ready事件前初始化路径
|
||
initializeAppPaths();
|
||
|
||
// 新增:添加获取路径信息的IPC处理程序
|
||
ipcMain.handle("get-exe-path", (event) => {
|
||
try {
|
||
const exePath = app.getPath("exe");
|
||
console.log("获取exe路径:", exePath);
|
||
return exePath;
|
||
} catch (error) {
|
||
console.error("获取exe路径失败:", error);
|
||
return null;
|
||
}
|
||
});
|
||
|
||
ipcMain.handle("get-database-paths", (event) => {
|
||
try {
|
||
const systemDbPath = getSystemDbPath();
|
||
const userDbPath = getUserDbPath();
|
||
console.log("获取数据库路径:");
|
||
console.log("system.db:", systemDbPath);
|
||
console.log("user.db:", userDbPath);
|
||
return { systemDbPath, userDbPath };
|
||
} catch (error) {
|
||
console.error("获取数据库路径失败:", error);
|
||
return { systemDbPath: "获取失败", userDbPath: "获取失败" };
|
||
}
|
||
});
|
||
|
||
// 在现有的IPC处理程序后面添加
|
||
ipcMain.handle("file-open-file", async (event, filePath) => {
|
||
try {
|
||
console.log("尝试打开文件:", filePath);
|
||
// 使用shell.openPath打开文件,这会使用系统默认应用打开指定文件
|
||
const result = await shell.openPath(filePath);
|
||
// 在Windows上,result是打开的文件路径;在macOS和Linux上,成功时返回空字符串
|
||
if (process.platform === "win32" || result === "") {
|
||
console.log("文件打开成功:", filePath);
|
||
return { success: true, message: "文件打开成功" };
|
||
} else {
|
||
console.error("文件打开失败:", result);
|
||
return { success: false, message: result };
|
||
}
|
||
} catch (error) {
|
||
console.error("打开文件时发生错误:", error);
|
||
return { success: false, message: error.message };
|
||
}
|
||
});
|
||
|
||
// 初始化配置相关的IPC处理程序
|
||
initConfigIpc(ipcMain);
|
||
// 初始化字典相关的IPC处理程序
|
||
initDictIpc(ipcMain);
|
||
// 初始化试题相关的IPC处理程序
|
||
initQuestionIpc(ipcMain);
|
||
// 初始化考试相关的IPC处理程序
|
||
initExamIpc(ipcMain);
|
||
// 初始化考生相关的IPC处理程序
|
||
initExamineeIpc(ipcMain);
|
||
// 初始化考生考试相关的IPC处理程序
|
||
initExamingIpc(ipcMain);
|
||
// 初始化文件相关的IPC处理程序
|
||
initFileIpc(ipcMain);
|
||
// 初始化试卷相关的IPC处理程序
|
||
initPaperIpc(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();
|
||
});
|
||
}
|
||
}
|