1. 项目背景与需求分析
在互联网医疗系统开发中,医生工作站经常需要处理复杂的病历文档。传统的纯文本输入方式无法满足图文混排、格式保留等临床文档需求。百度UEditor(简称百度UE)作为成熟的富文本编辑器,能够很好地解决医疗文档的编辑问题,但如何将编辑好的内容导出为标准化HTML文档并集成到医疗系统中,成为许多开发团队面临的挑战。
医疗行业对文档导出有特殊要求:
- 必须保留所有临床诊断相关的格式(如加粗的重点症状、带编号的检查项目列表)
- 需要兼容各类医疗影像的嵌入展示(DICOM图像、超声动态图等)
- 导出的HTML需符合医疗信息交换标准(如HL7 CDA)
- 文档结构要支持后续的数据挖掘和科研分析
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 百度UE编辑器集成方案
2.1 基础环境配置
医疗系统通常采用前后端分离架构,推荐以下技术栈组合:
javascript复制// 前端框架配置(Vue示例)
import '../public/UEditor/ueditor.config.js'
import '../public/UEditor/ueditor.all.min.js'
import '../public/UEditor/lang/zh-cn/zh-cn.js'
export default {
mounted() {
this.editor = UE.getEditor('medicalEditor', {
toolbars: [
['fullscreen', 'source', 'undo', 'redo', 'bold'],
['insertimage', 'insertvideo', 'attachment']
],
autoHeightEnabled: false,
initialFrameHeight: 600,
elementPathEnabled: false,
enableContextMenu: false // 医疗系统需要禁用右键菜单
})
}
}
2.2 医疗专用功能扩展
针对医疗场景需要特别定制:
- 病历模板功能:
javascript复制UE.registerUI('medical-template', function(editor) {
var btn = new UE.ui.Button({
name: 'template-btn',
title: '插入病历模板',
onclick: function() {
editor.execCommand('insertHtml', '<section class="medical-template">...</section>')
}
});
return btn;
});
- 医学术语自动补全:
javascript复制editor.addListener('ready', function() {
this.addInputRule(function(node) {
if (node.type === 'text' && node.data) {
// 匹配医学术语并自动标注
node.data = node.data.replace(/高血压/g,
'<span class="medical-term" data-code="ICD-10:I10">高血压</span>')
}
});
});
3. HTML导出核心实现
3.1 基础导出功能
获取编辑器内容的标准方法:
javascript复制const htmlContent = editor.getContent()
医疗系统需要处理的特殊场景:
- 清理非标准标签:
javascript复制function sanitizeMedicalHTML(html) {
const allowedTags = ['p', 'ul', 'ol', 'li', 'img', 'table', 'tr', 'td', 'th']
// 保留医疗专用标签
const medicalTags = ['medical-term', 'clinical-note']
return filterXSS(html, {
whiteList: Object.assign({},
allowedTags.reduce((o,t) => (o[t]=[],o), {}),
medicalTags.reduce((o,t) => (o[t]=['data-code'],o), {})
)
})
}
- 图片资源处理:
javascript复制function processMedicalImages(html) {
return html.replace(/<img[^>]+src="([^"]+)"[^>]*>/g, (match, src) => {
if (src.startsWith('data:')) {
const base64Data = src.split(',')[1]
const filePath = `/storage/medical_images/${Date.now()}.png`
fs.writeFileSync(path.join(__dirname, 'public', filePath), base64Data, 'base64')
return match.replace(src, filePath)
}
return match
})
}
3.2 医疗文档增强导出
完整导出流程示例:
javascript复制async function exportMedicalDocument() {
try {
// 1. 获取原始内容
let content = editor.getContent()
// 2. 医疗专用清洗
content = sanitizeMedicalHTML(content)
// 3. 处理嵌入式资源
content = await processMedicalImages(content)
content = await processAttachments(content)
// 4. 添加医疗文档头
const header = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>电子病历文档</title>
<style>
.medical-term { background-color: #f0f7ff; }
.clinical-note { border-left: 3px solid #1890ff; padding-left: 10px; }
</style>
</head>
<body>`
// 5. 生成完整文档
const fullHTML = `${header}
<section class="medical-record">
${content}
</section>
</body>
</html>`
// 6. 触发下载
const blob = new Blob([fullHTML], { type: 'text/html' })
saveAs(blob, `病历_${patientName}_${formatDate(new Date())}.html`)
} catch (err) {
console.error('导出失败:', err)
showMedicalAlert('文档导出失败,请稍后重试')
}
}
4. 医疗系统集成要点
4.1 与EMR系统对接
典型集成架构:
code复制医疗前端系统 → 百度UE编辑器 → 导出HTML → 文档存储服务 → CDR(临床数据仓库)
↓
FHIR/HL7转换器
↓
医院信息系统
关键接口示例:
javascript复制// 保存到电子病历系统
async function saveToEMR(htmlContent) {
const response = await fetch('/api/emr/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Medical-Auth': getAuthToken()
},
body: JSON.stringify({
patientId: currentPatient.id,
documentType: '门诊病历',
htmlContent: htmlContent,
metadata: {
createdBy: currentDoctor.id,
department: currentDepartment
}
})
})
if (!response.ok) {
throw new Error('病历保存失败')
}
return response.json()
}
4.2 性能优化方案
医疗文档特有的优化策略:
- 大文档分块处理:
javascript复制function chunkLargeDocument(html) {
const MAX_SIZE = 500000 // 500KB
if (html.length <= MAX_SIZE) return [html]
const chunks = []
const dom = new DOMParser().parseFromString(html, 'text/html')
let currentChunk = ''
dom.body.childNodes.forEach(node => {
const nodeHtml = node.outerHTML || node.textContent
if (currentChunk.length + nodeHtml.length > MAX_SIZE) {
chunks.push(currentChunk)
currentChunk = nodeHtml
} else {
currentChunk += nodeHtml
}
})
if (currentChunk) chunks.push(currentChunk)
return chunks
}
- 延迟加载医疗影像:
javascript复制function lazyLoadImages(html) {
return html.replace(/<img([^>]+)src="([^"]+)"/g, (match, attrs, src) => {
if (src.startsWith('/storage/large/')) {
return `<img${attrs} src="" data-src="${src}" class="lazy-load"`
}
return match
})
}
5. 医疗行业特殊处理
5.1 敏感信息处理
医疗文档脱敏方案:
javascript复制function deidentifyMedicalText(html) {
// 身份证号
html = html.replace(/(\d{6})\d{8}(\w{4})/g, '$1********$2')
// 手机号
html = html.replace(/(\d{3})\d{4}(\d{4})/g, '$1****$2')
// 特殊疾病标记
const sensitiveKeywords = ['艾滋病', '梅毒', '乙肝']
sensitiveKeywords.forEach(keyword => {
const regex = new RegExp(keyword, 'g')
html = html.replace(regex, `<span class="sensitive">${keyword}</span>`)
})
return html
}
5.2 结构化数据提取
从富文本中提取临床数据:
javascript复制function extractClinicalData(html) {
const dom = new DOMParser().parseFromString(html, 'text/html')
const result = {
diagnoses: [],
medications: [],
procedures: []
}
// 提取诊断(带ICD编码的术语)
dom.querySelectorAll('.medical-term[data-code^="ICD"]').forEach(el => {
result.diagnoses.push({
name: el.textContent,
code: el.dataset.code,
position: getNodePosition(el)
})
})
// 提取药品(特定格式的列表项)
const medRegex = /([\u4e00-\u9fa5]+)\s*([0-9.]+)(mg|g|ml|片|粒)/i
dom.querySelectorAll('li').forEach(li => {
const match = medRegex.exec(li.textContent)
if (match) {
result.medications.push({
name: match[1],
dosage: match[2],
unit: match[3]
})
}
})
return result
}
6. 实战问题排查
6.1 常见问题解决方案
医疗场景特有问题的解决方法:
- DICOM图像显示问题:
javascript复制// 转换DICOM为Web可显示的格式
async function convertDICOMToWeb(dicomUrl) {
const response = await fetch('/api/dicom/convert', {
method: 'POST',
body: JSON.stringify({ dicomUrl })
})
const { pngUrl } = await response.json()
return `<img src="${pngUrl}" class="dicom-image">`
}
- 医学术语丢失问题:
javascript复制// 确保术语标注不丢失
editor.addListener('beforeGetContent', function(type, content) {
if (type === 'html') {
// 保留所有medical-term标签
this.filterInputRule = false
this.filterOutputRule = false
}
})
6.2 医疗文档验证
导出后的质量检查:
javascript复制function validateMedicalHTML(html) {
const errors = []
// 1. 检查必填章节
const requiredSections = ['主诉', '现病史', '诊断']
requiredSections.forEach(section => {
if (!html.includes(`<h2>${section}</h2>`)) {
errors.push(`缺少必填章节: ${section}`)
}
})
// 2. 检查术语编码
const termElements = (new DOMParser())
.parseFromString(html, 'text/html')
.querySelectorAll('.medical-term')
termElements.forEach(el => {
if (!el.dataset.code) {
errors.push(`医学术语缺少编码: ${el.textContent}`)
}
})
return errors.length ? errors : null
}
7. 高级功能扩展
7.1 文档版本对比
医疗文档的版本控制实现:
javascript复制function compareMedicalVersions(html1, html2) {
// 使用专业diff算法
const diff = Diff.diffWords(
stripHTML(html1),
stripHTML(html2)
)
// 生成可视化对比结果
let result = '<div class="medical-diff">'
diff.forEach(part => {
const color = part.added ? 'green' :
part.removed ? 'red' : 'grey'
result += `<span style="color:${color}">${part.value}</span>`
})
result += '</div>'
return result
}
7.2 语音录入支持
医疗语音输入集成:
javascript复制class MedicalSpeechInput {
constructor(editor) {
this.editor = editor
this.recognition = new webkitSpeechRecognition()
this.recognition.lang = 'zh-CN'
this.recognition.interimResults = true
this.recognition.onresult = (event) => {
const transcript = Array.from(event.results)
.map(result => result[0])
.map(result => result.transcript)
.join('')
// 医疗术语自动修正
const medicalTranscript = this.correctMedicalTerms(transcript)
editor.execCommand('insertHtml', medicalTranscript)
}
}
correctMedicalTerms(text) {
const medicalDict = {
'ganmao': '感冒',
'weichangyan': '胃肠炎'
}
Object.entries(medicalDict).forEach(([wrong, correct]) => {
text = text.replace(new RegExp(wrong, 'gi'), correct)
})
return text
}
}
8. 医疗合规性处理
8.1 审计日志记录
记录文档操作轨迹:
javascript复制function logMedicalEdit(action, contentSnapshot) {
fetch('/api/audit/log', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
userId: currentUser.id,
action,
patientId: currentPatient.id,
timestamp: new Date().toISOString(),
contentHash: md5(contentSnapshot),
deviceInfo: navigator.userAgent
})
})
}
8.2 数字签名实现
医疗文档电子签名:
javascript复制async function signMedicalDocument(htmlContent) {
// 1. 生成文档摘要
const digest = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(htmlContent)
)
// 2. 使用医生私钥签名
const signature = await crypto.subtle.sign(
'RSASSA-PKCS1-v1_5',
doctorPrivateKey,
digest
)
// 3. 将签名嵌入文档
const signedHTML = htmlContent.replace(
'</body>',
`<script type="application/signature">
${arrayBufferToBase64(signature)}
</script>
</body>`
)
return signedHTML
}
