BodyBalanceEvaluation/frontend/src/renderer/main/main.js

337 lines
10 KiB
JavaScript
Raw Normal View History

const { app, BrowserWindow, ipcMain } = require('electron');
2025-08-15 10:12:38 +08:00
const path = require('path');
const http = require('http');
const fs = require('fs');
const url = require('url');
const { spawn } = require('child_process');
let mainWindow;
let localServer;
let backendProcess;
ipcMain.handle('generate-report-pdf', async (event, payload) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (!win) throw new Error('窗口未找到');
// 1. 准备打印环境:克隆节点到独立的打印容器,确保流式布局
await win.webContents.executeJavaScript(`
(function(){
const selector = '${(payload && payload.selector) ? payload.selector : '#report-root'}';
const root = document.querySelector(selector);
if (!root) throw new Error('报告根节点缺失');
// 创建打印容器
const printContainer = document.createElement('div');
printContainer.id = 'electron-print-container';
// 样式设置绝对定位覆盖全屏背景白z-index最高
printContainer.style.position = 'absolute';
printContainer.style.top = '0';
printContainer.style.left = '0';
printContainer.style.width = '100%';
printContainer.style.minHeight = '100vh';
printContainer.style.background = '#ffffff';
printContainer.style.zIndex = '2147483647';
printContainer.style.display = 'block';
printContainer.style.overflow = 'visible'; // 关键:允许内容溢出以触发分页
// 克隆目标节点
const cloned = root.cloneNode(true);
// 强制重置克隆节点的关键样式,确保它是流式布局
cloned.style.position = 'static'; // 必须是 static 或 relative
cloned.style.display = 'block';
cloned.style.width = '100%';
cloned.style.height = 'auto';
cloned.style.overflow = 'visible';
cloned.style.margin = '0';
cloned.style.transform = 'none'; // 移除可能影响打印的变换
// 将克隆节点加入容器
printContainer.appendChild(cloned);
// 将容器加入 body
document.body.appendChild(printContainer);
// 关键修复:打印时只保留我们的打印容器可见,其他所有元素隐藏
// 我们创建一个 style 标签来强制隐藏除了 printContainer 以外的所有 body 直接子元素
const style = document.createElement('style');
style.id = 'print-style-override';
style.innerHTML = \`
@media print {
body > *:not(#electron-print-container) {
display: none !important;
}
#electron-print-container {
display: block !important;
}
}
\`;
document.head.appendChild(style);
document.body.classList.add('print-mode');
return true;
})()
`);
try {
const pdf = await win.webContents.printToPDF({
pageSize: (payload && payload.pageSize) ? payload.pageSize : 'A4',
landscape: !!(payload && payload.landscape),
printBackground: !(payload && payload.printBackground === false),
marginsType: 0,
displayHeaderFooter: true, // 启用页眉页脚
headerTemplate: '<div></div>', // 空页眉
footerTemplate: `
<div style="width: 100%; text-align: center; font-size: 10px; font-family: 'Microsoft YaHei', sans-serif; padding-top: 5px; padding-bottom: 5px;">
<span class="pageNumber"></span> 页 / <span class="totalPages"></span>
</div>
`
});
return pdf;
} finally {
// 3. 清理环境
await win.webContents.executeJavaScript(`
(function(){
const container = document.getElementById('electron-print-container');
if (container) container.remove();
document.body.classList.remove('print-mode');
return true;
})()
`);
}
});
2025-08-15 10:12:38 +08:00
function startBackendService() {
// 在打包后的应用中使用process.resourcesPath获取resources目录
const resourcesPath = process.resourcesPath || path.join(__dirname, '../..');
2025-08-16 12:11:08 +08:00
const backendPath = path.join(resourcesPath, 'backend/BodyBalanceBackend/BodyBalanceBackend.exe');
2025-08-15 10:12:38 +08:00
const backendDir = path.join(resourcesPath, 'backend/');
console.log('Resources path:', resourcesPath);
console.log('Backend path:', backendPath);
console.log('Backend directory:', backendDir);
// 检查后端可执行文件是否存在
if (!fs.existsSync(backendPath)) {
console.error('Backend executable not found:', backendPath);
// 尝试备用路径
2025-08-16 12:11:08 +08:00
const fallbackPath = path.join(__dirname, 'resources/backend/BodyBalanceBackend/BodyBalanceBackend.exe');
2025-08-15 10:12:38 +08:00
console.log('Trying fallback path:', fallbackPath);
if (!fs.existsSync(fallbackPath)) {
console.error('Fallback backend executable not found:', fallbackPath);
return;
}
// 使用备用路径
2025-08-16 12:11:08 +08:00
const fallbackDir = path.join(__dirname, 'resources/backend/BodyBalanceBackend/');
2025-08-15 10:12:38 +08:00
backendProcess = spawn(fallbackPath, [], {
cwd: fallbackDir,
stdio: ['ignore', 'pipe', 'pipe']
});
console.log('Starting backend service with fallback path:', fallbackPath);
} else {
console.log('Starting backend service:', backendPath);
// 启动后端进程
backendProcess = spawn(backendPath, [], {
cwd: backendDir,
stdio: ['ignore', 'pipe', 'pipe']
});
}
backendProcess.stdout.on('data', (data) => {
console.log('Backend stdout:', data.toString());
});
backendProcess.stderr.on('data', (data) => {
console.error('Backend stderr:', data.toString());
});
backendProcess.on('close', (code) => {
console.log('Backend process exited with code:', code);
backendProcess = null;
});
backendProcess.on('error', (err) => {
console.error('Failed to start backend process:', err);
backendProcess = null;
});
}
function stopBackendService() {
if (backendProcess) {
console.log('Stopping backend service...');
backendProcess.kill('SIGTERM');
backendProcess = null;
}
// 强制杀死所有BodyBalanceBackend.exe进程
try {
const { exec } = require('child_process');
exec('taskkill /f /im BodyBalanceBackend.exe', (error, stdout, stderr) => {
if (error) {
// 如果没有找到进程taskkill会返回错误这是正常的
if (!error.message.includes('not found')) {
console.error('Error killing BodyBalanceBackend processes:', error.message);
}
} else {
console.log('Successfully killed all BodyBalanceBackend processes:', stdout);
}
});
} catch (err) {
console.error('Failed to execute taskkill command:', err);
}
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 1920,
height: 1080,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
2025-12-10 15:35:50 +08:00
// sandbox: false, // 显式关闭沙盒,避免 preload 加载问题
// backgroundThrottling: false,
2025-08-15 10:12:38 +08:00
preload: path.join(__dirname, 'preload.js')
},
2025-10-14 11:32:35 +08:00
icon: path.join(__dirname, '../public/logo.png'),
2025-08-15 10:12:38 +08:00
show: false,
fullscreen: false,
frame: true,
autoHideMenuBar: true,
backgroundColor: '#000000'
});
// 窗口创建后立即最大化
mainWindow.maximize();
2025-08-15 10:12:38 +08:00
// 开发环境加载本地服务器,生产环境加载打包后的文件
const isDev = process.env.NODE_ENV === 'development';
if (isDev) {
mainWindow.loadURL('http://localhost:3000');
// mainWindow.webContents.openDevTools(); // 如需调试,请手动打开或在 did-finish-load 之后打开
} else {
// 启动后端服务
startBackendService();
2025-10-11 16:58:52 +08:00
// 延迟2秒后启动本地HTTP服务器
2025-08-15 10:12:38 +08:00
setTimeout(() => {
startLocalServer(() => {
mainWindow.loadURL('http://localhost:3000');
});
2025-10-11 16:58:52 +08:00
}, 2000);
2025-08-15 10:12:38 +08:00
}
// 窗口就绪后再显示,避免白屏/闪烁
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
// 监听页面加载完成事件
mainWindow.webContents.once('did-finish-load', () => {
console.log('Page loaded completely');
});
// 添加加载失败的处理
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => {
console.log('Failed to load:', errorDescription, 'URL:', validatedURL);
mainWindow.show(); // 即使加载失败也显示窗口
});
mainWindow.on('closed', () => {
mainWindow = null;
if (localServer) {
localServer.close();
}
// 关闭后端服务
stopBackendService();
});
}
function startLocalServer(callback) {
const staticPath = path.join(__dirname, '../dist/');
localServer = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url);
let pathname = parsedUrl.pathname;
// 默认加载index.html
if (pathname === '/') {
pathname = '/index.html';
}
const filePath = path.join(staticPath, pathname);
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('File not found');
return;
}
// 设置正确的Content-Type
const ext = path.extname(filePath);
let contentType = 'text/html';
switch (ext) {
case '.js':
contentType = 'application/javascript';
break;
case '.css':
contentType = 'text/css';
break;
case '.json':
contentType = 'application/json';
break;
case '.png':
contentType = 'image/png';
break;
case '.jpg':
case '.jpeg':
contentType = 'image/jpeg';
break;
case '.svg':
contentType = 'image/svg+xml';
break;
}
res.writeHead(200, { 'Content-Type': contentType });
res.end(data);
});
});
localServer.listen(3000, 'localhost', () => {
console.log('Local server started on http://localhost:3000');
callback();
});
}
// 应用事件处理
// 关闭硬件加速以规避 GPU 进程异常导致的闪烁
app.disableHardwareAcceleration();
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
if (localServer) {
localServer.close();
}
// 关闭后端服务
stopBackendService();
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// 应用退出前清理资源
app.on('before-quit', () => {
stopBackendService();
});