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

View File

@ -1,54 +1,58 @@
'use strict'
"use strict";
import { app, protocol, BrowserWindow, ipcMain, dialog } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
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'
const bcrypt = require("bcryptjs");
const isDevelopment = process.env.NODE_ENV !== "production";
// 导入fs模块用于文件操作
const fs = require('fs')
const path = require('path')
const fs = require("fs");
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
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() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800, // 默认宽度(实际会被最大化覆盖)
width: 800, // 默认宽度(实际会被最大化覆盖)
height: 600, // 默认高度(实际会被最大化覆盖)
show: false, // 先隐藏窗口,避免闪烁
webPreferences: {
// 改为使用绝对路径解析
preload: require('path').join(process.cwd(), 'src/preload.js'),
preload: require("path").join(process.cwd(), "src/preload.js"),
nodeIntegration: false,
contextIsolation: true
}
})
contextIsolation: true,
},
});
// 设置隐藏菜单栏
mainWindow.setMenu(null);
@ -59,76 +63,76 @@ async function createWindow() {
mainWindow.show();
// 添加窗口关闭事件监听
mainWindow.on('close', (event) => {
console.log('检测到窗口关闭事件');
mainWindow.on("close", (event) => {
console.log("检测到窗口关闭事件");
// 阻止默认的关闭行为
event.preventDefault();
console.log('已阻止默认关闭行为');
console.log("已阻止默认关闭行为");
// 使用同步方式显示对话框Electron 11.5支持的方式)
try {
// 直接显示对话框并获取结果
const response = dialog.showMessageBoxSync(mainWindow, {
type: 'warning',
title: '确认关闭',
message: '确认要退出软件吗?退出后,未完成的考试将不会被保存。',
buttons: ['取消', '确认关闭'],
type: "warning",
title: "确认关闭",
message: "确认要退出软件吗?退出后,未完成的考试将不会被保存。",
buttons: ["取消", "确认关闭"],
defaultId: 0,
cancelId: 0
cancelId: 0,
});
console.log('用户选择了response:', response);
console.log("用户选择了response:", response);
// 检查用户选择的按钮索引
if (response === 1) {
console.log('用户确认关闭,准备退出应用');
console.log("用户确认关闭,准备退出应用");
// 移除所有事件监听器以防止任何干扰
mainWindow.removeAllListeners();
// 强制关闭窗口
mainWindow.destroy();
// 然后退出应用
setTimeout(() => {
console.log('强制退出应用');
console.log("强制退出应用");
app.quit();
}, 100);
} else {
console.log('用户取消关闭');
console.log("用户取消关闭");
}
} catch (error) {
console.error('显示对话框时出错:', 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()
await mainWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
if (!process.env.IS_TEST) mainWindow.webContents.openDevTools();
} else {
createProtocol('app')
createProtocol("app");
// Load the index.html when not in development
mainWindow.loadURL('app://./index.html')
mainWindow.loadURL("app://./index.html");
}
}
// 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
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
if (process.platform !== "darwin") {
app.quit();
}
})
});
app.on('activate', () => {
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()
})
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
// 添加退出确认逻辑
app.on('before-quit', (event) => {
console.log('检测到应用退出事件');
app.on("before-quit", (event) => {
console.log("检测到应用退出事件");
// 注释掉这部分代码因为我们已经在窗口的close事件中处理了确认逻辑
/*
// 如果是MacOS系统让系统默认处理
@ -161,16 +165,16 @@ app.on('before-quit', (event) => {
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();
const sqlite3 = require("sqlite3").verbose();
// 在app.on('ready')事件中添加
app.on('ready', async () => {
app.on("ready", async () => {
// 禁用Vue DevTools扩展
/*
if (isDevelopment && !process.env.IS_TEST) {
@ -205,119 +209,125 @@ app.on('ready', async () => {
await initializeDatabase();
}
} catch (error) {
console.error('数据库初始化失败:', error);
console.error("数据库初始化失败:", error);
}
createWindow()
})
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()
if (process.platform === "win32") {
process.on("message", (data) => {
if (data === "graceful-exit") {
app.quit();
}
})
});
} else {
process.on('SIGTERM', () => {
app.quit()
})
process.on("SIGTERM", () => {
app.quit();
});
}
}
// 实现hashTest接口替换原来的argon2Test
ipcMain.handle('hashTest', async (event, inputString) => {
ipcMain.handle("hashTest", async (event, inputString) => {
try {
// 使用bcrypt.hash方法计算哈希值
const salt = await bcrypt.genSalt(10)
const hash = await bcrypt.hash(inputString, salt)
return 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
console.error("Error in hashTest calculation:", error);
throw error;
}
})
});
// 数据库相关IPC接口
ipcMain.handle('check-database-initialized', async () => {
ipcMain.handle("check-database-initialized", async () => {
try {
return await checkDatabaseInitialized()
return await checkDatabaseInitialized();
} catch (error) {
console.error('Failed to check database initialization:', error)
return false
console.error("Failed to check database initialization:", error);
return false;
}
})
});
ipcMain.handle('initialize-database', async () => {
ipcMain.handle("initialize-database", async () => {
try {
return await initializeDatabase()
return await initializeDatabase();
} catch (error) {
console.error('Failed to initialize database:', error)
return false
console.error("Failed to initialize database:", error);
return false;
}
})
});
// 检查user.db是否存在
ipcMain.handle('checkUserDbExists', async () => {
ipcMain.handle("checkUserDbExists", async () => {
try {
const userDbPath = getUserDbPath()
return fs.existsSync(userDbPath)
const userDbPath = getUserDbPath();
return fs.existsSync(userDbPath);
} catch (error) {
console.error('检查user.db文件是否存在失败:', error)
return false
console.error("检查user.db文件是否存在失败:", error);
return false;
}
})
});
// 静默初始化用户数据库
ipcMain.handle('initializeUserDatabaseSilently', async () => {
ipcMain.handle("initializeUserDatabaseSilently", async () => {
try {
console.log('开始静默初始化用户数据库...')
const result = await initializeUserDatabase()
console.log('静默初始化用户数据库完成:', result)
return { success: true, result }
console.log("开始静默初始化用户数据库...");
const result = await initializeUserDatabase();
console.log("静默初始化用户数据库完成:", result);
return { success: true, result };
} catch (error) {
console.error('静默初始化用户数据库失败:', error)
return { success: false, error: error.message }
console.error("静默初始化用户数据库失败:", error);
return { success: false, error: error.message };
}
})
});
// 检测是否为便携模式运行
let isPortableMode = false
let appDataPath = ''
let isPortableMode = false;
let appDataPath = "";
// 初始化应用路径 - 兼容Node.js 12
// 初始化应用路径 - 兼容Node.js 12和Windows 7
function initializeAppPaths() {
// 检查应用根目录是否存在data文件夹如果存在则认为是便携模式
const portableDataPath = path.join(process.cwd(), 'data')
const defaultDataPath = path.join(app.getPath('userData'), '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)
isPortableMode = true;
appDataPath = portableDataPath;
console.log("检测到便携模式使用当前目录的data文件夹:", appDataPath);
} else {
isPortableMode = false
appDataPath = defaultDataPath
console.log('使用默认数据目录:', appDataPath)
isPortableMode = false;
appDataPath = defaultDataPath;
console.log("使用默认数据目录:", 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() {
return appDataPath
}
global.getAppDataPath = function () {
return appDataPath;
};
global.isPortableMode = function() {
return isPortableMode
}
global.isPortableMode = function () {
return isPortableMode;
};
// 在应用ready事件前初始化路径
initializeAppPaths()
initializeAppPaths();

View File

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