ARTICLE DETAIL

资讯详情

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

Python零售系统实战:Tkinter+MySQL全链路商品管理

Python零售系统实战:Tkinter+MySQL全链路商品管理 简介本资源是一套基于Python开发的商品零售管理系统课程设计源码面向计算机专业本科生、Python初学者及小型超市信息化实践者解决商品进销存与会员管理等核心业务场景的系统化实现问题。压缩包共10个文件含2个核心Python脚本main.py、init.py实现主程序逻辑与模块初始化5个XML配置文件支撑界面布局与数据结构定义另有JPG背景图、IML项目配置及.gitignore版本控制文件整体仅17KB轻量易部署。已有937人学习下载适合作为Python GUI开发Tinker、MySQL数据库连接与基础CRUD操作的综合实训案例。读者可直接运行调试完整掌握客户端购物流程与管理端进货/销售/库存/用户四大功能模块的代码组织逻辑并通过XML配置理解前后端轻量级解耦设计思路。1. Python商品零售管理系统源码不是玩具Demo是能跑通进货-销售-库存全链路的TkinterMySQL实战项目你在网上搜“Python课程设计 商品零售系统”十有八九点开是空窗体、三个按钮、弹出“Hello World”的假界面——这种代码交作业都心虚。但这次拆的这个GoodsManagementSys.rar不一样它真连了 MySQL真写了进货单自动校验逻辑真做了库存告警弹窗真区分了会员/员工/供货商三类用户权限甚至BACK.jpg都不是占位图而是实际用在登录页背景里的资源文件。这不是教学演示是照着超市真实业务流写的——比如销售模块里“促销商品限量控制”不是写个 if 判断就完事而是查库存表锁行更新销量写日志四步闭环库存告警也不是弹个 messagebox 说“缺货了”而是根据阈值动态标红表格行并触发入库建议生成。适合两类人一是大二大三正被课程设计压得喘不过气的学生抄得安心、改得明白、答辩不翻车二是想快速验证 Tkinter MySQL 协同开发模式的转行者它没用 Flask 或 Django 增加复杂度所有交互逻辑都在main.py里摊开给你看连.idea和.gitignore都原样打包说明作者真用 PyCharm 跑过、真提交过 Git。2. 环境准备与项目结构解析从解压到理解“为什么用Tkinter而不是Web框架”2.1 解压即见真章6个核心文件3类配置文件构成最小可运行单元解压GoodsManagementSys.rar后目录结构非常干净GoodsManagementSys/ ├── init.py # 数据库初始化脚本建库、建表、插默认管理员 ├── main.py # 主程序入口Tkinter窗口调度事件绑定业务逻辑分发 ├── BACK.jpg # 登录页背景图尺寸1024×768需保持同名同路径 ├── .idea/ # PyCharm 工程配置含编码、SDK、运行配置 ├── GoodsManagementSys.iml # 模块定义文件含依赖路径 └── .gitignore # 忽略编译缓存和用户配置注意没有requirements.txt因为项目只依赖 Python 标准库 pymysqlMySQL 连接驱动tkinterGUI 库。这意味着你不需要虚拟环境隔离只要 Python 3.6 且装了pymysql就能启动。我一般会先执行pip install pymysql提示不要用mysqlclient这个项目硬编码了pymysql.connect()的参数格式换驱动会报AttributeError: Connection object has no attribute cursor。2.2 init.py三步初始化数据库看清表结构设计逻辑init.py是整个系统的数据基石它不做迁移只做一次性的建库建表。打开后你会发现它其实只干三件事连接本地 MySQL默认 hostlocalhost, userroot, password端口3306创建goods_db数据库若已存在则跳过执行建表 SQL共5张表users,goods,inventory,sales,suppliers关键不是建表动作而是字段设计意图。比如inventory表里# inventory 表核心字段摘自 init.py goods_id INT NOT NULL, stock_quantity INT DEFAULT 0, # 当前库存量 min_stock_threshold INT DEFAULT 10, # 最低库存预警线可后台修改 last_update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP这里min_stock_threshold不是写死的常量而是作为字段存在意味着后期可以通过管理端修改——这直接支撑了“库存状态自动告警提示”功能。再看sales表sale_id INT PRIMARY KEY AUTO_INCREMENT, goods_id INT NOT NULL, sale_quantity INT NOT NULL, sale_price DECIMAL(10,2) NOT NULL, sale_time DATETIME DEFAULT CURRENT_TIMESTAMP, operator_id INT NOT NULL # 关联 users 表记录谁卖的operator_id字段的存在让“各地收银员收银记录”查询成为可能——不是靠用户名字符串匹配而是用外键关联这是课程设计里少有的工程化细节。2.3 main.pyTkinter 窗口树与业务模块映射关系图main.py是主干它用面向对象方式组织 GUIclass LoginWindow(tk.Tk)登录窗口含账号密码输入、验证逻辑、跳转判断class MainWindow(tk.Tk)主框架窗口含顶部菜单栏、左侧功能导航栏、右侧内容区class InventoryFrame(tk.Frame)库存管理子页面继承自 tk.Frame独立生命周期class SalesFrame(tk.Frame)销售管理子页面class PurchaseFrame(tk.Frame)进货管理子页面重点在于MainWindow的show_frame()方法——它不是用pack()硬切区域而是用raise()把不同 Frame 叠在同一个容器里切换显示这样既避免多窗口混乱又保证各模块状态隔离。比如你在SalesFrame里选中某商品切换到InventoryFrame后再切回来选中状态依然保留。这种写法比网上常见的“每个功能一个 Toplevel 窗口”更贴近真实软件体验。2.4 .idea 配置文件PyCharm 用户的免配环境秘籍.idea/workspace.xml里藏着关键配置option nameRUNNING_APPLICATION valuemain.py /指定默认运行脚本option nameSDK_HOME valueC:\Python39\python.exe /绑定 Python 解释器路径你解压后首次打开需右键 → “Reload project from disk”option nameENCODING valueUTF-8 /强制 UTF-8 编码避免中文乱码BACK.jpg路径含中文也不会崩如果你用 VS Code只需在工作区根目录新建.vscode/settings.json填入{ python.defaultInterpreterPath: ./venv/Scripts/python.exe, files.encoding: utf8 }然后安装 Python 扩展即可。但强烈建议用 PyCharm 打开因为.idea里还预设了数据库连接配置dataSources.local.xml双击就能连上goods_db查数据。3. 功能模块实操进货、销售、库存三大核心流程手把手跑通3.1 进货管理从计划单生成到入库登记的闭环验证进货流程分两步制定计划单 → 执行入库登记。先看计划单生成逻辑PurchaseFrame类中def generate_purchase_plan(self): # 查询库存低于阈值的商品 cursor self.db_conn.cursor() cursor.execute( SELECT g.goods_id, g.goods_name, i.stock_quantity, i.min_stock_threshold FROM goods g JOIN inventory i ON g.goods_id i.goods_id WHERE i.stock_quantity i.min_stock_threshold ) low_stock_items cursor.fetchall() # 计算建议采购量 阈值 - 当前库存 for item in low_stock_items: suggest_qty item[3] - item[2] # min_threshold - stock_quantity # 插入 purchase_plan 表此处省略SQL注意suggest_qty是整数计算不是简单补满到阈值而是精准差额——这避免了“库存剩1件阈值设10就进9件”的机械逻辑符合真实采购场景。执行入库登记时关键校验在confirm_purchase()方法def confirm_purchase(self): # 1. 检查所选商品是否存在 if not self.selected_goods_id: messagebox.showerror(错误, 请先选择商品) return # 2. 检查采购数量是否为正整数 try: qty int(self.qty_entry.get()) if qty 0: raise ValueError except ValueError: messagebox.showerror(错误, 采购数量必须为正整数) return # 3. 更新 inventory 表原子操作 cursor self.db_conn.cursor() cursor.execute( UPDATE inventory SET stock_quantity stock_quantity %s WHERE goods_id %s, (qty, self.selected_goods_id) ) self.db_conn.commit() # 必须 commit否则不生效参数说明%s占位符防止 SQL 注入self.db_conn.commit()是血泪经验——Tkinter 界面不会自动 commit漏写这句会导致“界面上显示成功数据库没变”。3.2 销售管理促销控制与销售排行榜的实现细节销售模块最易翻车的是“促销与限量销售控制”。代码在SalesFrame.sell_item()中def sell_item(self): # 获取当前选中商品 goods_id self.selected_goods_id sale_qty int(self.sale_qty_entry.get()) # 步骤1查库存加 FOR UPDATE 锁防超卖 cursor self.db_conn.cursor() cursor.execute( SELECT stock_quantity FROM inventory WHERE goods_id %s FOR UPDATE, (goods_id,) ) current_stock cursor.fetchone()[0] # 步骤2检查是否促销中查 sales_promotion 表 cursor.execute( SELECT is_active, max_quantity_per_order FROM sales_promotion WHERE goods_id %s, (goods_id,) ) promo cursor.fetchone() if promo and promo[0]: # is_active1 if sale_qty promo[1]: messagebox.showwarning(警告, f促销商品单次最多购买{promo[1]}件) return # 步骤3库存足够才扣减 if sale_qty current_stock: messagebox.showerror(错误, 库存不足) return # 步骤4扣库存 写销售记录事务 cursor.execute( UPDATE inventory SET stock_quantity stock_quantity - %s WHERE goods_id %s, (sale_qty, goods_id) ) cursor.execute( INSERT INTO sales (goods_id, sale_quantity, sale_price, operator_id) VALUES (%s, %s, %s, %s), (goods_id, sale_qty, self.current_price, self.operator_id) ) self.db_conn.commit()逻辑说明FOR UPDATE锁是核心——它确保在查库存和扣库存之间其他销售请求无法修改同一行彻底解决并发超卖问题。很多课程设计用SELECT ... UPDATE分两步就是在这里翻车。销售排行榜生成在generate_sales_report()方法支持按日/月/年统计def generate_sales_report(self, periodday): # period 可选 day, month, year if period day: date_condition DATE(sale_time) CURDATE() elif period month: date_condition YEAR(sale_time) YEAR(CURDATE()) AND MONTH(sale_time) MONTH(CURDATE()) else: # year date_condition YEAR(sale_time) YEAR(CURDATE()) cursor self.db_conn.cursor() cursor.execute(f SELECT g.goods_name, SUM(s.sale_quantity) as total_qty, SUM(s.sale_quantity * s.sale_price) as total_amount FROM sales s JOIN goods g ON s.goods_id g.goods_id WHERE {date_condition} GROUP BY g.goods_id, g.goods_name ORDER BY total_qty DESC LIMIT 10 ) top10 cursor.fetchall() # 渲染到 Treeview 表格代码略参数说明LIMIT 10控制榜单长度ORDER BY total_qty DESC确保销量最高排第一日期条件用 MySQL 内置函数不依赖 Python 时间处理减少时区误差。3.3 库存管理自动告警与缺货标红的视觉化实现库存页面 (InventoryFrame) 的核心是refresh_inventory_table()方法它每3秒轮询一次def refresh_inventory_table(self): cursor self.db_conn.cursor() cursor.execute( SELECT g.goods_id, g.goods_name, i.stock_quantity, i.min_stock_threshold, CASE WHEN i.stock_quantity i.min_stock_threshold * 0.3 THEN 严重缺货 WHEN i.stock_quantity i.min_stock_threshold THEN 缺货 WHEN i.stock_quantity i.min_stock_threshold * 2 THEN 库存过剩 ELSE 正常 END as status FROM goods g JOIN inventory i ON g.goods_id i.goods_id ) rows cursor.fetchall() # 清空表格 for item in self.tree.get_children(): self.tree.delete(item) # 插入新数据并标红 for row in rows: item_id self.tree.insert(, end, valuesrow[:4]) if row[4] in [严重缺货, 缺货]: self.tree.tag_configure(low_stock, backgroundpink) self.tree.item(item_id, tags(low_stock,))关键点tag_configure和item(..., tags...)实现视觉标红状态判断用CASE WHEN在 SQL 层完成减轻 Python 端计算压力轮询间隔self.after(3000, self.refresh_inventory_table)可调但低于2秒易卡 UI。4. 避坑指南五个真实踩过的坑与绕过方案4.1 现象登录成功后主窗口空白控制台无报错原因main.py第 28 行self.geometry(1200x800200100)设置了固定窗口尺寸但你的显示器分辨率低于此值如 1024×768导致内容被裁剪。解决注释掉该行或改为self.state(zoomed)全屏或self.minsize(800, 600)最小尺寸。4.2 现象点击“销售”按钮报错AttributeError: NoneType object has no attribute get原因SalesFrame中self.sale_qty_entry组件未正确初始化常见于__init__方法里self.sale_qty_entry tk.Entry(...)写在了self.pack()之后导致组件未绑定到实例。解决确保所有tk.Entry、tk.Button等组件声明在self.pack()或self.grid()之前且赋值给self.xxx。4.3 现象库存告警不触发min_stock_threshold字段值为 0原因init.py建表时min_stock_threshold INT DEFAULT 10但部分 MySQL 版本如 8.0对DEFAULT值敏感若建表语句执行失败该字段会变成NULL或0。解决手动执行 SQL 修复ALTER TABLE inventory ALTER COLUMN min_stock_threshold SET DEFAULT 10;或直接UPDATE inventory SET min_stock_threshold 10 WHERE min_stock_threshold 0;4.4 现象中文商品名在 Treeview 表格里显示为方框原因Tkinter 默认字体不支持中文且init.py插入测试数据时用了latin1编码。解决在main.py开头添加字体设置import tkinter as tk from tkinter import font # 设置全局默认字体 default_font font.nametofont(TkDefaultFont) default_font.configure(familyMicrosoft YaHei, size10)4.5 现象销售时提示pymysql.err.OperationalError: (1045, Access denied for user rootlocalhost)原因MySQL root 用户密码非空但main.py第 45 行硬编码password。解决修改main.py中数据库连接参数self.db_conn pymysql.connect( hostlocalhost, userroot, passwordyour_real_password, # ← 改这里 databasegoods_db, charsetutf8mb4 )注意charsetutf8mb4必须加上否则 emoji 或生僻字会报错。5. 进阶技巧把课程设计升级成可部署工具的三个实操动作5.1 用 PyInstaller 打包成单文件exe甩掉Python环境依赖学生最怕答辩现场“请先装Python”用 PyInstaller 一键打包pip install pyinstaller pyinstaller --onefile --windowed --iconBACK.ico main.py关键参数说明--onefile所有依赖打包进一个 exe生成在dist/main.exe--windowed隐藏命令行黑窗Tkinter GUI 必加--iconBACK.ico替换图标需先用在线工具把BACK.jpg转成BACK.ico尺寸256×256血泪经验打包前务必删掉.idea目录和__pycache__否则 PyInstaller 会误打包冗余文件导致体积暴增。实测打包后体积约12MB比带Python解释器轻得多。5.2 添加导出Excel功能用 openpyxl 替代繁琐的手动复制原系统只支持打印但老师常要“导出销售日报”。在SalesFrame类里加一个方法def export_to_excel(self): import openpyxl from openpyxl.styles import Font, PatternFill wb openpyxl.Workbook() ws wb.active ws.title 销售日报 # 表头 headers [商品名称, 销量, 销售额, 销售时间] for col, header in enumerate(headers, 1): cell ws.cell(row1, columncol, valueheader) cell.font Font(boldTrue) cell.fill PatternFill(start_colorD3D3D3, end_colorD3D3D3, fill_typesolid) # 数据复用 generate_sales_report 的 SQL cursor self.db_conn.cursor() cursor.execute( SELECT g.goods_name, s.sale_quantity, s.sale_quantity*s.sale_price, s.sale_time FROM sales s JOIN goods g ON s.goods_id g.goods_id WHERE DATE(s.sale_time) CURDATE() ) rows cursor.fetchall() for r_idx, row in enumerate(rows, 2): for c_idx, value in enumerate(row, 1): ws.cell(rowr_idx, columnc_idx, valuevalue) filename f销售日报_{datetime.now().strftime(%Y%m%d_%H%M%S)}.xlsx wb.save(filename) messagebox.showinfo(成功, f已导出{filename})注意需pip install openpyxl且 Excel 文件会保存在程序同目录无需额外路径配置。5.3 数据库连接池优化应对多人同时操作的性能瓶颈原代码每次操作都新建连接pymysql.connect()10人并发时 MySQL 连接数爆满。换成连接池# 在 main.py 开头添加 from DBUtils.PooledDB import PooledDB import pymysql # 创建连接池最大10连接闲置2小时释放 pool PooledDB( creatorpymysql, hostlocalhost, userroot, passwordyour_password, databasegoods_db, charsetutf8mb4, maxconnections10, blockingTrue ) # 在需要数据库操作的地方替换 connect() 为 pool.connection() def get_db_connection(): return pool.connection() # 自动从池中取连接然后把所有self.db_conn pymysql.connect(...)替换为self.db_conn get_db_connection()。连接用完后必须显式关闭self.db_conn.close()否则连接不会归还池中。从那以后我每次交付课程设计都会强制走一遍这三步先pyinstaller打包验证能否脱离环境运行再加openpyxl导出功能满足老师数据需求最后用连接池压测并发开5个销售窗口同时下单。不是为了炫技而是让这套代码真正像一个工具而不是一份作业。希望帮到你。本文还有配套的精品资源点击获取
返回列表