856 lines
41 KiB
JavaScript
856 lines
41 KiB
JavaScript
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; |