拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Python的药店药品管理系统源码解析与毕业设计实战指南

基于Python的药店药品管理系统源码解析与毕业设计实战指南

简介:这是一套面向计算机相关专业学生与Python初学者的药店药品管理系统完整项目源码,可直接用于毕业设计、课程设计或自学练手。系统围绕药品信息管理、用户权限、销售记录、库存预警与采购计划等核心业务展开,帮助读者理解如何将数据库设计、前后端交互与界面开发整合为可运行的实际应用。压缩包共392个文件,约20.63MB,包含36个py源码文件、16个vue组件、35个ts脚本及大量jpeg、svg、png等界面素材,另有json配置、md说明与less样式文件,覆盖后端逻辑、前端页面与静态资源。项目采用前后端分离结构,源码分层清晰,便于按模块阅读与二次修改。目前已有120人学习下载,适合需要完整项目案例、想快速搭建药店管理原型或补充工程实践经验的读者参考借鉴。

1. 从一份药店药品管理系统源码说起:它到底能解决什么问题

很多计算机专业的同学在选题阶段都会卡在同一个地方:想做一个能写进简历、答辩时又讲得清楚的项目,但不知道从哪下手。药店药品管理系统这个题目之所以每年都有人选,是因为它天然具备一个完整的业务闭环——药品入库、库存查询、销售出库、效期预警、用户权限,每一个环节都能对应到数据库设计和后端接口设计上,答辩老师随便挑一个点都能问出深度。而用 Python 来做,意味着你不需要在环境配置上耗掉大半时间,可以把精力放在业务逻辑和数据库设计上,这对毕业设计或课程设计的周期来说非常关键。

这篇文章面向的是拿到了一份「基于 Python 的药店药品管理系统」源码或选题、但不确定怎么把它跑起来、怎么改、怎么讲清楚的人。我会按实际开发顺序,从环境搭建、数据库设计、核心模块实现、常见报错排查,一直讲到答辩时怎么把这个项目说出技术含量。中间会给出可以直接复现的代码和命令,也会标注哪些地方容易翻车。如果你正在做基于 Python 的毕业设计或数据库课程设计,这篇内容可以当作一份落地参考。

2. 环境搭建与项目结构:把源码跑起来的最小路径

2.1 Python 版本选择与依赖安装

拿到一份 Python 项目源码,第一件事不是急着看代码,而是先把运行环境对齐。药店药品管理系统这类项目通常依赖 Flask 或 Django 做 Web 层,MySQL 或 SQLite 做数据存储,前端可能是简单的 HTML 模板或者 Vue 构建的页面。不同项目对 Python 版本的要求不一样,但绝大多数教学类项目在 Python 3.8 到 3.11 之间都能跑通。如果你本地装的是 Python 3.12 以上,部分老依赖可能会编译失败,这时候用 conda 或 pyenv 切一个 3.9 的环境是最省事的做法。

安装依赖之前,先看项目根目录有没有 requirements.txt。有的话直接装,没有的话根据 import 语句反推。下面是一个典型的安装流程:

# 创建虚拟环境,避免污染全局包 python -m venv venv # 激活虚拟环境(Windows) venv\Scripts\activate # 激活虚拟环境(macOS/Linux) source venv/bin/activate # 安装依赖,建议加国内镜像加速 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple # 如果项目没有 requirements.txt,手动装核心依赖 pip install flask flask-sqlalchemy pymysql flask-login

这里有几个参数值得说明。-i后面跟的是镜像地址,能明显加快下载速度,尤其是 numpy、pandas 这类包。虚拟环境的作用是隔离依赖,避免你本机其他项目的包版本冲突。如果安装过程中出现error: Microsoft Visual C++ 14.0 is required,说明某个包需要编译,最直接的办法是去下载对应的 whl 文件手动安装,或者换一个不需要编译的版本。

提示:不要用pip install不带版本号直接装最新版,教学项目里经常出现某个包升级后 API 变了导致启动报错的情况。requirements.txt 里锁定的版本尽量别动。

2.2 项目目录结构与入口文件定位

一个结构清晰的药店药品管理系统通常长这样:

pharmacy-system/ ├── app.py # 入口文件,启动 Flask 应用 ├── config.py # 数据库连接、密钥等配置 ├── models.py # 数据模型定义 ├── routes/ # 路由和视图函数 │ ├── drug.py # 药品相关接口 │ ├── stock.py # 库存相关接口 │ └── user.py # 用户和权限 ├── templates/ # HTML 模板 ├── static/ # CSS、JS、图片 ├── requirements.txt └── README.md

拿到源码后先找入口文件,一般是app.py、main.py或run.py。打开它,看if __name__ == '__main__':下面的启动参数,确认端口号和 debug 模式。然后看 config.py 里的数据库连接字符串,这是最容易出问题的地方。常见的配置格式是:

# config.py 典型配置 SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:password@localhost:3306/pharmacy_db' SQLALCHEMY_TRACK_MODIFICATIONS = False SECRET_KEY = 'your-secret-key-here'

mysql+pymysql表示用 pymysql 驱动连接 MySQL,root:password是数据库用户名和密码,localhost:3306是地址和端口,pharmacy_db是数据库名。如果你本地没装 MySQL,可以改成 SQLite:sqlite:///pharmacy.db,这样不需要额外安装数据库服务,适合快速验证功能。但答辩时如果老师问数据库选型,你要能说清楚 MySQL 和 SQLite 的区别——前者适合多用户并发、支持复杂查询和事务,后者轻量但并发能力弱。

2.3 数据库初始化与首次运行

数据库配置改好之后,需要建库建表。如果项目用了 Flask-Migrate,执行迁移命令;如果没有,通常会有个init_db.py或 SQL 文件。手动建库的命令如下:

# 登录 MySQL mysql -u root -p # 创建数据库,字符集用 utf8mb4 支持中文 CREATE DATABASE pharmacy_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 退出 exit;

然后运行初始化脚本:

# 如果有迁移工具 flask db init flask db migrate -m "init tables" flask db upgrade # 如果有 SQL 文件 mysql -u root -p pharmacy_db < init.sql # 直接启动应用 python app.py

启动后浏览器访问http://127.0.0.1:5000,能看到登录页就说明基本跑通了。如果报ModuleNotFoundError,缺什么装什么;如果报Access denied for user,检查数据库密码;如果报Unknown database,说明库没建成功。这几个错误覆盖了首次运行 80% 的问题。

3. 药品管理核心模块:数据模型与增删改查实现

3.1 药品表、库存表、销售表的设计逻辑

药店药品管理系统的核心在于数据模型设计。很多同学拿到源码后只关心页面能不能跑,但答辩时老师最可能问的就是「你的表结构为什么这么设计」。一个合理的模型至少包含三张核心表:药品信息表、库存表、销售记录表。药品信息表存药品的基础属性,库存表存当前数量和效期,销售表存每一笔出库记录。

用 SQLAlchemy 定义模型的典型写法:

from flask_sqlalchemy import SQLAlchemy from datetime import datetime db = SQLAlchemy() class Drug(db.Model): """药品基础信息表""" __tablename__ = 'drug' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False, index=True) # 药品名称 category = db.Column(db.String(50)) # 分类:处方药/非处方药 spec = db.Column(db.String(50)) # 规格 manufacturer = db.Column(db.String(100)) # 生产厂家 price = db.Column(db.Numeric(10, 2), nullable=False) # 单价 created_at = db.Column(db.DateTime, default=datetime.now) class Stock(db.Model): """库存表,与药品一对一""" __tablename__ = 'stock' id = db.Column(db.Integer, primary_key=True) drug_id = db.Column(db.Integer, db.ForeignKey('drug.id'), unique=True) quantity = db.Column(db.Integer, default=0) # 当前库存 expire_date = db.Column(db.Date) # 有效期 warn_threshold = db.Column(db.Integer, default=10) # 预警阈值 updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) class Sale(db.Model): """销售记录表""" __tablename__ = 'sale' id = db.Column(db.Integer, primary_key=True) drug_id = db.Column(db.Integer, db.ForeignKey('drug.id')) quantity = db.Column(db.Integer, nullable=False) total_price = db.Column(db.Numeric(10, 2)) sale_time = db.Column(db.DateTime, default=datetime.now) operator = db.Column(db.String(50)) # 操作员

这里有几个设计决策需要理解。price用Numeric(10, 2)而不是Float,是因为金额计算不能用浮点数,否则会出现0.1 + 0.2 != 0.3的问题。drug_id在 Stock 表里加了unique=True,表示一个药品只对应一条库存记录。warn_threshold是预警阈值,低于这个值就触发提醒,这是药店系统里很实用的一个功能点。index=True加在药品名称上,因为按名称搜索是最高频的查询。

3.2 药品入库与库存更新的代码实现

入库操作不是简单地在库存表上加一个数字,它涉及事务:先插入或更新库存,再记录入库日志。如果两步之间出错,数据就不一致了。下面是一个带事务控制的入库接口:

from flask import request, jsonify from sqlalchemy import exc @app.route('/api/stock/in', methods=['POST']) def stock_in(): """药品入库接口""" data = request.get_json() drug_id = data.get('drug_id') quantity = data.get('quantity', 0) expire_date = data.get('expire_date') # 参数校验 if not drug_id or quantity <= 0: return jsonify({'code': 400, 'msg': '参数不合法'}), 400 try: stock = Stock.query.filter_by(drug_id=drug_id).first() if stock: # 已有库存记录,累加数量 stock.quantity += quantity stock.expire_date = expire_date or stock.expire_date else: # 首次入库,新建记录 stock = Stock(drug_id=drug_id, quantity=quantity, expire_date=expire_date) db.session.add(stock) db.session.commit() return jsonify({'code': 200, 'msg': '入库成功', 'quantity': stock.quantity}) except exc.SQLAlchemyError as e: db.session.rollback() return jsonify({'code': 500, 'msg': f'入库失败: {str(e)}'}), 500

这段代码的关键点在于db.session.commit()和db.session.rollback()的配合。commit 之前的所有操作都在事务里,一旦任何一步抛异常,rollback 会把整个事务回滚,保证库存不会出现「加了一半」的情况。参数校验放在最前面,避免脏数据进入数据库。返回体里带上最新的库存数量,前端可以直接更新显示,不用再发一次查询请求。

3.3 效期预警与库存查询的 SQL 优化

效期预警是药店系统和普通进销存系统拉开差距的地方。实现逻辑是:查询所有expire_date在当前时间之后 30 天内的药品,以及quantity低于warn_threshold的记录。用 SQLAlchemy 写出来是这样:

from datetime import date, timedelta def get_warnings(): """获取效期预警和低库存预警""" today = date.today() thirty_days_later = today + timedelta(days=30) # 效期预警:30天内过期 expire_warnings = db.session.query(Drug, Stock).join( Stock, Drug.id == Stock.drug_id ).filter( Stock.expire_date <= thirty_days_later, Stock.expire_date >= today ).all() # 低库存预警 low_stock = db.session.query(Drug, Stock).join( Stock, Drug.id == Stock.drug_id ).filter( Stock.quantity <= Stock.warn_threshold ).all() return { 'expire': [{'name': d.name, 'expire_date': str(s.expire_date)} for d, s in expire_warnings], 'low_stock': [{'name': d.name, 'quantity': s.quantity} for d, s in low_stock] }

如果数据量大了,比如药品有几千种,这个查询会变慢。优化方向是在expire_date和quantity上建索引,或者用定时任务提前算好预警结果缓存起来。教学项目里数据量小,直接查没问题,但答辩时你可以主动提一句「如果数据量增长,我会在 expire_date 上加索引,并把预警结果缓存到 Redis」,这就是加分项。

4. 避坑与排查:跑药店管理系统时最容易翻车的 5 个地方

4.1 中文乱码:从数据库到页面的全链路排查

现象:药品名称在页面上显示成???或者测试。原因通常出在三个地方——数据库字符集不是 utf8mb4、连接字符串没指定编码、或者 HTML 页面没声明 charset。解决顺序是:先确认建库时用了CHARACTER SET utf8mb4,再检查连接字符串有没有?charset=utf8mb4,最后看模板文件头部有没有<meta charset="UTF-8">。三个都对了,中文就不会出问题。

4.2 外键约束报错:Cannot add or update a child row

现象:插入销售记录时报IntegrityError: Cannot add or update a child row。原因是drug_id在药品表里不存在,外键约束拦住了。解决方法是插入前先查一下药品是否存在,或者在前端做下拉选择而不是手动输入 ID。如果确实需要灵活处理,可以在模型里把外键的ondelete设为SET NULL,但不建议在教学项目里这么做,因为会掩盖数据不一致的问题。

4.3 端口占用与 debug 模式的安全隐患

现象:启动时报OSError: [Errno 98] Address already in use。原因是 5000 端口被占用了,可能是上次的进程没退干净,也可能是 macOS 的 AirPlay 占用了 5000。解决办法是换端口app.run(port=5001),或者用lsof -i:5000找到进程 kill 掉。另外,debug=True只应该在开发时开,部署到服务器上必须关掉,否则会暴露源码和堆栈信息。

4.4 依赖版本冲突:Flask 2.x 和 3.x 的 API 差异

现象:源码在别人机器上能跑,到你这里报ImportError: cannot import name 'url_quote' from 'werkzeug'。原因是 Flask 2.x 和 3.x 对 Werkzeug 的依赖版本不同,装错了版本就会报这个错。解决办法是看 requirements.txt 里有没有锁版本,没有的话手动降级:pip install werkzeug==2.3.7。这类问题在基于 Python 的毕业设计里非常常见,本质是教学项目往往没有严格锁定依赖。

4.5 数据库连接池耗尽:Too many connections

现象:系统跑一段时间后报Too many connections。原因是每次请求都新建了数据库连接,没有释放。Flask-SQLAlchemy 默认会用连接池,但如果你在代码里手动pymysql.connect()而忘了 close,就会泄漏。解决办法是统一用 ORM 的 session,或者在手动连接时用with语句确保释放。教学项目并发低,这个问题不容易暴露,但答辩时如果老师问「你的系统能支持多少并发」,你要能说出连接池的概念。

5. 从能跑到能讲:答辩演示与二次开发的进阶技巧

5.1 准备一套可复现的演示数据

答辩时最尴尬的情况是系统跑起来了但没数据,页面空空荡荡。提前准备一份种子数据脚本,包含 20 到 30 种常见药品、对应的库存记录和几条销售记录。用 Python 脚本批量插入比手动敲 SQL 快得多:

# seed_data.py 种子数据脚本 import random from datetime import date, timedelta from app import app, db from models import Drug, Stock, Sale drugs = [ ('阿莫西林胶囊', '处方药', '0.25g*24粒', '华北制药', 18.50), ('布洛芬缓释胶囊', '非处方药', '0.3g*20粒', '中美史克', 22.00), ('维生素C片', '非处方药', '100mg*100片', '石药集团', 8.00), # ... 更多药品 ] with app.app_context(): db.create_all() for name, cat, spec, mfr, price in drugs: drug = Drug(name=name, category=cat, spec=spec, manufacturer=mfr, price=price) db.session.add(drug) db.session.flush() # 拿到 drug.id stock = Stock(drug_id=drug.id, quantity=random.randint(5, 200), expire_date=date.today() + timedelta(days=random.randint(10, 700)), warn_threshold=20) db.session.add(stock) db.session.commit() print('种子数据插入完成')

db.session.flush()的作用是把当前对象刷到数据库但不提交事务,这样能拿到自增的drug.id,用于关联库存记录。跑完这个脚本,系统里就有数据可以演示了。

5.2 答辩时怎么把 CRUD 讲出技术含量

很多同学答辩时只会说「这是增删改查」,老师一听就觉得没深度。同样的代码,换一种讲法效果完全不同。比如入库接口,你可以这样讲:这个接口用了数据库事务保证库存和日志的一致性,参数校验在前端和后端各做一次防止脏数据,返回体里带了最新库存减少一次查询。再比如效期预警,你可以说这里用了联合查询加时间范围过滤,如果数据量增长会在 expire_date 上建索引,进一步可以用定时任务预计算。把「做了什么」升级成「为什么这么做、还能怎么优化」,答辩分数会明显不一样。

5.3 二次开发方向:从课程设计到简历项目

如果想让这个项目在简历上更有竞争力,可以往三个方向扩展。第一是加权限控制,用 Flask-Login 做基于角色的访问控制,管理员能管理药品和用户,普通员工只能做销售和查询。第二是加数据可视化,用 ECharts 或 Pyecharts 做一个销售趋势图和库存分布图,答辩时视觉效果很好。第三是加导出功能,把销售记录导出成 Excel,用 openpyxl 或 pandas 都能实现。这三个方向都不需要推翻现有代码,在原有结构上加模块就行,适合在课程设计验收前快速迭代。

我自己做这类项目最大的教训是:不要一上来就追求功能多,先把一条主流程跑通——登录、查药品、入库、销售、看库存——这条线通了,其他功能都是锦上添花。很多同学卡在环境配置和数据库连接上耗了一周,最后没时间做核心功能,答辩时只能演示一个登录页。先把最小闭环跑起来,再逐步加功能,这个节奏最稳。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表