简介面向医疗信息化前端初学者与课程实践者的一套HIS系统界面源码与说明包聚焦登录注册、预约挂号、个人病历和药方查询等典型模块帮助读者理解医疗业务页面的交互组织、数据获取与样式设计适合作为课程设计、毕业设计原型或前端综合练习的参考案例。压缩包共42个文件、约736KB以HTML页面为主体配合JavaScript脚本、CSS样式、JSON示例数据及界面图片素材并附说明文档便于按页面和功能对照阅读。已有2812人学习说明该案例在课程设计和前端练手中具有一定参考度。读者可从中梳理jQuery操作DOM与事件、Ajax局部请求及JSON数据展示的配合方式也能观察多页面结构、公共样式拆分和图片资源组织进而迁移到医疗类信息管理系统页面开发中。1. 从一张挂号小票说起HIS 系统源代码里到底装了什么周一早上八点半门诊大厅的队伍已经拐了两个弯。挂号窗口敲键盘、收费窗口打单、药房窗口叫号这几个动作背后是同一个系统在支撑也就是 HIS医疗信息系统。这份 HIS 系统源代码解压之后我看到的是一条完整门诊业务闭环挂号、分诊、医生站开单、收费结算、药房发药、住院医嘱前端用 JavaScript 写页面、用 Ajax 拉 JSON 接口页面不整页刷新。适合三类人看想搞懂医疗业务闭环到底长什么样的前后端开发者要给小型机构做定制、需要一个能改得动的底座的小团队还有接手老系统、每天被“这功能谁写的”折磨的维护人员。说明文档在压缩包根目录先读它再动代码能省掉半天瞎猜。2. 解压之后先看哪里目录结构与技术栈定位一个几十兆的源码包最怕的就是打开之后满屏文件不知道从哪下手。我的习惯是先花十分钟只做三件事认目录、认栈、认数据库。这三件事做完后面所有的调试动作都有落脚点。2.1 顶层目录把「能跑的部分」和「能改的部分」分开解压后先看根目录业务站点、分层程序集、数据库脚本、说明文档基本是分开摆的结构大致是这样HIS/ ├── Web/ # 站点根目录IIS/应用服务器指向这里 │ ├── Login.aspx # 登录页会话入口 │ ├── Register/ # 挂号、退号相关页面 │ ├── Charge/ # 收费、退费、发票 │ ├── Doctor/ # 医生站开单、医嘱、病历 │ ├── Pharmacy/ # 药房发药、退药、库存 │ ├── js/ # 公共脚本jquery、common.js、各模块脚本 │ ├── css/ │ └── Web.config # 连接字符串、会话、错误页配置 ├── BLL/ # 业务逻辑层扣号源、算金额都在这 ├── DAL/ # 数据访问层SQL 基本都落在这一层 ├── Model/ # 实体类字段与表一一对应 ├── Common/ # 工具类加密、日志、JSON 序列化 ├── DB/ │ ├── his_db.sql # 建库建表脚本 │ └── init_data.sql # 字典表科室、号别、收费项目、初始账号 └── 说明文档 # 部署步骤、默认账号、环境要求这里有个判断技巧Web/js/目录里的文件数量和命名方式能大致看出这套代码的“年龄”。如果每个页面配一个独立 JS、公共逻辑靠复制粘贴那前端就偏向传统写法改起来要小心如果已经抽出common.js统一封装请求说明作者至少考虑过维护性二次开发会舒服很多。BLL/和DAL/分开是不错的设计但实际源码包里经常能看到 SQL 语句直接写在页面后台代码里。这种情况不用惊讶老项目普遍如此改造的时候把它当成技术债记下来就行别一上来就大重构。2.2 技术栈反推三个文件就能判断底子不用通读代码看三个文件就够Web.config或web.config连接字符串、Session 超时、认证方式、错误页跳转都在这里它决定了你能不能连上数据库、掉线快不快。package.json或js/jquery-*.min.js的文件名jQuery 是 1.x 还是 3.x直接决定你能不能用Promise、$.ajax的新写法。1.x 时代很多回调还是 callback 套 callback改造时要补defer。项目文件.csproj/pom.xml/*.sln能看出目标框架版本、依赖了哪些第三方库。老包常见的是 .NET Framework 4.0 或 4.5这意味着你在新机器上装环境时不能直接上最新运行时得装对应版本。提示先确认框架版本再装环境比装完发现跑不起来再回头找要省事得多。老项目对环境版本很挑这一点确实不好掌控。2.3 数据库与初始账号不先建库前端调什么都白搭数据库脚本一般放在DB/下先执行建表脚本再执行初始化数据脚本。初始化脚本里通常包含科室字典、号别字典、收费项目价表、角色权限和默认管理员账号这四样缺一样登录进去也是一片空白。连接字符串在Web.config的connectionStrings节点里常见写法是!-- Web.config改这三处就能连上你自己的库 -- connectionStrings add nameHisConn connectionStringServer127.0.0.1;DatabaseHisDb;User Idsa;Password你的密码; providerNameSystem.Data.SqlClient / /connectionStrings system.web !-- 门诊窗口一坐一整天Session 给足不然收费收到一半掉线 -- sessionState modeInProc timeout120 / /system.web参数说明Server填实例地址命名实例要写成127.0.0.1\SQLEXPRESS这种形式Database必须和建库脚本里的库名一致不一致就直接报“无法打开登录所请求的数据库”timeout单位是分钟窗口业务建议不低于 60。改完连接字符串先用数据库客户端手动连一次能连上再启动站点。这一步跳过的后果是页面报 500你在代码里找半天其实只是密码错了。3. 前端这条线Ajax JSON 如何把门诊流程串起来这套系统的前端没什么花哨框架核心就是 JavaScript jQuery Ajax JSON 四件套。看着朴素但它是整个系统里最容易改、也最容易改出问题的地方。把这条线摸清你就能在不碰后端的情况下加字段、改校验、调页面。3.1 公共请求封装一个函数省掉一半重复代码先找js/common.js。做得比较规范的包会有一个统一请求入口所有模块都调它而不是各写各的$.ajax// js/common.js —— 全站公用请求封装 var HIS { post: function (url, data, cb, opts) { opts opts || {}; $.ajax({ url: url, type: POST, dataType: json, // 强制按 JSON 解析解析失败直接进 error contentType: application/json; charsetutf-8, data: JSON.stringify(data), // 统一序列化后端按流读取 timeout: opts.timeout || 15000, // 收费、发药这类操作别设太短 beforeSend: function () { if (opts.mask ! false) { $(#loading).show(); } }, success: function (res) { if (res.code 0) { cb cb(res.data); // 只把业务数据交给回调 } else if (res.code 401) { top.location.href /Login.aspx; // 会话过期统一跳登录 } else { alert(res.msg || 操作失败); } }, error: function (xhr, status) { // 这里最容易翻车返回的可能是一整页登录 HTML不是 JSON console.error(接口异常, url, status, xhr.status); alert(网络或服务异常请重试); }, complete: function () { if (opts.mask ! false) { $(#loading).hide(); } } }); } };参数说明url是接口相对路径一般以/api/开头data是纯业务对象不要在里塞 DOM 节点cb只在code 0时被调用业务失败不会污染回调opts.mask传false可以关掉遮罩比如轮询类请求就不该弹遮罩opts.timeout在药房发药、批量结算这类操作上可以调到 30 秒。逻辑说明把dataType写死成json是有意为之。有些老代码用dataType: text再手动eval一旦后端返回 HTML 错误页eval会把整段 HTML 当表达式解析报出的错完全看不出真实原因。固定成json解析失败会走error分支日志里能直接看到状态码。3.2 挂号页的一次完整交互从点击到表格渲染挂号页的动作很典型填几个字段、点确认、拿到单号和排队号、右侧列表局部刷新。这段代码可以直接照抄改// Register/register.js —— 点击确认挂号 $(#btnSave).on(click, function () { var form { patientId: $(#txtPatientId).val(), // 患者主索引刷卡或手输 deptCode: $(#selDept).val(), // 科室编码来自字典表 doctorCode: $(#selDoctor).val(), // 医生工号可为空表示不指定 regType: $(#selRegType).val(), // 号别普通/专家/急诊 payType: $(#selPayType).val(), // 支付方式 visitDate: $(#txtVisitDate).val() // 就诊日期 yyyy-MM-dd }; // 前端只做必填校验号源有没有、收多少钱一律由后端判定 if (!form.patientId || !form.deptCode) { alert(请先选择患者和科室); return; } HIS.post(/api/register/apply, form, function (data) { // data 结构{ regNo, queueNo, amount, deptName } $(#txtRegNo).text(data.regNo); $(#txtQueueNo).text(data.queueNo); $(#txtAmount).text(data.amount.toFixed(2)); loadRegList(); // 局部刷新列表不整页回发 }); });这里有两个要点。第一前端校验只做“必填”和“格式”不做业务判定因为号源余量和金额都是会变的前端算出来的结果永远可能是旧值。第二loadRegList()是局部刷新只在同一个页面里重发一次查询请求然后重绘表格。老项目里经常能看到location.reload()那就是整页刷新体验差而且会丢表单状态。3.3 收费与退费金额为什么必须回后端重算收费模块是整套系统里最不能省事的地方。我见过有人为了“体验好”在前端把明细金额累加出来直接显示总额然后提交给后端。这种做法一旦上线改价就是一行浏览器控制台的事。// Charge/charge.js —— 结算只传单号和支付方式 HIS.post(/api/charge/settle, { regNo: $(#txtRegNo).val(), payType: $(#selPayType).val(), paid: parseFloat($(#txtPaid).val()) // 患者实付仅用于算找零 // 注意故意不传 amount总额以后端为准 }, function (res) { // res: { total, paid, change, items: [{ name, qty, price, amount }] } renderItems(res.items); $(#txtTotal).text(res.total.toFixed(2)); $(#txtChange).text(res.change.toFixed(2)); // 找零也由后端算 });接口返回的 JSON 约定长这样改动的时候保持结构不变前端就不用跟着动{ code: 0, msg: ok, data: { regNo: MZ20240513001, total: 128.50, paid: 200.00, change: 71.50, items: [ { name: 挂号费, qty: 1, price: 8.00, amount: 8.00 }, { name: 血常规, qty: 1, price: 25.00, amount: 25.00 } ] } }退费比收费更麻烦。合格的做法是生成一张红冲单据原单标记为已退不是把原记录删掉或者改成负数金额。红冲的好处是账能对上、审计能查、报表不会因为一条记录消失而偏差。4. 后端接口与落库一次挂号请求的数据旅程前端点一下按钮后端要跑完校验、扣号源、写单、算钱、写日志一整套动作。这一段是整套系统里最值得细看的地方也是二次开发时改动风险最高的地方。4.1 接口分层Handler → Service → DAO 这条链路不同版本的后端形态不太一样常见的是通用处理程序Handler接请求、Service 做业务、DAO 走 SQLJava 版本对应的是 Controller Service Mapper调用关系一一对应。以 Handler 为例// Handler/RegisterHandler.cs —— 挂号接口入口 public class RegisterHandler : IHttpHandler { public void ProcessRequest(HttpContext ctx) { ctx.Response.ContentType application/json; var body new StreamReader(ctx.Request.InputStream).ReadToEnd(); var param JsonConvert.DeserializeObjectRegParam(body); Result r; try { var svc new RegisterService(); // 操作员从会话里取绝不信任前端传上来的工号 r svc.Apply(param, ctx.Session[UserCode] as string); } catch (BizException ex) { r Result.Fail(ex.Message); } // 业务异常提示可读 catch (Exception ex) { Log.Error(ex); // 系统异常落日志 r Result.Fail(系统繁忙请重试); // 不回显堆栈 } ctx.Response.Write(JsonConvert.SerializeObject(r)); } public bool IsReusable { get { return false; } } }参数说明RegParam是前端 JSON 反序列化出来的对象字段名要和前端一致大小写敏感ctx.Session[UserCode]是登录时写入的操作员工号用于记录操作痕迹和权限判定BizException是自定义业务异常和系统异常分开捕获前者提示给用户后者只进日志。逻辑说明把异常分两类是关键。业务异常号挂满了、患者不存在要让用户看到原因系统异常数据库超时、空引用不能让用户看到堆栈否则等于把内部结构暴露出去。很多老代码把两者混在一起catch完直接返回ex.Message上线后前端弹出一串英文堆栈既不专业也不安全。4.2 三张核心表挂号、收费、医嘱怎么连业务主键用字符串单号不用自增 ID这是医疗系统的常见做法因为单号要打印、要口头报、要跨系统传。核心三张表大致是这样-- 挂号主表一次挂号一行退号用 Status 标记不做物理删除 CREATE TABLE HIS_REGISTER ( RegNo VARCHAR(20) NOT NULL PRIMARY KEY, -- 门诊号业务主键 PatientId VARCHAR(20) NOT NULL, -- 患者主索引 DeptCode VARCHAR(10) NOT NULL, DoctorCode VARCHAR(10) NULL, RegType VARCHAR(10) NOT NULL, -- 普通/专家/急诊 VisitDate DATE NOT NULL, Amount DECIMAL(10,2) NOT NULL DEFAULT 0, -- 挂号费后端算 Status TINYINT NOT NULL DEFAULT 0, -- 0正常 1已退 2已就诊 OperatorCode VARCHAR(20) NOT NULL, CreateTime DATETIME NOT NULL DEFAULT GETDATE() ); CREATE INDEX IX_REG_PATIENT ON HIS_REGISTER(PatientId, VisitDate); -- 收费明细一张收费单多行RedFlag 标记红冲 CREATE TABLE HIS_CHARGE_ITEM ( Id BIGINT IDENTITY PRIMARY KEY, ChargeNo VARCHAR(20) NOT NULL, -- 收费单号 RegNo VARCHAR(20) NOT NULL, -- 关联挂号单 ItemCode VARCHAR(20) NOT NULL, -- 收费项目编码 Qty DECIMAL(8,2) NOT NULL DEFAULT 1, Price DECIMAL(10,2) NOT NULL, -- 下单时快照价不随价表变 Amount DECIMAL(10,2) NOT NULL, RedFlag TINYINT NOT NULL DEFAULT 0, -- 1 表示红冲行 CreateTime DATETIME NOT NULL DEFAULT GETDATE() ); -- 医嘱表医生站开出来的原始指令收费从这来 CREATE TABLE HIS_ORDER ( OrderNo VARCHAR(20) NOT NULL PRIMARY KEY, RegNo VARCHAR(20) NOT NULL, ItemCode VARCHAR(20) NOT NULL, Qty DECIMAL(8,2) NOT NULL, Status TINYINT NOT NULL DEFAULT 0, -- 0新开 1已收费 2已执行 3已作废 DoctorCode VARCHAR(20) NOT NULL, CreateTime DATETIME NOT NULL DEFAULT GETDATE() );有个细节值得注意HIS_CHARGE_ITEM.Price存的是下单那一刻的价格快照不是去价表里实时查。价表调整是常态如果收费单实时关联价表历史单据的金额会跟着变对账永远对不平。这个坑我在别的项目里踩过改回去要重刷数据。4.3 事务与并发号和钱不能出错的三个位置门诊早高峰的并发量不小同一时间十几个人抢同一个专家的最后几个号是常事。扣号源必须在一个带条件的更新里完成不能先查再改// BLL/RegisterService.cs —— 扣号源和写单必须同事务 public Result Apply(RegParam p, string operatorCode) { using (var tran db.BeginTransaction()) { // 1) 带条件的原子更新影响行数为 0 说明号已被抢完 int rows db.Execute( UPDATE HIS_REGSOURCE SET Used Used 1, Version Version 1 WHERE DeptCode d AND RegType t AND RegDate dt AND Used Total, new { d p.DeptCode, t p.RegType, dt p.VisitDate }); if (rows 0) { tran.Rollback(); return Result.Fail(该号别已挂满); } // 2) 生成门诊号并写主表 string regNo Sequence.Next(MZ); db.Execute( INSERT INTO HIS_REGISTER (RegNo,PatientId,DeptCode,DoctorCode,RegType, VisitDate,Amount,Status,OperatorCode) VALUES (regNo,pid,dept,doc,type,dt,amt,0,op), new { regNo, pid p.PatientId, dept p.DeptCode, doc p.DoctorCode, type p.RegType, dt p.VisitDate, amt 8.00m, op operatorCode }); tran.Commit(); return Result.Ok(new { regNo regNo, amount 8.00m }); } }逻辑说明WHERE Used Total这个条件写在UPDATE里交给数据库做原子判断比“先SELECT余量、再UPDATE”安全得多。后者在两人同时操作时会双双通过校验最后超发。第二个隐患是单号生成Sequence.Next(MZ)必须是线程安全且不依赖数据库自增的事务回滚自增不回滚单号会出现空洞对账时容易被误认为丢单。第三个隐患是号源表和挂号表必须同事务只扣号不写单会白占号只写单不扣号会超发。5. 部署与联调避坑五个真会卡住你的地方这套代码我前后部署过几次卡住的地方翻来覆去就那几个。下面按“现象 → 原因 → 解决”记下来遇到时直接对号入座。5.1 启动阶段站点起不来的两个典型症状第一条访问首页直接 500.19提示配置节无法识别。原因通常是Web.config引用了当前服务器没装的模块比如 URL 重写、自定义 HTTP 模块或者是配置节被锁定。解决方式是先把Web.config里对应节点注释掉把站点点起来再去装缺失的模块回来恢复。定位的时候不要靠猜打开 IIS 的详细错误页错误码后面会直接写出是哪个配置节、哪一行。第二条首页能开一点查询就 404路径里还多了一段目录名。原因是接口地址在 JS 里写成了/api/xxx这种从根起步的绝对路径而你把站点部署在了虚拟目录下比如/his请求就飞到了根站点。解决办法有两种一是在common.js里加一个baseUrl变量由页面统一注入二是部署时干脆建成独立站点不挂虚拟目录。我一般选第一种因为改一处就够而且换部署方式时不用再动。5.2 联调阶段三个最容易被误判的问题第三条Ajax 一直进 error 分支控制台看到返回的是一整页 HTML。现象看着像“接口不存在”实际是会话过期被认证模块重定向到了Login.aspx返回状态码 200、内容类型text/html而前端dataType: json解析失败。解决分两步后端对接口请求统一返回401加 JSON 体而不是 302 重定向前端在error回调里判断状态码401 直接跳登录页。两边都做才算稳。第四条写进去的中文变成问号或者页面显示的是一串乱码。原因是编码不统一。老项目页面头部可能是gb2312而接口返回头是utf-8或者数据库字段排序规则是Chinese_PRC_CI_AS、连接字符串没指定编码。解决顺序是先统一数据库字段排序规则再把页面、接口响应头、连接字符串三处的编码统一到utf-8最后清一次浏览器缓存重新验证。只改一处往往只是把乱码换了个位置出现。第五条金额显示成 128.49999999或者明细累加和总额差一分钱。前端用浮点数做加法就会出现这个。解决方式是后端一律返回两位小数的字符串或以“分”为单位的整数前端只做格式化和展示不做累加运算。明细的总额、找零、折扣全部后端算好再下发。这条看着琐碎但退费、对账、发票打印全依赖它改晚了就是一片返工。注意遇到怪问题先看返回内容类型和状态码再看业务代码。多数“玄学”问题在第一步就能定位。6. 二次开发把通用 HIS 改成贴合自己流程的四个着力点这套代码拿到手大多数人最终都会改。改的位置其实很集中找准了改动量不大。一是字典和价表。科室、号别、收费项目都在初始化脚本的字典表里新增一个项目不用改代码插数据就行但Price字段要理解清楚它是快照源头别直接改历史单据的价格。二是打印模板。挂号条、发票、医嘱单基本都是独立模板文件改版式不动业务逻辑。三是报表。日常报表多半是 SQL 拼出来的加一张统计表就是加一段查询加一个页面。四是对外接口。检验检查、结算对接这类建议单独加一个 JSON 网关层不要让外部系统直接打到业务 Handler 上否则后面每次对接都要动核心代码。验证方法我习惯用一条链路走通挂号 → 开单 → 收费 → 发药 → 退费 → 红冲每一步都查数据库里的状态字段和金额全部对上才算改完。中间任何一步状态不对先停别往下走。早些年我图快改完收费逻辑只测了收费本身退费没测第二天窗口退号直接金额对不上被叫回去加班重刷了一遍数据。从那以后我每次动账相关的代码都强制把“收费—退费—红冲—再对账”这条链路完整走一遍哪怕改动只有一行。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?