引言
随着医疗信息化向基层机构渗透,中小型药房与社区诊所对轻量化药品管理工具的需求持续增长。传统商业管理系统普遍存在部署成本高、功能冗余、定制难度大的问题,而基于嵌入式数据库的桌面应用方案,凭借零配置、易维护、低成本的优势,成为小型机构信息化的合适选择。
本文基于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的事务支持,将两个更新操作放在同一事务上下文中执行。通过上下文管理器封装数据库连接,正常退出时自动提交事务,发生异常时自动回滚,从机制上保证数据一致性。同时在销售历史表中冗余存储药品快照信息,即使后续药品记录被删除,历史销售数据仍可完整追溯。
核心实现
Python
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.Row
cursor = 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 drugs
SET 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的信号/槽机制,实现模块间的低耦合通信。
设计思路
为每个功能模块定义独立的更新信号,当某一模块执行了影响全局数据的操作(如完成销售)时,仅需发射对应信号,其他关联模块监听信号并执行自身的数据刷新,无需知道信号发射方的内部实现。
实现方案
Python
class MainWindow(QMainWindow):
# 定义全局数据更新信号
drug_data_changed = pyqtSignal()
sales_data_changed = pyqtSignal()
def __init__(self, db_manager, username):
super().__init__()
self.db_manager = db_manager
self.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 效期预警的视觉化渲染
效期管理是药房系统的高频使用功能,纯数字展示效率低下,通过视觉化高亮可以显著提升信息获取效率。
设计思路
将效期预警逻辑与表格渲染分离,工具函数负责计算剩余天数与对应预警等级,渲染逻辑根据等级批量设置单元格样式。支持多维度筛选,可快速定位不同临期等级的药品。
关键实现
Python
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表达式的默认参数进行值捕获,在创建按钮时就将当前行数据绑定到函数参数中,确保每个按钮对应正确的行数据。
Python
# 正确写法:通过默认参数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站 @兵慌码乱 查看。