This commit is contained in:
chenqiang 2025-09-09 21:52:31 +08:00
parent b65ff88896
commit 7f90027647
4 changed files with 259 additions and 165 deletions

View File

@ -1,5 +1,5 @@
const path = require('path');
const fs = require('fs');
const path = require('path'); // 添加缺失的path模块导入
const { app } = require('electron');
// 判断是否为开发环境
@ -7,14 +7,27 @@ const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
// 检测是否为便携模式
let isPortable = false;
const exePath = app.getPath('exe');
const appDir = path.dirname(exePath);
const portableFlagPath = path.join(appDir, 'portable.txt');
let appDir;
let portableFlagPath;
// 如果应用目录中存在portable.txt文件则启用便携模式
if (!isDev && fs.existsSync(portableFlagPath)) {
isPortable = true;
console.log('启用便携模式');
// 确保app已经初始化后再获取路径
if (app && app.getPath) {
try {
const exePath = app.getPath('exe');
appDir = path.dirname(exePath);
portableFlagPath = path.join(appDir, 'portable.txt');
// 如果应用目录中存在portable.txt文件则启用便携模式
if (!isDev && fs.existsSync(portableFlagPath)) {
isPortable = true;
console.log('启用便携模式');
}
} catch (error) {
console.error('获取应用路径时出错:', error);
appDir = process.cwd();
}
} else {
appDir = process.cwd();
}
// 根据模式选择数据目录
@ -49,5 +62,7 @@ function getUserDbPath() {
// 导出函数供其他模块使用
module.exports = {
getSystemDbPath,
getUserDbPath
getUserDbPath,
dataDir, // 导出数据目录路径,方便其他地方使用
isPortable // 导出便携模式状态
};

View File

@ -41,77 +41,97 @@ protocol.registerSchemesAsPrivileged([
let mainWindow = null;
async function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800, // 默认宽度(实际会被最大化覆盖)
height: 600, // 默认高度(实际会被最大化覆盖)
show: false, // 先隐藏窗口,避免闪烁
webPreferences: {
// 改为使用绝对路径解析
preload: require("path").join(process.cwd(), "src/preload.js"),
nodeIntegration: false,
contextIsolation: true,
},
});
// 设置隐藏菜单栏
mainWindow.setMenu(null);
// 在窗口显示前设置最大化
mainWindow.maximize();
// 然后显示窗口
mainWindow.show();
// 添加窗口关闭事件监听
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("用户取消关闭");
try {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
show: true, // 直接显示窗口,避免隐藏后显示可能导致的问题
webPreferences: {
// 修复preload路径使用更可靠的解析方式
preload: path.join(__dirname, '..', 'src', 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
}
} 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");
// 设置隐藏菜单栏
mainWindow.setMenu(null);
// 设置窗口最大化(放在窗口创建后,确保窗口已经初始化)
try {
mainWindow.maximize();
} catch (error) {
console.warn('窗口最大化失败:', error);
}
// 加载URL
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");
}
// 添加窗口关闭事件监听
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);
}
});
// 添加错误监听
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
console.error(`页面加载失败: ${errorCode} - ${errorDescription}`);
});
// 添加崩溃监听
mainWindow.webContents.on('crashed', (event, killed) => {
console.error(`渲染进程崩溃: killed=${killed}`);
});
} catch (error) {
console.error('创建窗口时出错:', error);
// 如果创建窗口失败,显示错误对话框
dialog.showErrorBox('应用启动失败', `无法创建应用窗口: ${error.message}`);
}
}
@ -175,92 +195,104 @@ 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();
// 禁用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);
}
// 添加数据库初始化相关的IPC处理
ipcMain.handle('initialize-database', async (event) => {
try {
console.log('收到初始化数据库请求');
await initializeDatabase();
console.log('数据库初始化成功');
return { success: true };
} catch (error) {
console.error('数据库初始化失败:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('check-database-initialized', async (event) => {
try {
return await checkDatabaseInitialized();
} catch (error) {
console.error('检查数据库初始化状态失败:', error);
return false;
}
});
// 检查user.db是否存在的IPC处理
ipcMain.handle('checkUserDbExists', async (event) => {
try {
const userDbPath = getUserDbPath();
const exists = fs.existsSync(userDbPath);
console.log(`用户数据库检查结果: ${exists ? '存在' : '不存在'}`);
return exists;
} catch (error) {
console.error('检查用户数据库失败:', error);
return false;
}
});
// 静默初始化用户数据库的IPC处理
ipcMain.handle('initializeUserDatabaseSilently', async (event) => {
try {
await initializeUserDatabase();
console.log('用户数据库静默初始化成功');
return { success: true };
} catch (error) {
console.error('用户数据库静默初始化失败:', error);
return { success: false, error: error.message };
}
});
// 检测是否为便携模式运行
try {
const isInitialized = await checkDatabaseInitialized();
console.log(`应用启动 - 数据库状态: ${isInitialized ? '已初始化' : '未初始化'}`);
createWindow();
} catch (error) {
console.error('检查数据库状态时出错:', error);
createWindow();
}
} catch (error) {
console.error("数据库初始化失败:", error);
console.error('应用初始化失败:', error);
dialog.showErrorBox('应用初始化失败', `无法初始化应用: ${error.message}`);
}
// 添加数据库初始化相关的IPC处理
ipcMain.handle('initialize-database', async (event) => {
try {
console.log('收到初始化数据库请求');
await initializeDatabase();
console.log('数据库初始化成功');
return { success: true };
} catch (error) {
console.error('数据库初始化失败:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('check-database-initialized', async (event) => {
try {
return await checkDatabaseInitialized();
} catch (error) {
console.error('检查数据库初始化状态失败:', error);
return false;
}
});
// 检查user.db是否存在的IPC处理
ipcMain.handle('checkUserDbExists', async (event) => {
try {
const userDbPath = getUserDbPath();
const exists = fs.existsSync(userDbPath);
console.log(`用户数据库检查结果: ${exists ? '存在' : '不存在'}`);
return exists;
} catch (error) {
console.error('检查用户数据库失败:', error);
return false;
}
});
// 静默初始化用户数据库的IPC处理
ipcMain.handle('initializeUserDatabaseSilently', async (event) => {
try {
await initializeUserDatabase();
console.log('用户数据库静默初始化成功');
return { success: true };
} catch (error) {
console.error('用户数据库静默初始化失败:', error);
return { success: false, error: error.message };
}
});
// 检测是否为便携模式运行
createWindow();
});
// Exit cleanly on request from parent process in development mode.

View File

@ -1,4 +1,4 @@
const fs = require('fs');
const fs = require('fs'); // 添加缺失的fs模块导入
const path = require('path');
const { execSync } = require('child_process');
@ -23,7 +23,6 @@ function deleteFolderRecursive(dir) {
}
// 创建打包前的准备工作
// 在prepareForBuild函数中添加
function prepareForBuild() {
console.log('开始准备Windows 7便携应用打包...');
@ -69,20 +68,64 @@ function buildPortableApp() {
process.env.ELECTRON_BUILDER_SKIP_NOTARIZATION = 'true';
process.env.ELECTRON_SKIP_NOTARIZE = 'true';
// 使用最基本的命令行参数,移除不支持的--no-sign
// 使用最基本的命令行参数
execSync(
'npm run electron:build -- --win portable --ia32 --x64 --publish never',
{ stdio: 'inherit' }
);
// 构建完成后,创建最终的便携应用结构
console.log('创建便携应用最终结构...');
createPortableStructure();
console.log('Windows 7便携应用构建完成');
console.log('构建产物位于 dist_electron 目录');
console.log('构建产物位于 dist_electron 目录下的 portable-app 文件夹');
} catch (error) {
console.error('构建失败:', error);
process.exit(1);
}
}
// 创建便携应用最终结构可执行文件和data目录在同一级
function createPortableStructure() {
const distDir = path.join(__dirname, 'dist_electron');
const portableAppDir = path.join(distDir, 'portable-app');
// 创建目标目录
if (fs.existsSync(portableAppDir)) {
deleteFolderRecursive(portableAppDir);
}
fs.mkdirSync(portableAppDir, { recursive: true });
// 复制可执行文件
const exeFiles = fs.readdirSync(distDir).filter(file => file.endsWith('.exe'));
if (exeFiles.length > 0) {
const exeFile = exeFiles[0];
fs.copyFileSync(path.join(distDir, exeFile), path.join(portableAppDir, exeFile));
console.log(`已复制可执行文件: ${exeFile}`);
}
// 创建data目录并复制数据库文件
const srcDataDir = path.join(__dirname, 'data');
const destDataDir = path.join(portableAppDir, 'data');
fs.mkdirSync(destDataDir, { recursive: true });
// 复制data目录下的所有文件
if (fs.existsSync(srcDataDir)) {
const dataFiles = fs.readdirSync(srcDataDir);
dataFiles.forEach(file => {
const srcPath = path.join(srcDataDir, file);
const destPath = path.join(destDataDir, file);
fs.copyFileSync(srcPath, destPath);
console.log(`已复制数据文件: ${file}`);
});
}
// 确保portable.txt文件在可执行文件同级目录
fs.copyFileSync(path.join(__dirname, 'portable.txt'), path.join(portableAppDir, 'portable.txt'));
console.log('已复制便携模式标识文件');
}
// 主函数
function main() {
console.log('==== Windows 7便携应用打包工具 ====');

View File

@ -86,7 +86,11 @@ module.exports = {
{
from: "data",
to: "data",
filter: ["**/*"], // 修改为包含所有文件
filter: ["**/*"], // 确保包含data目录下的所有文件
},
{
from: "portable.txt",
to: ".", // 确保portable.txt文件放在根目录
},
],
},