
简介一份基于Python开发的超市管理系统完整毕业设计资源面向计算机、通信、人工智能、自动化等专业的学生、老师及从业者适用于课程设计、期末大作业或毕业设计参考项目整体完成度高答辩评审表现优异适合从入门到进阶的读者学习。压缩包共8个文件包含Python源码、可直接运行的exe程序、docx格式的用户使用手册与说明文档以及txt密码/详情文件和md说明文件总大小约9.89MB文件类型覆盖运行、阅读、配置等多种用途。已有55人学习/下载。除系统主体外资料还附有完整说明文档与用户手册读者可先运行exe直观体验功能再对照源码和文档理清系统模块、数据结构与部署流程既能用于答辩演示和课程汇报也方便在此基础上修改扩展进行二次开发。整体学习借鉴价值较高。1. 超市管理系统是毕业设计里的经典题为什么做的人多、拿高分的人少“基于Python实现的超市管理系统”这类题目在毕业设计里出现频率极高但答辩时翻车率也极高。原因不是Python不行而是很多人把“超市管理系统”做成了“货品增删改查”一个ListView配三个Button就交了。真正能拿高分的设计要回答的不是“怎么存商品”而是“一笔交易从扫码到小票打印中间经过哪些状态、哪些数据必须一致”。货架上的库存、购物车里的临时明细、订单表和会员积分任何一个环节脱节这个系统就只是玩具。本篇按“表结构 → 结算事务 → 权限与报表 → 答辩前的数据把关”四个层面把一条能写到论文里、也能在答辩现场跑通的实现路线讲透。适合正在选题、已经开工但被库存和订单搞得焦头烂额、以及想给系统补上“设计感”的本科毕业生。2. 先把表设计明白会员价、库存和散称商品这三个坎超市管理系统听起来是标准的CRUD但一到表结构设计就露馅。超市比一般进销存系统多出来的两个麻烦是同一商品有两种计价单位整件卖和散称卖以及同一商品至少有两套价格普通价和会员价。很多参考源码里只放一张goods表字段是name和price这基本承载不了真实业务。数据模型不到位后面的结算、库存、报表全都要打补丁。2.1 商品档案表的三个字段决定了系统整体长相商品主表至少要有这些字段id、barcode条码、name、spec规格、unit_type计价方式按件/按重量、sale_price、member_price、stock、category_id、status。条码是超市系统的灵魂它不只是“商品编号”还是收银台扫码的查询键。unit_type决定了结算模块怎么处理数量按件的商品 quantity 是整数按重的商品 quantity 是小数同时还要记一个weight或让quantity本身支持三位小数。会员价不是可选字段。很多毕业设计把会员做成独立表但结算时查不到会员价最后打折逻辑只能用“整单9折”蒙混过关。正确做法是在商品表里直接放member_price会员结算时逐行换价效果清晰且慢表现在也好写。库存字段stock放在商品表里是毕业设计阶段最合理的折中不需要单独建库存流水表但要在代码里保证每次写stock都发生在事务里。2.1.1 建议的SQLite建表语句以下建表语句适用于Python自带sqlite3也便于改写成MySQL针对小型单机超市系统做了适当冗余CREATE TABLE category ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, sort_order INTEGER DEFAULT 0 ); CREATE TABLE goods ( id INTEGER PRIMARY KEY AUTOINCREMENT, barcode TEXT UNIQUE NOT NULL, name TEXT NOT NULL, spec TEXT DEFAULT , unit_type INTEGER DEFAULT 0, sale_price INTEGER NOT NULL, member_price INTEGER NOT NULL, stock REAL DEFAULT 0, category_id INTEGER REFERENCES category(id), status INTEGER DEFAULT 1 );这里故意把价格字段设计成INTEGER单位是“分”。这不是笔误而是为了避免浮点数精度问题。sale_price存 350 表示 3.50 元界面层负责转成“元”。涉及的金额计算全部用整数完成只有最后展示时才除以 100这个细节可以作为论文里的“系统设计亮点”写进数据字典章节。unit_type用 0 和 1 分别代表按件和按重。按重商品的stock单位是“千克”它同样允许小数。如果不打算做电子秤对接可以在界面上提供“称重后手工输入重量”的入口数据上完全兼容。2.2 订单表和订单明细表一条流水里要能追溯全部业务订单头表orders记录一次收银的完整信息单号、收银员、会员、应收金额、实收金额、支付方式、创建时间。订单明细表order_items则逐行记录每一件商品的快照商品名、单价、数量、行小计。快照这两个字是关键——订单明细里的名称和价格不能去关联goods表现在的最新值否则三个月后的统计报表里价格全变了。CREATE TABLE orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_no TEXT UNIQUE NOT NULL, cashier TEXT NOT NULL, member_id INTEGER, total_amount INTEGER NOT NULL, pay_type TEXT DEFAULT cash, create_time TEXT NOT NULL ); CREATE TABLE order_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_id INTEGER NOT NULL REFERENCES orders(id), goods_id INTEGER NOT NULL, goods_name TEXT NOT NULL, unit_price INTEGER NOT NULL, quantity REAL NOT NULL, subtotal INTEGER NOT NULL );可以看到orders表里有total_amount这属于冗余字段但它让日报表统计不需要每次SUM明细行。论文里的解释是“以空间换查询性能”。order_no的生成规则建议做成“日期收银机号流水号”三段式例如2025061001 0001这种组合方便对账。写代码时要保证order_no唯一生成时可以对当前日期的序号加锁或者直接用datetime.now().strftime拼接后用uuid的短片段做兜底。不建议把订单主键id直接暴露成单号答辩老师问“单号规则怎么设计”时三段式的可解释性远好于自增主键。2.2.1 会员表不要为了设计而设计会员表在毕业设计里经常被过度设计成“会员等级、成长值、积分规则”三件套导致写代码的人最后只实现了积分累加其他全是摆设。最小可用会员表应该包含id、phone、name、points、created_at。积分规则固定为“消费满1元积1分”在系统里写死不要把规则表做进去。积分抵扣的常见做法是“100分抵1元”这个规则可以放在设置表里。orders表里加一个points_used字段记录本次使用了多少积分。这些字段都是围绕“一笔订单”设计的答辩时可以从“为什么积分不单独建一张流水表”展开毕业设计阶段订单明细已经能反推积分变动独立流水表只增加数据冗余。2.3 数据字典和ER图是“详细文档”里最值钱的部分毕业设计要交的“详细文档”里老师第一个翻的就是数据字典。很多人的文档里贴的是自动生成的表结构截图那不算数据字典。真正的数据字典要对着字段逐个解释业务含义、取值来源、是否允许为空。举个例子orders.pay_type这一字段文档里应该写清楚“取值cash为现金alipay为支付宝wechat为微信card为储值卡”而不是只写一个“支付方式”。这些文字不需要很高深的表达但能直接证明系统是你自己设计的而不是从某个源码站下载的。这一步建议先写字表设计文档再写代码顺序不要反。写文档的过程中会自然发现漏掉的字段例如何时记录库存锁定、退款时订单状态怎么标记。库存、订单、会员这三张核心表的ER关系几乎就是整个系统架构图。文档这部分写得越细后面实现越顺利最后答辩讲PPT也更有底气。3. 用Python实现订单结算事务、锁和一行报表表结构定好后核心代码就是两个函数生成订单和扣库存。这两个动作必须发生在同一个数据库事务里。如果不放在一个事务里会出现“订单创建了库存没减”或者反过来“库存扣了订单没了”的情况。用sqlite3写事务不复杂但要注意Python的sqlite3模块默认是自动开启事务的提交和回滚要显式调用。3.1 一个带事务的结算函数可直接跑通最小链路import sqlite3 from datetime import datetime DB_PATH supermarket.db def create_order(items, cashier, member_idNone, points_used0): items: list of dict, 每个元素包含 goods_id, quantity 返回 order_no失败抛异常 conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row try: conn.execute(BEGIN) total 0 order_items [] for it in items: gid it[goods_id] qty it[quantity] # 查询商品信息并加锁防止并发修改库存 row conn.execute( SELECT id, name, sale_price, member_price, stock FROM goods WHERE id? AND status1 FOR UPDATE, (gid,) ).fetchone() if not row: raise ValueError(f商品 {gid} 不存在或已下架) if qty 0: raise ValueError(数量必须大于0) if row[stock] qty: raise ValueError(f商品 {row[name]} 库存不足) # 会员价与普通价二选一 price row[member_price] if member_id else row[sale_price] subtotal int(price * qty) total subtotal order_items.append({ goods_id: gid, goods_name: row[name], unit_price: price, quantity: qty, subtotal: subtotal, }) # 扣减库存 conn.execute( UPDATE goods SET stock stock - ? WHERE id ?, (qty, gid) ) # 扣减积分后再计算总价 if points_used: total max(0, total - points_used * 100) # 100分抵1元 order_no datetime.now().strftime(%Y%m%d%H%M%S) str(hash(cashier) % 1000) cur conn.execute( INSERT INTO orders (order_no, cashier, member_id, total_amount, create_time) VALUES (?, ?, ?, ?, ?), (order_no, cashier, member_id, total, datetime.now().strftime(%Y-%m-%d %H:%M:%S)) ) order_id cur.lastrowid conn.executemany( INSERT INTO order_items (order_id, goods_id, goods_name, unit_price, quantity, subtotal) VALUES (?, ?, ?, ?, ?, ?), [(order_id, i[goods_id], i[goods_name], i[unit_price], i[quantity], i[subtotal]) for i in order_items] ) conn.execute(COMMIT) return order_no except Exception: conn.execute(ROLLBACK) raise finally: conn.close()这段代码的核心逻辑是“先查后扣”并在同一事务里完成。用了FOR UPDATE对行加锁SQLite 不真正支持该语法但这套写法对MySQL可直接迁移SQLite 下会直接忽略这句代码完整性不受影响。如果有人要偷懒改成“先UPDATE再SELECT”容易在并发时产生库存负数的风险。3.1.1 商品表加锁与不加锁的差别如果去掉FOR UPDATE两个窗口同时卖同一件库存为1的商品两个请求都查到了 stock1都认为自己能卖最后两条订单都成功库存变成-1。这是典型的“超卖”问题。虽然毕业设计不需要支撑高并发但这一处设计写到论文里比写“系统稳定可靠”有说服力得多。price字段直接与quantity相乘可能涉及小数与整数的转换。前面约定价格为整数分后这里的int(price * qty)不会碰到浮点数误差。代价是展示层要做一次除100但这是值得的。3.2 支付方式、退货单与积分更新的代码分支结算时还要处理支付方式和会员积分更新。在同一个事务里支付方式只是往orders.pay_type写一个字符串。会员积分更新用UPDATE members SET points points ? WHERE id ?其中加的分数等于本次订单实付金额除以100。退货单不用单独建表用订单状态位status标记即可比如 1 正常、-1 已退。退货时执行反方向操作库存加回去、积分扣除、订单状态置为-1。这里有一个常见坑订单表没有status字段。退货必须改库存和积分如果连订单状态都没有退货逻辑无法安全地防止重复退。加一个status字段成本极低但能让系统在“订单生命周期”的设计上自洽。3.3 用5行SQL生成销售日报别在Python里循环很多初学者会在代码里把订单明细查出来然后在Python里用for循环累加每个商品卖了多少件。这个习惯本身不算错但一份销售日报涉及的统计维度太多Python侧循环会变成上百行。SQL 的GROUP BY一行就能干完。SELECT goods_name, SUM(quantity) AS total_qty, SUM(subtotal) AS total_amount FROM order_items WHERE order_id IN ( SELECT id FROM orders WHERE create_time LIKE 2025-06-10% ) GROUP BY goods_id, goods_name;运行这一段SQL时注意WHERE create_time LIKE 2025-06-10%依赖create_time的统一格式。这就要求写订单时create_time严格用YYYY-MM-DD HH:MM:SS格式否则所有日报统计都会错。这个格式的约定比任何“时间戳转日期”函数都更值得在代码里规范。对SQLite来说用LIKE做前缀匹配能走到索引上这一招在实际项目里也算小技巧。对于时间段筛选也可以用WHERE create_time ? AND create_time ?配合datetime参数保证边界正确。SELECT DATE(create_time) AS day, COUNT(*) AS order_count, SUM(total_amount) AS revenue FROM orders GROUP BY DATE(create_time) ORDER BY day DESC LIMIT 30;这段日报查询展示的是“最近30天每天的订单数和营业额”。DATE(create_time)能直接从完整时间字符串里取出日期部分按日期分组。如果未来要按月统计只需要把DATE(create_time)换成STRFTIME(%Y-%m, create_time)。用 SQL 做聚合统计比先把数据拉回 Python 再做groupby快得多也更贴合技术面试时的Common SQL题目套路。4. 权限、会员积分与小票打印让“设计”二字站得住超市系统最容易被当成课设作业的地方是“一个主界面 一个详情界面”的堆叠架构。要做到“设计”而不是“拼凑功能”至少要体现出两个与真实业务打交道的点不同角色的操作边界以及小票上的金额要绝对准确。4.1 基于角色的最小权限控制不要给每个用户硬编码功能清单毕业设计里最常见的权限写法是给user表加一个is_admin字段值为1就显示所有按钮值为0就只显示部分。这个做法在小系统里勉强能用但一旦需要“店长能看利润报表收银员只能收银仓管只能改库存”时字段会膨胀成三个布尔值。比较好的做法是用role表存角色用permission表存功能码用户和角色、角色和权限分别构成两张关联表。CREATE TABLE role ( id INTEGER PRIMARY KEY, name TEXT UNIQUE ); CREATE TABLE user_role ( user_id INTEGER, role_id INTEGER ); CREATE TABLE role_permission ( role_id INTEGER, perm_code TEXT );用户登录后把当前用户的所有perm_code查出来放进一个集合里每个按钮绑定一个功能码渲染界面时只在权限集合里有对应perm_code才显示按钮。后端接口里同样要加一道校验这是“越权”一词在答辩时最容易被追问的点。注意按钮隐藏不是安全的权限控制只能算用户体验优化。真正的权限判断要在后端每一个写操作里执行。开个后门按钮直接访问API如果后端不校验前端隐藏没有任何意义。这个理念要写进文档里哪怕只是几段文字。4.2 小票打印与金额舍入几分钱的问题最考验工程细节小票打印机的对接是“真实超市项目”里逃不掉的一环。如果用的是一般的USB或网口小票打印机EPSON模板指令里常用的ESC/POS支持在Python里控制。选型通常有两种用python-escpos库或者直接通过socket向打印机9100端口发送纯文本。第二种方案更简单但只能打印纯文本打印不了条码和加粗字体。金额舍入规则要写对每一行商品的subtotal由单价乘数量后四舍五入到分。如果每行都舍一次再相加总价可能和“按总数量乘单价”有一两分之差。行业惯例是“每行四舍五入到分最后汇总后再四舍五入一次”活跃在系统里要统一。建议在结算函数里加一行断言assert abs(total - sum(i[subtotal] for i in order_items)) 1这个断言在开发阶段能立刻暴露出舍入不一致的问题。如果担心零点几分钱的误差最好的解决办法是前面提到的“价格以分为单位用整数计算”而不是用Decimal复杂化显示逻辑。答辩时能主动说出“分以下金额不做四舍五入直接截断”这个取舍会显得对细节有控制力。5. 答辩演示前要过的四道数据关很多系统在开发机上跑得好好的一到演示就翻车。大部分翻车不是代码逻辑问题而是环境、数据残留、日期边界、数据库路径这类“看起来不重要”的细节。这里列四个我自己在帮人调试时最常遇到的问题每个都值得在答辩前一天手动检查一遍。5.1 编码、日期和数据库路径的三种崩法第一种崩法是中文字符集。Windows 下用 Tkinter 或者 PyQt 跑起来控制台显示乱码通常是因为代码文件没有保存成 UTF-8。Python 3 源码默认 UTF-8但 Windows 控制台默认 GBKprint 中文会抛UnicodeEncodeError。最简单的验证是在入口文件顶部写import sys import io sys.stdout io.TextIOWrapper(sys.stdout.buffer, encodingutf-8)这段代码的作用是把标准输出重新包装成 UTF-8 编码避免中文打印报错。它不适合生产系统但作为答辩演示环境下的兜底很有用。第二种崩法是日期边界。create_time LIKE 2025-06-10%这类查询如果订单录入时日期格式是2025/06/10就会查不出数据。强制约定格式并写入文档比在代码里多次做容错更有价值。第三种崩法是数据库文件路径写成了绝对路径。答辩现场机器换了数据库找不到系统直接白屏。正确做法是让数据库路径相对于代码文件定位import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) DB_PATH os.path.join(BASE_DIR, supermarket.db)这样代码放到任何目录都能找到数据库u盘拷贝、压缩包解压到别的电脑都能直接运行。5.2 用一条命令做SQLite自动备份毕业设计答辩前随手备份数据库是好习惯。SQLite 的备份不需要停服务Python 自带备份接口一条命令就能把当前数据文件复制到一个带时间戳的新文件里。import sqlite3, shutil, datetime src sqlite3.connect(supermarket.db) dst sqlite3.connect(fbackup_{datetime.datetime.now():%Y%m%d_%H%M%S}.db) src.backup(dst) dst.close() src.close()src.backup(dst)是 SQLite 官方支持的在线备份接口它会把源数据库完整复制到目标连接中过程中源数据库还能正常读写。这个脚本适合放到每天定时任务的角落比如 Windows 计划任务或 Linux crontab。它体现的是“数据安全”而非“功能扩展”答辩时被问“系统怎么防数据丢失”时这一段就是答案。5.3 自测脚本一分钟验证核心链路是否正常最后写一个简单的冒烟测试脚本来验证核心链路而不是在界面上手动点来点去。建议放在test_smoke.py里顺序执行插入测试分类和商品 → 创建一笔订单 → 校验订单金额和库存变化 → 执行销售日报 → 汇总退款后状态。不需要引入pytest直接用assert就能完成。from settlement import create_order import sqlite3 # 准备测试商品 conn sqlite3.connect(supermarket.db) conn.execute(DELETE FROM goods WHERE name LIKE TEST%) conn.execute(INSERT INTO goods (barcode,name,sale_price,member_price,stock,unit_type) VALUES (TEST001,TEST可乐,350,300,10,0)) conn.commit() # 购买2件非会员 order_no create_order([{goods_id: 1, quantity: 2}], cashierdemo) row conn.execute(SELECT stock FROM goods WHERE id1).fetchone() assert row[stock] 8, f库存扣减失败: {row[stock]} print(冒烟测试通过:, order_no)脚本第9行查找商品时用了id1在实际项目里应该在插入后通过cursor.lastrowid获取真实ID并将其传入create_order避免测试环境里删过数据后商品ID对不上。该测试能覆盖“商品查询、库存扣减、订单生成”这条最核心链路跑完能说明系统从数据到代码是通的互相独立的模块之间没有没接上的线。答辩前花十分钟跑一遍这个脚本再把backup目录里的最新备份文件打开看一眼演示翻车的概率会下降八成。这里没有高深技巧全是一线工程里最该做但最常被忽略的数据把关。本文还有配套的精品资源点击获取