BodyBalanceEvaluation/frontend/src/renderer/main/main.js
2025-12-10 15:35:50 +08:00

337 lines
10 KiB
JavaScript
Raw 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 } = require('electron');
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;
})()
`);
}
});
function startBackendService() {
// 在打包后的应用中使用process.resourcesPath获取resources目录
const resourcesPath = process.resourcesPath || path.join(__dirname, '../..');
const backendPath = path.join(resourcesPath, 'backend/BodyBalanceBackend/BodyBalanceBackend.exe');
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);
// 尝试备用路径
const fallbackPath = path.join(__dirname, 'resources/backend/BodyBalanceBackend/BodyBalanceBackend.exe');
console.log('Trying fallback path:', fallbackPath);
if (!fs.existsSync(fallbackPath)) {
console.error('Fallback backend executable not found:', fallbackPath);
return;
}
// 使用备用路径
const fallbackDir = path.join(__dirname, 'resources/backend/BodyBalanceBackend/');
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,
// sandbox: false, // 显式关闭沙盒,避免 preload 加载问题
// backgroundThrottling: false,
preload: path.join(__dirname, 'preload.js')
},
icon: path.join(__dirname, '../public/logo.png'),
show: false,
fullscreen: false,
frame: true,
autoHideMenuBar: true,
backgroundColor: '#000000'
});
// 窗口创建后立即最大化
mainWindow.maximize();
// 开发环境加载本地服务器,生产环境加载打包后的文件
const isDev = process.env.NODE_ENV === 'development';
if (isDev) {
mainWindow.loadURL('http://localhost:3000');
// mainWindow.webContents.openDevTools(); // 如需调试,请手动打开或在 did-finish-load 之后打开
} else {
// 启动后端服务
startBackendService();
// 延迟2秒后启动本地HTTP服务器
setTimeout(() => {
startLocalServer(() => {
mainWindow.loadURL('http://localhost:3000');
});
}, 2000);
}
// 窗口就绪后再显示,避免白屏/闪烁
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();
});