Files

268 lines
7.2 KiB
JavaScript
Raw Permalink Normal View History

const { app, BrowserWindow, ipcMain, Menu } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
const dotenv = require('dotenv');
const fs = require('fs');
// 设置控制台输出编码为 UTF-8Windows 兼容)
if (process.platform === 'win32') {
// 设置 stdout 和 stderr 的编码
if (process.stdout.setDefaultEncoding) {
process.stdout.setDefaultEncoding('utf8');
}
if (process.stderr.setDefaultEncoding) {
process.stderr.setDefaultEncoding('utf8');
}
// 尝试设置控制台代码页为 UTF-8
try {
require('child_process').exec('chcp 65001', { encoding: 'utf8' });
} catch (e) {
// 忽略错误
}
}
// 加载环境变量
const envPath = path.join(__dirname, '..', '.env');
if (fs.existsSync(envPath)) {
dotenv.config({ path: envPath });
}
let mainWindow;
let backendProcess;
// 判断是否为开发模式
// 主要依赖 app.isPackaged如果为 false 则是开发环境
// 或者检查路径是否包含 app.asar打包后的应用
const isDevelopment = !app.isPackaged || process.env.NODE_ENV === 'development';
console.log('运行模式检测:');
console.log(' - app.isPackaged:', app.isPackaged);
console.log(' - process.resourcesPath:', process.resourcesPath);
console.log(' - process.env.NODE_ENV:', process.env.NODE_ENV);
console.log(' - isDevelopment:', isDevelopment);
/**
* 创建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 (isDevelopment || process.env.NODE_ENV === 'development') {
console.log('开发模式:打开开发者工具');
mainWindow.webContents.openDevTools();
// 过滤掉 DevTools 的 Autofill 相关错误
mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
if (message.includes('Autofill.enable') || message.includes('Autofill.setAddresses')) {
event.preventDefault();
}
});
}
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() {
let backendPath;
if (app.isPackaged) {
// 生产环境:使用 app.asar.unpacked 中的文件
backendPath = path.join(process.resourcesPath, 'app.asar.unpacked', 'dist', 'main.js');
} else {
// 开发环境:使用项目根目录下的 dist 文件夹
backendPath = path.join(__dirname, '..', 'dist', 'main.js');
}
// 检查后端构建文件是否存在
if (!fs.existsSync(backendPath)) {
console.error('后端服务构建文件不存在,路径:', backendPath);
console.error('请先执行 npm run build');
return;
}
console.log('启动后端服务:', backendPath);
// 启动后端服务
backendProcess = spawn('node', [backendPath], {
env: {
...process.env,
NODE_ENV: isDevelopment ? 'development' : (process.env.NODE_ENV || 'production'),
// 设置编码环境变量Windows
...(process.platform === 'win32' && {
PYTHONIOENCODING: 'utf-8',
LANG: 'en_US.UTF-8'
}),
},
stdio: 'pipe',
// Windows 上设置 shell 选项以确保编码正确
...(process.platform === 'win32' && { shell: false }),
});
// 捕获并显示后端进程的输出
backendProcess.stdout.on('data', (data) => {
// 确保正确解码 UTF-8 编码的数据
const output = Buffer.isBuffer(data) ? data.toString('utf8') : data.toString();
console.log(`[后端输出] ${output}`);
});
backendProcess.stderr.on('data', (data) => {
// 确保正确解码 UTF-8 编码的数据
const output = Buffer.isBuffer(data) ? data.toString('utf8') : data.toString();
console.error(`[后端错误] ${output}`);
});
backendProcess.on('error', (error) => {
console.error('后端服务启动失败:', error);
});
let backendExited = false;
backendProcess.on('exit', (code, signal) => {
backendExited = true;
if (code !== 0 && code !== null) {
console.error(`后端服务异常退出,退出码: ${code}, 信号: ${signal}`);
} else {
console.log(`后端服务退出,退出码: ${code}`);
}
backendProcess = null;
});
// 等待后端服务启动完成
try {
await waitForBackend();
// 检查后端是否在等待期间就退出了
if (backendExited) {
throw new Error('后端服务在启动过程中退出');
}
} catch (error) {
console.error('等待后端服务启动失败:', error.message);
if (backendProcess) {
console.error('正在停止后端进程...');
backendProcess.kill();
backendProcess = null;
}
throw error; // 重新抛出错误,让调用者知道启动失败
}
}
/**
* 停止后端服务
*/
function stopBackend() {
if (backendProcess) {
console.log('正在停止后端服务...');
backendProcess.kill();
backendProcess = null;
}
}
// 应用就绪时启动后端服务,然后创建窗口
app.on('ready', async () => {
try {
await startBackend();
createWindow();
Menu.setApplicationMenu(null);
} catch (error) {
console.error('应用启动失败:', error);
// 显示错误对话框
const { dialog } = require('electron');
dialog.showErrorBox(
'启动失败',
`后端服务启动失败: ${error.message}\n\n请检查控制台输出以获取更多信息。`
);
}
});
// 所有窗口关闭时退出应用
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];
});