This commit is contained in:
chenqiang 2025-09-09 02:52:00 +08:00
parent 431dfaa45d
commit 657d8df9fc
3 changed files with 195 additions and 183 deletions

View File

@ -1,6 +1,6 @@
const { execSync } = require('child_process'); const { execSync } = require("child_process");
const fs = require('fs'); const fs = require("fs");
const path = require('path'); const path = require("path");
// 创建一个兼容Node.js 12的删除文件夹函数 // 创建一个兼容Node.js 12的删除文件夹函数
function deleteFolderRecursive(dir) { function deleteFolderRecursive(dir) {
@ -24,60 +24,61 @@ function deleteFolderRecursive(dir) {
// 创建打包前的准备工作 // 创建打包前的准备工作
function prepareForBuild() { function prepareForBuild() {
console.log('开始准备Windows 7便携应用打包...'); console.log("开始准备Windows 7便携应用打包...");
try { try {
// 清理之前的构建文件 - 使用兼容Node.js 12的方法 // 清理之前的构建文件 - 使用兼容Node.js 12的方法
const buildDir = path.join(__dirname, 'dist_electron'); const buildDir = path.join(__dirname, "dist_electron");
if (fs.existsSync(buildDir)) { if (fs.existsSync(buildDir)) {
console.log('清理之前的构建文件...'); console.log("清理之前的构建文件...");
deleteFolderRecursive(buildDir); deleteFolderRecursive(buildDir);
} }
// 确保data目录存在便携应用需要 // 确保data目录存在便携应用需要
const dataDir = path.join(__dirname, 'data'); const dataDir = path.join(__dirname, "data");
if (!fs.existsSync(dataDir)) { if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir); fs.mkdirSync(dataDir);
fs.writeFileSync(path.join(dataDir, '.gitignore'), '*\n!.gitignore'); fs.writeFileSync(path.join(dataDir, ".gitignore"), "*\n!.gitignore");
console.log('创建data目录完成'); console.log("创建data目录完成");
} }
console.log('准备工作完成'); console.log("准备工作完成");
} catch (error) { } catch (error) {
console.error('准备工作失败:', error); console.error("准备工作失败:", error);
process.exit(1); process.exit(1);
} }
} }
// 执行打包命令 // 执行打包命令
function buildPortableApp() { function buildPortableApp() {
console.log('开始构建Windows 7便携应用...'); console.log("开始构建Windows 7便携应用...");
try { try {
// 设置构建环境变量 // 设置构建环境变量
process.env.WIN_CSC_LINK = ''; process.env.WIN_CSC_LINK = "";
process.env.WIN_CSC_KEY_PASSWORD = ''; process.env.WIN_CSC_KEY_PASSWORD = "";
// 执行构建命令 - 特别针对Windows 7优化兼容Electron 11 // 执行构建命令 - 移除不兼容的--no-compress选项
// Electron Builder 22.9.1版本不支持--no-compress命令行参数
execSync( execSync(
'npm run electron:build -- --win portable --ia32 --x64 --no-compress --publish never', "npm run electron:build -- --win portable --ia32 --x64 --publish never",
{ stdio: 'inherit' } { stdio: "inherit" }
); );
console.log('Windows 7便携应用构建完成'); console.log("Windows 7便携应用构建完成");
console.log('构建产物位于 dist_electron 目录'); console.log("构建产物位于 dist_electron 目录");
} catch (error) { } catch (error) {
console.error('构建失败:', error); console.error("构建失败:", error);
process.exit(1); process.exit(1);
} }
} }
// 主函数 // 主函数
function main() { function main() {
console.log('==== Windows 7便携应用打包工具 ===='); console.log("==== Windows 7便携应用打包工具 ====");
console.log('Node.js版本:', process.version); console.log("Node.js版本:", process.version);
console.log('Electron版本: 11.5.0'); console.log("Electron版本: 11.5.0");
console.log('Vue版本: 2.6.11'); console.log("Vue版本: 2.6.11");
prepareForBuild(); prepareForBuild();
buildPortableApp(); buildPortableApp();

View File

@ -1,40 +1,44 @@
'use strict' "use strict";
import { app, protocol, BrowserWindow, ipcMain, dialog } from 'electron' import { app, protocol, BrowserWindow, ipcMain, dialog } from "electron";
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib' import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
// 替换argon2为bcryptjs // 替换argon2为bcryptjs
const bcrypt = require('bcryptjs') const bcrypt = require("bcryptjs");
const isDevelopment = process.env.NODE_ENV !== 'production' const isDevelopment = process.env.NODE_ENV !== "production";
// 导入fs模块用于文件操作 // 导入fs模块用于文件操作
const fs = require('fs') const fs = require("fs");
const path = require('path') const path = require("path");
// 导入数据库相关函数 // 导入数据库相关函数
const { checkDatabaseInitialized, initializeDatabase, initializeUserDatabase } = require('./db/index.js'); const {
checkDatabaseInitialized,
initializeDatabase,
initializeUserDatabase,
} = require("./db/index.js");
// 导入数据库路径函数 // 导入数据库路径函数
const { getUserDbPath } = require('./db/path.js'); const { getUserDbPath } = require("./db/path.js");
// 导入配置服务 // 导入配置服务
const { initConfigIpc } = require('./service/configService.js'); const { initConfigIpc } = require("./service/configService.js");
// 导入字典服务 // 导入字典服务
const { initDictIpc } = require('./service/dictService.js'); const { initDictIpc } = require("./service/dictService.js");
// 导入试题服务 // 导入试题服务
const { initQuestionIpc } = require('./service/questionService.js'); const { initQuestionIpc } = require("./service/questionService.js");
// 导入考试服务 // 导入考试服务
const { initExamIpc } = require('./service/examService.js'); const { initExamIpc } = require("./service/examService.js");
// 导入考生服务 // 导入考生服务
const { initExamineeIpc } = require('./service/examineeService.js'); const { initExamineeIpc } = require("./service/examineeService.js");
// 导入考生考试服务 // 导入考生考试服务
const { initExamingIpc } = require('./service/examingService.js'); const { initExamingIpc } = require("./service/examingService.js");
// 导入文件服务 // 导入文件服务
const { initFileIpc } = require('./service/fileService.js'); const { initFileIpc } = require("./service/fileService.js");
// Scheme must be registered before the app is ready // Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([ protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } } { scheme: "app", privileges: { secure: true, standard: true } },
]) ]);
// 添加全局变量保存窗口引用 // 添加全局变量保存窗口引用
let mainWindow = null let mainWindow = null;
async function createWindow() { async function createWindow() {
// Create the browser window. // Create the browser window.
@ -44,11 +48,11 @@ async function createWindow() {
show: false, // 先隐藏窗口,避免闪烁 show: false, // 先隐藏窗口,避免闪烁
webPreferences: { webPreferences: {
// 改为使用绝对路径解析 // 改为使用绝对路径解析
preload: require('path').join(process.cwd(), 'src/preload.js'), preload: require("path").join(process.cwd(), "src/preload.js"),
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true contextIsolation: true,
} },
}) });
// 设置隐藏菜单栏 // 设置隐藏菜单栏
mainWindow.setMenu(null); mainWindow.setMenu(null);
@ -59,76 +63,76 @@ async function createWindow() {
mainWindow.show(); mainWindow.show();
// 添加窗口关闭事件监听 // 添加窗口关闭事件监听
mainWindow.on('close', (event) => { mainWindow.on("close", (event) => {
console.log('检测到窗口关闭事件'); console.log("检测到窗口关闭事件");
// 阻止默认的关闭行为 // 阻止默认的关闭行为
event.preventDefault(); event.preventDefault();
console.log('已阻止默认关闭行为'); console.log("已阻止默认关闭行为");
// 使用同步方式显示对话框Electron 11.5支持的方式) // 使用同步方式显示对话框Electron 11.5支持的方式)
try { try {
// 直接显示对话框并获取结果 // 直接显示对话框并获取结果
const response = dialog.showMessageBoxSync(mainWindow, { const response = dialog.showMessageBoxSync(mainWindow, {
type: 'warning', type: "warning",
title: '确认关闭', title: "确认关闭",
message: '确认要退出软件吗?退出后,未完成的考试将不会被保存。', message: "确认要退出软件吗?退出后,未完成的考试将不会被保存。",
buttons: ['取消', '确认关闭'], buttons: ["取消", "确认关闭"],
defaultId: 0, defaultId: 0,
cancelId: 0 cancelId: 0,
}); });
console.log('用户选择了response:', response); console.log("用户选择了response:", response);
// 检查用户选择的按钮索引 // 检查用户选择的按钮索引
if (response === 1) { if (response === 1) {
console.log('用户确认关闭,准备退出应用'); console.log("用户确认关闭,准备退出应用");
// 移除所有事件监听器以防止任何干扰 // 移除所有事件监听器以防止任何干扰
mainWindow.removeAllListeners(); mainWindow.removeAllListeners();
// 强制关闭窗口 // 强制关闭窗口
mainWindow.destroy(); mainWindow.destroy();
// 然后退出应用 // 然后退出应用
setTimeout(() => { setTimeout(() => {
console.log('强制退出应用'); console.log("强制退出应用");
app.quit(); app.quit();
}, 100); }, 100);
} else { } else {
console.log('用户取消关闭'); console.log("用户取消关闭");
} }
} catch (error) { } catch (error) {
console.error('显示对话框时出错:', error); console.error("显示对话框时出错:", error);
} }
}); });
if (process.env.WEBPACK_DEV_SERVER_URL) { if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode // Load the url of the dev server if in development mode
await mainWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL) await mainWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
if (!process.env.IS_TEST) mainWindow.webContents.openDevTools() if (!process.env.IS_TEST) mainWindow.webContents.openDevTools();
} else { } else {
createProtocol('app') createProtocol("app");
// Load the index.html when not in development // Load the index.html when not in development
mainWindow.loadURL('app://./index.html') mainWindow.loadURL("app://./index.html");
} }
} }
// Quit when all windows are closed. // Quit when all windows are closed.
app.on('window-all-closed', () => { app.on("window-all-closed", () => {
// On macOS it is common for applications and their menu bar // On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q // to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') { if (process.platform !== "darwin") {
app.quit() app.quit();
} }
}) });
app.on('activate', () => { app.on("activate", () => {
// On macOS it's common to re-create a window in the app when the // 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. // dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow() if (BrowserWindow.getAllWindows().length === 0) createWindow();
}) });
// 添加退出确认逻辑 // 添加退出确认逻辑
app.on('before-quit', (event) => { app.on("before-quit", (event) => {
console.log('检测到应用退出事件'); console.log("检测到应用退出事件");
// 注释掉这部分代码因为我们已经在窗口的close事件中处理了确认逻辑 // 注释掉这部分代码因为我们已经在窗口的close事件中处理了确认逻辑
/* /*
// 如果是MacOS系统让系统默认处理 // 如果是MacOS系统让系统默认处理
@ -161,16 +165,16 @@ app.on('before-quit', (event) => {
console.error('显示退出确认对话框失败:', err) console.error('显示退出确认对话框失败:', err)
}) })
*/ */
}) });
// This method will be called when Electron has finished // This method will be called when Electron has finished
// initialization and is ready to create browser windows. // initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs. // Some APIs can only be used after this event occurs.
// 在文件顶部添加 // 在文件顶部添加
const sqlite3 = require('sqlite3').verbose(); const sqlite3 = require("sqlite3").verbose();
// 在app.on('ready')事件中添加 // 在app.on('ready')事件中添加
app.on('ready', async () => { app.on("ready", async () => {
// 禁用Vue DevTools扩展 // 禁用Vue DevTools扩展
/* /*
if (isDevelopment && !process.env.IS_TEST) { if (isDevelopment && !process.env.IS_TEST) {
@ -205,119 +209,125 @@ app.on('ready', async () => {
await initializeDatabase(); await initializeDatabase();
} }
} catch (error) { } catch (error) {
console.error('数据库初始化失败:', error); console.error("数据库初始化失败:", error);
} }
createWindow() createWindow();
}) });
// Exit cleanly on request from parent process in development mode. // Exit cleanly on request from parent process in development mode.
if (isDevelopment) { if (isDevelopment) {
if (process.platform === 'win32') { if (process.platform === "win32") {
process.on('message', (data) => { process.on("message", (data) => {
if (data === 'graceful-exit') { if (data === "graceful-exit") {
app.quit() app.quit();
} }
}) });
} else { } else {
process.on('SIGTERM', () => { process.on("SIGTERM", () => {
app.quit() app.quit();
}) });
} }
} }
// 实现hashTest接口替换原来的argon2Test // 实现hashTest接口替换原来的argon2Test
ipcMain.handle('hashTest', async (event, inputString) => { ipcMain.handle("hashTest", async (event, inputString) => {
try { try {
// 使用bcrypt.hash方法计算哈希值 // 使用bcrypt.hash方法计算哈希值
const salt = await bcrypt.genSalt(10) const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash(inputString, salt) const hash = await bcrypt.hash(inputString, salt);
return hash return hash;
} catch (error) { } catch (error) {
console.error('Error in hashTest calculation:', error) console.error("Error in hashTest calculation:", error);
throw error throw error;
} }
}) });
// 数据库相关IPC接口 // 数据库相关IPC接口
ipcMain.handle('check-database-initialized', async () => { ipcMain.handle("check-database-initialized", async () => {
try { try {
return await checkDatabaseInitialized() return await checkDatabaseInitialized();
} catch (error) { } catch (error) {
console.error('Failed to check database initialization:', error) console.error("Failed to check database initialization:", error);
return false return false;
} }
}) });
ipcMain.handle('initialize-database', async () => { ipcMain.handle("initialize-database", async () => {
try { try {
return await initializeDatabase() return await initializeDatabase();
} catch (error) { } catch (error) {
console.error('Failed to initialize database:', error) console.error("Failed to initialize database:", error);
return false return false;
} }
}) });
// 检查user.db是否存在 // 检查user.db是否存在
ipcMain.handle('checkUserDbExists', async () => { ipcMain.handle("checkUserDbExists", async () => {
try { try {
const userDbPath = getUserDbPath() const userDbPath = getUserDbPath();
return fs.existsSync(userDbPath) return fs.existsSync(userDbPath);
} catch (error) { } catch (error) {
console.error('检查user.db文件是否存在失败:', error) console.error("检查user.db文件是否存在失败:", error);
return false return false;
} }
}) });
// 静默初始化用户数据库 // 静默初始化用户数据库
ipcMain.handle('initializeUserDatabaseSilently', async () => { ipcMain.handle("initializeUserDatabaseSilently", async () => {
try { try {
console.log('开始静默初始化用户数据库...') console.log("开始静默初始化用户数据库...");
const result = await initializeUserDatabase() const result = await initializeUserDatabase();
console.log('静默初始化用户数据库完成:', result) console.log("静默初始化用户数据库完成:", result);
return { success: true, result } return { success: true, result };
} catch (error) { } catch (error) {
console.error('静默初始化用户数据库失败:', error) console.error("静默初始化用户数据库失败:", error);
return { success: false, error: error.message } return { success: false, error: error.message };
} }
}) });
// 检测是否为便携模式运行 // 检测是否为便携模式运行
let isPortableMode = false let isPortableMode = false;
let appDataPath = '' let appDataPath = "";
// 初始化应用路径 - 兼容Node.js 12 // 初始化应用路径 - 兼容Node.js 12和Windows 7
function initializeAppPaths() { function initializeAppPaths() {
// 检查应用根目录是否存在data文件夹如果存在则认为是便携模式 // 检查应用根目录是否存在data文件夹如果存在则认为是便携模式
const portableDataPath = path.join(process.cwd(), 'data') // Windows路径兼容处理
const defaultDataPath = path.join(app.getPath('userData'), 'data') const appRoot = process.cwd();
const portableDataPath = path.join(appRoot, "data");
const defaultDataPath = path.join(app.getPath("userData"), "data");
// 检查是否为便携模式 // 检查是否为便携模式
if (fs.existsSync(portableDataPath)) { if (fs.existsSync(portableDataPath)) {
isPortableMode = true isPortableMode = true;
appDataPath = portableDataPath appDataPath = portableDataPath;
console.log('检测到便携模式使用当前目录的data文件夹:', appDataPath) console.log("检测到便携模式使用当前目录的data文件夹:", appDataPath);
} else { } else {
isPortableMode = false isPortableMode = false;
appDataPath = defaultDataPath appDataPath = defaultDataPath;
console.log('使用默认数据目录:', appDataPath) console.log("使用默认数据目录:", appDataPath);
// 确保默认数据目录存在 // 确保默认数据目录存在
if (!fs.existsSync(appDataPath)) { if (!fs.existsSync(appDataPath)) {
fs.mkdirSync(appDataPath, { recursive: true }) // 递归创建目录兼容Windows路径
try {
fs.mkdirSync(appDataPath, { recursive: true });
} catch (error) {
console.error("创建默认数据目录失败:", error);
}
} }
} }
} }
// 添加全局函数获取数据目录 // 添加全局函数获取数据目录
global.getAppDataPath = function () { global.getAppDataPath = function () {
return appDataPath return appDataPath;
} };
global.isPortableMode = function () { global.isPortableMode = function () {
return isPortableMode return isPortableMode;
} };
// 在应用ready事件前初始化路径 // 在应用ready事件前初始化路径
initializeAppPaths() initializeAppPaths();

View File

@ -1,77 +1,78 @@
module.exports = { module.exports = {
// 添加应用标题配置 // 添加应用标题配置
chainWebpack: config => { chainWebpack: (config) => {
config config.plugin("html").tap((args) => {
.plugin('html') args[0].title = "统计技能考试系统";
.tap(args => { return args;
args[0].title = '统计技能考试系统' });
return args
})
}, },
pluginOptions: { pluginOptions: {
electronBuilder: { electronBuilder: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
preload: 'src/preload.js', preload: "src/preload.js",
mainProcessFile: 'src/background/main.js', mainProcessFile: "src/background/main.js",
lintPreloadFiles: false, lintPreloadFiles: false,
// 将externals改为数组格式 // 将externals改为数组格式
externals: ['fontkit', 'pdfkit'], externals: ["fontkit", "pdfkit"],
chainWebpackMainProcess: (config) => { chainWebpackMainProcess: (config) => {
config.module config.module
.rule('babel') .rule("babel")
.test(/\.js$/) .test(/\.js$/)
.use('babel-loader') .use("babel-loader")
.loader('babel-loader') .loader("babel-loader")
.options({ .options({
presets: ['@babel/preset-env'], presets: ["@babel/preset-env"],
plugins: [ plugins: [
'@babel/plugin-proposal-optional-chaining', "@babel/plugin-proposal-optional-chaining",
'@babel/plugin-proposal-class-properties' "@babel/plugin-proposal-class-properties",
] ],
}) })
.end() .end();
// 添加对mjs文件的处理 - 兼容Electron 11 // 添加对mjs文件的处理 - 兼容Electron 11
config.module config.module
.rule('mjs') .rule("mjs")
.test(/\.mjs$/) .test(/\.mjs$/)
.include .include.add(/node_modules/)
.add(/node_modules/)
.end() .end()
.type('javascript/auto') .type("javascript/auto");
}, },
// Windows 7便携应用配置 - 兼容Electron 11 // Windows 7便携应用配置 - 兼容Electron 11和electron-builder 22.9.1
win: { win: {
target: [ target: [
{ {
target: 'portable', target: "portable",
arch: ['ia32', 'x64'] arch: ["ia32", "x64"],
} },
], ],
icon: 'public/favicon.ico', icon: "public/favicon.ico",
// 关闭压缩以提高在Windows 7上的兼容性 // 在electron-builder 22.9.1中compression选项应该在这里设置
compression: 'store', // 删除了之前的错误配置
// 针对Windows 7的特殊配置 // 针对Windows 7的特殊配置
extraResources: [ extraResources: [
{ {
from: 'src/background/font', from: "src/background/font",
to: 'font', to: "font",
filter: ['**/*'] filter: ["**/*"],
}, },
{ {
from: 'data', from: "data",
to: 'data', to: "data",
filter: ['.gitignore'] filter: [".gitignore"],
} },
] ],
}, },
// 便携应用配置 - 兼容Electron 11 // 便携应用配置 - 兼容Electron 11
portable: { portable: {
artifactName: '统计技能考试系统_便携版_${version}_${arch}.exe', artifactName: "统计技能考试系统_便携版_${version}_${arch}.exe",
// 启用便携模式标志 // 启用便携模式标志
target: 'portable' target: "portable",
} },
} // 在electron-builder 22.9.1中压缩配置应该在build选项中设置
} builderOptions: {
} compression: "store", // 等同于--no-compress不压缩应用
},
},
},
};