基于Python+PyQt5+SQLite的药房管理系统实现:事务一致性与界面解耦全流程解析
引言
随着医疗信息化向基层机构渗透,中小型药房与社区诊所对轻量化药品管理工具的需求持续增长。传统商业管理系统普遍存在部署成本高、功能冗余、定制难度大的问题,而基于嵌入式数据库的桌面应用方案,凭借零配置、易维护、低成本的优势,成为小型机构信息化的合适选择。
本文基于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.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的信号/槽机制,实现模块间的低耦合通信。
设计思路
为每个功能模块定义独立的更新信号,当某一模块执行了影响全局数据的操作(如完成销售)时,仅需发射对应信号,其他关联模块监听信号并执行自身的数据刷新,无需知道信号发射方的内部实现。
实现方案
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, "成功", "销售登记完成") |
更多推荐
所有评论(0)