ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

C# WinForms医院挂号管理系统:从设计到实现,解决门诊挂号崩溃

C# WinForms医院挂号管理系统:从设计到实现,解决门诊挂号崩溃 简介这是一份基于C#语言与WinForm技术栈开发的医院挂号管理系统源码采用C/S架构与MVC分层设计面向计算机专业学生、.NET初学者及需要课程设计或毕业设计参考的开发者可用于学习桌面端信息管理系统的完整实现思路。压缩包共185个文件约10.03MB以64个cs源码文件为核心辅以resx与resources资源文件、jpg医生照片素材、sql建库脚本、chm帮助文档及exe、dll等可执行与依赖文件结构较为完整。系统涵盖用户管理、科室管理、医生管理、门急诊挂号、挂号查询、修改口令、挂号单打印与帮助文档等模块表示层、业务层、数据访问层分离清晰数据库操作借助存储过程完成用户密码采用MD5加密统计信息以报表图文形式呈现并支持医生照片上传。目前已有1267人学习下载适合作为分层架构与WinForm界面开发的实战参考。1. 从一次门诊高峰的挂号崩溃说起某高校附属门诊在早高峰时段做过一次内部演练三个挂号窗口同时开放人工登记、找零、核对医保信息平均每位患者耗时接近四分钟队伍很快排到走廊尽头。问题不在于人手不够而在于信息流是断的——号源余量靠喊、患者信息靠手写、退号改号靠翻本子。这套用 C# WinForms 做的医院挂号管理系统要解决的就是把这条断掉的信息流接起来号源集中管理、患者建档、挂号退号改号、医生排班、收费统计全部收进一个桌面端程序里。它适合两类人正在做课程设计或毕业设计、需要一个结构完整且能跑起来的桌面信息系统的学生以及需要一套可交付、可二次修改的门诊挂号原型的从业者。下面按需求、设计、实现、避坑、验收的顺序把这份方案拆开讲清楚。2. 需求与选型为什么是 WinForms 而不是 Web2.1 先把业务边界划清楚挂号系统的功能看起来简单真正落到代码里边界必须先定死否则做到一半会发现表结构撑不住。我一般会把需求分成三层核心业务、支撑数据、辅助功能。核心业务是挂号、退号、改号、收费支撑数据是患者档案、医生信息、科室与排班、号源池辅助功能是查询统计、用户权限、日志。三层之外的东西比如在线支付、短信通知、医保接口对接一律划到范围外课程设计阶段不要碰碰了就是给自己挖坑。号源是这套系统的核心资源必须明确它的粒度。通常的做法是按「科室 医生 日期 时段」生成号源记录每条记录带总号数、已挂数、挂号费。这样退号时只需把已挂数减一改号时先退后挂逻辑闭环。如果把号源做成一个整数计数器挂在医生身上跨天、跨时段就会乱这是血泪经验。患者档案要区分「首次建档」和「复诊」用身份证号或就诊卡号做唯一键。医生信息要绑定科室和职称职称影响挂号费。排班表是号源生成的上游先有排班才有号源这个依赖顺序在数据库设计时就要体现出来。2.2 技术选型的取舍逻辑选 WinForms 不是因为它先进而是因为它匹配交付场景。桌面端程序部署简单双击即用不需要配服务器、配数据库连接池、配前端构建工具对于单机或局域网内几台机器的门诊场景SQL Server Express 或 SQLite 完全够用。C# 的强类型和 Visual Studio 的设计器让界面拖拽加事件绑定的开发效率很高学生能在有限周期内做出可演示的成品。数据访问层有两种常见做法直接拼 SQL 用SqlCommand或者用轻量 ORM。课程设计我建议前者因为每一句 SQL 都看得见答辩时能讲清楚如果追求代码整洁可以用 Dapper 这类轻量映射但不要上 Entity Framework 的 Code First 迁移配置成本会把时间吃掉。数据库选 SQL Server 的话连接字符串写在配置文件里方便换机器时改选 SQLite 则整个库就是一个文件拷贝即迁移适合演示。界面层用多窗体加 MDC 容器主窗体做导航各业务窗体独立。不要把所有功能塞进一个窗体用 TabControl 切换后期改一个模块会牵动全局。权限控制用简单的角色表加菜单可见性判断管理员看全部挂号员只看挂号与收费医生只看自己的排班和就诊列表。提示技术选型要在开题时就写进文档答辩时被问「为什么不用 Web」是高频问题答案要落在部署成本、开发周期和场景匹配上而不是「我不会」。3. 总体设计四层结构与六张核心表3.1 分层结构与模块职责系统按四层组织界面层、业务逻辑层、数据访问层、数据存储层。界面层只负责收集输入和展示结果不做任何计算业务逻辑层承载挂号规则、号源扣减、费用计算数据访问层封装增删改查数据存储层是数据库。这样分层的好处是挂号规则变化时只改业务层界面不用动。模块划分成六个登录与权限、患者管理、医生与排班管理、号源与挂号管理、收费与退费、查询统计。每个模块对应一组窗体和一个业务类。模块之间的调用走业务层界面层不直接跨模块访问数据库这条纪律能避免后期出现「改一处崩三处」的连锁反应。下面这张表是核心数据表的设计字段名和类型可以直接照搬表名关键字段说明PatientPatientId, Name, IdCard, Phone, CardNo患者档案IdCard 唯一DoctorDoctorId, Name, DeptId, Title, RegFee医生信息RegFee 随职称ScheduleScheduleId, DoctorId, WorkDate, TimeSlot, MaxCount排班决定号源上限RegSourceSourceId, ScheduleId, Total, Booked, Fee号源池Booked 实时扣减RegistrationRegId, PatientId, SourceId, RegTime, Status, Fee挂号记录Status 标记有效/已退UsersUserId, UserName, Password, Role登录与角色3.2 号源扣减的事务设计挂号最怕的是超挂两个人同时抢最后一个号。单机程序并发低但局域网多终端就会有竞争。正确做法是把「查余量」和「扣减」放进同一个事务并且用条件更新防止负数-- 挂号时扣减号源条件更新保证不会超挂 UPDATE RegSource SET Booked Booked 1 WHERE SourceId SourceId AND Booked Total; -- 关键余量为 0 时更新影响行数为 0 -- 判断影响行数为 0 说明号已挂满回滚并提示这段 SQL 的要点在AND Booked Total这个条件。数据库执行更新时会加行锁两个并发请求只有一个能成功另一个影响行数为 0业务层据此提示「该号源已满」。比起先SELECT再判断再UPDATE的写法这种条件更新把判断和写入合成一个原子操作是防超挂最省事的手段。参数SourceId来自界面选中的号源Total和Booked都是整型不要用浮点。退号则是反向操作把Booked减一同时把挂号记录的Status改为已退两个动作也要在同一事务里。改号按「先退后挂」处理先释放原号源再走一次挂号流程任何一步失败就整体回滚。事务边界用SqlTransaction显式控制不要依赖自动提交。3.3 界面导航与状态管理主窗体用菜单栏加侧边导航登录成功后根据角色动态显示菜单项。窗体之间传值用构造函数参数或公共属性不要用全局静态变量静态变量在多次打开窗体时会残留旧数据是典型的翻车点。挂号窗体的流程是选科室 → 选医生 → 选日期时段 → 显示余量 → 选患者或新建档→ 确认挂号。每一步的选中项存在窗体私有字段里确认时一次性提交业务层。4. 关键模块实现从登录到收费的代码骨架4.1 登录与权限校验登录模块要做三件事验证用户名密码、加载角色、按角色控制菜单。密码不要明文存用 SHA256 加盐哈希后存库登录时同样计算再比对。下面是一个可用的校验方法// 登录校验返回角色失败返回 null public string Login(string userName, string password) { string sql SELECT Password, Salt, Role FROM Users WHERE UserName name; using (var conn new SqlConnection(connStr)) using (var cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(name, userName); conn.Open(); using (var reader cmd.ExecuteReader()) { if (!reader.Read()) return null; // 用户不存在 string dbPwd reader.GetString(0); string salt reader.GetString(1); string role reader.GetString(2); // 用相同盐值计算哈希后比对 return ComputeHash(password, salt) dbPwd ? role : null; } } }ComputeHash把密码和盐拼接后做 SHA256输出十六进制字符串。盐值在注册时随机生成每个用户不同。这段代码的关键点是参数化查询AddWithValue防住了 SQL 注入返回 null 统一表示失败调用方据此提示不区分「用户不存在」和「密码错误」避免被枚举账号。角色字符串直接用于主窗体菜单的可见性判断管理员显示全部挂号员隐藏医生管理医生隐藏收费模块。4.2 挂号业务的事务封装挂号业务层的方法接收患者 ID 和号源 ID内部完成扣减号源、写入挂号记录、返回结果三步。核心代码如下// 挂号扣号源 写记录同一事务 public bool Register(int patientId, int sourceId, out string msg) { using (var conn new SqlConnection(connStr)) { conn.Open(); using (var tran conn.BeginTransaction()) { try { // 第一步条件扣减号源 string upd UPDATE RegSource SET Booked Booked 1 WHERE SourceId sid AND Booked Total; using (var cmd new SqlCommand(upd, conn, tran)) { cmd.Parameters.AddWithValue(sid, sourceId); if (cmd.ExecuteNonQuery() 0) { tran.Rollback(); msg 该号源已挂满; return false; } } // 第二步写入挂号记录 string ins INSERT INTO Registration (PatientId, SourceId, RegTime, Status, Fee) SELECT pid, SourceId, GETDATE(), 有效, Fee FROM RegSource WHERE SourceId sid; using (var cmd new SqlCommand(ins, conn, tran)) { cmd.Parameters.AddWithValue(pid, patientId); cmd.Parameters.AddWithValue(sid, sourceId); cmd.ExecuteNonQuery(); } tran.Commit(); msg 挂号成功; return true; } catch (Exception ex) { tran.Rollback(); msg 挂号失败 ex.Message; return false; } } } }两个 SQL 语句共用同一个SqlTransaction任何一步抛异常都整体回滚号源不会出现「扣了但没记录」的脏状态。Fee从号源表带过来保证收费金额和号源定义一致不在代码里硬编码。out参数把提示信息带回界面层界面只负责弹窗不参与业务判断。退号方法结构相同把UPDATE改成Booked - 1并加Booked 0条件同时更新挂号记录状态。4.3 收费与统计查询收费模块按挂号记录汇总支持按日期、科室、医生三个维度筛选。统计用一条分组查询搞定避免在代码里循环累加-- 按科室统计某日挂号量与收入 SELECT d.DeptName, COUNT(r.RegId) AS 挂号量, SUM(r.Fee) AS 收入合计 FROM Registration r JOIN RegSource s ON r.SourceId s.SourceId JOIN Schedule sc ON s.ScheduleId sc.ScheduleId JOIN Doctor doc ON sc.DoctorId doc.DoctorId JOIN Department d ON doc.DeptId d.DeptId WHERE r.Status 有效 AND CAST(r.RegTime AS DATE) date GROUP BY d.DeptName ORDER BY 挂号量 DESC;查询结果直接绑定到DataGridView列名用中文别名界面不用再做映射。date参数由日期选择器传入CAST(... AS DATE)去掉时间部分保证当天记录全部命中。统计模块最容易出的问题是把已退号的记录也算进去所以Status 有效这个条件不能漏。如果要做月度报表把日期条件换成范围查询其余结构不变。5. 避坑与排查五个最容易翻车的地方5.1 号源显示有余量但挂号提示已满现象是界面上显示还剩 1 个号点确认却提示挂满。原因通常是界面加载余量后没有刷新或者余量计算用了缓存值。解决方法是每次打开挂号窗体时重新查询号源确认挂号时以数据库条件更新的结果为准界面显示只作参考。更彻底的做法是挂号成功后立即刷新当前号源列表让用户看到实时余量。5.2 退号后号源没有回补现象是退了号但余量没变号白白浪费。原因多半是退号只改了挂号记录状态忘了更新RegSource.Booked或者两个更新不在同一事务里一个成功一个失败。解决方法是把退号写成「更新记录状态 号源减一」的事务并在测试时专门验证「挂满 → 退一个 → 再挂一个」这条链路。5.3 多台机器同时挂号出现重复记录现象是同一患者同一时段出现两条有效挂号。原因是程序没有做重复校验或者校验和写入之间有间隙。解决方法是在业务层先查该患者在该时段是否已有有效记录再执行挂号数据库层可以加唯一索引兜底索引建在(PatientId, SourceId, Status)上状态为有效时唯一。注意唯一索引和软删除的配合退号后状态变了索引自然放行。5.4 窗体重复打开导致数据错乱现象是同一个挂号窗体开了好几个各自显示不同数据操作后互相覆盖。原因是用了Show()而不是ShowDialog()或者没有判断窗体是否已打开。解决方法是业务窗体统一用模态打开主窗体持有子窗体引用打开前判断是否已存在存在则激活而不是新建。全局静态变量传参也要一并清理改成构造函数传值。5.5 连接字符串写死导致换机即崩现象是程序在自己电脑上跑得好好的拷到答辩教室就打不开数据库。原因是连接字符串硬编码在代码里指向了本机实例名。解决方法是把连接字符串放进App.config用ConfigurationManager读取部署时只改配置文件。如果用 SQLite数据库文件路径用相对路径随程序目录走拷过去就能用。6. 验收与答辩怎么证明这套系统真的能用验收不是把功能点念一遍而是用一条完整的业务链路证明系统闭环。我一般会准备一条「黄金路径」新建患者档案 → 选科室医生 → 查号源余量 → 挂号成功 → 收费记录出现 → 退号 → 余量回补 → 再挂同一号成功。这条路径走通说明核心业务的事务、状态流转、数据一致性都成立。答辩时先演示这条路径再打开数据库表展示记录变化比讲十页 PPT 有说服力。验收标准建议列成可勾选的清单下面这张表可以直接放进文档验收项通过标准验证方式登录权限三种角色菜单不同分别登录截图挂号闭环挂号后余量减一、记录生成黄金路径演示防超挂余量为 0 时挂号被拒挂满后连续点击退号回补退号后余量加一退号后查库统计准确收入合计与记录一致手工核对当日数据换机部署改配置后可直接运行换一台机器启动答辩被问得最多的是「并发怎么处理」和「数据一致性怎么保证」答案就落在第 3、4 章的条件更新和事务上把那段 SQL 讲清楚即可。另一个高频问题是「为什么不用 Web」回到第 2 章的选型逻辑讲部署成本和场景匹配。演示时提前把测试数据准备好不要现场录入大量数据时间不够。有个习惯我每次都强制走一遍答辩前一天把程序拷到一台没装过开发环境的机器上从改配置到跑通黄金路径完整走一次。有一次就是这一步发现数据库文件路径写成了绝对路径当场改掉才没在答辩时翻车。从那以后我每次交付前都强制走一遍换机验证希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表