升级electron egg脚手架版本
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 87 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 254 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 87 KiB |
@@ -45,7 +45,7 @@ module.exports = {
|
|||||||
win64: {
|
win64: {
|
||||||
cmd: 'electron-builder',
|
cmd: 'electron-builder',
|
||||||
directory: './',
|
directory: './',
|
||||||
args: ['--config=./cmd/builder.json', '-w=nsis', '--x64'],
|
args: ['--config=./cmd/builder.json', '-w=dir', '--x64'],
|
||||||
},
|
},
|
||||||
win32: {
|
win32: {
|
||||||
args: ['--config=./cmd/builder.json', '-w=nsis', '--ia32'],
|
args: ['--config=./cmd/builder.json', '-w=nsis', '--ia32'],
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
"output": "out"
|
"output": "out"
|
||||||
},
|
},
|
||||||
"asar": true,
|
"asar": true,
|
||||||
|
"asarUnpack": [
|
||||||
|
"public/images/**/*"
|
||||||
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"**/*",
|
"**/*",
|
||||||
"!cmd/",
|
"!cmd/",
|
||||||
@@ -17,28 +20,47 @@
|
|||||||
"!go/",
|
"!go/",
|
||||||
"!python/"
|
"!python/"
|
||||||
],
|
],
|
||||||
"extraResources": {
|
"extraResources": [
|
||||||
"from": "build/extraResources/",
|
{
|
||||||
"to": "extraResources"
|
"from": "build/extraResources/dll",
|
||||||
},
|
"to": "extraResources/dll",
|
||||||
"nsis": {
|
"filter": ["**/*"]
|
||||||
"oneClick": false,
|
},
|
||||||
"allowElevation": true,
|
{
|
||||||
"allowToChangeInstallationDirectory": true,
|
"from": "build/extraResources/java",
|
||||||
"installerIcon": "build/icons/icon.ico",
|
"to": "extraResources/java",
|
||||||
"uninstallerIcon": "build/icons/icon.ico",
|
"filter": ["**/*"]
|
||||||
"installerHeaderIcon": "build/icons/icon.ico",
|
},
|
||||||
"createDesktopShortcut": true,
|
{
|
||||||
"createStartMenuShortcut": true,
|
"from": "build/extraResources/read.txt",
|
||||||
"shortcutName": "灿能检测"
|
"to": "extraResources/read.txt"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"from": "scripts/",
|
||||||
|
"to": "scripts",
|
||||||
|
"filter": ["**/*"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extraFiles": [
|
||||||
|
{
|
||||||
|
"from": "build/extraResources/mysql",
|
||||||
|
"to": "mysql",
|
||||||
|
"filter": ["**/*"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "build/extraResources/jre",
|
||||||
|
"to": "jre",
|
||||||
|
"filter": ["**/*"]
|
||||||
|
}
|
||||||
|
],
|
||||||
"win": {
|
"win": {
|
||||||
"icon": "build/icons/icon.ico",
|
|
||||||
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
|
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
|
||||||
"target": [
|
"target": [
|
||||||
{
|
{
|
||||||
"target": "portable"
|
"target": "dir",
|
||||||
|
"arch": ["x64"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"compression": "store"
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ module.exports = () => {
|
|||||||
singleLock: true,
|
singleLock: true,
|
||||||
windowsOption: {
|
windowsOption: {
|
||||||
title: 'NPQS9100-自动检测平台',
|
title: 'NPQS9100-自动检测平台',
|
||||||
menuBarVisible: false,
|
menuBarVisible: true, // 显示菜单栏,方便查看日志
|
||||||
width: 1920,
|
width: 1920,
|
||||||
height: 1000,
|
height: 1000,
|
||||||
minWidth: 1024,
|
minWidth: 1024,
|
||||||
@@ -24,7 +24,7 @@ module.exports = () => {
|
|||||||
//preload: path.join(getElectronDir(), 'preload', 'bridge.js'),
|
//preload: path.join(getElectronDir(), 'preload', 'bridge.js'),
|
||||||
},
|
},
|
||||||
frame: true,
|
frame: true,
|
||||||
show: true,
|
show: false, // 初始不显示,等待服务启动完成后再显示
|
||||||
icon: path.join(getBaseDir(), 'public', 'images', 'logo-32.png'),
|
icon: path.join(getBaseDir(), 'public', 'images', 'logo-32.png'),
|
||||||
},
|
},
|
||||||
logger: {
|
logger: {
|
||||||
@@ -66,6 +66,20 @@ module.exports = () => {
|
|||||||
mainServer: {
|
mainServer: {
|
||||||
indexPath: '/public/dist/index.html',
|
indexPath: '/public/dist/index.html',
|
||||||
channelSeparator: '/',
|
channelSeparator: '/',
|
||||||
|
},
|
||||||
|
// MySQL配置
|
||||||
|
mysql: {
|
||||||
|
enable: true,
|
||||||
|
autoStart: true, // 应用启动时自动启动MySQL
|
||||||
|
path: path.join(getBaseDir(), 'mysql'),
|
||||||
|
connection: {
|
||||||
|
host: 'localhost',
|
||||||
|
port: 3306,
|
||||||
|
user: 'root',
|
||||||
|
password: 'njcnpqs',
|
||||||
|
database: 'npqs9100_db',
|
||||||
|
charset: 'utf8mb4'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,87 @@
|
|||||||
const { ElectronEgg } = require('ee-core');
|
const { ElectronEgg } = require('ee-core');
|
||||||
const { Lifecycle } = require('./preload/lifecycle');
|
const { app, Menu, ipcMain } = require('electron');
|
||||||
|
const lifecycle = require('./preload/lifecycle');
|
||||||
const { preload } = require('./preload');
|
const { preload } = require('./preload');
|
||||||
|
|
||||||
// new app
|
// new app
|
||||||
const app = new ElectronEgg();
|
const electronApp = new ElectronEgg();
|
||||||
|
|
||||||
// register lifecycle
|
// 创建应用菜单
|
||||||
const life = new Lifecycle();
|
function createApplicationMenu() {
|
||||||
app.register("ready", life.ready);
|
const template = [
|
||||||
app.register("electron-app-ready", life.electronAppReady);
|
{
|
||||||
app.register("window-ready", life.windowReady);
|
label: '查看',
|
||||||
app.register("before-close", life.beforeClose);
|
submenu: [
|
||||||
|
{
|
||||||
|
label: '显示/隐藏服务日志',
|
||||||
|
accelerator: 'F12',
|
||||||
|
click: () => {
|
||||||
|
if (lifecycle.logWindowManager) {
|
||||||
|
lifecycle.logWindowManager.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'reload', label: '刷新' },
|
||||||
|
{ role: 'forceReload', label: '强制刷新' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'toggleDevTools', label: '开发者工具' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '帮助',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: '使用说明',
|
||||||
|
click: () => {
|
||||||
|
// 可以打开帮助文档
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: '关于',
|
||||||
|
click: () => {
|
||||||
|
// 可以显示关于信息
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const menu = Menu.buildFromTemplate(template);
|
||||||
|
Menu.setApplicationMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册 IPC 处理器
|
||||||
|
ipcMain.handle('show-log-window', () => {
|
||||||
|
if (lifecycle.logWindowManager) {
|
||||||
|
lifecycle.logWindowManager.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('hide-log-window', () => {
|
||||||
|
if (lifecycle.logWindowManager) {
|
||||||
|
lifecycle.logWindowManager.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('toggle-log-window', () => {
|
||||||
|
if (lifecycle.logWindowManager) {
|
||||||
|
lifecycle.logWindowManager.toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// register lifecycle (绑定 this 上下文)
|
||||||
|
electronApp.register("ready", lifecycle.ready.bind(lifecycle));
|
||||||
|
electronApp.register("electron-app-ready", () => {
|
||||||
|
lifecycle.electronAppReady.bind(lifecycle)();
|
||||||
|
createApplicationMenu();
|
||||||
|
});
|
||||||
|
electronApp.register("window-ready", lifecycle.windowReady.bind(lifecycle));
|
||||||
|
electronApp.register("before-close", lifecycle.beforeClose.bind(lifecycle));
|
||||||
|
|
||||||
// register preload
|
// register preload
|
||||||
app.register("preload", preload);
|
electronApp.register("preload", preload);
|
||||||
|
|
||||||
// run
|
// run
|
||||||
app.run();
|
electronApp.run();
|
||||||
@@ -7,4 +7,8 @@ const { contextBridge, ipcRenderer } = require('electron')
|
|||||||
|
|
||||||
contextBridge.exposeInMainWorld('electron', {
|
contextBridge.exposeInMainWorld('electron', {
|
||||||
ipcRenderer: ipcRenderer,
|
ipcRenderer: ipcRenderer,
|
||||||
|
// 日志窗口控制
|
||||||
|
showLogWindow: () => ipcRenderer.invoke('show-log-window'),
|
||||||
|
hideLogWindow: () => ipcRenderer.invoke('hide-log-window'),
|
||||||
|
toggleLogWindow: () => ipcRenderer.invoke('toggle-log-window'),
|
||||||
})
|
})
|
||||||
@@ -1,20 +1,484 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
const { logger } = require('ee-core/log');
|
const { logger } = require('ee-core/log');
|
||||||
const { getConfig } = require('ee-core/config');
|
const { getConfig } = require('ee-core/config');
|
||||||
const { getMainWindow } = require('ee-core/electron');
|
const { getMainWindow } = require('ee-core/electron');
|
||||||
|
const { getBaseDir } = require('ee-core/ps');
|
||||||
|
const { app } = require('electron');
|
||||||
|
|
||||||
|
// 动态获取 scripts 目录路径
|
||||||
|
function getScriptsPath(scriptName) {
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// 判断是否是打包后的环境
|
||||||
|
const isProd = process.resourcesPath && process.resourcesPath.includes('win-unpacked');
|
||||||
|
|
||||||
|
if (isProd) {
|
||||||
|
// 生产环境(打包后):从 resources 目录
|
||||||
|
const prodPath = path.join(process.resourcesPath, 'scripts', scriptName);
|
||||||
|
console.log(`[getScriptsPath] Production mode, using: ${prodPath}`);
|
||||||
|
return prodPath;
|
||||||
|
} else {
|
||||||
|
// 开发环境:从项目根目录
|
||||||
|
// __dirname 是 electron/preload 或 public/electron/preload
|
||||||
|
// 需要找到项目根目录
|
||||||
|
let currentDir = __dirname;
|
||||||
|
let scriptsPath = null;
|
||||||
|
|
||||||
|
// 向上查找,直到找到 scripts 目录
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
currentDir = path.join(currentDir, '..');
|
||||||
|
const testPath = path.join(currentDir, 'scripts', scriptName + '.js');
|
||||||
|
if (fs.existsSync(testPath)) {
|
||||||
|
scriptsPath = path.join(currentDir, 'scripts', scriptName);
|
||||||
|
console.log(`[getScriptsPath] Development mode, found at: ${scriptsPath}`);
|
||||||
|
return scriptsPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果找不到,返回一个默认路径
|
||||||
|
console.warn(`[getScriptsPath] Cannot find ${scriptName}, returning default path`);
|
||||||
|
return path.join(__dirname, '../../../scripts', scriptName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟加载 scripts
|
||||||
|
let MySQLManager, JavaRunner, ConfigGenerator, PortChecker, StartupManager, LogWindowManager;
|
||||||
|
|
||||||
|
function loadScripts() {
|
||||||
|
if (!MySQLManager) {
|
||||||
|
MySQLManager = require(getScriptsPath('start-mysql'));
|
||||||
|
JavaRunner = require(getScriptsPath('java-runner'));
|
||||||
|
ConfigGenerator = require(getScriptsPath('config-generator'));
|
||||||
|
PortChecker = require(getScriptsPath('port-checker'));
|
||||||
|
StartupManager = require(getScriptsPath('startup-manager'));
|
||||||
|
LogWindowManager = require(getScriptsPath('log-window-manager'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Lifecycle {
|
class Lifecycle {
|
||||||
|
constructor() {
|
||||||
|
this.mysqlManager = null;
|
||||||
|
this.javaRunner = null;
|
||||||
|
this.startupManager = null;
|
||||||
|
this.logWindowManager = null;
|
||||||
|
this.mysqlPort = null;
|
||||||
|
this.javaPort = null;
|
||||||
|
this.autoRefreshTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* core app have been loaded
|
* core app have been loaded
|
||||||
*/
|
*/
|
||||||
async ready() {
|
async ready() {
|
||||||
logger.info('[lifecycle] ready');
|
logger.info('[lifecycle] ready');
|
||||||
// 在这里可以做:
|
// 延迟加载 scripts
|
||||||
// - 初始化数据库连接
|
loadScripts();
|
||||||
// - 加载配置文件
|
}
|
||||||
// - 初始化全局变量
|
|
||||||
|
/**
|
||||||
|
* 完整的应用启动流程
|
||||||
|
*/
|
||||||
|
async startApplication() {
|
||||||
|
const config = getConfig();
|
||||||
|
|
||||||
|
// 步骤1: 初始化
|
||||||
|
this.startupManager.updateProgress('init');
|
||||||
|
await this.sleep(500);
|
||||||
|
|
||||||
|
// 步骤2: 检测 MySQL 端口
|
||||||
|
this.startupManager.updateProgress('check-mysql-port');
|
||||||
|
this.logWindowManager.addLog('system', '正在检测可用的 MySQL 端口(从3306开始)...');
|
||||||
|
|
||||||
|
this.mysqlPort = await PortChecker.findAvailablePort(3306, 100);
|
||||||
|
|
||||||
|
if (this.mysqlPort === -1) {
|
||||||
|
this.logWindowManager.addLog('error', 'MySQL 端口检测失败:3306-3405 全部被占用');
|
||||||
|
throw new Error('无法找到可用的 MySQL 端口(3306-3405 全部被占用)');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mysqlPort !== 3306) {
|
||||||
|
this.logWindowManager.addLog('warn', `MySQL 默认端口 3306 已被占用,自动切换到端口: ${this.mysqlPort}`);
|
||||||
|
} else {
|
||||||
|
this.logWindowManager.addLog('success', `MySQL 将使用默认端口: ${this.mysqlPort}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`[lifecycle] MySQL will use port: ${this.mysqlPort}`);
|
||||||
|
this.startupManager.updateProgress('check-mysql-port', { mysqlPort: this.mysqlPort });
|
||||||
|
await this.sleep(500);
|
||||||
|
|
||||||
|
// 步骤3: 启动 MySQL
|
||||||
|
if (config.mysql && config.mysql.enable && config.mysql.autoStart) {
|
||||||
|
this.startupManager.updateProgress('start-mysql', { mysqlPort: this.mysqlPort });
|
||||||
|
this.logWindowManager.addLog('mysql', `正在启动 MySQL,端口: ${this.mysqlPort}`);
|
||||||
|
|
||||||
|
this.mysqlManager = new MySQLManager();
|
||||||
|
|
||||||
|
// 监听 MySQL 输出
|
||||||
|
const actualPort = await this.mysqlManager.start(this.mysqlPort);
|
||||||
|
this.setupMySQLLogging(this.mysqlManager.process);
|
||||||
|
|
||||||
|
logger.info(`[lifecycle] MySQL started on port: ${actualPort}`);
|
||||||
|
this.logWindowManager.addLog('success', `MySQL 已启动,端口: ${actualPort}`);
|
||||||
|
|
||||||
|
// 步骤4: 等待 MySQL 就绪
|
||||||
|
this.startupManager.updateProgress('wait-mysql', { mysqlPort: actualPort });
|
||||||
|
this.logWindowManager.addLog('mysql', '等待 MySQL 就绪...');
|
||||||
|
|
||||||
|
const mysqlReady = await PortChecker.waitForPort(actualPort, 30000);
|
||||||
|
|
||||||
|
if (!mysqlReady) {
|
||||||
|
this.logWindowManager.addLog('error', `MySQL 启动超时(端口 ${actualPort} 未响应)`);
|
||||||
|
throw new Error(`MySQL 启动超时(端口 ${actualPort} 未响应)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('[lifecycle] MySQL is ready');
|
||||||
|
this.logWindowManager.addLog('success', 'MySQL 已就绪!');
|
||||||
|
await this.sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤5: 检测 Java 端口
|
||||||
|
this.startupManager.updateProgress('check-java-port', { mysqlPort: this.mysqlPort });
|
||||||
|
this.logWindowManager.addLog('system', '正在检测可用的 Java 端口(从18092开始)...');
|
||||||
|
|
||||||
|
this.javaPort = await PortChecker.findAvailablePort(18092, 100);
|
||||||
|
|
||||||
|
if (this.javaPort === -1) {
|
||||||
|
this.logWindowManager.addLog('error', 'Java 端口检测失败:18092-18191 全部被占用');
|
||||||
|
throw new Error('无法找到可用的后端服务端口(18092-18191 全部被占用)');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.javaPort !== 18092) {
|
||||||
|
this.logWindowManager.addLog('warn', `Java 默认端口 18092 已被占用,自动切换到端口: ${this.javaPort}`);
|
||||||
|
} else {
|
||||||
|
this.logWindowManager.addLog('success', `Java 将使用默认端口: ${this.javaPort}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`[lifecycle] Spring Boot will use port: ${this.javaPort}`);
|
||||||
|
this.startupManager.updateProgress('check-java-port', {
|
||||||
|
mysqlPort: this.mysqlPort,
|
||||||
|
javaPort: this.javaPort
|
||||||
|
});
|
||||||
|
await this.sleep(500);
|
||||||
|
|
||||||
|
// 步骤6: 生成配置文件
|
||||||
|
this.startupManager.updateProgress('generate-config', {
|
||||||
|
mysqlPort: this.mysqlPort,
|
||||||
|
javaPort: this.javaPort
|
||||||
|
});
|
||||||
|
|
||||||
|
const configGenerator = new ConfigGenerator();
|
||||||
|
const { configPath, dataPath } = await configGenerator.generateConfig({
|
||||||
|
mysqlPort: this.mysqlPort,
|
||||||
|
javaPort: this.javaPort,
|
||||||
|
mysqlPassword: 'njcnpqs'
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`[lifecycle] Configuration generated at: ${configPath}`);
|
||||||
|
logger.info(`[lifecycle] Data directory: ${dataPath}`);
|
||||||
|
this.startupManager.updateProgress('generate-config', {
|
||||||
|
mysqlPort: this.mysqlPort,
|
||||||
|
javaPort: this.javaPort,
|
||||||
|
dataPath: dataPath
|
||||||
|
});
|
||||||
|
await this.sleep(500);
|
||||||
|
|
||||||
|
// 步骤7: 启动 Spring Boot
|
||||||
|
this.startupManager.updateProgress('start-java', {
|
||||||
|
mysqlPort: this.mysqlPort,
|
||||||
|
javaPort: this.javaPort,
|
||||||
|
dataPath: dataPath
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.startSpringBoot(configPath);
|
||||||
|
|
||||||
|
// 步骤8: 等待 Spring Boot 就绪
|
||||||
|
this.startupManager.updateProgress('wait-java', {
|
||||||
|
mysqlPort: this.mysqlPort,
|
||||||
|
javaPort: this.javaPort,
|
||||||
|
dataPath: dataPath
|
||||||
|
});
|
||||||
|
|
||||||
|
const javaReady = await PortChecker.waitForPort(this.javaPort, 60000);
|
||||||
|
|
||||||
|
if (!javaReady) {
|
||||||
|
logger.warn(`[lifecycle] Spring Boot 启动超时,但继续启动应用`);
|
||||||
|
} else {
|
||||||
|
logger.info('[lifecycle] Spring Boot is ready');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.sleep(1000);
|
||||||
|
|
||||||
|
// 步骤9: 完成
|
||||||
|
this.startupManager.updateProgress('done', {
|
||||||
|
mysqlPort: this.mysqlPort,
|
||||||
|
javaPort: this.javaPort,
|
||||||
|
dataPath: dataPath
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('[lifecycle] Application startup completed');
|
||||||
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
||||||
|
this.logWindowManager.addLog('success', '✓ NPQS9100 启动完成!所有服务正常运行');
|
||||||
|
this.logWindowManager.addLog('system', `✓ MySQL 端口: ${this.mysqlPort}`);
|
||||||
|
this.logWindowManager.addLog('system', `✓ Java 端口: ${this.javaPort}`);
|
||||||
|
this.logWindowManager.addLog('system', `✓ 数据目录: ${dataPath}`);
|
||||||
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
||||||
|
this.logWindowManager.addLog('system', '应用即将启动...');
|
||||||
|
|
||||||
|
// 先关闭 Loading 窗口
|
||||||
|
this.startupManager.closeLoadingWindow();
|
||||||
|
|
||||||
|
// 等待3秒,让用户看清日志信息,然后再显示主窗口
|
||||||
|
await this.sleep(3000);
|
||||||
|
|
||||||
|
// 显示主窗口
|
||||||
|
const win = getMainWindow();
|
||||||
|
win.show();
|
||||||
|
win.focus();
|
||||||
|
|
||||||
|
// 添加主窗口关闭事件监听
|
||||||
|
win.on('close', async () => {
|
||||||
|
logger.info('[lifecycle] Main window closing, cleaning up...');
|
||||||
|
await this.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 立即刷新一次,确保显示最新内容
|
||||||
|
setTimeout(() => {
|
||||||
|
if (win && !win.isDestroyed()) {
|
||||||
|
logger.info('[lifecycle] Reloading main window to ensure fresh content');
|
||||||
|
this.logWindowManager.addLog('system', '正在加载应用界面...');
|
||||||
|
win.reload();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// 设置主窗口加载超时自动刷新(30秒)
|
||||||
|
this.setupAutoRefresh(win);
|
||||||
|
|
||||||
|
// 提示用户
|
||||||
|
this.logWindowManager.addLog('system', '主应用已打开!此日志窗口可随时关闭');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置主窗口自动刷新机制
|
||||||
|
*/
|
||||||
|
setupAutoRefresh(win) {
|
||||||
|
// 30秒后检查页面是否还在loading,如果是则刷新
|
||||||
|
this.autoRefreshTimer = setTimeout(() => {
|
||||||
|
if (win && !win.isDestroyed()) {
|
||||||
|
// 检查页面是否还在loading状态
|
||||||
|
win.webContents.executeJavaScript('document.readyState').then(state => {
|
||||||
|
logger.info(`[lifecycle] Page readyState after 30s: ${state}`);
|
||||||
|
if (state !== 'complete') {
|
||||||
|
logger.warn('[lifecycle] Page still loading after 30s, reloading...');
|
||||||
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
||||||
|
this.logWindowManager.addLog('warn', '页面加载超时,自动刷新...');
|
||||||
|
}
|
||||||
|
win.reload();
|
||||||
|
} else {
|
||||||
|
logger.info('[lifecycle] Page loaded successfully');
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
logger.error('[lifecycle] Failed to check page state:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 30000); // 30秒超时
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理所有资源
|
||||||
|
*/
|
||||||
|
async cleanup() {
|
||||||
|
logger.info('[lifecycle] Starting cleanup...');
|
||||||
|
|
||||||
|
// 清除自动刷新定时器
|
||||||
|
if (this.autoRefreshTimer) {
|
||||||
|
clearTimeout(this.autoRefreshTimer);
|
||||||
|
this.autoRefreshTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在日志窗口显示清理信息(在关闭之前)
|
||||||
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
||||||
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
||||||
|
this.logWindowManager.addLog('system', '应用正在关闭,清理资源中...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭 Loading 窗口
|
||||||
|
if (this.startupManager) {
|
||||||
|
try {
|
||||||
|
this.startupManager.closeLoadingWindow();
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止 Spring Boot
|
||||||
|
if (this.javaRunner) {
|
||||||
|
try {
|
||||||
|
logger.info('[lifecycle] Stopping Spring Boot...');
|
||||||
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
||||||
|
this.logWindowManager.addLog('system', '正在停止 Spring Boot...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置超时,3秒后强制继续
|
||||||
|
const stopPromise = this.javaRunner.stopSpringBoot();
|
||||||
|
const timeoutPromise = new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
await Promise.race([stopPromise, timeoutPromise]);
|
||||||
|
|
||||||
|
logger.info('[lifecycle] Spring Boot stopped');
|
||||||
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
||||||
|
this.logWindowManager.addLog('success', 'Spring Boot 已停止');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[lifecycle] Failed to stop Spring Boot:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止 MySQL
|
||||||
|
const config = getConfig();
|
||||||
|
if (config.mysql && config.mysql.enable && config.mysql.autoStart) {
|
||||||
|
try {
|
||||||
|
logger.info('[lifecycle] Stopping MySQL...');
|
||||||
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
||||||
|
this.logWindowManager.addLog('system', '正在停止 MySQL(最多等待10秒)...');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mysqlManager) {
|
||||||
|
// MySQL 的 stop() 方法内部使用 mysqladmin shutdown(最多10秒)
|
||||||
|
// 设置12秒超时作为保险
|
||||||
|
const stopPromise = this.mysqlManager.stop();
|
||||||
|
const timeoutPromise = new Promise(resolve => setTimeout(resolve, 12000));
|
||||||
|
await Promise.race([stopPromise, timeoutPromise]);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('[lifecycle] MySQL stopped');
|
||||||
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
||||||
|
this.logWindowManager.addLog('success', 'MySQL 已停止');
|
||||||
|
this.logWindowManager.addLog('system', '清理完成,应用即将退出');
|
||||||
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[lifecycle] Failed to stop MySQL:', error);
|
||||||
|
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
|
||||||
|
this.logWindowManager.addLog('error', 'MySQL 停止失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待500ms让用户看到清理日志
|
||||||
|
await this.sleep(500);
|
||||||
|
|
||||||
|
// 最后关闭日志窗口
|
||||||
|
if (this.logWindowManager) {
|
||||||
|
try {
|
||||||
|
logger.info('[lifecycle] Closing log window...');
|
||||||
|
this.logWindowManager.close();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[lifecycle] Failed to close log window:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('[lifecycle] Cleanup completed');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 MySQL 日志监听
|
||||||
|
*/
|
||||||
|
setupMySQLLogging(mysqlProcess) {
|
||||||
|
if (!mysqlProcess) return;
|
||||||
|
|
||||||
|
mysqlProcess.stdout.on('data', (data) => {
|
||||||
|
const output = data.toString().trim();
|
||||||
|
if (output) {
|
||||||
|
// 根据内容判断日志类型
|
||||||
|
if (output.includes('[ERROR]') || output.includes('ERROR')) {
|
||||||
|
this.logWindowManager.addLog('error', `[MySQL] ${output}`);
|
||||||
|
} else if (output.includes('[Warning]') || output.includes('WARNING')) {
|
||||||
|
this.logWindowManager.addLog('warn', `[MySQL] ${output}`);
|
||||||
|
} else if (output.includes('ready for connections')) {
|
||||||
|
this.logWindowManager.addLog('success', `[MySQL] ${output}`);
|
||||||
|
} else {
|
||||||
|
this.logWindowManager.addLog('mysql', `[MySQL] ${output}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mysqlProcess.stderr.on('data', (data) => {
|
||||||
|
const output = data.toString().trim();
|
||||||
|
if (output) {
|
||||||
|
this.logWindowManager.addLog('warn', `[MySQL Error] ${output}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动 Spring Boot 应用
|
||||||
|
*/
|
||||||
|
async startSpringBoot(configPath) {
|
||||||
|
try {
|
||||||
|
logger.info('[lifecycle] Starting Spring Boot application...');
|
||||||
|
this.logWindowManager.addLog('java', '正在启动 Spring Boot 应用...');
|
||||||
|
|
||||||
|
// 启动Java应用
|
||||||
|
this.javaRunner = new JavaRunner();
|
||||||
|
|
||||||
|
// 开发环境:build/extraResources/java/entrance.jar
|
||||||
|
// 打包后:resources/extraResources/java/entrance.jar
|
||||||
|
const isDev = !process.resourcesPath;
|
||||||
|
const jarPath = isDev
|
||||||
|
? path.join(__dirname, '..', 'build', 'extraResources', 'java', 'entrance.jar')
|
||||||
|
: path.join(process.resourcesPath, 'extraResources', 'java', 'entrance.jar');
|
||||||
|
|
||||||
|
const javaProcess = this.javaRunner.runSpringBoot(jarPath, configPath, {
|
||||||
|
javaPort: this.javaPort
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听Java进程输出
|
||||||
|
javaProcess.stdout.on('data', (data) => {
|
||||||
|
const output = data.toString().trim();
|
||||||
|
if (output) {
|
||||||
|
logger.info('[SpringBoot]', output);
|
||||||
|
|
||||||
|
// 根据内容判断日志类型
|
||||||
|
if (output.includes('ERROR') || output.includes('Exception')) {
|
||||||
|
this.logWindowManager.addLog('error', `[Java] ${output}`);
|
||||||
|
} else if (output.includes('WARN')) {
|
||||||
|
this.logWindowManager.addLog('warn', `[Java] ${output}`);
|
||||||
|
} else if (output.includes('Started') || output.includes('Completed')) {
|
||||||
|
this.logWindowManager.addLog('success', `[Java] ${output}`);
|
||||||
|
} else {
|
||||||
|
this.logWindowManager.addLog('java', `[Java] ${output}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
javaProcess.stderr.on('data', (data) => {
|
||||||
|
const output = data.toString().trim();
|
||||||
|
if (output) {
|
||||||
|
logger.error('[SpringBoot]', output);
|
||||||
|
this.logWindowManager.addLog('error', `[Java Error] ${output}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
javaProcess.on('close', (code) => {
|
||||||
|
logger.info(`[SpringBoot] Process exited with code ${code}`);
|
||||||
|
this.logWindowManager.addLog('system', `Spring Boot 进程退出,代码: ${code}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('[lifecycle] Spring Boot application started');
|
||||||
|
this.logWindowManager.addLog('success', 'Spring Boot 应用已启动!');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[lifecycle] Failed to start Spring Boot:', error);
|
||||||
|
this.logWindowManager.addLog('error', `Spring Boot 启动失败: ${error.message}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 睡眠函数
|
||||||
|
*/
|
||||||
|
sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,29 +494,58 @@ class Lifecycle {
|
|||||||
async windowReady() {
|
async windowReady() {
|
||||||
logger.info('[lifecycle] window-ready');
|
logger.info('[lifecycle] window-ready');
|
||||||
|
|
||||||
// 延迟加载,无白屏
|
// 创建日志窗口
|
||||||
const win = getMainWindow();
|
this.logWindowManager = new LogWindowManager();
|
||||||
const { windowsOption } = getConfig();
|
this.logWindowManager.createLogWindow();
|
||||||
if (windowsOption.show == false) {
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
||||||
win.once('ready-to-show', () => {
|
this.logWindowManager.addLog('system', 'NPQS9100 启动中...');
|
||||||
|
this.logWindowManager.addLog('system', '='.repeat(60));
|
||||||
|
|
||||||
|
// 创建 Loading 窗口
|
||||||
|
this.startupManager = new StartupManager();
|
||||||
|
this.startupManager.createLoadingWindow();
|
||||||
|
|
||||||
|
// 开始启动流程
|
||||||
|
try {
|
||||||
|
await this.startApplication();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[lifecycle] Failed to start application:', error);
|
||||||
|
this.logWindowManager.addLog('error', `启动失败: ${error.message}`);
|
||||||
|
this.logWindowManager.addLog('system', '请检查日志窗口了解详细错误信息');
|
||||||
|
this.startupManager.showError(error.message || '启动失败,请查看日志');
|
||||||
|
|
||||||
|
// 显示错误5秒后关闭Loading窗口,但不关闭日志窗口
|
||||||
|
setTimeout(() => {
|
||||||
|
this.startupManager.closeLoadingWindow();
|
||||||
|
|
||||||
|
// 即使启动失败,也显示主窗口(但用户可能需要手动修复问题)
|
||||||
|
const win = getMainWindow();
|
||||||
win.show();
|
win.show();
|
||||||
win.focus();
|
win.focus();
|
||||||
})
|
|
||||||
} else {
|
// 添加主窗口关闭事件监听
|
||||||
win.show();
|
win.on('close', async () => {
|
||||||
win.focus();
|
logger.info('[lifecycle] Main window closing (after error), cleaning up...');
|
||||||
|
await this.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logWindowManager.addLog('warn', '应用已启动,但部分服务可能未正常运行');
|
||||||
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 主窗口初始化但不显示,等待启动流程完成后再显示
|
||||||
|
// 在 startApplication() 中会调用 win.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* before app close
|
* before app close (框架生命周期钩子)
|
||||||
*/
|
*/
|
||||||
async beforeClose() {
|
async beforeClose() {
|
||||||
logger.info('[lifecycle] before-close');
|
logger.info('[lifecycle] before-close hook triggered');
|
||||||
|
await this.cleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Lifecycle.toString = () => '[class Lifecycle]';
|
Lifecycle.toString = () => '[class Lifecycle]';
|
||||||
|
|
||||||
module.exports = {
|
// 导出实例而不是类
|
||||||
Lifecycle
|
module.exports = new Lifecycle();
|
||||||
};
|
|
||||||
|
|||||||
@@ -23,6 +23,6 @@ VITE_PWA=true
|
|||||||
|
|
||||||
# 线上环境接口地址
|
# 线上环境接口地址
|
||||||
#VITE_API_URL="/api" # 打包时用
|
#VITE_API_URL="/api" # 打包时用
|
||||||
VITE_API_URL="http://192.168.1.125:18092/"
|
VITE_API_URL="http://127.0.0.1:18092/"
|
||||||
# 开启激活验证
|
# 开启激活验证
|
||||||
VITE_ACTIVATE_OPEN=false
|
VITE_ACTIVATE_OPEN=false
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { pa } from 'element-plus/es/locale/index.mjs';
|
|
||||||
import http from '@/api'
|
import http from '@/api'
|
||||||
import {CheckData} from '@/api/check/interface'
|
import {CheckData} from '@/api/check/interface'
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { pa } from 'element-plus/es/locale/index.mjs';
|
|
||||||
import type {Device} from '@/api/device/interface/device'
|
import type {Device} from '@/api/device/interface/device'
|
||||||
import http from '@/api'
|
import http from '@/api'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { ICD } from '@/api/device/interface/icd'
|
import type { ICD } from '@/api/device/interface/icd'
|
||||||
import http from '@/api'
|
import http from '@/api'
|
||||||
import { pa } from 'element-plus/es/locale/index.mjs'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name ICD管理模块
|
* @name ICD管理模块
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { pa } from 'element-plus/es/locale/index.mjs';
|
|
||||||
import type {Login} from '@/api/user/interface/user'
|
import type {Login} from '@/api/user/interface/user'
|
||||||
import {ADMIN as rePrefix} from '@/api/system/config/serviceName'
|
import {ADMIN as rePrefix} from '@/api/system/config/serviceName'
|
||||||
import http from '@/api'
|
import http from '@/api'
|
||||||
|
|||||||
@@ -131,8 +131,6 @@ import {getBigTestItem} from '@/api/check/test'
|
|||||||
import {getAutoGenerate} from '@/api/user/login'
|
import {getAutoGenerate} from '@/api/user/login'
|
||||||
import {useModeStore} from '@/stores/modules/mode' // 引入模式 store
|
import {useModeStore} from '@/stores/modules/mode' // 引入模式 store
|
||||||
import {useDictStore} from '@/stores/modules/dict'
|
import {useDictStore} from '@/stores/modules/dict'
|
||||||
import { ca } from 'element-plus/es/locale'
|
|
||||||
|
|
||||||
const checkStore = useCheckStore()
|
const checkStore = useCheckStore()
|
||||||
const modeStore = useModeStore()
|
const modeStore = useModeStore()
|
||||||
const dictStore = useDictStore()
|
const dictStore = useDictStore()
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ import CustomEdge from './RemoveableEdge.vue' // 导入自定义连接线组件
|
|||||||
import { jwtUtil } from '@/utils/jwtUtil'
|
import { jwtUtil } from '@/utils/jwtUtil'
|
||||||
import { useCheckStore } from '@/stores/modules/check'
|
import { useCheckStore } from '@/stores/modules/check'
|
||||||
import { Plan } from '@/api/plan/interface'
|
import { Plan } from '@/api/plan/interface'
|
||||||
import { fa } from 'element-plus/es/locale'
|
|
||||||
|
|
||||||
const checkStore = useCheckStore()
|
const checkStore = useCheckStore()
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const selectTestItemPopupRef = ref<InstanceType<typeof SelectTestItemPopup>>()
|
const selectTestItemPopupRef = ref<InstanceType<typeof SelectTestItemPopup>>()
|
||||||
|
|||||||
@@ -26,8 +26,6 @@
|
|||||||
import { ref, reactive, onMounted, watch, nextTick } from 'vue'
|
import { ref, reactive, onMounted, watch, nextTick } from 'vue'
|
||||||
|
|
||||||
import { CheckData } from '@/api/check/interface'
|
import { CheckData } from '@/api/check/interface'
|
||||||
import { da } from 'element-plus/es/locale'
|
|
||||||
import { on } from 'events'
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
treeData: {
|
treeData: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
|||||||
@@ -1,94 +1,215 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" />
|
<title>NPQS9100 正在启动...</title>
|
||||||
<style>
|
<style>
|
||||||
#loadingPage {
|
* {
|
||||||
background-color: #dedede;
|
margin: 0;
|
||||||
font-size: 12px;
|
padding: 0;
|
||||||
}
|
box-sizing: border-box;
|
||||||
.base {
|
}
|
||||||
left: 50%;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
.desc {
|
|
||||||
margin: 0, 0, 20px, 0;
|
|
||||||
}
|
|
||||||
.loading,
|
|
||||||
.loading > div {
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.loading {
|
|
||||||
display: block;
|
|
||||||
font-size: 0;
|
|
||||||
color: #06b359;
|
|
||||||
}
|
|
||||||
.loading.la-dark {
|
|
||||||
color: #07C160;
|
|
||||||
}
|
|
||||||
.loading > div {
|
|
||||||
display: inline-block;
|
|
||||||
float: none;
|
|
||||||
background-color: currentColor;
|
|
||||||
border: 0 solid currentColor;
|
|
||||||
}
|
|
||||||
.loading {
|
|
||||||
width: 92px;
|
|
||||||
height: 92px;
|
|
||||||
}
|
|
||||||
.loading > div {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
background: transparent;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 2px;
|
|
||||||
border-radius: 100%;
|
|
||||||
animation: ball-clip-rotate-multiple-rotate 1s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
.loading > div:first-child {
|
|
||||||
position: absolute;
|
|
||||||
width: 92px;
|
|
||||||
height: 92px;
|
|
||||||
border-right-color: transparent;
|
|
||||||
border-left-color: transparent;
|
|
||||||
}
|
|
||||||
.loading > div:last-child {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-top-color: transparent;
|
|
||||||
border-bottom-color: transparent;
|
|
||||||
animation-duration: 0.5s;
|
|
||||||
animation-direction: reverse;
|
|
||||||
}
|
|
||||||
@keyframes ball-clip-rotate-multiple-rotate {
|
|
||||||
0% {
|
|
||||||
transform: translate(-50%, -50%) rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
body {
|
||||||
transform: translate(-50%, -50%) rotate(180deg);
|
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
||||||
}
|
background: transparent;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
100% {
|
.loading-container {
|
||||||
transform: translate(-50%, -50%) rotate(360deg);
|
width: 480px;
|
||||||
}
|
padding: 40px;
|
||||||
}
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
</style>
|
border-radius: 20px;
|
||||||
</head>
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
<body>
|
text-align: center;
|
||||||
<div id="boot">
|
color: white;
|
||||||
<div class='base'>
|
}
|
||||||
<div class="loading">
|
|
||||||
<div></div>
|
.logo {
|
||||||
<div></div>
|
font-size: 32px;
|
||||||
</div>
|
font-weight: bold;
|
||||||
</div>
|
margin-bottom: 10px;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
min-height: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, #ffffff 0%, #f0f0f0 100%);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
width: 0%;
|
||||||
|
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-percent {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin: 4px;
|
||||||
|
border: 3px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spinner 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||||
|
border-color: #fff transparent transparent transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner div:nth-child(1) {
|
||||||
|
animation-delay: -0.45s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner div:nth-child(2) {
|
||||||
|
animation-delay: -0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner div:nth-child(3) {
|
||||||
|
animation-delay: -0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container {
|
||||||
|
display: none;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: rgba(255, 0, 0, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra-info {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="loading-container">
|
||||||
|
<div class="logo">NPQS9100</div>
|
||||||
|
<div class="subtitle">南京灿能电气自动化 · 自动检测平台</div>
|
||||||
|
|
||||||
|
<div class="status-text" id="statusText">正在初始化应用...</div>
|
||||||
|
|
||||||
|
<div class="progress-bar-container">
|
||||||
|
<div class="progress-bar" id="progressBar"></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
|
||||||
|
<div class="progress-percent" id="progressPercent">0%</div>
|
||||||
|
|
||||||
|
<div class="spinner">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error-container" id="errorContainer">
|
||||||
|
<div class="error-title">启动失败</div>
|
||||||
|
<div class="error-message" id="errorMessage"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="extra-info" id="extraInfo"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const { ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
const statusText = document.getElementById('statusText');
|
||||||
|
const progressBar = document.getElementById('progressBar');
|
||||||
|
const progressPercent = document.getElementById('progressPercent');
|
||||||
|
const errorContainer = document.getElementById('errorContainer');
|
||||||
|
const errorMessage = document.getElementById('errorMessage');
|
||||||
|
const extraInfo = document.getElementById('extraInfo');
|
||||||
|
|
||||||
|
// 监听启动进度
|
||||||
|
ipcRenderer.on('startup-progress', (event, data) => {
|
||||||
|
statusText.textContent = data.label;
|
||||||
|
progressBar.style.width = data.progress + '%';
|
||||||
|
progressPercent.textContent = data.progress + '%';
|
||||||
|
|
||||||
|
// 显示额外信息
|
||||||
|
let info = '';
|
||||||
|
if (data.mysqlPort) {
|
||||||
|
info += `MySQL 端口: ${data.mysqlPort} `;
|
||||||
|
}
|
||||||
|
if (data.javaPort) {
|
||||||
|
info += `后端端口: ${data.javaPort} `;
|
||||||
|
}
|
||||||
|
if (data.dataPath) {
|
||||||
|
info += `数据目录: ${data.dataPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info) {
|
||||||
|
extraInfo.textContent = info;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听启动错误
|
||||||
|
ipcRenderer.on('startup-error', (event, data) => {
|
||||||
|
errorMessage.textContent = data.error;
|
||||||
|
errorContainer.classList.add('show');
|
||||||
|
progressBar.style.background = 'linear-gradient(90deg, #ff6b6b 0%, #ee5a6f 100%)';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user