2026-01-15 00:45:30 +08:00
|
|
|
const { app, BrowserWindow, ipcMain, Menu } = require('electron');
|
2026-01-15 00:35:19 +08:00
|
|
|
const path = require('path');
|
|
|
|
|
const { spawn } = require('child_process');
|
|
|
|
|
const dotenv = require('dotenv');
|
|
|
|
|
const fs = require('fs');
|
|
|
|
|
|
|
|
|
|
// 加载环境变量
|
|
|
|
|
const envPath = path.join(__dirname, '..', '.env');
|
|
|
|
|
if (fs.existsSync(envPath)) {
|
|
|
|
|
dotenv.config({ path: envPath });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mainWindow;
|
|
|
|
|
let backendProcess;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建Electron主窗口
|
|
|
|
|
*/
|
|
|
|
|
function createWindow() {
|
|
|
|
|
mainWindow = new BrowserWindow({
|
|
|
|
|
width: 1200,
|
|
|
|
|
height: 800,
|
|
|
|
|
webPreferences: {
|
|
|
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
|
|
|
nodeIntegration: false,
|
|
|
|
|
contextIsolation: true,
|
|
|
|
|
},
|
|
|
|
|
autoHideMenuBar: false,
|
|
|
|
|
title: '投标应用',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 加载前端页面
|
|
|
|
|
const indexPath = path.join(__dirname, '..', 'frontend', 'dist', 'index.html');
|
|
|
|
|
mainWindow.loadFile(indexPath);
|
|
|
|
|
|
|
|
|
|
// 开发环境下打开开发者工具
|
|
|
|
|
if (process.env.NODE_ENV === 'development') {
|
|
|
|
|
mainWindow.webContents.openDevTools();
|
2026-01-15 01:30:47 +08:00
|
|
|
|
|
|
|
|
// 过滤掉 DevTools 的 Autofill 相关错误
|
|
|
|
|
mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
|
|
|
|
|
if (message.includes('Autofill.enable') || message.includes('Autofill.setAddresses')) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-01-15 00:35:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mainWindow.on('closed', () => {
|
|
|
|
|
mainWindow = null;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 等待后端服务启动
|
|
|
|
|
*/
|
|
|
|
|
function waitForBackend(port = 3000, maxRetries = 30, interval = 1000) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
let retries = 0;
|
|
|
|
|
|
|
|
|
|
const checkBackend = () => {
|
|
|
|
|
const net = require('net');
|
|
|
|
|
const client = new net.Socket();
|
|
|
|
|
|
|
|
|
|
client.once('connect', () => {
|
|
|
|
|
client.destroy();
|
|
|
|
|
console.log('后端服务已启动');
|
|
|
|
|
resolve();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
client.once('error', () => {
|
|
|
|
|
client.destroy();
|
|
|
|
|
retry();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
client.once('timeout', () => {
|
|
|
|
|
client.destroy();
|
|
|
|
|
retry();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
client.connect(port, 'localhost');
|
|
|
|
|
client.setTimeout(1000);
|
|
|
|
|
|
|
|
|
|
function retry() {
|
|
|
|
|
retries++;
|
|
|
|
|
if (retries >= maxRetries) {
|
|
|
|
|
reject(new Error('后端服务启动超时'));
|
|
|
|
|
} else {
|
|
|
|
|
console.log(`等待后端服务启动... (${retries}/${maxRetries})`);
|
|
|
|
|
setTimeout(checkBackend, interval);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
checkBackend();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 启动后端服务
|
|
|
|
|
*/
|
|
|
|
|
async function startBackend() {
|
2026-01-15 01:30:47 +08:00
|
|
|
let backendPath;
|
|
|
|
|
|
|
|
|
|
if (process.resourcesPath) {
|
|
|
|
|
// 生产环境:使用 app.asar.unpacked 中的文件
|
|
|
|
|
backendPath = path.join(process.resourcesPath, 'app.asar.unpacked', 'dist', 'main.js');
|
|
|
|
|
} else {
|
|
|
|
|
// 开发环境
|
|
|
|
|
backendPath = path.join(__dirname, '..', 'dist', 'main.js');
|
|
|
|
|
}
|
2026-01-15 00:35:19 +08:00
|
|
|
|
|
|
|
|
// 检查后端构建文件是否存在
|
|
|
|
|
if (!fs.existsSync(backendPath)) {
|
2026-01-15 01:30:47 +08:00
|
|
|
console.error('后端服务构建文件不存在,路径:', backendPath);
|
|
|
|
|
console.error('请先执行 npm run build');
|
2026-01-15 00:35:19 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:30:47 +08:00
|
|
|
console.log('启动后端服务:', backendPath);
|
|
|
|
|
|
2026-01-15 00:35:19 +08:00
|
|
|
// 启动后端服务
|
|
|
|
|
backendProcess = spawn('node', [backendPath], {
|
|
|
|
|
env: {
|
|
|
|
|
...process.env,
|
|
|
|
|
NODE_ENV: process.env.NODE_ENV || 'production',
|
|
|
|
|
},
|
|
|
|
|
stdio: 'inherit',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
backendProcess.on('error', (error) => {
|
|
|
|
|
console.error('后端服务启动失败:', error);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
backendProcess.on('exit', (code) => {
|
|
|
|
|
console.log(`后端服务退出,退出码: ${code}`);
|
|
|
|
|
backendProcess = null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 等待后端服务启动完成
|
|
|
|
|
try {
|
|
|
|
|
await waitForBackend();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('等待后端服务启动失败:', error.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 停止后端服务
|
|
|
|
|
*/
|
|
|
|
|
function stopBackend() {
|
|
|
|
|
if (backendProcess) {
|
|
|
|
|
console.log('正在停止后端服务...');
|
|
|
|
|
backendProcess.kill();
|
|
|
|
|
backendProcess = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 应用就绪时启动后端服务,然后创建窗口
|
|
|
|
|
app.on('ready', async () => {
|
|
|
|
|
await startBackend();
|
|
|
|
|
createWindow();
|
2026-01-15 00:45:30 +08:00
|
|
|
Menu.setApplicationMenu(null);
|
2026-01-15 00:35:19 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 所有窗口关闭时退出应用
|
|
|
|
|
app.on('window-all-closed', () => {
|
|
|
|
|
stopBackend();
|
|
|
|
|
if (process.platform !== 'darwin') {
|
|
|
|
|
app.quit();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// MacOS上点击dock图标时重新创建窗口
|
|
|
|
|
app.on('activate', async () => {
|
|
|
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
|
|
|
if (!backendProcess) {
|
|
|
|
|
await startBackend();
|
|
|
|
|
}
|
|
|
|
|
createWindow();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 应用退出前停止后端服务
|
|
|
|
|
app.on('before-quit', () => {
|
|
|
|
|
stopBackend();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 处理来自渲染进程的IPC消息
|
|
|
|
|
ipcMain.handle('get-env', (event, key) => {
|
|
|
|
|
return process.env[key];
|
|
|
|
|
});
|