This commit is contained in:
2025-09-29 15:01:01 +08:00
parent 96b396500e
commit 9719228d6d
8 changed files with 1092 additions and 433 deletions

View File

@@ -203,7 +203,12 @@
"WebFetch(domain:www.electronjs.org)", "WebFetch(domain:www.electronjs.org)",
"Bash(test:*)", "Bash(test:*)",
"Bash(sqlite3:*)", "Bash(sqlite3:*)",
"Bash(npx electron:*)" "Bash(npx electron:*)",
"Bash(.erpClient.exe)",
"Bash(start erpClient.exe)",
"Bash(grep:*)",
"Bash(npx asar list:*)",
"Bash(npx @electron/asar extract:*)"
], ],
"deny": [], "deny": [],
"ask": [], "ask": [],

View File

@@ -1,7 +1,13 @@
{ {
"appId": "com.erp.client", "appId": "com.erp.client",
"productName": "erpClient", "productName": "erpClient",
"asar": true, "asar": {
"smartUnpack": false
},
"compression": "maximum",
"asarUnpack": [
"public/**/*"
],
"directories": { "directories": {
"output": "dist" "output": "dist"
}, },
@@ -38,10 +44,57 @@
"to": "static", "to": "static",
"filter": ["**/*"] "filter": ["**/*"]
}, },
{
"from": "public",
"to": "public",
"filter": [
"**/*",
"!jre/bin/jabswitch.exe",
"!jre/bin/jaccessinspector.exe",
"!jre/bin/jaccesswalker.exe",
"!jre/bin/jar.exe",
"!jre/bin/jarsigner.exe",
"!jre/bin/javac.exe",
"!jre/bin/javadoc.exe",
"!jre/bin/javap.exe",
"!jre/bin/jcmd.exe",
"!jre/bin/jconsole.exe",
"!jre/bin/jdb.exe",
"!jre/bin/jdeprscan.exe",
"!jre/bin/jdeps.exe",
"!jre/bin/jfr.exe",
"!jre/bin/jhsdb.exe",
"!jre/bin/jimage.exe",
"!jre/bin/jinfo.exe",
"!jre/bin/jlink.exe",
"!jre/bin/jmap.exe",
"!jre/bin/jmod.exe",
"!jre/bin/jpackage.exe",
"!jre/bin/jps.exe",
"!jre/bin/jrunscript.exe",
"!jre/bin/jshell.exe",
"!jre/bin/jstack.exe",
"!jre/bin/jstat.exe",
"!jre/bin/jstatd.exe",
"!jre/bin/keytool.exe",
"!jre/bin/kinit.exe",
"!jre/bin/klist.exe",
"!jre/bin/ktab.exe",
"!jre/bin/rmiregistry.exe",
"!jre/bin/serialver.exe",
"!jre/include/**",
"!jre/lib/src.zip",
"!jre/lib/ct.sym",
"!jre/lib/jvm.lib",
"!icon/image.png",
"!icon/img.png"
]
},
"!build", "!build",
"!dist", "!dist",
"!scripts" "!scripts"
], ],
"electronLanguages": ["en", "zh-CN"],
"extraResources": [ "extraResources": [
{ {
"from": "update-helper.bat", "from": "update-helper.bat",

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,8 @@
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"electron": "^38.1.2", "electron": "^38.1.2",
"electron-builder": "^25.1.6", "electron-builder": "^25.1.6",
"express": "^5.1.0",
"fs-extra": "^11.3.2",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^4.5.0" "vite": "^4.5.0"
}, },

View File

@@ -8,7 +8,7 @@
html, body { height: 100%; margin: 0; } html, body { height: 100%; margin: 0; }
body { body {
background: #fff; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif; background: #fff; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;
background-image: url('image/splash_screen.png'); background-image: url('./image/splash_screen.png');
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
background-size: cover; background-size: cover;

View File

@@ -3,6 +3,7 @@ const Chalk = require('chalk');
const FileSystem = require('fs'); const FileSystem = require('fs');
const Vite = require('vite'); const Vite = require('vite');
const compileTs = require('./private/tsc'); const compileTs = require('./private/tsc');
const copyAssets = require('./copy-assets');
function buildRenderer() { function buildRenderer() {
return Vite.build({ return Vite.build({
@@ -27,6 +28,8 @@ console.log(Chalk.blueBright('Transpiling renderer & main...'));
Promise.allSettled([ Promise.allSettled([
buildRenderer(), buildRenderer(),
buildMain(), buildMain(),
]).then(() => { ]).then(async () => {
console.log(Chalk.blueBright('Copying necessary assets...'));
await copyAssets();
console.log(Chalk.greenBright('Renderer & main successfully transpiled! (ready to be built with electron-builder)')); console.log(Chalk.greenBright('Renderer & main successfully transpiled! (ready to be built with electron-builder)'));
}); });

View File

@@ -1,11 +1,11 @@
import {app, BrowserWindow, ipcMain, Menu, screen, dialog} from 'electron'; import {app, BrowserWindow, ipcMain, Menu, screen, dialog} from 'electron';
import {existsSync, createWriteStream, promises as fs} from 'fs'; import {existsSync, createWriteStream, promises as fs} from 'fs';
import {join, dirname} from 'path'; import {join, dirname} from 'path';
import {spawn, ChildProcessWithoutNullStreams} from 'child_process'; import {spawn, ChildProcess} from 'child_process';
import * as https from 'https'; import * as https from 'https';
import * as http from 'http'; import * as http from 'http';
let springProcess: ChildProcessWithoutNullStreams | null = null; let springProcess: ChildProcess | null = null;
let mainWindow: BrowserWindow | null = null; let mainWindow: BrowserWindow | null = null;
let splashWindow: BrowserWindow | null = null; let splashWindow: BrowserWindow | null = null;
let appOpened = false; let appOpened = false;
@@ -17,33 +17,106 @@ let downloadedFilePath: string | null = null;
function openAppIfNotOpened() { function openAppIfNotOpened() {
if (appOpened) return; if (appOpened) return;
appOpened = true; appOpened = true;
// SpringBoot 启动完成,现在加载前端页面
if (mainWindow) { if (mainWindow) {
mainWindow.webContents.once('did-finish-load', () => {
setTimeout(() => {
mainWindow?.show();
mainWindow?.focus();
if (splashWindow) {
splashWindow.close();
splashWindow = null;
}
}, 1000);
});
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
const rendererPort = process.argv[2] || 8083; const rendererPort = process.argv[2] || 8083;
mainWindow.loadURL(`http://localhost:${rendererPort}`); mainWindow.loadURL(`http://localhost:${rendererPort}`);
} else { } else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html')); mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
} }
mainWindow.show();
mainWindow.focus();
}
if (splashWindow) {
splashWindow.close();
splashWindow = null;
} }
} }
function getJavaExecutablePath(): string {
if (process.env.NODE_ENV === 'development') {
return 'java';
}
const bundledJavaPath = join(process.resourcesPath, 'app.asar.unpacked/public/jre/bin/java.exe');
if (existsSync(bundledJavaPath)) {
return bundledJavaPath;
}
const devJavaPath = join(__dirname, '../../public/jre/bin/java.exe');
if (existsSync(devJavaPath)) {
return devJavaPath;
}
return 'java';
}
function getJarFilePath(): string {
if (process.env.NODE_ENV === 'development') {
return join(__dirname, '../../public/erp_client_sb-2.4.7.jar');
}
const bundledJarPath = join(process.resourcesPath, 'app.asar.unpacked/public/erp_client_sb-2.4.7.jar');
if (existsSync(bundledJarPath)) {
return bundledJarPath;
}
return join(__dirname, '../../public/erp_client_sb-2.4.7.jar');
}
function getSplashPath(): string {
if (process.env.NODE_ENV === 'development') {
return join(__dirname, '../../public/splash.html');
}
const bundledSplashPath = join(process.resourcesPath, 'app.asar.unpacked/public/splash.html');
if (existsSync(bundledSplashPath)) {
return bundledSplashPath;
}
return join(__dirname, '../../public/splash.html');
}
function getIconPath(): string {
if (process.env.NODE_ENV === 'development') {
return join(__dirname, '../../public/icon/icon.png');
}
const bundledIconPath = join(process.resourcesPath, 'app.asar.unpacked/public/icon/icon.png');
if (existsSync(bundledIconPath)) {
return bundledIconPath;
}
return join(__dirname, '../renderer/icon/icon.png');
}
function startSpringBoot() { function startSpringBoot() {
const jarPath = join(__dirname, '../../public/erp_client_sb-2.4.7.jar'); const jarPath = getJarFilePath();
springProcess = spawn('java', ['-jar', jarPath], { const javaPath = getJavaExecutablePath();
if (!existsSync(jarPath)) {
dialog.showErrorBox('启动失败', `JAR 文件不存在:\n${jarPath}`);
app.quit();
return;
}
try {
springProcess = spawn(javaPath, ['-jar', jarPath], {
cwd: dirname(jarPath), cwd: dirname(jarPath),
detached: false detached: false
}); });
springProcess.stdout.on('data', (data) => {
if (data.toString().includes('Started Success')) { let startupCompleted = false;
springProcess.stdout?.on('data', (data) => {
const output = data.toString();
if (!startupCompleted && (output.includes('Started Success') || output.includes('Started ErpClientSbApplication'))) {
startupCompleted = true;
openAppIfNotOpened(); openAppIfNotOpened();
} }
}); });
@@ -51,6 +124,26 @@ function startSpringBoot() {
springProcess.on('close', (code) => { springProcess.on('close', (code) => {
mainWindow ? mainWindow.close() : app.quit(); mainWindow ? mainWindow.close() : app.quit();
}); });
springProcess.on('error', (error) => {
let errorMessage = '启动 Java 应用失败';
if (error.message.includes('ENOENT')) {
errorMessage = '找不到 Java 运行环境\n请确保应用包含 JRE 或系统已安装 Java';
}
dialog.showErrorBox('启动失败', errorMessage);
app.quit();
});
setTimeout(() => {
if (!startupCompleted) {
openAppIfNotOpened();
}
}, 15000);
} catch (error) {
dialog.showErrorBox('启动异常', `无法启动应用: ${error}`);
app.quit();
}
} }
startSpringBoot(); startSpringBoot();
@@ -81,7 +174,7 @@ function createWindow() {
height: 800, height: 800,
show: false, show: false,
autoHideMenuBar: true, autoHideMenuBar: true,
icon: join(__dirname, '../renderer/icon/icon.png'), icon: getIconPath(),
webPreferences: { webPreferences: {
preload: join(__dirname, 'preload.js'), preload: join(__dirname, 'preload.js'),
nodeIntegration: false, nodeIntegration: false,
@@ -89,7 +182,6 @@ function createWindow() {
} }
}); });
mainWindow.webContents.openDevTools();
Menu.setApplicationMenu(null); Menu.setApplicationMenu(null);
mainWindow.setMenuBarVisibility(false); mainWindow.setMenuBarVisibility(false);
@@ -97,12 +189,7 @@ function createWindow() {
setTimeout(() => checkPendingUpdate(), 500); setTimeout(() => checkPendingUpdate(), 500);
}); });
if (process.env.NODE_ENV === 'development') { // 不立即加载页面,等 SpringBoot 启动完成后再加载
const rendererPort = process.argv[2] || 8083;
mainWindow.loadURL(`http://localhost:${rendererPort}`);
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
}
} }
app.whenReady().then(() => { app.whenReady().then(() => {
@@ -115,12 +202,17 @@ app.whenReady().then(() => {
frame: false, frame: false,
transparent: false, transparent: false,
resizable: false, resizable: false,
alwaysOnTop: true, alwaysOnTop: false,
show: true, show: true,
center: true, center: true,
icon: getIconPath(),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
}
}); });
const splashPath = join(__dirname, '../../public', 'splash.html'); const splashPath = getSplashPath();
if (existsSync(splashPath)) { if (existsSync(splashPath)) {
splashWindow.loadFile(splashPath); splashWindow.loadFile(splashPath);
} }

View File

@@ -8,7 +8,7 @@ const { defineConfig } = require('vite');
*/ */
const config = defineConfig({ const config = defineConfig({
root: Path.join(__dirname, 'src', 'renderer'), root: Path.join(__dirname, 'src', 'renderer'),
publicDir: Path.join(__dirname, 'public'), publicDir: Path.join(__dirname, 'src', 'renderer', 'public'), // 使用renderer下的public目录
server: { server: {
port: 8083, port: 8083,
}, },