资讯动态

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

发布时间:2026/9/14 20:37:51 来源:尧图企业网站定制
简介这是一份面向前端开发者与医疗信息化学习者的Vue3实战项目源码聚焦电子病历编辑器这一垂直场景解决医疗文档结构化编辑、富文本交互与跨平台桌面集成等核心需求。资源共28个文件涵盖7个JSON配置与数据模板、7个TSX组件逻辑与UI定义、5个TS工具函数与类型声明、2个YAML工程配置、2个CSS样式定制及SVG/MD/HTML等辅助文件整体仅52KB轻量易读目录结构清晰体现ViteVue3Electron分层架构。已有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而是基于defineComponent和ref/reactive手动实现字段级响应链路确保每个输入框、下拉项、时间选择器均绑定到统一的formState对象并通过watch监听变化触发校验与术语映射。关键设计在于自定义的 Schema 描述语言DSL它用 JSON 定义字段行为而非硬编码 UI2.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.vue、TermSelect.vue、DateTimeRange.vue并通过provide/inject向下透传formState和onFieldChange回调。这种设计使新增字段类型如“中医四诊合参”复合字段只需编写新组件并注册到schemaTypeMap无需修改主渲染逻辑。2.2 基于 Proxy 的双向绑定与实时校验传统v-model在复合字段中易丢失深层响应性。本源码使用reactive包裹formState再通过Proxy拦截set操作实现字段级细粒度控制// src/composables/useFormState.ts export function useFormState(initialState: Recordstring, 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 Mapstring, 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: Recordstring, 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 医嘱联动字段间条件触发与自动填充“诊断→用药”“检查→报告模板”等临床路径需自动响应。源码通过computed与watch构建依赖图谱// 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字段注册watchwatch( () 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代替divh2标记章节标题并内联打印专用样式!-- src/components/PrintLayout.vue -- template div classprint-container header classprint-header h1{{ hospitalName }}/h1 p电子病历 · {{ patientName }} · {{ formatDate(new Date()) }}/p /header main classprint-main section v-forblock in printBlocks :keyblock.key h2{{ block.label }}/h2 div v-htmlrenderBlockContent(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; } } /stylerenderBlockContent方法将结构化数据转换为纯 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.ts中base: /emr/保持一致。4.2 术语库热替换无需重新编译即可更新编码集术语 JSON 文件位于public/terminologies/非src/构建时被原样复制到dist/。更新流程如下编辑public/terminologies/icd10-zh.json添加新诊断编码重启 Nginx或刷新 CDN 缓存前端termService会在下次searchTerms调用时自动加载新文件。此设计使术语维护人员非程序员可独立完成编码集更新符合医院信息科分工习惯。4.3 权限隔离基于角色的字段可见性控制病历不同部分对医生、护士、药师可见性不同。源码通过v-if与role状态结合实现!-- src/components/PrescriptionSection.vue -- template v-ifcurrentUser.role doctor || currentUser.role pharmacist section h2处方信息/h2 MedicationList v-modelformState.medication_list / /section /template template v-else-ifcurrentUser.role nurse section h2护理记录/h2 NursingNotes v-modelformState.nursing_notes / /section /templatecurrentUser由登录后存入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是否被null或undefined覆盖。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 }若无错误提示检查Proxy的set拦截器是否生效或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字段的命名约定是否与后端要求一致。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价