多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

电子病历系统架构与医嘱闭环实践:Winex深度解析

电子病历系统架构与医嘱闭环实践:Winex深度解析 简介这是一份卫宁健康新一代医疗数字转型平台WiNEX的产品介绍PDF面向医院信息科、HIT产品经理与架构师、医疗信息化从业者重点解答医疗机构在流程再造、信息共享、系统集成、数据标准化等方面的痛点概述平台如何利用互联网、AI、物联网与大数据驱动业务升级。内容围绕“重塑业务、重塑标准、重塑架构”展开详细介绍基于临床思维的智能辅助症状采集与检验检查、引入SNOMED-CT和HL7 FHIR等国际标准的数据模型、参照265个卫生健康行业标准的数据值域、以“1X”中台与开放服务层支撑多类场景并覆盖在线诊疗、药品配送、健康管理等无边界医疗服务模式以及多态客户端和DevOps敏捷交付体系强调数据资产从治理到应用的核心价值。资源共1个PDF文件压缩包大小4.32MB已有860人学习浏览适合需要快速理解WiNEX产品全景与设计理念的读者作为入门参考和方案沟通素材。1. 从HIS到Winex电子病历为什么不再只是一张录入表单HIS系统换了三代临床科室最头疼的依然是病历模板翻半天既往史贴不对归档时被质控打回一改就是一上午。国内某医疗信息化厂商发布的新一代产品Winex把电子病历EMR从录入工具升级成贯穿医嘱、诊断、护理、质控的临床数据引擎核心思路很直接——用结构化模板约束书写用流程引擎驱动病历状态流转用主索引把患者全周期数据串起来。适合谁正在做HIS/EMR选型评估的信息科人员、负责二次开发的工程师、想了解医疗业务闭环的产品经理。这篇按架构、数据模型、医嘱闭环、部署上线的顺序讲透重点放在参数配置和坑上照着能复现。2. Winex的系统架构微服务拆分、网关路由与插件化设计的关键取舍2.1 为什么Winex不沿用单体架构并发模型与科室边界决定了必须拆分老一代HIS普遍是单体应用所有模块共用一个数据库连接池门诊高峰期挂号和开方抢同一个事务线程经常出现“开了药保存失败、挂号却扣了费”的尴尬。Winex在设计上把业务按“临床域”拆分每个域独立部署、独立扩容这是医疗业务本身决定的门诊挂号是高并发低事务医嘱写入是中并发强事务病历文书是低并发大字段三者混在一起必然互相拖累。我一般会在部署环境里重点看两个指标网关层的线程池大小以及各服务允许的最大连接数。Winex网关用的是Spring Cloud Gateway核心参数有两个一个是spring.threads.max决定网关同时能处理多少请求另一个是spring.threads.queue-capacity队列满了之后新请求会直接被拒绝。按一个三甲医院日门诊量8000到10000人来估网关线程池设在200、队列容量设500是够用的超过这个量优先横向扩容网关实例而不是调大线程数——线程调太大反而增加上下文切换开销。spring: cloud: gateway: server: port: 8080 threads: max: 200 queue-capacity: 500 routes: - id: emr-service uri: lb://winex-emr predicates: - Path/api/emr/** filters: - StripPrefix1 - id: order-service uri: lb://winex-order predicates: - Path/api/order/** filters: - StripPrefix1这段配置里lb://winex-emr表示通过注册中心负载均衡到emr服务StripPrefix1表示把/api/emr前缀剥掉后再转发给下游。常见误用是把所有路由都配成Path/api/**这样网关会把请求转发到固定一个服务导致业务模块之间互相抢资源。按域拆分路由是Winex推荐的用法排错时也能通过网关日志快速定位是哪个服务超时。2.2 插件化设计临床路径、移动护理、危急值推送都是“插上去的”Winex把临床路径、移动护理、危急值推送这类外围功能做成了插件核心服务只保留患者主索引、电子病历、医嘱、术语字典、质控这五个基础模块。这样做的好处是医院不需要一次性购买全套功能按需启用插件即可二次开发时也不动核心库表降低升级冲突的概率。插件接口是Java SPI机制实现的核心接口就一个ClinicalPlugin里面定义了onLoad()、onEnable()、onExecute(ClinicalContext context)三个方法。每个插件在META-INF/services里注册服务启动时通过ServiceLoader加载。我在给某医院做对接时危急值推送就是在这层实现的写一个插件监听检验结果事件触发时调用推送接口再把推送记录写回消息表。整个插件包打成jar放到plugins目录就能热加载Winex会定期扫描目录变化不用重启服务。public class CriticalValuePushPlugin implements ClinicalPlugin { Override public void onLoad() { // 初始化推送通道比如短信网关、企业微信机器人 } Override public void onEnable() { // 订阅检验结果事件 EventBus.subscribe(LAB_RESULT_PUBLISHED, this::handleLabResult); } private void handleLabResult(ClinicalContext context) { String patientId context.getPatientId(); String resultValue context.getLabResult(); if (isCritical(resultValue)) { pushService.push(patientId, 危急值提醒: resultValue); } } }这段代码里的EventBus.subscribe是关键Winex的事件总线默认是内存实现的如果插件需要持久化订阅关系就得改用KafkaEventBus实现类。我的经验是只要涉及跨服务的事件通知比如检验结果推送到门诊医生站一定要走消息队列而不是内存事件否则服务重启期间的事件全丢。插件化还有个隐含坑插件之间的调用顺序不保证如果危急值推送和病历自动归档两个插件都监听同一个事件别在插件里假设谁先执行需要顺序的话改成串行消息队列。2.3 前端与接口层为什么推荐用官方封装组件而不是裸写请求Winex前端基于Vue官方提供了一套win-ui组件库里面封装了患者列表、病历编辑器、医嘱录入、质控面板等医疗专属组件。我见过不少团队为了“轻量”直接调用底层HTTP接口结果病历编辑器上方的工具栏按钮失效、CtrlEnter快捷键不触发排查半天发现是缺了win-ui的键盘事件绑定。import { WinEmrEditor } from win-ui; export default { components: { WinEmrEditor }, data() { return { recordId: EMR20240115001, templateCode: INPATIENT_ADMISSION }; }, methods: { onEditorReady(editor) { // 编辑器加载完成后注入自定义宏命令 editor.registerMacro(${presentIllness}, 患者自述症状...); } } }这段代码展示了如何用win-ui的WinEmrEditor组件加载一份住院病历并注册一个宏命令${presentIllness}。注意recordId对应的是Winex里emr_document表的主键templateCode对应模板编码两个参数都必须在患者主索引下有效否则编辑器会报“病历不存在”。我在联调时发现一个规律如果是空白病历打不开多半是recordId传错如果是打开后模板内容不对才需要查templateCode的绑定关系。接口层Winex统一走RESTful风格返回结构固定为{ code, message, data }。code0是成功非0需要看message字段。联调阶段最实用的做法是先在Swagger页面里把每个接口的请求样例跑通再从前端调。直接从前端调接口查问题会被CORS、登录态、参数格式三层问题干扰反而定位不准。3. 电子病历数据模型MongoDB与MySQL并存模板绑定和主索引怎么玩3.1 文档型存储与关系型并存病历为什么不能只放MySQL电子病历的最大特点是“结构不稳定”入院记录、手术记录、出院小结的字段差异很大同一份文书不同科室要求的段落也不同。如果用MySQL建固定字段表每加一个字段就要改库表结构上线的阻力会非常大。Winex的做法是双库并存病历正文用MongoDB存JSON文档患者基本信息、用户、科室、字典这类强关系数据放MySQL模板的版本管理也放MySQL。MongoDB里核心集合是emr_document一个文档对应一份病历。它的结构大致是document_id、patient_id、visit_id、template_code、contentJSON字符串、status、create_time。content里保存的是结构化病历内容比如主诉、现病史、既往史、体格检查等段落每段是JSON对象字段名和模板定义一致。db.emr_document.aggregate([ { $match: { patient_id: P202400001, status: COMPLETED } }, { $project: { template_code: 1, content.chief_complaint: 1, content.present_illness: 1, create_time: 1 } }, { $sort: { create_time: -1 } }, { $limit: 20 } ])这是一条很实用的查询按patient_id取该患者最近20份已完成的病历只返回主诉和现病史字段。注意content.chief_complaint这种点路径写法要求content必须存成嵌套JSON而不是字符串如果发现查不到字段优先检查写入端是否用了JSON.stringify()把整个对象序列化成了字符串存进去。我排查过的问题里十个有八个是这个原因。MySQL侧主要存模板定义和模板版本。模板表emr_template里有template_code、template_name、schema_json、version四个核心字段。schema_json定义了这个模板包含哪些段落、每段是文本框还是下拉框、是否必填。每次改动模板会生成新版本旧版本的version不变新版本version1病历正文里会保存当时用的template_code和version这样归档后模板改了历史病历的展示逻辑依然按当时版本渲染不会出现“病历内容还在排版乱了”的问题。3.2 模板绑定与变量替换${}语法是怎么执行的Winex的模板解析核心是变量替换。医生打开病历编辑器时系统拿到template_code找到对应版本的schema_json再结合当前患者的上下文数据把模板里的${patientName}、${age}、${visitDate}这类占位符替换成真实值。上下文数据从哪里来从患者主索引和当前就诊记录里取。import re from datetime import datetime def render_template(template_content, context): pattern r\$\{([a-zA-Z_][a-zA-Z0-9_]*)\} def replace(match): key match.group(1) if key in context: value context[key] if isinstance(value, datetime): return value.strftime(%Y-%m-%d) return str(value) return ${%s} % key # 未命中的变量保留原样 return re.sub(pattern, replace, template_content)这段Python代码演示了模板渲染的核心逻辑。正则\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}匹配模板里的变量占位符context字典里存的是从主索引和就诊记录里查到的值。关键处理在第8行未命中的变量不替换为空字符串而是保留${xxx}原样。这样做的用途是在保存时校验——如果一份出院小结里还有未替换的占位符说明上下文数据不完整质控会提示“存在未填充变量”减少漏填。参数说明context里的键名必须和模板设计器里定义的一致比如患者姓名是patientName不是name容易翻车的地方就在这。我遇到过一次开发者以为age是患者年龄实际Winex的age是“就诊年龄”计算依据是就诊日期减去出生日期而不是当前日期。如果按当前日期算跨年之后复诊的病历年龄会突然跳一岁病历归档后和实际不符。3.3 患者主索引EMPI为什么病历不能直接用科室内部ID关联一个患者可能在门诊挂过号、住过院、做过体检每个场景下业务系统给他生成的ID可能不一样。如果没有主索引同一个人的病历在三个系统里就是三条记录查不到完整历史。Winex的MPI模块负责把不同来源的患者标识统一到全局唯一patient_id上匹配规则按优先级走身份证号全国唯一优先级最高手机号和姓名组合次之最后才是社保卡号。主索引合并的代码逻辑在Winex里是一套加权匹配算法我复现过一个简化版def match_patient(id_card, name, phone, birth_date): score 0 if id_card and len(id_card) 18: score 100 # 身份证匹配权重最高 if name and id_card and id_card[:6] 110101: score 20 # 姓名地区码辅助匹配 if phone and len(phone) 11: score 10 # 手机号辅助匹配 if score 100: return 強匹配, 可直接合并 elif score 30: return 弱匹配, 进入人工审核池 else: return 不匹配, 新建患者档案这段代码的匹配逻辑里身份证号是唯一能单独决定“强匹配”的字段其他字段加起来的权重也不足以自动合并。这个小细节直接影响临床使用如果患者入院时没登记身份证只靠姓名和手机号合并Winex会把疑似重复的档案放进人工审核池等审核人员确认后才合并。如果你在对接时发现患者既往史“丢失”先查MPI的审核池多半是档案还没合并完。规则看似简单但真放在生产环境里弱匹配的审核队列会被大量“姓名手机号相同但身份证不同”的假阳性占满所以后来我接项目都会建议把手机号权重调低优先保证身份证号这一硬指标。4. 医嘱闭环与医疗流程引擎状态机设计是Winex最不能省的一环4.1 医嘱状态机从“开立”到“停止”的六个状态怎么流转医嘱是HIS里最敏感的业务对象和钱、药、护理执行强绑定。Winex把医嘱生命周期定义成六个状态开立、审核、执行、停止、作废、完成。每个状态之间的流转条件有严格限制“执行”状态不能直接跳“作废”必须先“停止”再“作废”这是医疗合规的要求也是审计留痕的基础。当前状态允许流向触发条件开立审核 / 作废医生提交后进审核开立后未审核可作废审核执行 / 作废药师审核通过后执行审核不通过则作废执行停止 / 完成长期医嘱执行到停止时间短期医嘱执行完毕后完成停止作废 / 完成停止后未执行部分作废已执行部分保留作废无终态不可逆必须记录作废原因完成无终态医嘱全部执行完毕这张表对应到代码里就是状态机枚举。Winex用的是Java枚举实现核心方法是transition(OrderStatus target, OrderContext ctx)每次状态变更前校验当前状态和触发条件校验不通过直接抛异常。public enum OrderStatus { CREATED(开立), REVIEWED(审核), EXECUTING(执行), STOPPED(停止), CANCELLED(作废), COMPLETED(完成); public OrderStatus transition(OrderStatus target, OrderContext ctx) { if (target CANCELLED this EXECUTING) { throw new IllegalStateException(执行中的医嘱不能直接作废必须先停止); } if (target COMPLETED this CREATED) { throw new IllegalStateException(未审核的医嘱不能直接完成); } // 业务校验通过后执行状态流转并写入审计日志 auditLogger.log(this.name(), target.name(), ctx.getOperatorId(), ctx.getReason()); return target; } }这段代码里最容易被忽略的是ctx.getReason()作废和停止必须填写原因这是质控和审计的硬性要求。我在某医院的实施中发现有些科室为了避免麻烦作废时统一填“医生操作失误”这会导致后续统计分析时误把“用药调整”当成“医疗差错”。建议二次开发时把原因字段做成下拉框禁止自由输入减少脏数据。状态机还有一个隐含要求禁止跨服务直接改数据库里的状态字段。如果你在排查“医嘱状态不对”的问题先去查是不是有定时任务或报表程序通过UPDATE语句直接改了order_status绕过状态机的结果就是后续所有流转逻辑全乱。4.2 定时扫描与异步补偿医嘱卡在“审核中”到底是谁的问题医嘱流转依赖事件驱动但消息队列也有丢消息的时候。Winex里有个兜底机制定时任务每5分钟扫描一次超时未流转的医嘱把超过30分钟还停在“开立”状态的医嘱重新推送审核事件。这个扫描任务是我看过的项目里问题最多的模块主要原因是开发者图省事直接用SELECT * FROM orders WHERE statusCREATED AND create_time NOW() - INTERVAL 30 MINUTE然后在应用层一条条补发事件。import pymysql import json import redis def scan_timeout_orders(): conn pymysql.connect(host10.0.0.5, userwin, passwordxxx, databasewinex) cursor conn.cursor() cursor.execute( SELECT order_id, patient_id, create_time FROM orders WHERE statusCREATED AND create_time NOW() - INTERVAL 30 MINUTE AND timeout_processed 0 LIMIT 200 ) rows cursor.fetchall() r redis.Redis(host10.0.0.6, port6379, db0) for row in rows: order_id row[0] # 用Redis SETNX做幂等防止多个扫描实例重复处理 ok r.set(ftimeout:scan:{order_id}, 1, nxTrue, ex600) if ok: publish_order_review_event(order_id) cursor.execute(UPDATE orders SET timeout_processed1 WHERE order_id%s, (order_id,)) conn.commit() cursor.close() conn.close()这里最关键的是timeout_processed字段和Redis的SETNX幂等标记。timeout_processed0确保一条医嘱只被扫描一次Redis的SETNX确保即使部署了多个扫描实例也只有第一个实例能拿到锁避免重复推送审核事件。忘记加幂等控制是我见过最常见的翻车点——每次部署多实例系统里就会出现大量重复医嘱和重复扣费。这条定时任务建议在凌晨低峰期跑避免和门诊高峰抢数据库连接。部署时的参数设置扫描间隔5分钟、每次最多处理200条、单条处理失败不中断整体任务这三项按这个值配基本够用。如果发现“审核中”的医嘱持续增多先看消息队列的消费速率是不是下降了再看数据库的连接池是否被慢查询占满不要一上来就调扫描任务的频率那样只会放大问题。4.3 危急值闭环事件怎么在30分钟内完成“提醒-确认-处置”链路危急值管理是等级评审的必查项。Winex的闭环链路是检验系统发布危急值事件 - 医生站弹窗提醒 - 医生确认 - 填写处置意见 - 系统记录时间戳 - 超时未确认自动升级提醒到科室主任。整条链路由状态字段critical_status跟踪PUBLISHED、ACKNOWLEDGED、HANDLED、ESCALATED。def handle_critical_value_event(event): # 调用医生站提醒接口 ack_result call_doctor_station_remind(event) if ack_result.get(status) ACKNOWLEDGED: update_critical_status(event[record_id], ACKNOWLEDGED) # 等待医生填写处置意见异步处理后续事件 wait_for_handling(event[record_id]) else: # 30分钟内未确认升级到科室主任 escalate_to_department_head(event)call_doctor_station_remind怎么判断“未确认”Winex的做法是当医生点开弹窗时前端会调一个确认接口接口里写入ack_time。如果接口一直没被调用定时任务每5分钟扫一次ack_time IS NULL的记录超过30分钟就触发escalate_to_department_head。这里的核心参数是30分钟这是行业惯例里的“危急值通报时限”如果你的医院制度更严格可以改到15分钟但必须在配置中心改而不是改代码。我在对接某医院时踩过的一个坑是弹窗提醒被浏览器拦截了护士以为没收到提醒等升级到主任才发现问题出在前端权限配置。从那以后我每次部署提醒类功能都会专门做一遍浏览器弹窗权限检查而不是只看后端日志确认推送成功。5. 部署与上线注意真实环境里最容易翻车的六个位置5.1 Docker编排与资源配置内存、磁盘、时区一个都不能少Winex官方推荐用Docker Compose部署服务清单包括网关、认证、MPI、EMR、医嘱、术语字典、质控、消息队列、MySQL、MongoDB、Redis、Nacos。整套下来服务数量超过12个资源规划不合理的话上线第一天就可能OOM。services: winex-emr: image: winex/emr:3.2.1 ports: - 8082:8082 environment: - SPRING_PROFILES_ACTIVEprod - TZAsia/Shanghai - MONGO_URImongodb://mongodb:27017/winex - MYSQL_DSNjdbc:mysql://mysql:3306/winex?useSSLfalseserverTimezoneAsia/Shanghai - REDIS_HOSTredis - NACOS_ADDRnacos:8848 volumes: - /data/winex/logs:/logs - /data/winex/plugins:/plugins deploy: resources: limits: memory: 2048M reservations: memory: 1024M这段编排文件里有三个容易翻车的点。第一是TZAsia/Shanghai不设这个环境变量容器默认时区是UTC日志时间和医疗文书的记录时间全部差8小时病历打印出来的时间直接不对。第二是volumes必须挂载/plugins目录Winex的插件热加载依赖这个目录不挂载的话每次重启容器插件就丢了。第三是SPRING_PROFILES_ACTIVEprod必须显式指定默认是dev配置连接的是内存数据库数据不持久化重启就全没了。内存规划方面EMR服务给2G医嘱服务给2G网关给1GMySQL给4GMongoDB给4GRedis给1G这是一套能跑中小型医院日门诊3000人左右的配置。如果后续要跑质控报表这种重查询任务建议单独部署一个只读的报表实例不要在生产库上跑大聚合查询。5.2 六个高频故障现象、原因与解决办法以下六条是我在不同医院实施Winex项目时真实遇到过的故障按“现象 - 原因 - 解决”整理前三条和升级迁移有关后三条和运行期配置有关。故障一升级到新版本后历史病历模板错位。现象升级前归档的病历打开后某些段落里的内容跑到了错误的位置比如“既往史”字段显示在“现病史”下面。原因模板新版本的schema_json里段落顺序调整了但病历正文里存的是旧模板的JSON结构渲染时按新模板的段落顺序去解析旧数据字段就错位了。解决Winex的模板渲染必须严格按文档里保存的template_version来加载对应版本的schema_json。升级前先确认所有历史病历的template_version字段不为空为空的需要用旧版本模板重新扫描生成一次版本快照。我在一次升级里漏了这一步结果有几百份归档病历模板错位最后靠写脚本按旧版schema重新解析才救回来。故障二两个医生同时编辑同一份病历后保存的覆盖了先保存的。现象护士和住院医生同时打开同一份入院记录护士先保存了护理评估医生后保存了主诉结果护士的护理评估内容消失了。原因Winex的病历保存没有加乐观锁默认是“后写覆盖”。我翻过数据库emr_document表里有一个rev字段但默认实现没校验。解决二次开发时在保存接口里加上版本号校验。前端打开病历后记录rev值保存时带上这个rev后端执行UPDATE ... WHERE document_id? AND rev?影响行数为0就提示“病历已被他人修改请刷新后重试”。改动量不大但对临床科室的协作体验提升非常明显。故障三病历打印出来的时间是1970-01-01。现象出院病历打印出来入院日期和出院日期全部显示为1970-01-01 08:00。原因前端传时间时用的格式是yyyy-MM-dd HH:mm:ss但后端接收到的是LocalDateTime中间经过JSON序列化时没有配置格式Spring Boot默认把它序列化成时间戳数字前端拿到后当毫秒数直接new Date()解析就变成了1970年。解决在全局JSON配置里统一设置时间格式。注意serverTimezoneAsia/Shanghai这个参数如果漏了也会导致时间偏移8小时。我排查这个故障时一开始只查后端代码查了很久才发现是两个问题叠加后端没配序列化格式数据库连接串也漏了serverTimezone。两个参数配好之后打印时间恢复正常。故障四医嘱提交后一直停在“审核中”药师端看不到待审核任务。现象医生提交了长期医嘱状态显示“审核中”但药师工作站刷新一上午都没有新任务进入队列。原因医嘱提交后往消息队列发了一条“ORDER_REVIEW_REQUESTED”事件药师的待办列表消费了这个事件才展示任务。消息队列的消费者服务挂了或者消费速度太慢事件积压在队列里药师端自然看不到。解决登录消息队列控制台看ORDER_REVIEW_REQUESTED这个topic的积压量。积压量在几百条以内说明消费者处理慢优先检查消费者的数据库连接池是否被慢查询占满积压量持续增长说明消费者宕机需要先恢复服务再重放消息。这里注意一个顺序一定要先恢复消费端再重放消息否则重放的消息又会直接积压。故障五Nacos配置改了不生效重启服务后配置还是旧的。现象在Nacos控制台修改了一个配置项等了半小时服务里的值还是没变重启服务后依然读的是旧配置。原因Nacos配置的refresh机制依赖服务端的RefreshScope注解。默认情况下Winex的基础配置类是普通Component字段不会自动刷新。解决修改配置后先在Nacos控制台确认配置已发布再确认服务的dataId和group和服务端配置的一致。如果服务端动态刷新不生效直接在Nacos控制台点“发布”后手动重启该服务实例。我习惯是在部署脚本里加一个健康检查先用API读一遍配置值再改动Nacos配置再读一遍对比是否变化能自动判断刷新是否生效。故障六网关偶发504超时但下游服务日志显示处理时间只有200毫秒。现象前端调用医嘱查询接口偶尔返回504后端日志里该接口的耗时只有200ms服务本身没报错。原因网关的默认空闲超时时间太短长连接在处理慢请求的空闲期被网关断开客户端收到504。解决把网关的idle-timeout从默认的30秒调到60秒同时把request-timeout调到30秒。这个配置在网关服务里是Spring Cloud Gateway的spring.cloud.gateway.httpclient.connect-timeout和response-timeout两个参数。我见过不少团队遇到504就盲目加下游服务的线程数实际上问题出在网关层下游线程加再多也没用。6. 二次开发进阶自定义表单怎么做到“改配置不改代码”医院信息科最常提的需求是“加一张表”。入院评估表、跌倒风险评估表、压疮风险评估表每个临床科室都有自己的评估工具而且表格内容经常调整。如果每次调整都走代码发布IT部门的工时根本扛不住。Winex的表单设计器就是为了解决这个问题在管理端拖拽字段、绑定模板、配置必填项保存后前端动态渲染表单后端动态校验JSON结构全程不写一行业务代码。具体操作是三步。第一步在表单设计器里新建一个“入院跌倒风险评估表”添加“最近一年跌倒史”“意识状态”“步态异常”这三个下拉框字段字段的code分别设为fall_history、consciousness、gait_abnormal选项值用术语字典管理。第二步给这个表单绑定一个template_code比如FALL_RISK_ASSESSMENT然后在模板里加入一段自定义脚本// 模板自定义脚本根据三个字段自动计算风险等级 function calculateRiskLevel(formData) { let score 0; if (formData.fall_history YES) score 5; if (formData.consciousness CONFUSED) score 3; if (formData.gait_abnormal SEVERE) score 4; if (score 7) return 高风险; if (score 4) return 中风险; return 低风险; }这段脚本运行在表单提交前的校验阶段formData就是前端收集的整个表单数据对象。风险等级计算出来后会作为隐藏字段存进emr_document.content里质控报表可以按这个值做统计。第三步在科室的护理工作站菜单里把“入院跌倒风险评估表”挂到对应病区的导航上保存后医生打开新病历就能在文书列表里看到这张表。我建议所有评估表类需求都用这个方案做而不是在代码里硬编码新页面。为什么因为评估表的字段组合是临床科室自己定义的你很难预料他们下周会不会再加一个“使用助行器”字段。走表单设计器科室护士自己就能维护IT部门只需要在字段变更后清理一下脏数据。接口调试方面的习惯也提一下Winex的所有写操作接口都要求传入operator_id和dept_code这两个字段不传会直接报“操作人信息缺失”。传了但格式不对会报“操作人不在当前科室”。我遇到过开发者在联调时因为没传科室编码代码被前端拦截报了一个“无权限访问”的错误排查很久才发现是参数缺失根本不是权限问题。做一个评估表功能从设计到上线大约耗时一天其中半天花在字段梳理和科室确认上半天花在表单配置和验证上。上线后我会把整个评估表的配置过程整理成一份给科室看的操作说明这样后续科室要加字段能自己照着说明操作不再需要重复走IT工单。这一步看起来琐碎但能明显减少日后的沟通成本。医疗信息化的项目交付真正难的不是技术本身而是业务变化的速度——从那以后我每接到一个“加表”的需求都强制自己先问一句“这个表是交付即冻结还是长期会变化”再决定是做死页面还是走表单设计器。这个习惯帮我省掉了不少重复开发工单希望帮到你。本文还有配套的精品资源点击获取
返回列表