news 2026/9/14 20:37:45

Vue3结构化电子病历编辑器:合规、可计算、可回溯的临床表单系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue3结构化电子病历编辑器:合规、可计算、可回溯的临床表单系统

简介:这是一份面向前端开发者与医疗信息化学习者的Vue3实战项目源码,聚焦电子病历编辑器这一垂直场景,解决医疗文档结构化编辑、富文本交互与跨平台桌面集成等核心需求。资源共28个文件,涵盖7个JSON(配置与数据模板)、7个TSX(组件逻辑与UI定义)、5个TS(工具函数与类型声明)、2个YAML(工程配置)、2个CSS(样式定制)及SVG/MD/HTML等辅助文件,整体仅52KB,轻量易读,目录结构清晰体现Vite+Vue3+Electron分层架构。已有1232人学习下载,适合中高级前端工程师深入理解Composition API实践、Quill/TinyMCE富文本集成、病历字段模板化设计及Electron主渲染进程协同机制。读者可直接运行调试,掌握响应式状态管理、医疗数据验证逻辑、版本快照实现及Electron-builder打包全流程。

1. 这不是富文本编辑器,而是一套面向临床场景的结构化病历构建系统

当你打开“基于vue3的电子病历编辑器源码.zip”,第一眼看到的可能是一个带工具栏的编辑区域——但真正关键的,是它背后对《电子病历系统功能应用水平分级评价标准(试行)》中“结构化录入”“术语约束”“版本留痕”“医嘱联动”四类刚性要求的落地实现。它不追求通用 Markdown 渲染或所见即所得排版,而是把“主诉、现病史、既往史、体格检查、诊断、处置意见”等字段拆解为可配置的 Schema 节点,每个节点绑定 SNOMED CT 或中文临床术语集(如《中医临床术语集》)的编码规则,并强制校验逻辑依赖(例如:若选择“糖尿病”诊断,则“糖化血红蛋白”字段必须填写且值域在 4.0–14.0%)。这套源码适合医院信息科二次开发、HIT 厂商快速集成 HIS/EMR 模块,或医学 AI 团队构建训练数据标注前端——它解决的不是“怎么写得好看”,而是“怎么写得合规、可计算、能回溯”。


2. 用 Composition API + 自定义 Schema DSL 构建可扩展病历模型

Vue 3 的响应式系统与类型安全能力,是支撑结构化病历动态渲染的核心基础。本源码未采用第三方富文本库(如 Quill、Tiptap),而是基于defineComponentref/reactive手动实现字段级响应链路,确保每个输入框、下拉项、时间选择器均绑定到统一的formState对象,并通过watch监听变化触发校验与术语映射。关键设计在于自定义的 Schema 描述语言(DSL),它用 JSON 定义字段行为而非硬编码 UI:

2.1 病历字段 Schema 的五层约束结构

一个典型“血压”字段的 Schema 如下(节选自src/schemas/vitals.json):

{ "key": "blood_pressure", "label": "血压", "type": "composite", "required": true, "validation": { "systolic": { "min": 70, "max": 220, "unit": "mmHg" }, "diastolic": { "min": 40, "max": 120, "unit": "mmHg" } }, "terms": ["SNOMED:271649006", "ICD10:I10"], "dependencies": ["heart_rate"], "history": true }

提示type: "composite"表示该字段由收缩压(systolic)和舒张压(diastolic)两个子字段组成;dependencies字段声明其与心率存在临床逻辑关联(如血压骤降时需同步记录心率);history: true开启版本留痕,每次修改自动存入formHistory数组并标记操作人、时间戳、变更 diff。

该 Schema 由SchemaRenderer.vue组件解析:它遍历formSchema数组,根据type动态import()对应的子组件(如CompositeField.vueTermSelect.vueDateTimeRange.vue),并通过provide/inject向下透传formStateonFieldChange回调。这种设计使新增字段类型(如“中医四诊合参”复合字段)只需编写新组件并注册到schemaTypeMap,无需修改主渲染逻辑。

2.2 基于 Proxy 的双向绑定与实时校验

传统v-model在复合字段中易丢失深层响应性。本源码使用reactive包裹formState,再通过Proxy拦截set操作,实现字段级细粒度控制:

// src/composables/useFormState.ts export function useFormState(initialState: Record<string, any>) { const state = reactive(initialState); const proxy = new Proxy(state, { set(target, key, value) { // 1. 触发字段级校验(如数值范围、必填) const schema = getSchemaByKey(key); if (schema?.validation && !validateValue(value, schema.validation)) { throw new ValidationError(`字段 ${schema.label} 校验失败`); } // 2. 记录变更前快照(用于 diff) const oldValue = target[key]; Reflect.set(target, key, value); // 3. 发布变更事件(供历史模块捕获) emit('field-change', { key, oldValue, newValue: value }); return true; } }); return proxy; }

validateValue函数根据schema.validation中的min/max/pattern/required等规则执行同步校验,错误信息直接注入errors[key]并触发 UI 提示。相比Vuelidate等库,此方案减少运行时依赖,且校验逻辑与 Schema 紧耦合,避免配置错位。

2.3 术语服务集成:对接本地术语库而非远程 API

为满足等保要求与离线使用场景,术语查询不依赖外部 HTTP 请求,而是预加载本地 JSON 文件(如src/assets/terminologies/snomed-zh.json)并构建内存索引:

// src/services/termService.ts const termIndex = new Map<string, TermItem>(); loadTermsFromJSON().forEach(term => { termIndex.set(term.code, term); // code 为主键(如 "SNOMED:271649006") term.synonyms?.forEach(syn => termIndex.set(syn, term)); // 支持同义词模糊匹配 }); export function searchTerms(keyword: string): TermItem[] { return Array.from(termIndex.values()) .filter(term => term.name.includes(keyword) || term.synonyms?.some(s => s.includes(keyword)) ) .slice(0, 10); // 限制返回数量防卡顿 }

TermSelect.vue组件调用searchTerms实现输入联想,选中后将code写入formStatelabel仅用于显示。此举规避了网络延迟与术语服务不可用导致的录入中断,符合医疗场景高可靠性要求。


3. 实现临床必需的三大核心能力:版本留痕、医嘱联动、打印适配

电子病历系统区别于普通表单的关键,在于其承载法律效力与临床决策支持功能。本源码通过 Vue 3 的组合式 API 将这三类能力深度嵌入编辑流程,而非作为插件附加。

3.1 版本留痕:基于 JSON Patch 的轻量级变更追踪

每次字段修改均生成符合 RFC 6902 标准的 JSON Patch 操作描述,并与用户身份、时间戳打包存入formHistory

// src/composables/useHistory.ts interface HistoryRecord { timestamp: number; operator: string; patch: Operation[]; // JSON Patch operations array diff: { old: any; new: any }; // 便于前端快速比对 } export function useHistory(formState: Record<string, any>) { const history: HistoryRecord[] = ref([]); watch(formState, (newVal, oldVal) => { const patch = generatePatch(oldVal, newVal); // 使用 fast-json-patch 库 history.value.push({ timestamp: Date.now(), operator: getCurrentUser().name, patch, diff: { old: oldVal, new: newVal } }); }, { deep: true }); return { history, rollbackTo: (index: number) => applyPatch(formState, history.value[index].patch) }; }

generatePatch函数对比前后状态,仅输出replace/add/remove操作,体积比全量快照小 80% 以上。rollbackTo方法支持一键回退到任意历史节点,且回退操作本身也计入新历史记录,形成完整审计链。

3.2 医嘱联动:字段间条件触发与自动填充

“诊断→用药”“检查→报告模板”等临床路径需自动响应。源码通过computedwatch构建依赖图谱:

// src/schemas/diagnosis.json { "key": "diagnosis_code", "type": "term-select", "terms": ["ICD10:I10", "ICD10:E11.9"], "triggers": [ { "target": "medication_list", "action": "append", "value": { "drug": "二甲双胍", "dose": "500mg", "frequency": "bid" } } ] }

SchemaRenderer解析triggers后,为diagnosis_code字段注册watch

watch( () => formState.diagnosis_code, (newCode) => { const triggers = getTriggersForField('diagnosis_code', newCode); triggers.forEach(trigger => { if (trigger.action === 'append') { formState[trigger.target].push(trigger.value); } }); } );

此机制支持多级联动(如选择“急性阑尾炎”后,自动展开“手术方式”“术后护理”子表单),且所有触发逻辑集中管理,避免散落在各组件中难以维护。

3.3 打印适配:CSS @media print 与语义化 HTML 结构

病历打印需符合《电子病历系统功能应用水平分级评价标准》中“打印内容完整、格式规范、无信息缺失”要求。源码采用语义化 HTML 标签(<section>代替<div><h2>标记章节标题)并内联打印专用样式:

<!-- src/components/PrintLayout.vue --> <template> <div class="print-container"> <header class="print-header"> <h1>{{ hospitalName }}</h1> <p>电子病历 · {{ patientName }} · {{ formatDate(new Date()) }}</p> </header> <main class="print-main"> <section v-for="block in printBlocks" :key="block.key"> <h2>{{ block.label }}</h2> <div v-html="renderBlockContent(block)"></div> </section> </main> </div> </template> <style scoped> .print-container { font-family: "SimSun", "Noto Serif CJK SC", serif; } @media print { body { margin: 0; } .print-container { max-width: 210mm; margin: 0 auto; } .no-print { display: none !important; } /* 强制分页:每章节后插入分页符 */ section { page-break-after: always; } } </style>

renderBlockContent方法将结构化数据转换为纯 HTML(非 Vue 指令),确保打印时无框架干扰;@media print规则移除按钮、输入框等交互元素,并设置宋体字体以满足医疗文书规范。实际部署时,可通过window.print()或后端 PDF 生成服务调用此组件。


4. 部署与定制:Nginx 静态托管 + 术语库热替换 + 权限隔离

源码开箱即用,但生产环境需针对性调整。以下为医院信息科最常执行的三项操作,均无需修改核心逻辑。

4.1 Nginx 静态部署:支持子路径与跨域代理

Vue 3 项目经vite build输出至dist/目录。Nginx 配置需处理两点:一是路由 fallback(避免刷新 404),二是 API 代理(若需对接后端 HIS 接口):

# /etc/nginx/conf.d/emr.conf server { listen 80; server_name emr.hospital.local; # 子路径部署(如部署在 /emr/ 下) location /emr/ { alias /var/www/emr/dist/; try_files $uri $uri/ /emr/index.html; # fallback 到 index.html # 静态资源缓存 expires 1y; add_header Cache-Control "public, immutable"; } # 代理后端 API(仅开发/测试环境启用) location /api/ { proxy_pass https://his-backend.hospital.internal/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

注意:生产环境应关闭location /api/代理,改用后端网关统一鉴权;try_files中的/emr/index.html路径需与vite.config.tsbase: '/emr/'保持一致。

4.2 术语库热替换:无需重新编译即可更新编码集

术语 JSON 文件位于public/terminologies/(非src/),构建时被原样复制到dist/。更新流程如下:

  1. 编辑public/terminologies/icd10-zh.json,添加新诊断编码;
  2. 重启 Nginx(或刷新 CDN 缓存);
  3. 前端termService会在下次searchTerms调用时自动加载新文件。

此设计使术语维护人员(非程序员)可独立完成编码集更新,符合医院信息科分工习惯。

4.3 权限隔离:基于角色的字段可见性控制

病历不同部分对医生、护士、药师可见性不同。源码通过v-ifrole状态结合实现:

<!-- src/components/PrescriptionSection.vue --> <template v-if="currentUser.role === 'doctor' || currentUser.role === 'pharmacist'"> <section> <h2>处方信息</h2> <MedicationList v-model="formState.medication_list" /> </section> </template> <template v-else-if="currentUser.role === 'nurse'"> <section> <h2>护理记录</h2> <NursingNotes v-model="formState.nursing_notes" /> </section> </template>

currentUser由登录后存入localStorage的 JWT payload 解析而来,role字段直接决定 DOM 渲染分支。若需更细粒度控制(如“住院医师仅可编辑现病史”),可在SchemaRenderer中增加visibleWhen属性:

{ "key": "physical_exam", "visibleWhen": { "role": ["attending", "resident"] } }

SchemaRenderer解析时读取visibleWhen并与当前用户角色比对,v-if控制该字段是否渲染。


5. 调试与验证:用 Chrome DevTools 快速定位三类高频问题

上线前必须验证结构化逻辑是否准确。以下方法可绕过 UI,直击数据层。

5.1 检查 Schema 加载与字段绑定

在 Chrome 控制台执行:

// 查看当前加载的 Schema console.log(window.__VUE_DEVTOOLS_GLOBAL_HOOK__.app._context.provides.schema); // 检查 formState 是否响应式 const state = window.__VUE_DEVTOOLS_GLOBAL_HOOK__.app._context.provides.formState; console.log('formState is reactive:', !!state.__ob__); // Vue 2 用 __ob__,Vue 3 用 __v_skip console.log('Current blood pressure:', state.blood_pressure);

formState为空或非响应式,检查useFormState是否正确调用,以及initialState是否被nullundefined覆盖。

5.2 模拟字段变更并观察校验结果

手动触发字段修改,验证实时校验:

// 模拟输入非法血压值 const state = window.__VUE_DEVTOOLS_GLOBAL_HOOK__.app._context.provides.formState; state.blood_pressure = { systolic: 300, diastolic: 80 }; // 查看 errors 对象(需在 setup 中暴露 errors) console.log(window.__VUE_DEVTOOLS_GLOBAL_HOOK__.app._context.provides.errors); // 应输出 { blood_pressure: "收缩压不能超过 220 mmHg" }

若无错误提示,检查Proxyset拦截器是否生效,或validation规则是否拼写错误(如min写成minValue)。

5.3 导出结构化 JSON 并验证术语编码

点击“保存”后,前端生成的 JSON 数据需符合 HL7 CDA 或医院自定义规范。在控制台执行:

// 获取最终提交数据 const submitData = window.__VUE_DEVTOOLS_GLOBAL_HOOK__.app._context.provides.getSubmitData(); console.log(JSON.stringify(submitData, null, 2)); // 验证关键术语字段是否存在编码 console.log('Diagnosis code:', submitData.diagnosis_code); // 应为 "ICD10:I10" console.log('Medication drug code:', submitData.medication_list[0]?.drug_code); // 应为 "ATC:A10BA02"

drug_code为空,检查TermSelect组件是否将code而非name写入formState;若编码格式不符,核对术语 JSON 中code字段的命名约定是否与后端要求一致。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 20:37:29

Flutter与鸿蒙融合:any_base库实现高效ID转换

1. 项目背景与核心价值在鸿蒙生态快速发展的当下&#xff0c;跨平台开发框架Flutter与鸿蒙系统的融合成为开发者关注的热点。any_base作为Flutter生态中优秀的进制转换库&#xff0c;能够实现任意进制之间的数值转换&#xff0c;这为构建轻量级ID生成系统提供了数学基础。传统U…

作者头像 李华
网站建设 2026/9/14 20:36:37

大数据诊断性分析:10个核心技巧与实战案例

1. 大数据诊断性分析概述大数据诊断性分析是通过对海量数据进行深度挖掘和分析&#xff0c;识别数据中的模式、异常和关联关系&#xff0c;从而发现业务问题根源的一种分析方法。与传统的描述性分析不同&#xff0c;诊断性分析更注重"为什么"而非"是什么"&…

作者头像 李华
网站建设 2026/9/14 20:35:57

LangChain与LangGraph 1.0:AI智能体开发的革命性升级

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 20:35:18

ESP32八区气象感知喷灌控制器实战设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 20:34:49

具身智能体轻量化实现:树莓派上的高效感知决策闭环

1. 项目概述&#xff1a;具身智能体的轻量化实现路径在机器人学和人工智能交叉领域&#xff0c;具身智能体&#xff08;Embodied Agent&#xff09;正经历从实验室走向实际应用的转型期。传统方案常受限于计算资源消耗大、响应延迟高等问题&#xff0c;而我们的Python实现方案通…

作者头像 李华
网站建设 2026/9/14 20:34:46

Spring Boot电商项目实战:优品家居销售平台源码解析与部署

如果说让我给初学者推荐一个Spring Boot练手项目&#xff0c;我的答案十有八九还是销售类平台。这类项目覆盖面太典型了&#xff1a;用户登录、商品展示、购物车、下单、库存扣减、订单流转、管理后台、统计报表&#xff0c;几乎把后端开发日常要碰的东西全串了一遍。“springb…

作者头像 李华