资讯详情

资讯详情

WinForm医疗设备管理系统:分层台账与借还事务实现

简介这款医疗设备管理系统WinForm面向医院设备科、医疗信息化初学者及课程设计开发者围绕设备台账、合同、配件、维修、统计和打印等业务给出可运行的桌面端管理方案并附需求文档与操作手册便于理解流程和二次修改。压缩包为 rar 格式共 155 个文件、约 12.32MB含 30 个 cs 源码、20 个 dll 程序集、17 个 resources 资源、9 个 resx以及 config、txt、png/jpg、exe、mdb 等覆盖源码、界面资源、配置、数据库和说明文档。系统提供管理员与普通账号两套登录入口默认密码可自行调整。已有 778 人学习或下载读者可借此梳理设备全生命周期管理模块结合文档完成环境搭建、功能验证和二次开发。1. 医院设备台账不是表单堆叠WinForm 医疗管理系统 CS 版的真实使用场景一套医院系统如果只做设备台账最怕的不是功能少而是数据对不上。这套医疗管理系统 CSWinForm版本把设备入库、借用归还、维修记录和科室领用放在同一个桌面端里适合中小型医院设备科或第三方运维团队做本地化部署。它不依赖浏览器双击 exe 就能跑数据库用 SQL Server界面是典型的 WinForm 窗体加 DataGridView。医疗设备管理系统的核心不是界面多漂亮而是每一台设备的当前状态、责任人、维修历史能在一个窗口里查清楚。下面按我拆项目的顺序从分层目录、数据库初始化、核心模块代码一路讲到二次开发时怎么加字段、控按钮权限。2. 分层与目录医疗设备管理系统从登录窗体到台账模块的代码地图2.1 解决方案分层UI / BLL / DAL / Models 各管什么WinForm 项目最容易写成“窗体里塞 SQL”一开始跑得快后面改一个字段要翻十几个窗体。这套医疗管理系统 CS 版按 UI、BLL、DAL、Models 四层拆窗体只负责收集输入和展示结果业务规则放在 BLLSQL 集中在 DAL实体和分页参数放 Models。常见做法是 UI 引用 BLLBLL 引用 DAL 和 ModelsDAL 只依赖 Models不反向引用 UI。这样设备列表窗体不需要知道连接串也不用手写SqlCommand。目录结构大致如下文件名和层级能直接对应到后面的代码MedicalDeviceManager/ ├─ MedicalDeviceManager.sln ├─ src/ │ ├─ MedicalDeviceManager.UI/ # WinForm 窗体与控件 │ │ ├─ Forms/ │ │ │ ├─ FrmLogin.cs # 登录窗体 │ │ │ ├─ FrmMain.cs # 主窗体与菜单 │ │ │ ├─ FrmDeviceList.cs # 设备台账列表 │ │ │ ├─ FrmBorrow.cs # 借用与归还 │ │ │ └─ FrmRepair.cs # 维修记录 │ │ ├─ Controls/ │ │ └─ Program.cs # 启动入口 │ ├─ MedicalDeviceManager.BLL/ # 业务规则与事务入口 │ │ ├─ DeviceService.cs │ │ ├─ BorrowService.cs │ │ └─ RepairService.cs │ ├─ MedicalDeviceManager.DAL/ # ADO.NET 数据访问 │ │ ├─ DbHelper.cs │ │ ├─ DeviceRepository.cs │ │ ├─ BorrowRepository.cs │ │ └─ RepairRepository.cs │ └─ MedicalDeviceManager.Models/ # 实体与查询参数 │ ├─ Device.cs │ ├─ BorrowRecord.cs │ └─ PagedResult.cs ├─ db/ │ ├─ 01_schema.sql # 建库建表 │ └─ 02_seed.sql # 科室、角色、初始管理员 └─ publish/ # 发布输出目录各项目职责可以按下面这张表对照二次开发时先判断改哪一层再动文件减少到处补引用的概率项目主要职责依赖UI窗体事件、控件绑定、输入校验提示BLL、ModelsBLL业务校验、事务边界、状态流转DAL、ModelsDAL参数化 SQL、连接管理、结果映射ModelsModels实体、分页参数、查询条件无2.2 目录与核心文件清单窗体、服务、实体、SQL 脚本登录窗体FrmLogin.cs只做三件事读取账号密码、调用 BLL 的登录方法、把当前用户信息存到静态会话里。主窗体FrmMain.cs根据角色显示菜单设备科能看到入库和维修普通科室只能看借用。设备列表FrmDeviceList.cs里放 DataGridView、搜索框、状态下拉框和分页按钮翻页时把pageIndex、pageSize、keyword、status传给DeviceService不直接拼 SQL。DbHelper.cs是数据访问层的入口常见做法是读取App.config里的连接字符串提供ExecuteNonQuery、ExecuteScalar、ExecuteDataTable和带事务的重载。所有外部输入都走SqlParameter不拿字符串拼接。DeviceService.cs负责设备编号唯一性校验、状态是否允许修改、删除前是否有关联借用记录。BorrowService.cs负责借用和归还的事务先查状态再更新更新影响行数为 0 就回滚。db/01_schema.sql是建库脚本db/02_seed.sql初始化科室、供应商、角色和默认管理员。运行顺序不能反先建表再插种子数据。种子脚本里的默认管理员只用于第一次登录上线前必须改密码这条我一般会写进发布检查表。2.3 数据库表设计设备台账、科室、供应商、维修记录设备台账的核心表是Device科室和供应商用外键关联借用和维修各一张流水表。设备状态用整数表示0 在库、1 借出、2 维修、3 报废。借用记录单独记BorrowStatus避免设备表被历史记录撑大。建表脚本可以按下面这段执行字段名和后面 C# 实体保持一致-- 科室表 CREATE TABLE Department ( DeptId INT IDENTITY(1,1) PRIMARY KEY, DeptName NVARCHAR(50) NOT NULL, IsActive BIT NOT NULL DEFAULT(1) ); -- 供应商表 CREATE TABLE Supplier ( SupplierId INT IDENTITY(1,1) PRIMARY KEY, SupplierName NVARCHAR(100) NOT NULL, ContactPhone NVARCHAR(30) NULL ); -- 设备台账主表 CREATE TABLE Device ( DeviceId INT IDENTITY(1,1) PRIMARY KEY, DeviceCode NVARCHAR(30) NOT NULL UNIQUE, -- 设备编号 DeviceName NVARCHAR(100) NOT NULL, DeptId INT NOT NULL, SupplierId INT NULL, PurchaseDate DATE NULL, Price DECIMAL(18,2) NULL, Status TINYINT NOT NULL DEFAULT(0), -- 0在库 1借出 2维修 3报废 Keeper NVARCHAR(30) NULL, -- 当前保管人 Remark NVARCHAR(200) NULL, CONSTRAINT FK_Device_Dept FOREIGN KEY (DeptId) REFERENCES Department(DeptId), CONSTRAINT FK_Device_Supplier FOREIGN KEY (SupplierId) REFERENCES Supplier(SupplierId) ); -- 借用归还流水 CREATE TABLE BorrowRecord ( BorrowId INT IDENTITY(1,1) PRIMARY KEY, DeviceId INT NOT NULL, Borrower NVARCHAR(30) NOT NULL, BorrowDate DATETIME NOT NULL DEFAULT(GETDATE()), ReturnDate DATETIME NULL, BorrowStatus TINYINT NOT NULL DEFAULT(0) -- 0借出 1已归还 ); -- 维修记录 CREATE TABLE RepairRecord ( RepairId INT IDENTITY(1,1) PRIMARY KEY, DeviceId INT NOT NULL, FaultDesc NVARCHAR(200) NOT NULL, RepairDate DATETIME NOT NULL DEFAULT(GETDATE()), FinishDate DATETIME NULL, Cost DECIMAL(18,2) NULL, RepairStatus TINYINT NOT NULL DEFAULT(0) -- 0维修中 1已完成 );DeviceCode加唯一约束防止重复入库Status用TINYINT比字符串省空间查询也稳定。BorrowRecord和RepairRecord都只存DeviceId需要显示设备名称时联表查不冗余存设备名避免设备改名后历史记录对不上。常见做法是给Device(DeptId, Status)和BorrowRecord(DeviceId, BorrowStatus)建索引列表页按科室和状态筛选时不会全表扫。3. 跑通第一步SQL Server 建库、连接串配置与登录链路3.1 开发环境与依赖.NET Framework、Visual Studio、SQL Server这套 WinForm 医疗管理系统建议用 .NET Framework 4.7.2 或 4.8老机器兼容性好Windows 10/11 自带运行时的情况也多。开发工具用 Visual Studio安装时勾选“.NET 桌面开发”工作负载里面包含 WinForms 设计器和 .NET Framework 目标包。数据库用 SQL Server本地开发可以用 Express 版生产环境按医院信息科要求部署。项目不依赖第三方 UI 库DataGridView、TextBox、ComboBox 都是原生控件减少发布时缺 DLL 的概率。打开解决方案后先看每个项目的“目标框架”是否一致UI 项目是 .NET FrameworkBLL 和 DAL 也必须是同框架或兼容框架。常见翻车点是 DAL 用了 .NET Standard 类库UI 引不进来报一串找不到类型的错。把四个项目统一到同一个 .NET Framework 版本再编译。3.2 数据库连接串和初始化脚本连接字符串放在 UI 项目的App.config里DAL 通过ConfigurationManager.ConnectionStrings读取。不要写死在DbHelper里换一台机器就要重编译。下面这段是本地开发常用的配置Data Source改成实际实例名Initial Catalog对应建库脚本里的数据库名?xml version1.0 encodingutf-8 ? configuration connectionStrings !-- 本地开发示例Data Source 换成 .\SQLEXPRESS 或实际实例名 -- add nameMedicalDb connectionStringData Source.\SQLEXPRESS;Initial CatalogMedicalDeviceDb;Integrated SecurityTrue; providerNameSystem.Data.SqlClient / /connectionStrings /configurationIntegrated SecurityTrue表示用 Windows 身份验证适合本地开发。生产环境如果 SQL Server 和客户端不在同一台机器常见做法是建一个只对业务库有读写权限的账号把连接串改成User ID...;Password...;并且不要把明文连接串提交到代码仓库。初始化时先在 SQL Server 里执行01_schema.sql再执行02_seed.sql最后在Department和Supplier表里补几条测试数据。执行脚本前确认当前数据库上下文避免建到master里。3.3 登录与主窗体启动的完整链路登录按钮的事件里不直接查数据库调用UserService.Login返回用户实体或null。登录成功后把UserId、UserName、RoleCode存到静态会话类主窗体根据RoleCode控制菜单可见性。下面这段是登录链路的核心代码DbHelper内部用参数化查询Program.cs先显示登录窗体登录成功后再启动主窗体// Program.cs先登录再进主窗体 static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); using (var login new FrmLogin()) { if (login.ShowDialog() ! DialogResult.OK) { return; // 用户取消登录直接退出 } } Application.Run(new FrmMain()); } // FrmLogin.cs登录按钮事件 private void btnLogin_Click(object sender, EventArgs e) { var userName txtUserName.Text.Trim(); var password txtPassword.Text; // 实际项目里存哈希不存明文 if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(password)) { lblMsg.Text 账号和密码不能为空; return; } var user _userService.Login(userName, password); if (user null) { lblMsg.Text 账号或密码不正确; return; } CurrentUser.UserId user.UserId; CurrentUser.UserName user.UserName; CurrentUser.RoleCode user.RoleCode; DialogResult DialogResult.OK; Close(); }ShowDialog返回DialogResult.OK才进主窗体取消登录直接退出不会出现“没登录也能进主界面”的漏洞。CurrentUser是静态类只存当前会话必要字段不存密码。Login方法里先按用户名查用户再校验密码哈希最后检查账号是否启用。参数说明userName去空格后比对password不记日志RoleCode后面用来控菜单和按钮不直接控数据库权限。4. 核心模块拆解设备入库、借用归还和维修记录的完整实现4.1 设备入库与台账列表DataGridView 绑定与分页查询设备列表页的 DataGridView 不要直接绑定整张表设备多了会卡。常见做法是分页查询每页 20 或 50 条搜索条件带设备编号、设备名称、科室和状态。下面这段是DeviceRepository的分页方法用ROW_NUMBER()兼容 SQL Server 2012 及以上返回DataTable给 UI 绑定public DataTable QueryDevicePage(string keyword, int? status, int pageIndex, int pageSize) { var sql ;WITH T AS ( SELECT d.DeviceId, d.DeviceCode, d.DeviceName, dept.DeptName, d.Status, d.Keeper, d.PurchaseDate, ROW_NUMBER() OVER (ORDER BY d.DeviceId DESC) AS RowNo FROM Device d INNER JOIN Department dept ON dept.DeptId d.DeptId WHERE (Keyword OR d.DeviceCode LIKE Keyword OR d.DeviceName LIKE Keyword) AND (Status IS NULL OR d.Status Status) ) SELECT * FROM T WHERE RowNo BETWEEN (PageIndex - 1) * PageSize 1 AND PageIndex * PageSize;; var parameters new[] { new SqlParameter(Keyword, $%{keyword}%), new SqlParameter(Status, (object)status ?? DBNull.Value), new SqlParameter(PageIndex, pageIndex), new SqlParameter(PageSize, pageSize) }; return DbHelper.ExecuteDataTable(sql, parameters); }keyword传空字符串时LIKE %%不筛选status为null时查全部状态。pageIndex从 1 开始pageSize建议 2050太大 UI 渲染会慢。UI 层拿到DataTable后设置dataGridView1.DataSource table不要手动一行行Rows.Add否则排序和列宽都要自己维护。入库时先校验DeviceCode是否重复再插入插入成功刷新当前页。4.2 借用归还事务库存状态流转和并发处理借用和归还必须在同一个事务里完成先查设备当前状态再更新设备状态再写借用流水。两个人同时点“借出”时只靠先查后更新会超借常见做法是更新语句带状态条件用影响行数判断是否抢到。下面这段是BorrowService.Borrow的核心逻辑public bool Borrow(int deviceId, string borrower, out string message) { using (var conn DbHelper.CreateConnection()) { conn.Open(); using (var tran conn.BeginTransaction()) { try { // 只有“在库”状态才允许借出条件更新防止并发超借 var updateSql UPDATE Device SET Status 1, Keeper Borrower WHERE DeviceId DeviceId AND Status 0; var affected DbHelper.ExecuteNonQuery(conn, tran, updateSql, new SqlParameter(Borrower, borrower), new SqlParameter(DeviceId, deviceId)); if (affected 0) { tran.Rollback(); message 设备已被借出或状态不允许借用; return false; } var insertSql INSERT INTO BorrowRecord(DeviceId, Borrower, BorrowStatus) VALUES(DeviceId, Borrower, 0); DbHelper.ExecuteNonQuery(conn, tran, insertSql, new SqlParameter(DeviceId, deviceId), new SqlParameter(Borrower, borrower)); tran.Commit(); message 借用成功; return true; } catch (Exception ex) { tran.Rollback(); message 借用失败 ex.Message; return false; } } } }affected 0说明设备已经不在库事务回滚不会写出脏流水。归还时同理更新Device.Status 0且Keeper NULL条件加上Status 1再把BorrowRecord.BorrowStatus改成 1、填ReturnDate。参数deviceId从列表选中行取不要从界面文本反解析borrower去空格并限制长度避免超字段。4.3 维修记录与折旧提醒定时任务或启动检查维修记录页在设备送修时把Device.Status改成 2维修完成改回 0 并写FinishDate。折旧提醒不需要跑后台服务主窗体启动时查一次超过使用年限仍在用的设备弹一个提醒列表即可。下面这段 SQL 按采购日期算使用年限DepreciationYears可以先放在配置表里简单项目直接写 5 年-- 查询使用超过 5 年且未报废的设备启动时提醒 SELECT DeviceId, DeviceCode, DeviceName, PurchaseDate, DATEDIFF(YEAR, PurchaseDate, GETDATE()) AS UsedYears FROM Device WHERE Status 3 AND PurchaseDate IS NOT NULL AND DATEDIFF(YEAR, PurchaseDate, GETDATE()) 5 ORDER BY PurchaseDate ASC;DATEDIFF(YEAR, ...)按年份差算跨年当天可能有 1 天误差设备管理场景够用。启动提醒不要用模态框连续弹容易烦常见做法是在主窗体底部加一个Label显示“有 N 台设备超过使用年限”点开再看列表。维修中设备不计入折旧提醒的“在用”范围避免刚送修就弹提醒。5. 避坑与排查WinForm 医院系统上线前最容易翻车的 5 个点5.1 连接串写死导致换机器就报错现象是开发机能登录拷到设备科电脑上一点登录就报“未找到或无法访问服务器”。原因通常是连接串写死在DbHelper里Data Source还是开发机的实例名或者用了 Windows 身份验证但目标机器没有对应权限。解决办法是把连接串放App.config发布时只改配置文件不重编译生产环境建独立数据库账号只给业务库的db_datareader、db_datawriter和执行存储过程的权限不用sa。5.2 DataGridView 直接改数据没有回写现象是界面上改了设备保管人关掉窗口再打开又变回原值。原因是 DataGridView 只绑定了内存里的DataTable没有UPDATE语句用户以为改的是数据库。解决方法是列表页设ReadOnly true编辑走独立窗体确实要支持单元格编辑时在CellEndEdit事件里拿主键更新并检查影响行数。更稳的做法是禁止直接编辑所有修改走“编辑”按钮打开详情窗体保存时再校验状态。5.3 借用归还并发导致库存负数现象是两个人同时借同一台设备两条借用记录都成功设备状态却只改了一次。原因是先SELECT查状态再UPDATE中间没有锁。解决方法是把状态条件写进UPDATE的WHERE用影响行数判断影响 0 行就回滚事务里先更新设备再插流水顺序不要反。高并发场景还可以给Device行加UPDLOCK但中小医院设备科用条件更新已经够。5.4 发布时缺少 DLL 或 .NET Framework 版本不匹配现象是开发机双击 exe 正常拷到目标机报“缺少某某 DLL”或“需要 .NET Framework 4.8”。原因是发布目录只拷了 exe没有带依赖文件或者目标机没装对应运行时。解决方法是发布时选“Release”把整个publish目录打包在目标机先装 .NET Framework 运行时再运行 exe。用了第三方库就把 DLL 一起放进目录不要依赖 GAC。发布前在一台干净虚拟机上跑一遍登录和列表查询比在开发机反复点更有效。5.5 数据库备份与还原的排序规则不一致现象是脚本在开发库能建表还原到另一台 SQL Server 上报排序规则冲突联表查中文出现乱码或比较失败。原因是两边数据库排序规则不同NVARCHAR字段联表时按不同规则比较。解决方法是建库时显式指定排序规则或者还原时用“还原数据库”而不是只附加 MDF跨库查询时在字段上统一COLLATE。上线前把建库脚本在目标实例跑一遍别只还原数据。6. 二次开发把固定台账改成可配置的医院设备管理模块设备属性经常变今天加“注册证号”明天加“维保到期日”直接改Device表会让老代码到处报错。我一般加一张DeviceExtField字典表和DeviceExtValue值表主表不动列表页动态拼列。字典表存字段编码、显示名、控件类型值表存DeviceId FieldCode FieldValue。新增字段只插字典数据不改表结构老设备没有值就显示空。权限控制别只控菜单按钮也要控。角色表加RoleCode权限表存RoleCode MenuCode ButtonCode主窗体加载时把当前角色能看的按钮设Visible false服务层再校验一次。前端隐藏不是安全边界BLL 里根据CurrentUser.RoleCode判断是否允许报废、是否允许改价格。操作日志表记录UserId、Action、TargetId、ActionTime借用、归还、报废三个动作必须写日志。验证方法按“新字段能不能存、老数据能不能显示、无权限按钮能不能绕过”三条走。发布清单我一般固定五项连接串是否指向目标库、脚本是否按顺序执行、默认管理员密码是否改过、发布目录是否包含全部 DLL、目标机运行时是否安装。从那以后我每次改完设备模块都强制在干净环境走一遍登录、入库、借用、归还、维修、导出再交付。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →