const puppeteer = require('puppeteer'); const path = require('path'); const fs = require('fs-extra'); const dotenv = require('dotenv'); // 加载环境变量 dotenv.config(); const logFile = path.join(__dirname, '../logs/debug.log'); fs.ensureDirSync(path.dirname(logFile)); function logToFile(message) { const timestamp = new Date().toISOString(); fs.appendFileSync(logFile, `${timestamp}: ${message}\n`); } class PDFGenerator { constructor(options = {}) { // 从环境变量获取PDF输出目录,支持绝对路径和相对路径 let outputDirFromEnv; if (process.env.PDF_OUTPUT_DIR) { // 检查是否是绝对路径 if (path.isAbsolute(process.env.PDF_OUTPUT_DIR)) { outputDirFromEnv = process.env.PDF_OUTPUT_DIR; logToFile(`使用绝对路径: ${outputDirFromEnv}`); } else { // 相对路径,相对于项目根目录 outputDirFromEnv = path.join(__dirname, '..', process.env.PDF_OUTPUT_DIR); logToFile(`使用相对路径: ${outputDirFromEnv}`); } } else { // 默认路径 outputDirFromEnv = path.join(__dirname, '../public/pdfs'); logToFile(`使用默认路径: ${outputDirFromEnv}`); } this.options = { templatePath: path.join(__dirname, '../public/templates/report-template.html'), outputDir: outputDirFromEnv, ...options }; logToFile(`PDF输出目录设置为: ${this.options.outputDir}`); } async generatePDF(reportData) { // 确保我们使用的是 data 字段中的数据 const data = reportData.data; let browser = null; try { logToFile('开始生成PDF...'); logToFile('启动浏览器...'); browser = await puppeteer.launch({ headless: 'new', defaultViewport: { width: 1200, height: 800 }, executablePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', args: [ '--allow-file-access-from-files', '--disable-web-security', '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage' ], timeout: 60000 // 增加浏览器启动超时时间 }); logToFile('浏览器启动成功'); const page = await browser.newPage(); logToFile('创建新页面成功'); // 设置页面超时时间 page.setDefaultNavigationTimeout(60000); page.setDefaultTimeout(60000); logToFile('读取模板文件...'); let htmlContent = await fs.readFile(this.options.templatePath, 'utf-8'); logToFile('模板文件读取成功'); // 更新模板内容 logToFile('开始更新模板内容...'); htmlContent = await this.updateTemplateContent(htmlContent, data); logToFile('模板内容更新完成'); logToFile('设置页面内容...'); try { await page.setContent(htmlContent, { waitUntil: 'domcontentloaded', // 改为domcontentloaded,不等待所有资源加载完成 timeout: 60000 // 增加超时时间 }); logToFile('页面内容设置完成'); } catch (error) { logToFile(`设置页面内容失败: ${error.message}`); // 尝试使用更宽松的加载策略 await page.setContent(htmlContent, { waitUntil: 'load', // 只等待页面加载完成 timeout: 60000 }); logToFile('使用备用策略设置页面内容完成'); } // 等待一段时间确保页面稳定 logToFile('等待页面稳定...'); await page.evaluate(() => { return new Promise(resolve => { setTimeout(resolve, 2000); }); }); logToFile('页面稳定等待完成'); // 隐藏所有 print-only pdf-container 容器 logToFile('开始处理PDF容器...'); await page.evaluate(() => { const containers = document.querySelectorAll('.print-only.pdf-container'); containers.forEach(container => { container.style.display = 'none'; }); }); logToFile('PDF容器处理完成'); // 等待图片加载 logToFile('开始等待图片加载...'); try { await page.evaluate(async () => { console.log('开始检查图片加载状态...'); try { const imageLoadResults = await Promise.all( Array.from(document.images) .map((img, index) => { console.log(`图片 ${index}: ${img.src}`); if (img.complete) { console.log(`图片 ${index} 已完成加载`); return Promise.resolve(img.src); } return new Promise((resolve, reject) => { img.onload = () => { console.log(`图片 ${index} 加载成功`); resolve(img.src); }; img.onerror = (e) => { console.error(`图片 ${index} 加载失败: ${img.src}`); resolve('error'); }; }); }) ); console.log('所有图片加载结果:', imageLoadResults); } catch (error) { console.error('图片加载过程中出错:', error); } }); logToFile('图片加载完成'); } catch (error) { logToFile(`图片加载过程中出错: ${error.message}`); // 继续执行,不中断流程 } // 调整主检医生签名样式 await page.evaluate(() => { const doctorImages = document.querySelectorAll('#summary-doctor-image, #summary-doctor-image2'); doctorImages.forEach(img => { img.style.width = '30px'; img.style.height = '30px'; }); }); // 等待所有 PDF 渲染完成 console.log('等待 PDF 渲染完成...'); await page.evaluate(async () => { return new Promise((resolve) => { // 获取所有需要转换的图片容器 const containers = document.querySelectorAll('.ultrasound-image, .blood-image, .biochemistry-image, .urine-image, .dr-image, .hba1c-image'); if (containers.length === 0) { console.log('没有找到需要渲染的图片容器'); resolve(); return; } let completedCount = 0; const totalCount = containers.length; // 为每个容器执行渲染 containers.forEach(async (container) => { const pdfUrl = container.getAttribute('data-pdf-url'); if (pdfUrl) { try { await renderPDFAsImage(pdfUrl, container); completedCount++; console.log(`PDF渲染进度: ${completedCount}/${totalCount}`); if (completedCount === totalCount) { console.log('所有 PDF 渲染完成'); resolve(); } } catch (error) { console.error('PDF 渲染失败:', error); completedCount++; if (completedCount === totalCount) { resolve(); } } } else { completedCount++; if (completedCount === totalCount) { resolve(); } } }); // 设置超时保护 setTimeout(() => { if (completedCount < totalCount) { console.log('PDF 渲染超时,继续执行'); resolve(); } }, 60000); }); }); // 额外等待一段时间确保渲染完全完成 await new Promise(resolve => setTimeout(resolve, 2000)); // 添加额外的检查,确保所有加载提示都被隐藏 await page.evaluate(() => { document.querySelectorAll('.pdf-container').forEach(container => { const loadingElem = container.querySelector('.pdf-loading'); const imageContainer = container.querySelector('.pdf-image-container'); // 只要图片容器存在,就隐藏加载提示 if (imageContainer && loadingElem) { loadingElem.style.display = 'none'; imageContainer.style.display = 'block'; // 确保图片容器显示 } }); }); // 再次等待一段时间,确保所有更改都已应用 await new Promise(resolve => setTimeout(resolve, 1000)); // 处理汇总内容分页 if (data.summaryResult) { await new Promise(resolve => { page.evaluate((summaryResult) => { if (typeof handleSummaryPagination === 'function') { handleSummaryPagination(summaryResult); const summaryPage2 = document.getElementById('summary-page-2'); if (summaryPage2) { const content = document.getElementById('summary-content-2'); if (content && content.textContent.trim() !== '') { summaryPage2.style.display = 'block'; } } } }, data.summaryResult); setTimeout(resolve, 2000); }); } // 处理糖化血红蛋白分页 await page.evaluate(() => { const hba1cPage = document.getElementById('hba1c-report-page'); if (hba1cPage && hba1cPage.style.display !== 'none') { if (typeof handleHba1cPagination === 'function') { const hba1cText = document.getElementById('hba1c-summary-content')?.textContent; handleHba1cPagination(hba1cText); } } if (typeof updatePageNumbers === 'function') { updatePageNumbers(); } }); await new Promise(resolve => setTimeout(resolve, 1000)); // 确保输出目录存在 await fs.ensureDir(this.options.outputDir); // 生成PDF文件名 - 修改为"体检编号-姓名"格式 const fileName = `${data.medicalSn || 'unknown'}-${data.pName || 'noname'}.pdf`; const pdfPath = path.join(this.options.outputDir, fileName); console.log('生成PDF...'); const pdf = await page.pdf({ path: pdfPath, format: 'A4', printBackground: true, margin: { top: '0', right: '0', bottom: '0', left: '0' }, preferCSSPageSize: true }); // 在PDF生成完成后保存HTML用于调试 try { const debugHtmlPath = path.join(__dirname, '../logs/last-report.html'); fs.writeFileSync(debugHtmlPath, htmlContent); } catch (error) { logToFile(`保存调试HTML出错: ${error.message}`); } return { success: true, fileName, path: pdfPath }; } catch (error) { console.error('PDF生成失败:', error); throw error; } finally { if (browser) { await browser.close(); } } } async updateTemplateContent(html, data) { // 添加数据结构调试 console.log('Received data:', JSON.stringify(data, null, 2)); // 替换图片相对路径为base64编码 try { const templateDir = path.dirname(this.options.templatePath); const imagePath = path.join(templateDir, '首页标签.png'); console.log('图片路径:', imagePath); // 读取图片文件并转换为Base64 const imageBuffer = await fs.readFile(imagePath); const base64Image = `data:image/png;base64,${imageBuffer.toString('base64')}`; console.log('图片已转换为base64格式'); // 替换所有图片引用 html = html.replace(/\s*]*>\s*]*>.*?<\/span>\s*]*>填充值<\/span>\s*<\/div>\s*<\/div>/g; const replacement = `
${data.medicalSn || '--'} ${data.pName || '--'} ${data.gender || '--'} ${age}岁
`; html = html.replace(reportTitleRegex, replacement); // 更新体检编号 const reportTitleRegex2 = /体检编号:.*?]*class="avatar-image"[^>]*>/g, `头像`); } // 格式化体检日期 let examDate = '--'; if (data.medicalDateTime) { const date = new Date(data.medicalDateTime); examDate = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; } // 更新个人信息 const personalInfoMap = { '姓名': data.pName || '--', '性别': data.gender || '--', '年龄': age, '身份证号': data.cardId || '--', '联系电话': data.phoneNum || '--', '体检日期': examDate }; // 遍历并更新每个字段 Object.entries(personalInfoMap).forEach(([title, value]) => { // 根据字段名称确定是否需要 letter-spacing 样式 const needsLetterSpacing = ['身份证号', '联系电话', '体检日期'].includes(title); // 构建正则表达式 let regex; if (needsLetterSpacing) { // 对于需要 letter-spacing 的字段,匹配带有样式的标签 regex = new RegExp(`${title}]*class="person_content"[^>]*>[^<]*`); } else { // 对于普通字段,匹配不带样式的标签 regex = new RegExp(`${title}]*class="person_content"[^>]*>[^<]*`); } const matches = html.match(regex); if (matches) { // 构建替换内容,保持原有样式 const replacement = needsLetterSpacing ? `${title}${value}` : `${title}${value}`; html = html.replace(regex, replacement); } }); // 更新前言中的姓名 const prefaceNameRegex = /.*?<\/span>/; if (data.pName) { html = html.replace(prefaceNameRegex, `${data.pName}`); } // 更新一般检查数据 if (data.data && Array.isArray(data.data)) { // 与 ReportPreview.vue 一致:视力可能为 itemCode SL001 或名称含「视力」,itemResult 可能为 JSON const formatVisionResult = (item) => { if (!item?.itemResult || !String(item.itemResult).trim()) return '--'; try { const v = JSON.parse(item.itemResult); const parts = []; if (v.visionleft || v.visionright) { parts.push(`左眼:${v.visionleft || '--'} 右眼:${v.visionright || '--'}`); } if (v.bscvaleft || v.bscvaright) { parts.push(`矫正左眼:${v.bscvaleft || '--'} 矫正右眼:${v.bscvaright || '--'}`); } return parts.length ? parts.join(';') : '--'; } catch { return item.itemResult || '--'; } }; const visionItem = data.data.find( (d) => d.itemCode === 'SL001' || (d.itemName && String(d.itemName).includes('视力')) ); const visionDisplay = formatVisionResult(visionItem); const examData = { '体温': `${data.data.find(item => item.itemName === '体温')?.itemResult || '--'}`, '脉率': `${data.data.find(item => item.itemName === '脉率')?.itemResult || '--'}`, '呼吸频率': `${data.data.find(item => item.itemName === '呼吸频率')?.itemResult || '--'}`, '血压': `${data.data.find(item => item.itemName === '血压')?.itemResult || '--'}`, '身高': `${data.data.find(item => item.itemName === '身高')?.itemResult || '--'}`, '体重': `${data.data.find(item => item.itemName === '体重')?.itemResult || '--'}`, '腰围': `${data.data.find(item => item.itemName === '腰围')?.itemResult || '--'}`, '体质指数': `${data.data.find(item => item.itemName === '体质指数(BMI)')?.itemResult || '--'}`, '视力': visionDisplay }; const units = { '体温': '℃', '脉率': '次/分', '呼吸频率': '次/分', '血压': 'mmHg', '身高': 'cm', '体重': 'kg', '腰围': 'cm', '体质指数': 'kg/m²', '视力': '' }; // 遍历并更新每个检查项目 Object.entries(examData).forEach(([key, value]) => { if (value !== '--') { const unit = units[key] || ''; // 对于 BMI,使用特殊的正则表达式 const regex = key === '体质指数' ? new RegExp(`体质指数\\(BMI\\)\\s*]*>.*?`, 'g') : new RegExp(`${key}\\s*]*>.*?`, 'g'); const replacement = key === '体质指数' ? `体质指数(BMI)${value}${unit}` : `${key}${value}${unit}`; html = html.replace(regex, replacement); } }); // 查找身高项目数据 const heightData = data.data.find(item => item.itemName === '身高'); // 尝试多种可能的项目名和字段 let summaryItem = null; let summaryContent = '--'; // 1. 先记录所有带有analyse字段的项目 const itemsWithAnalyse = data.data.filter(item => item.analyse && item.analyse.trim() !== '--'); // 2. 优先使用身高项目 summaryItem = data.data.find(item => item.itemName === '身高' && item.analyse && item.analyse.trim() !== '--' ); // 3. 如果没找到,尝试一般检查 if (!summaryItem) { summaryItem = data.data.find(item => (item.itemName === '一般检查' || item.itemName === '体格检查') && item.analyse && item.analyse.trim() !== '--' ); } // 4. 如果还没找到,尝试使用第一个有analyse的项目 if (!summaryItem && itemsWithAnalyse.length > 0) { summaryItem = itemsWithAnalyse[0]; } // 5. 记录找到的结果 if (summaryItem) { summaryContent = summaryItem.analyse; } else { logToFile(`未找到任何带有analyse字段的项目,使用默认值"--"`); } // 使用找到的内容替换 try { html = html.replace( /
[\s\S]*?<\/div>/s, `

一般检查小结:

${summaryContent}

` ); } catch (error) { logToFile(`替换出错: ${error.message}`); } } // 更新体质辨识结果 html = html.replace( /().*?(<\/td>)/s, `$1${data.zybs || '--'}$2` ); // 更新汇总结果 if (data.summaryResult) { // 先更新第一页内容 html = html.replace( /(

]*class="summary-content"[^>]*>).*?(<\/p>)/s, `$1${data.summaryResult}$2` ); // 更新主检医生签名 if (data.sign) { // 更新第一页签名 const summaryDoctorImageRegex = /(]*id="summary-doctor-image"[^>]*>)/; if (html.match(summaryDoctorImageRegex)) { html = html.replace(summaryDoctorImageRegex, `主检医生签名`); } // 更新第二页签名 const summaryDoctorImage2Regex = /(]*id="summary-doctor-image2"[^>]*>)/; if (html.match(summaryDoctorImage2Regex)) { html = html.replace(summaryDoctorImage2Regex, `主检医生签名`); } } // 添加分页处理脚本到 HTML 中 const paginationScript = ` `; html = html.replace('', `${paginationScript}`); } // 更新体质结果详情 if (data.features) { html = html.replace( /(

).*?(<\/div>)/s, `$1${data.features.replace(/\\r\\n/g, '
')}$2` ); } // 更新图片和 PDF 链接 if (data.data && Array.isArray(data.data)) { const IMAGE_RENDER_SCALES = { ultrasoundLike: '2.0', labLike: '1.5' }; const buildDrSummaryText = (row) => { const a = row.analyse && String(row.analyse).trim(); if (a) return a; const desc = row.examDescription && String(row.examDescription).trim(); const res = row.itemResult != null && String(row.itemResult).trim(); if (desc && res) return `检查所见:${desc}\n检查结果:${res}`; if (desc) return desc; if (res) return res; return '--'; }; const buildHba1cSummaryText = (row) => { if (row.analyse && String(row.analyse).trim()) return String(row.analyse).trim(); if (row.itemResult != null && String(row.itemResult).trim()) return String(row.itemResult).trim(); if (row.itemValue != null && String(row.itemValue).trim()) return String(row.itemValue).trim(); return '--'; }; const bodyForHtmlReplace = (s) => String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/\$/g, '$$'); const buildPdfImageTag = (alt, className, pdfUrl, renderScale, extraAttrs = '') => `${alt}`; data.data.forEach((item) => { if (item.data) { let pc = item.pacsDataType; if (pc == null || String(pc).trim() === '') { const typeStr = item.type != null ? String(item.type) : ''; const code = item.itemCode != null ? String(item.itemCode).toUpperCase() : ''; if ( code === 'DR001' || item.itemName === 'DR' || typeStr === 'DR' || typeStr.toUpperCase() === 'DR' ) { pc = 'DR'; } else if ( code.includes('THXHDB') || (item.itemName && item.itemName.includes('糖化')) || (typeStr && typeStr.includes('糖化')) ) { pc = 'thxhdb'; } } const pcKey = String(pc || '').trim().toLowerCase(); let contentId = ''; let summaryId = ''; let pdfContainerId = ''; switch (pcKey) { case 'cbc': contentId = 'blood-exam-content'; summaryId = 'blood-summary-div'; pdfContainerId = 'blood-exam-pdf-container'; break; case 'ecg': contentId = 'ecg-exam-content'; summaryId = 'ecg-summary'; break; case 'bt': contentId = 'biochemistry-exam-content'; summaryId = 'biochemistry-summary-div'; pdfContainerId = 'biochemistry-exam-pdf-container'; break; case 'us': contentId = 'ultrasound-exam-content'; summaryId = 'ultrasound-summary'; break; case 'rt': contentId = 'urine-exam-content'; summaryId = 'urine-summary-div'; pdfContainerId = 'urine-exam-pdf-container'; break; case 'dr': contentId = 'dr-exam-content'; summaryId = 'dr-summary'; break; case 'thxhdb': case 'hba1c': contentId = 'hba1c-exam-content'; summaryId = 'hba1c-summary-div'; pdfContainerId = 'hba1c-exam-pdf-container'; break; default: break; } if (contentId) { console.log(`处理报告类型: ${pcKey} (pacsDataType=${item.pacsDataType}), 内容ID: ${contentId}`); // 构建正则表达式来匹配特定ID的内容区域 const contentRegex = new RegExp( `(
)(.*?)(
)`, 's' ); // 为PDF URL添加参数 const pdfUrl = item.data + '#toolbar=0&navpanes=0&view=Fit'; console.log(`PDF URL: ${pdfUrl}`); // 准备替换内容 let replacement; if (pcKey === 'ecg') { // 心电图使用img标签 replacement = `$1心电图检查$3`; // 更新心电图所见所得 if (item.analyse) { console.log(`处理心电图报告,ID: ${summaryId}`); console.log('原始analyse:', item.analyse); // 对于心电图,使用 analyse 按行分割 const analyseLines = item.analyse.split('\n'); const findings = analyseLines[0]?.replace('检查所见:', '') || '--'; const conclusion = analyseLines[1]?.replace('检查结果:', '') || '--'; console.log('处理后的所见:', findings); console.log('处理后的所得:', conclusion); // 更新所见所得 const findingsRegex = new RegExp( `(]*id="ecg-summary"[^>]*>).*?(
)`, 's' ); const findingsMatch = html.match(findingsRegex); console.log('所见正则匹配结果:', findingsMatch ? '成功' : '失败'); html = html.replace(findingsRegex, `$1

【所见】
${findings}

【所得】
${conclusion}

$2`); } } else if (pcKey === 'us') { // 超声检查报告处理 - 设置 PDF URL replacement = `$1${buildPdfImageTag('超声检查报告', 'ultrasound-image', pdfUrl, IMAGE_RENDER_SCALES.ultrasoundLike)}$3`; // 更新超声所见所得 if (item.analyse) { // 使用与心电图报告相同的处理逻辑 console.log(`处理超声报告,ID: ${summaryId}`); // 记录分析内容 const logMessage = `超声报告分析内容: ${item.analyse}`; console.log(logMessage); // 同时写入文件日志 // fs.appendFileSync(path.join(__dirname, '../logs/debug.log'), //`${new Date().toISOString()} - ${logMessage}\n`); // 处理分析结果 const analyseLines = item.analyse.split('\n'); const findings = analyseLines[0]?.replace('检查所见:', '') || '--'; const conclusion = analyseLines[1]?.replace('检查结果:', '') || '--'; // 使用更精确的正则表达式匹配模板中的结构 const findingsRegex = new RegExp( `(
\\s*

\\s*【所见】\\s*
)[^<]*(

\\s*

\\s*【所得】\\s*
)[^<]*(

\\s*
)`, 's' ); const findingsMatch = html.match(findingsRegex); // 记录匹配结果 const matchResult = `超声所见所得匹配结果: ${findingsMatch ? '成功' : '失败'}`; console.log(matchResult); //fs.appendFileSync(path.join(__dirname, '../logs/debug.log'), //`${new Date().toISOString()} - ${matchResult}\n`); // 使用更精确的替换,保持原有的HTML结构 html = html.replace(findingsRegex, `$1${findings}$2${conclusion}$3`); // 检查替换后的内容 const afterReplacement = html.match(/
[\s\S]*?<\/div>/); const resultMessage = `替换后的内容: ${afterReplacement ? afterReplacement[0].substring(0, 100) + '...' : '未找到'}`; console.log(resultMessage); fs.appendFileSync(path.join(__dirname, '../logs/debug.log'), `${new Date().toISOString()} - ${resultMessage}\n`); } } else if (pcKey === 'dr') { html = html.replace( /(]*id="dr-report-page"[^>]*)style="display: none;"/, '$1style="display: block;"' ); if (item.itemStatus === "2") { const contentRegex2 = new RegExp( `(]*id="dr-exam-content"[^>]*>).*?(
)`, 's' ); html = html.replace(contentRegex2, `$1
$2
`); const summaryRegex = new RegExp( `(]*id="dr-summary"[^>]*>).*?(
)`, 's' ); const abandonNotice = '

该项目已弃检

'; html = html.replace(summaryRegex, `$1${abandonNotice}$2`); replacement = `$1${buildPdfImageTag('DR检查报告', 'dr-image', '', IMAGE_RENDER_SCALES.ultrasoundLike, ' style="display:none"')}$3`; } else { // DR 与超声相同:img 占位 + data-pdf-url,由 renderPDFAsImage 渲染 replacement = `$1${buildPdfImageTag('DR检查报告', 'dr-image', pdfUrl, IMAGE_RENDER_SCALES.ultrasoundLike)}$3`; // DR 小结与超声相同:所见/所得格式 const drSummaryText = buildDrSummaryText(item); if (drSummaryText && drSummaryText !== '--') { console.log('更新DR小结内容:', drSummaryText); const analyseLines = drSummaryText.split('\n'); const findings = bodyForHtmlReplace( (analyseLines[0] || '').replace('检查所见:', '') || '--' ); const conclusion = bodyForHtmlReplace( (analyseLines[1] || '').replace('检查结果:', '') || '--' ); const findingsRegex = new RegExp( `(]*id="dr-summary"[^>]*>\\s*

\\s*【所见】\\s*
)[^<]*(

\\s*

\\s*【所得】\\s*
)[^<]*(

\\s*)`, 's' ); html = html.replace(findingsRegex, `$1${findings}$2${conclusion}$3`); } } } else if (pcKey === 'cbc') { // 血常规PDF处理 console.log('处理血常规数据:', item); console.log('血常规data字段:', item.data); if (!item.data) { console.error('血常规数据中缺少data字段'); return; } const pdfUrl = item.data + '#toolbar=0&navpanes=0&view=Fit'; console.log(`血常规PDF URL: ${pdfUrl}`); // 检查是否为弃检项目 if (item.itemStatus === "2") { console.log('血常规项目已弃检'); // 隐藏内容区域 const contentRegex = new RegExp( `(]*id="blood-exam-content"[^>]*>).*?()`, 's' ); html = html.replace(contentRegex, `$1
$2
`); // 更新小结区域为弃检提示 const summaryRegex = new RegExp( `(]*id="blood-summary-div"[^>]*>).*?()`, 's' ); const abandonNotice = '

该项目已弃检

'; html = html.replace(summaryRegex, `$1${abandonNotice}$2`); } else { // 构建替换内容 replacement = `$1${buildPdfImageTag('血常规检查报告', 'blood-image', pdfUrl, IMAGE_RENDER_SCALES.labLike)}$3`; // 更新小结内容 if (item.analyse) { console.log('更新血常规小结内容:', item.analyse); const summaryRegex = new RegExp( `(]*id="blood-summary-div"[^>]*>\\s*

血常规小结:

\\s*]*style="white-space: pre-line"[^>]*>).*?(

)`, 's' ); html = html.replace(summaryRegex, `$1${item.analyse}$2`); } } } else if (pcKey === 'bt') { // 生化检查报告处理 if (!item.data) { console.error('生化数据中缺少data字段'); return; } const pdfUrl = item.data + '#toolbar=0&navpanes=0&view=Fit'; // 检查是否为弃检项目 if (item.itemStatus === "2") { // 隐藏内容区域 const contentRegex = new RegExp( `(]*id="biochemistry-exam-content"[^>]*>).*?()`, 's' ); html = html.replace(contentRegex, `$1
$2
`); // 更新小结区域为弃检提示 const summaryRegex = new RegExp( `(]*id="biochemistry-summary-div"[^>]*>).*?()`, 's' ); const abandonNotice = '

该项目已弃检

'; html = html.replace(summaryRegex, `$1${abandonNotice}$2`); } else { // 构建替换内容 replacement = `$1${buildPdfImageTag('生化检查报告', 'biochemistry-image', pdfUrl, IMAGE_RENDER_SCALES.labLike)}$3`; // 更新小结内容 if (item.analyse) { const summaryRegex = new RegExp( `(]*id="biochemistry-summary-div"[^>]*>\\s*

生化小结:

\\s*]*style="white-space: pre-line"[^>]*>).*?(

)`, 's' ); html = html.replace(summaryRegex, `$1${item.analyse}$2`); } } } else if (pcKey === 'rt') { // 尿常规检查报告处理 if (!item.data) { console.error('尿常规数据中缺少data字段'); return; } const pdfUrl = item.data + '#toolbar=0&navpanes=0&view=Fit'; // 检查是否为弃检项目 if (item.itemStatus === "2") { // 隐藏内容区域 const contentRegex = new RegExp( `(]*id="urine-exam-content"[^>]*>).*?()`, 's' ); html = html.replace(contentRegex, `$1
$2
`); // 更新小结区域为弃检提示 const summaryRegex = new RegExp( `(]*id="urine-summary-div"[^>]*>).*?()`, 's' ); const abandonNotice = '

该项目已弃检

'; html = html.replace(summaryRegex, `$1${abandonNotice}$2`); } else { // 构建替换内容 replacement = `$1${buildPdfImageTag('尿常规检查报告', 'urine-image', pdfUrl, IMAGE_RENDER_SCALES.labLike)}$3`; // 更新小结内容 if (item.analyse) { const summaryRegex = new RegExp( `(]*id="urine-summary-div"[^>]*>\\s*

尿常规小结:

\\s*]*style="white-space: pre-line"[^>]*>).*?(

)`, 's' ); html = html.replace(summaryRegex, `$1${item.analyse}$2`); } } } else if (pcKey === 'thxhdb' || pcKey === 'hba1c') { // 糖化血红蛋白检查报告处理 - 与血常规相同方式 html = html.replace( /(]*id="hba1c-report-page"[^>]*)style="display: none;"/, '$1style="display: block;"' ); if (item.itemStatus === "2") { const contentRegex2 = new RegExp( `(]*id="hba1c-exam-content"[^>]*>).*?()`, 's' ); html = html.replace(contentRegex2, `$1
$2
`); const summaryRegex = new RegExp( `(]*id="hba1c-summary-div"[^>]*>).*?()`, 's' ); const abandonNotice = '

该项目已弃检

'; html = html.replace(summaryRegex, `$1${abandonNotice}$2`); replacement = `$1${buildPdfImageTag('糖化血红蛋白检查报告', 'hba1c-image', '', IMAGE_RENDER_SCALES.labLike, ' style="display:none"')}$3`; } else { replacement = `$1${buildPdfImageTag('糖化血红蛋白检查报告', 'hba1c-image', pdfUrl, IMAGE_RENDER_SCALES.labLike)}$3`; const hba1cSummaryText = buildHba1cSummaryText(item); if (hba1cSummaryText && hba1cSummaryText !== '--') { console.log('更新糖化血红蛋白小结内容:', hba1cSummaryText); const summaryRegex = new RegExp( `(]*id="hba1c-summary-div"[^>]*>\\s*

糖化血红蛋白小结:

\\s*]*style="white-space: pre-line"[^>]*>).*?(

)`, 's' ); html = html.replace(summaryRegex, `$1${bodyForHtmlReplace(hba1cSummaryText)}$2`); } } } // 执行替换 const match = html.match(contentRegex); if (match && replacement !== undefined) { console.log(`成功匹配内容区域: ${contentId}`); html = html.replace(contentRegex, replacement); } else if (!match) { console.log(`未找到内容区域: ${contentId}`); } } } }); } console.log('一般检查数据:', JSON.stringify(data.data.find(item => item.itemName === '一般检查'), null, 2) ); console.log('替换后的小结内容:', html.match(/
([\s\S]*?)<\/div>/)[0] ); return html; } replaceExamContent(html, examType, content) { const examIds = { '超声': 'ultrasound', '尿常规': 'urine', '生化': 'biochemistry', '血常规': 'blood', '心电图': 'ecg', 'DR': 'dr', '糖化血红蛋白': 'hba1c' }; const id = examIds[examType]; if (id) { const regex = new RegExp(`(
).*?(<\/div>)`, 's'); return html.replace(regex, `$1${content}$2`); } return html; } updatePdfUrl(html, examType, pdfUrl) { const examIds = { '超声': 'ultrasound', '生化': 'biochemistry', '血常规': 'blood', 'DR': 'dr', '糖化血红蛋白': 'hba1c' }; const id = examIds[examType]; if (id) { // 更新屏幕显示的iframe const iframeRegex = new RegExp(`(
【所见】
${findings.replace('检查所见:', '')}

【所得】
${conclusion.replace('检查结果:', '')}

`; } else { return `

${examData.analyse || ''}

`; } } } module.exports = PDFGenerator;