report-service/utils/pdfGenerator.js
2025-04-15 11:38:14 +08:00

856 lines
41 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 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');
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 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(/<img\s+src="首页标签.png"/g, `<img src="${base64Image}"`);
} catch (error) {
console.error('处理图片时出错:', error);
}
// 计算年龄
let age = '--';
if (data.birthday) {
const birthDate = new Date(data.birthday);
const today = new Date();
let calculatedAge = today.getFullYear() - birthDate.getFullYear();
const m = today.getMonth() - birthDate.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
calculatedAge--;
}
age = calculatedAge.toString();
}
// 更新所有报告标题中的填充值
const reportTitleRegex = /<div class="report-title">\s*<div[^>]*>\s*<span[^>]*>.*?<\/span>\s*<span[^>]*>填充值<\/span>\s*<\/div>\s*<\/div>/g;
const replacement = `<div class="report-title">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span></span>
<span>${data.medicalSn || '--'} ${data.pName || '--'} ${data.gender || '--'} ${age}岁</span>
</div>
</div>`;
html = html.replace(reportTitleRegex, replacement);
// 更新体检编号
const reportTitleRegex2 = /体检编号:.*?</;
html = html.replace(reportTitleRegex2, `体检编号:${data.medicalSn || '--'}<`);
// 更新头像
if (data.headPicUrl) {
const avatarSrc = data.headPicUrl.startsWith('data:image') ? data.headPicUrl : `data:image/jpeg;base64,${data.headPicUrl}`;
html = html.replace(/<img[^>]*class="avatar-image"[^>]*>/g, `<img src="${avatarSrc}" alt="头像" class="avatar-image" id="avatar-image">`);
}
// 格式化体检日期
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(`<span class="person_title" style="letter-spacing: 4px;">${title}</span><span[^>]*class="person_content"[^>]*>[^<]*</span>`);
} else {
// 对于普通字段,匹配不带样式的标签
regex = new RegExp(`<span class="person_title">${title}</span><span[^>]*class="person_content"[^>]*>[^<]*</span>`);
}
const matches = html.match(regex);
if (matches) {
// 构建替换内容,保持原有样式
const replacement = needsLetterSpacing
? `<span class="person_title" style="letter-spacing: 4px;">${title}</span><span class="person_content">${value}</span>`
: `<span class="person_title">${title}</span><span class="person_content">${value}</span>`;
html = html.replace(regex, replacement);
}
});
// 更新前言中的姓名
const prefaceNameRegex = /<span class="underline">.*?<\/span>/;
if (data.pName) {
html = html.replace(prefaceNameRegex, `<span class="underline">${data.pName}</span>`);
}
// 更新一般检查数据
if (data.data && Array.isArray(data.data)) {
// 使用更精确的正则表达式来匹配表格单元格
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 || '--'}`
};
// 添加单位
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(`<td>体质指数\\(BMI\\)</td>\\s*<td[^>]*>.*?</td>`, 'g')
: new RegExp(`<td>${key}</td>\\s*<td[^>]*>.*?</td>`, 'g');
const replacement = key === '体质指数'
? `<td>体质指数(BMI)</td><td>${value}${unit}</td>`
: `<td>${key}</td><td>${value}${unit}</td>`;
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(
/<div class="report-summary" id="general-exam-summary">[\s\S]*?<\/div>/s,
`<div class="report-summary" id="general-exam-summary">
<h4>一般检查小结:</h4>
<p id="general-summary-content">${summaryContent}</p>
</div>`
);
} catch (error) {
logToFile(`替换出错: ${error.message}`);
}
}
// 更新体质辨识结果
html = html.replace(
/(<td width="50%" class="constitution-value" style="text-align: center;">).*?(<\/td>)/s,
`$1${data.zybs || '--'}$2`
);
// 更新汇总结果
if (data.summaryResult) {
// 先更新第一页内容
html = html.replace(
/(<p id="summary-content-1"[^>]*class="summary-content"[^>]*>).*?(<\/p>)/s,
`$1${data.summaryResult}$2`
);
// 更新主检医生签名
if (data.sign) {
// 更新第一页签名
const summaryDoctorImageRegex = /(<img[^>]*id="summary-doctor-image"[^>]*>)/;
if (html.match(summaryDoctorImageRegex)) {
html = html.replace(summaryDoctorImageRegex, `<img id="summary-doctor-image" src="${data.sign}" alt="主检医生签名">`);
}
// 更新第二页签名
const summaryDoctorImage2Regex = /(<img[^>]*id="summary-doctor-image2"[^>]*>)/;
if (html.match(summaryDoctorImage2Regex)) {
html = html.replace(summaryDoctorImage2Regex, `<img id="summary-doctor-image2" src="${data.sign}" alt="主检医生签名">`);
}
}
// 添加分页处理脚本到 HTML 中
const paginationScript = `
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof handleSummaryPagination === 'function') {
handleSummaryPagination('${data.summaryResult.replace(/'/g, "\\'")}');
}
});
</script>
`;
html = html.replace('</body>', `${paginationScript}</body>`);
}
// 更新体质结果详情
if (data.features) {
html = html.replace(
/(<div id="constitution-detail-container".*?>).*?(<\/div>)/s,
`$1${data.features.replace(/\\r\\n/g, '<br>')}$2`
);
}
// 更新图片和 PDF 链接
if (data.data && Array.isArray(data.data)) {
data.data.forEach((item) => {
if (item.data) {
let contentId = '';
let summaryId = '';
let pdfContainerId = '';
switch (item.pacsDataType) {
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;
default:
break;
}
if (contentId) {
console.log(`处理报告类型: ${item.pacsDataType}, 内容ID: ${contentId}`);
// 构建正则表达式来匹配特定ID的内容区域
const contentRegex = new RegExp(
`(<div class="report-content" id="${contentId}">)(.*?)(</div>)`, 's'
);
// 为PDF URL添加参数
const pdfUrl = item.data + '#toolbar=0&navpanes=0&view=Fit';
console.log(`PDF URL: ${pdfUrl}`);
// 准备替换内容
let replacement;
if (item.pacsDataType === 'ecg') {
// 心电图使用img标签
replacement = `$1<img src="${item.data}" alt="心电图检查">$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(
`(<div[^>]*id="ecg-summary"[^>]*>).*?(</div>)`, 's'
);
const findingsMatch = html.match(findingsRegex);
console.log('所见正则匹配结果:', findingsMatch ? '成功' : '失败');
html = html.replace(findingsRegex, `$1<p><strong>【所见】</strong><br>${findings}</p><p><strong>【所得】</strong><br>${conclusion}</p>$2`);
}
} else if (item.pacsDataType === 'US') {
// 超声检查报告处理 - 设置 PDF URL
replacement = `$1<img src="" alt="超声检查报告" class="ultrasound-image" data-pdf-url="${pdfUrl}">$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(
`(<div class="report-summary" id="ultrasound-summary">\\s*<p>\\s*<strong>【所见】</strong>\\s*<br>)[^<]*(</p>\\s*<p>\\s*<strong>【所得】</strong>\\s*<br>)[^<]*(</p>\\s*</div>)`, '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(/<div class="report-summary" id="ultrasound-summary">[\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 (item.pacsDataType === '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(
`(<div[^>]*id="blood-exam-content"[^>]*>).*?(</div>)`, 's'
);
html = html.replace(contentRegex, `$1<div style="display:none;">$2</div>`);
// 更新小结区域为弃检提示
const summaryRegex = new RegExp(
`(<div[^>]*id="blood-summary-div"[^>]*>).*?(</div>)`, 's'
);
const abandonNotice = '<p class="abandon-notice" style="color:red;font-weight:bold;text-align:center;padding:20px;">该项目已弃检</p>';
html = html.replace(summaryRegex, `$1${abandonNotice}$2`);
} else {
// 构建替换内容
replacement = `$1<img src="" alt="血常规检查报告" class="blood-image" data-pdf-url="${pdfUrl}">$3`;
// 更新小结内容
if (item.analyse) {
console.log('更新血常规小结内容:', item.analyse);
const summaryRegex = new RegExp(
`(<div[^>]*id="blood-summary-div"[^>]*>\\s*<h4>血常规小结:</h4>\\s*<p[^>]*style="white-space: pre-line"[^>]*>).*?(</p>)`, 's'
);
html = html.replace(summaryRegex, `$1${item.analyse}$2`);
}
}
} else if (item.pacsDataType === '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(
`(<div[^>]*id="biochemistry-exam-content"[^>]*>).*?(</div>)`, 's'
);
html = html.replace(contentRegex, `$1<div style="display:none;">$2</div>`);
// 更新小结区域为弃检提示
const summaryRegex = new RegExp(
`(<div[^>]*id="biochemistry-summary-div"[^>]*>).*?(</div>)`, 's'
);
const abandonNotice = '<p class="abandon-notice" style="color:red;font-weight:bold;text-align:center;padding:20px;">该项目已弃检</p>';
html = html.replace(summaryRegex, `$1${abandonNotice}$2`);
} else {
// 构建替换内容
replacement = `$1<img src="" alt="生化检查报告" class="biochemistry-image" data-pdf-url="${pdfUrl}">$3`;
// 更新小结内容
if (item.analyse) {
const summaryRegex = new RegExp(
`(<div[^>]*id="biochemistry-summary-div"[^>]*>\\s*<h4>生化小结:</h4>\\s*<p[^>]*style="white-space: pre-line"[^>]*>).*?(</p>)`, 's'
);
html = html.replace(summaryRegex, `$1${item.analyse}$2`);
}
}
} else if (item.pacsDataType === '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(
`(<div[^>]*id="urine-exam-content"[^>]*>).*?(</div>)`, 's'
);
html = html.replace(contentRegex, `$1<div style="display:none;">$2</div>`);
// 更新小结区域为弃检提示
const summaryRegex = new RegExp(
`(<div[^>]*id="urine-summary-div"[^>]*>).*?(</div>)`, 's'
);
const abandonNotice = '<p class="abandon-notice" style="color:red;font-weight:bold;text-align:center;padding:20px;">该项目已弃检</p>';
html = html.replace(summaryRegex, `$1${abandonNotice}$2`);
} else {
// 构建替换内容
replacement = `$1<img src="" alt="尿常规检查报告" class="urine-image" data-pdf-url="${pdfUrl}">$3`;
// 更新小结内容
if (item.analyse) {
const summaryRegex = new RegExp(
`(<div[^>]*id="urine-summary-div"[^>]*>\\s*<h4>尿常规小结:</h4>\\s*<p[^>]*style="white-space: pre-line"[^>]*>).*?(</p>)`, 's'
);
html = html.replace(summaryRegex, `$1${item.analyse}$2`);
}
}
}
// 执行替换
const match = html.match(contentRegex);
if (match) {
console.log(`成功匹配内容区域: ${contentId}`);
html = html.replace(contentRegex, replacement);
} else {
console.log(`未找到内容区域: ${contentId}`);
}
}
}
});
}
console.log('一般检查数据:',
JSON.stringify(data.data.find(item => item.itemName === '一般检查'), null, 2)
);
console.log('替换后的小结内容:',
html.match(/<div class="report-summary" id="general-exam-summary">([\s\S]*?)<\/div>/)[0]
);
return html;
}
replaceExamContent(html, examType, content) {
const examIds = {
'超声': 'ultrasound',
'尿常规': 'urine',
'生化': 'biochemistry',
'血常规': 'blood',
'心电图': 'ecg'
};
const id = examIds[examType];
if (id) {
const regex = new RegExp(`(<div id="${id}-summary".*?>).*?(<\/div>)`, 's');
return html.replace(regex, `$1${content}$2`);
}
return html;
}
updatePdfUrl(html, examType, pdfUrl) {
const examIds = {
'超声': 'ultrasound',
'生化': 'biochemistry',
'血常规': 'blood'
};
const id = examIds[examType];
if (id) {
// 更新屏幕显示的iframe
const iframeRegex = new RegExp(`(<div class="${id}-exam".*?<iframe.*?src=").*?(")`);
html = html.replace(iframeRegex, `$1${pdfUrl}$2`);
// 更新打印用的PDF容器
const containerRegex = new RegExp(`(<div class="${id}-exam".*?data-pdf-url=").*?(")`);
html = html.replace(containerRegex, `$1${pdfUrl}$2`);
}
return html;
}
generateExamHtml(examType, examData) {
if (examType === '超声' || examType === '心电图') {
const [findings = '', conclusion = ''] = (examData.analyse || '').split('\n');
return `
<p><strong>【所见】</strong><br>${findings.replace('检查所见:', '')}</p>
<p><strong>【所得】</strong><br>${conclusion.replace('检查结果:', '')}</p>
`;
} else {
return `<p>${examData.analyse || ''}</p>`;
}
}
}
module.exports = PDFGenerator;