Files
bidding_watcher/app/main.js
dmy 37200aa115 fix(electron): 更新开发模式支持和控制台编码设置
- 在主进程中添加对Windows控制台UTF-8编码的支持
- 优化开发模式检测逻辑,确保在开发环境下正确打开开发者工具
- 改进后端进程的启动逻辑,确保在启动失败时提供详细错误信息
2026-01-15 10:14:54 +08:00

268 lines
7.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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];
});