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; app.disableDomainBlockingFor3DAPIs(); // app.disableHardwareAcceleration(); app.commandLine.appendSwitch('ignore-gpu-blocklist'); app.commandLine.appendSwitch('enable-webgl'); app.commandLine.appendSwitch('use-angle', 'd3d11'); 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: '
', // 空页眉 footerTemplate: `
页 / 共
` }); 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 加载问题 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(); });