
引言
随着医疗信息化向基层机构渗透,中小型药房与社区诊所对轻量化药品管理工具的需求持续增长。传统商业管理系统普遍存在部署成本高、功能冗余、定制难度大的问题,而基于嵌入式数据库的桌面应用方案,凭借零配置、易维护、低成本的优势,成为小型机构信息化的合适选择。
本文基于Python技术栈,结合PyQt5图形框架与SQLite嵌入式数据库,设计并实现了一套轻量级药房管理系统。系统采用分层架构设计,重点解决了销售操作数据一致性、多模块界面解耦、效期数据可视化预警等核心技术问题,具备良好的实用性与可扩展性。
一、系统整体架构与技术选型
1.1 分层架构设计
系统采用经典的三层架构模式,实现关注点分离,提升代码可维护性:
-
表示层:基于PyQt5控件构建,负责用户交互与数据展示。主窗口采用标签页式布局,整合各业务模块,通过信号/槽机制与业务层通信,界面样式通过QSS统一管理,实现表现与逻辑分离。
-
业务逻辑层:封装业务规则与流程控制,负责参数校验、业务编排、异常处理,协调数据层完成业务操作,是系统的核心调度层。
-
数据访问层:通过DatabaseManager类封装所有数据库操作,对外提供领域化接口,内部屏蔽SQL细节与连接管理,采用参数化查询保障数据安全,通过事务保障写操作的原子性。
横向支撑模块包括工具函数库(日期处理、格式校验、数值计算)与样式管理模块,为各层提供通用能力。
1.2 技术选型与理由
| 技术选型 | 选型理由 |
|---|---|
| Python 3.8 | 语法简洁,开发效率高,标准库功能完善,内置sqlite3模块无需额外依赖 |
| PyQt5 | 控件库丰富,文档完善,跨平台兼容性好,信号/槽机制适合模块化桌面开发 |
| SQLite | 嵌入式零配置数据库,单文件存储便于备份迁移,支持事务与标准SQL,适配单机应用场景 |
| PyQtChart | Qt官方图表组件,原生集成度高,支持多种图表类型与动画效果,满足数据可视化需求 |
| QSS样式表 | 语法类似CSS,可集中管理界面样式,支持差异化控件定制,便于主题调整 |
1.3 核心业务流程
系统核心业务围绕药品全生命周期管理展开:药品信息录入 → 库存维护 → 销售登记(事务性扣减库存+记录流水) → 效期监控预警 → 销售数据统计分析。所有写操作均经过业务校验与事务封装,确保数据完整性。
二、核心模块深度实现
2.1 事务性销售数据处理
销售操作是系统的核心写场景,涉及药品表库存更新与销售历史表记录写入两个关联操作,必须保证原子性,避免出现数据不一致。
设计思路
利用SQLite的事务支持,将两个更新操作放在同一事务上下文中执行。通过上下文管理器封装数据库连接,正常退出时自动提交事务,发生异常时自动回滚,从机制上保证数据一致性。同时在销售历史表中冗余存储药品快照信息,即使后续药品记录被删除,历史销售数据仍可完整追溯。
核心实现
def record_sale(self, sale_data):"""事务性提交销售订单:param sale_data: 包含drug_id, sell_quantity, sell_price, sell_amount, sell_date的字典:return: 执行成功返回True"""with self.get_connection() as conn:conn.row_factory = sqlite3.Rowcursor = conn.cursor()# 查询药品当前状态cursor.execute("SELECT id, stock, drug_name, spec, batch_no FROM drugs WHERE id = ?", (sale_data['drug_id'],))drug_info = cursor.fetchone()if not drug_info:raise ValueError("目标药品不存在")if drug_info['stock'] < sale_data['sell_quantity']:raise ValueError("库存不足,无法完成销售")# 计算库存变化remain_stock = drug_info['stock'] - sale_data['sell_quantity']# 更新药品主表cursor.execute("""UPDATE drugsSET stock = ?,sell_quantity = sell_quantity + ?,sell_amount = sell_amount + ?,sell_date = ?WHERE id = ?""", (remain_stock, sale_data['sell_quantity'], sale_data['sell_amount'], sale_data['sell_date'],sale_data['drug_id']))# 写入销售流水,冗余存储药品快照cursor.execute("""INSERT INTO sales_history (drug_id, drug_name, spec, batch_no,sell_quantity, sell_price, sell_amount,sell_date, original_stock, remaining_stock) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", (drug_info['id'], drug_info['drug_name'], drug_info['spec'], drug_info['batch_no'],sale_data['sell_quantity'], sale_data['sell_price'], sale_data['sell_amount'],sale_data['sell_date'], drug_info['stock'], remain_stock))return True
调优说明
-
采用参数化查询,避免SQL注入风险
-
单次事务内完成两次写操作,减少连接开销
-
流水表保留库存前后快照,满足审计与问题排查需求
2.2 基于信号/槽的模块化通信
桌面应用中多模块数据同步是常见难点,直接跨模块调用会导致代码耦合严重,难以维护。系统利用PyQt5的信号/槽机制,实现模块间的低耦合通信。
设计思路
为每个功能模块定义独立的更新信号,当某一模块执行了影响全局数据的操作(如完成销售)时,仅需发射对应信号,其他关联模块监听信号并执行自身的数据刷新,无需知道信号发射方的内部实现。
实现方案
class MainWindow(QMainWindow):# 定义全局数据更新信号drug_data_changed = pyqtSignal()sales_data_changed = pyqtSignal()def __init__(self, db_manager, username):super().__init__()self.db_manager = db_managerself.username = username# 信号绑定:销售数据变更后刷新药品表与效期表self.sales_data_changed.connect(self.refresh_drug_table)self.sales_data_changed.connect(self.refresh_expiry_table)def submit_sales(self):# 销售提交逻辑if self.db_manager.record_sale(sale_data):# 发射数据变更信号,通知关联模块刷新self.sales_data_changed.emit()QMessageBox.information(self, "成功", "销售登记完成")
该方案实现了模块间的解耦,新增功能模块时只需绑定对应信号,无需修改原有业务代码,扩展性良好。
2.3 效期预警的视觉化渲染
效期管理是药房系统的高频使用功能,纯数字展示效率低下,通过视觉化高亮可以显著提升信息获取效率。
设计思路
将效期预警逻辑与表格渲染分离,工具函数负责计算剩余天数与对应预警等级,渲染逻辑根据等级批量设置单元格样式。支持多维度筛选,可快速定位不同临期等级的药品。
关键实现
def highlight_expiry_rows(table_widget, row_index, remaining_days):"""根据剩余天数为指定表格行设置预警背景色"""color_map = {"expired": QColor("#f8d7da"),"urgent": QColor("#fff3cd"),"warning": QColor("#ffe5d0"),"normal": None}if remaining_days < 0:level = "expired"elif remaining_days <= 7:level = "urgent"elif remaining_days <= 30:level = "warning"else:level = "normal"bg_color = color_map[level]if bg_color:for col in range(table_widget.columnCount()):item = table_widget.item(row_index, col)if item:item.setBackground(bg_color)
三、关键技术难点与解决方案
3.1 销售操作的数据一致性问题
问题表现:销售操作同时涉及库存更新与流水写入,若中途程序异常退出,可能出现只扣库存不记流水,或只记流水不扣库存的情况,导致账目不符。
产生原因:两次数据库操作独立执行,缺少原子性保障。
解决方案:采用数据库事务封装两次写操作,利用SQLite事务的ACID特性,保证操作要么全部成功,要么全部回滚。通过上下文管理器自动管理事务提交与回滚,避免手动处理异常遗漏。
3.2 表格动态按钮的变量捕获问题
问题表现:在表格循环生成行内操作按钮时,直接绑定槽函数会出现所有按钮都指向最后一行数据的问题。
产生原因:Python闭包的延迟绑定特性,循环变量在槽函数触发时才会取值,此时变量已变为最后一次循环的值。
解决方案:使用lambda表达式的默认参数进行值捕获,在创建按钮时就将当前行数据绑定到函数参数中,确保每个按钮对应正确的行数据。
# 正确写法:通过默认参数d=drug捕获当前循环值
edit_btn.clicked.connect(lambda checked, d=drug: self.edit_drug(d))
3.3 多页面数据同步刷新问题
问题表现:在销售页面完成操作后,药品管理、效期管理等页面的数据不会自动更新,用户需手动刷新,体验不佳。
产生原因:各标签页独立封装,缺少数据变更的通知机制。
解决方案:在主窗口定义全局数据变更信号,各功能模块初始化时绑定对应信号。当数据发生变更时发射信号,所有关联模块自动执行刷新逻辑,实现一次操作、全局同步。
四、系统效果与性能分析
功能覆盖
系统覆盖用户管理、药品进销存、销售追溯、效期预警、数据统计五大核心模块,完整满足中小型药房日常运营的基础管理需求,操作流程贴合实际业务场景。
性能表现
基于测试环境(Intel i5-8250U,8GB内存)实测:
-
100条药品数据加载耗时约0.15秒
-
1000条销售记录的条件查询耗时约0.08秒
-
30天维度的销售统计图表渲染耗时约0.2秒
各项操作响应均在1秒以内,满足日常使用的性能要求。
适用场景与局限
本方案适用于单用户使用的中小型药房、社区诊所、卫生室等场景,部署简单,维护成本低。当前版本为单机桌面应用,暂不支持多用户并发访问与远程联网使用。
五、优化方向与扩展思路
-
安全增强:用户密码采用哈希加盐存储,替代明文存储方案;增加操作日志记录,满足审计需求。
-
功能扩展:增加药品入库管理模块,支持批次进价管理与利润核算;增加报表打印功能,支持标准化单据输出。
-
架构升级:可升级为C/S架构,替换为MySQL等网络数据库,支持多终端并发访问;也可迁移为Web版本,适配更多使用场景。
-
智能预警:增加库存下限预警、滞销药品提醒等功能,辅助库存优化决策。
全文总结
本文实现的药房管理系统,验证了Python+PyQt5+SQLite技术栈在小型管理信息系统开发中的可行性与实用性。系统通过分层架构保证了代码结构的清晰性,利用事务机制保障了业务数据的一致性,借助信号/槽机制实现了模块间的低耦合通信,整体方案轻量高效,具备良好的工程参考价值。
完整的系统运行演示视频,可在B站 @兵慌码乱 查看。
