Python酒店点餐系统源码与数据库设计实战:从课设到毕设的完整链路解析

发布时间:2026/10/25 21:54:14
Python酒店点餐系统源码与数据库设计实战:从课设到毕设的完整链路解析

简介:这是一套基于Python开发的酒店点餐系统毕业设计项目,面向计算机科学、人工智能、物联网等专业的在校生与教师,尤其适合正在筹备毕业设计或课程设计的学生。项目为个人大四毕业设计,经导师指导并获得98.5分的评审高分,系统功能完整、运行稳定,可直接用于学习借鉴或二次开发。资源共705个文件,涵盖py后端逻辑、vue前端页面、js脚本、css样式、sql数据库、png/jpg图标素材与mp4演示视频,压缩包整体44.77MB,目录结构清晰便于分模块查看。除完整源码与数据库外,另附论文文档和操作演示视频,既可对照学习系统设计与实现思路,也可快速掌握部署运行流程。目前已有128人学习,对于希望提升Python Web开发能力并完成高质量毕设的读者,具有不错的参考价值。

1. 这个zip里到底有什么:酒店点餐系统的定位与交付物

搜索“基于Python的酒店点餐系统源码+数据库+论文+演示视频.zip”的人,多半是在做课程设计或毕业设计,需要一个能跑、能讲、能写进文档的完整项目。这类交付包通常包含一份Python源码、一份建库SQL、一篇可直接改的论文初稿,以及录好的演示视频。它解决的最大问题不是“代码难写”,而是“不知道系统该怎么完整地搭起来”。多数打包项目由Tkinter或Flask写成,搭配MySQL或SQLite,覆盖了从菜品管理、点餐下单到订单结算的完整闭环。

这套东西适合两类人用:一类是时间紧的学生,需要快速看懂结构并改写成自己的东西;另一类是刚学完Python基础、想拿真实业务练“数据库增删改查”的初级开发者。但这类包的源码质量参差不齐,能不能跑起来主要看环境配置,其次才是代码本身。下面按“系统拆解→核心业务→论文与视频→跑通避坑→值不值得做”的顺序,把这条链路讲透。

2. 系统拆解:用Python做酒店点餐系统的常见架构与数据库设计

2.1 技术选型:Tkinter版为什么是这类源码包的主流,Web版什么时候更划算

打开这类源码包,第一件事是看它是桌面程序还是网页程序。常见做法是Tkinter + MySQL,少数用PyQt5,也有用Flask做简单网页版的。Tkinter成为主流不是因为它好看,而是因为它是Python自带的GUI库,不用额外安装,双击就能出窗口,答辩时录屏也从容。PyQt5界面更精致,但依赖更大,打包成exe时经常漏掉插件,开箱翻车的概率更高。

方案界面形态额外依赖打包/部署适合场景主要坑
Tkinter + MySQL桌面窗口PyMySQLPyInstaller打exe课设/毕设快速验收界面朴素,需要手动处理图片路径
PyQt5 + MySQL桌面窗口PyQt5打包需带插件目录想让截图更精致信号槽语法和QSS样式耗时
Flask + MySQL浏览器页面Flask局域网起服务想写进简历前端HTML/CSS增多页面

如果你只是要过答辩,Tkinter最省事;如果以后想把这个项目放进作品集或简历,Flask版更容易往外讲,因为面试时现场起一个Web服务和浏览器演示比在答辩机上跑桌面程序更有说服力。但你要为Flask版多准备前端页面,哪怕是几个简单的HTML模板,工作量会多出两三天。选型这件事,别在下载包那一刻才做,先想清楚你要的是什么,再决定要不要换方案。要是包里的方案和你想的不一致,我一般会直接改一小层结构,而不是推倒重来:把业务逻辑和界面分开,后续想从Tkinter换到Flask,代价会小很多。

2.2 数据库设计:酒店点餐最少需要哪几张表,字段怎么定

点餐系统的核心数据是菜品、订单、订单明细,再加一张客房或餐桌表。网上流传的源码包通常就是这四张表,多了的比如会员表和营销表,在课设阶段反而是负担,因为论文里要解释的东西更多。下面这套建表SQL是这类项目最常见的骨架,MySQL和MariaDB都能直接跑:

CREATE DATABASE IF NOT EXISTS hotel_order DEFAULT CHARSET utf8mb4; USE hotel_order; CREATE TABLE room ( id INT PRIMARY KEY AUTO_INCREMENT, room_no VARCHAR(20) NOT NULL, status TINYINT DEFAULT 0 COMMENT '0空闲 1入住 2已退房' ); CREATE TABLE menu_item ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, image_path VARCHAR(255), status TINYINT DEFAULT 1 COMMENT '1上架 0下架' ); CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, room_id INT, total_amount DECIMAL(10,2) DEFAULT 0, pay_status TINYINT DEFAULT 0 COMMENT '0未付 1已付', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (room_id) REFERENCES room(id) ); CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, menu_id INT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL, FOREIGN KEY (order_id) REFERENCES orders(id), FOREIGN KEY (menu_id) REFERENCES menu_item(id) );

把订单拆成orders和order_item两张表,是为了支持“一单多菜”,这是点餐系统的基本要求。order_item里的price字段单独保存一份下单时的价格,而不是下单后去查menu_item的价格,为的是防止以后改价导致历史订单金额对不上。room表的status只存TINYINT状态码,比存字符串更省空间,也方便前端用数字判断。

我见过不少源码把表名取成order,结果insert语句因为order是MySQL保留字反复报语法错误,卡了整整一晚上,最后把表名改成orders才通过。这个“保留字坑”不算技术问题,但属于新手最容易踩的哑巴坑。还有,如果项目中途要加字段,比如给菜品加分类,直接用ALTER TABLE menu_item ADD COLUMN category VARCHAR(20);,但执行前先用SELECT * FROM menu_item LIMIT 1;看一眼结构,再确认没有依赖这个表的代码用了SELECT *的列顺序,否则界面显示会错位。

如果你的电脑装不上MySQL,这类源码也可以改成SQLite。SQLite把整个数据库放在一个.db文件里,演示时干净利落,论文里可以写“使用轻量级嵌入式数据库”,也不丢人。但要注意SQLite默认不强制外键,连接后要执行PRAGMA foreign_keys = ON;,否则外键约束都是摆设。

2.3 数据库增删改查:把PyMySQL连接封装成自己的DBHelper

很多下载包的代码,每个按钮回调里都重新new一个connection,写几步就忘了close,程序运行一晚上后连接数暴涨,数据库直接拒绝连接。我一般会做一层DBHelper,把数据库增删改查的公共逻辑收敛到同一个类里,界面代码只调用query和execute两个方法。下面这个类是这类项目中非常通用的写法:

import pymysql class DBHelper: def __init__(self, host="localhost", port=3306, user="root", password="123456", database="hotel_order", charset="utf8mb4"): self.conn = pymysql.connect( host=host, port=port, user=user, password=password, database=database, charset=charset, cursorclass=pymysql.cursors.DictCursor ) def query(self, sql, args=None): try: with self.conn.cursor() as cursor: cursor.execute(sql, args) return cursor.fetchall() except Exception as e: self.conn.rollback() raise e def execute(self, sql, args=None): try: with self.conn.cursor() as cursor: rows = cursor.execute(sql, args) self.conn.commit() return rows except Exception as e: self.conn.rollback() raise e def close(self): self.conn.close()

这里query负责SELECT,返回的是字典列表,每行可以直接按字段名取值;execute负责INSERT、UPDATE、DELETE,并在结束后commit。PyMySQL默认不自动提交,很多人只执行了insert却忘了commit,界面提示下单成功,数据库里却查不到数据,这种“黑匣子”问题最磨人。还有一个细节是SQL参数一律用%s占位并传args元组,不要自己拼f-string,否则用户输入一个引号就能让整个系统崩掉,论文里的安全性也会被老师质疑。

调用方就非常简单:

db = DBHelper() items = db.query("SELECT id, name, price FROM menu_item WHERE status=%s", (1,)) db.execute( "INSERT INTO orders(order_no, room_id, total_amount, pay_status) VALUES(%s,%s,%s,%s)", ("H20250601001", 1, 88.00, 0) ) db.close()

注意这里的连接是在程序启动时创建、退出时统一关闭,不属于“用完就关”。如果你把DBHelper用在Flask里,还要考虑请求结束后归还连接的问题,但课设场景下单连接够用。关闭时不要在每个按钮里调close,否则连接一断,后面再点按钮就会拿到一个无效连接。这类问题有个标志性的报错:pymysql.err.InterfaceError: (0, ''),之后会专门讲。

3. 把核心业务跑通:点餐下单、菜品管理与订单结算的代码实现

3.1 点餐下单:从Treeview选中菜品到写入订单表,事务怎么包

界面部分常用的控件是ttk.Treeview表格,左边显示菜品列表和库存,右边放一个购物车列表。用户双击菜品或点“加入购物车”时,把menu_id、名称、单价和数量存进内存列表。真正写库发生在点“提交订单”时,下面这段代码是这类系统的核心脉络:

import tkinter as tk from tkinter import ttk, messagebox from datetime import datetime class OrderFrame(tk.Frame): def __init__(self, master, db): super().__init__(master) self.db = db self.cart = [] # 购物车内存列表: (menu_id, name, price, quantity) def add_to_cart(self): selected = self.tree.selection() if not selected: return item = self.tree.item(selected[0], "values") menu_id, name, price = int(item[0]), item[1], float(item[2]) for i, (mid, n, p, qty) in enumerate(self.cart): if mid == menu_id: self.cart[i] = (mid, n, p, qty + 1) break else: self.cart.append((menu_id, name, price, 1)) self.refresh_cart() def submit_order(self, room_id): if not self.cart: return total = sum(p * qty for _, _, p, qty in self.cart) order_no = f"H{datetime.now():%Y%m%d%H%M%S}" try: self.db.conn.begin() with self.db.conn.cursor() as cursor: cursor.execute( "INSERT INTO orders(order_no, room_id, total_amount, pay_status) VALUES(%s,%s,%s,0)", (order_no, room_id, total)) order_id = cursor.lastrowid for mid, name, p, qty in self.cart: cursor.execute( "INSERT INTO order_item(order_id, menu_id, quantity, price) VALUES(%s,%s,%s,%s)", (order_id, mid, qty, p)) cursor.execute( "UPDATE menu_item SET stock=stock-%s WHERE id=%s", (qty, mid)) # 扣减库存 self.db.conn.commit() self.cart.clear() self.refresh_cart() except Exception as e: self.db.conn.rollback() messagebox.showerror("下单失败", str(e))

这里的关键是手动控制事务:先begin(),所有写操作在同一个事务里执行,全部成功再commit(),中途任何一步失败就rollback()。为什么不直接调用DBHelper.execute?因为execute内部会自动commit,如果购物车里有两道菜,第一道插入了明细、第二道因库存不足失败,第一道菜的库存扣减也已经提交了,数据就变得非常难看。事务是点餐系统最容易漏的部分,也是最能让答辩老师问出细节的考点。

参数说明:cursor.lastrowid拿到的是刚插入的orders表自增主键,它是后面写明细的外键;stock=stock-qty的写法比“先查库存再更新”更安全,至少在简单场景下能避免两人同时下单时的超卖,真正高并发就得用行锁了。还有一个容易被忽略的点:下单前应该检查菜品状态,如果你把下架菜也从Treeview里选中提交,库存一样会被扣。常见做法是查询菜品时直接WHERE status=1,下架菜品不进入用户界面。

3.2 菜品管理:上下架、库存扣减和图片路径的三种常见写法

酒店端要管理菜品,至少要有添加、改价、上下架、搜索这几个动作。它们本质都是数据库增删改查,界面布局不同但换汤不换药。下面这段代码演示了最常用的几个方法:

def add_menu(self, name, price, stock, image_path): # 新增菜品,status=1表示立即上架 sql = "INSERT INTO menu_item(name, price, stock, image_path, status) VALUES(%s,%s,%s,%s,1)" self.db.execute(sql, (name, price, stock, image_path)) def toggle_status(self, menu_id, status): self.db.execute("UPDATE menu_item SET status=%s WHERE id=%s", (status, menu_id)) def search_menu(self, keyword): kw = f"%{keyword}%" return self.db.query( "SELECT * FROM menu_item WHERE name LIKE %s OR id LIKE %s", (kw, kw))

LIKE查询一定要用参数化,不要写成WHERE name LIKE '%{keyword}%'。参数化写法是先组装kw = f"%{keyword}%",再把kw传给占位符。搜索里那句id LIKE有点多余,但很多源码包都会加,用来支持“输编号查菜品”。如果keyword是数字,MySQL会尝试把id转成字符串匹配,性能可以忽略,课设阶段不必较真。

图片路径是菜品管理里翻车率最高的一环。很多源码直接在库里存D:/picture/鱼香肉丝.jpg,在作者电脑上能显示,换个答辩机就一片空白。我一般会在代码顶部统一设置图片目录:

import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) IMAGE_DIR = os.path.join(BASE_DIR, "images") def get_image_path(filename): if not filename: return os.path.join(IMAGE_DIR, "default.png") return os.path.join(IMAGE_DIR, os.path.basename(filename))

用os.path.basename去掉路径里的上级目录,可以顺手挡掉路径注入。如果图片显示逻辑写在界面层,还要注意Tkinter的PhotoImage只支持PNG和GIF,jpg要先用PIL的Image模块转一下再放进Label。很多下载包在图片这一块直接用tk.PhotoImage(file=image_path),遇到jpg时直接报“ couldn't recognize data”,这就是用PIL前没读文档的典型翻车。把Image.open读出来的对象转成PhotoImage,再保留引用,就不会被GC回收导致图片消失。

3.3 订单结算:总价计算、找零与小票打印的实现取舍

结算模块比下单简单,但容易在金额精度上出问题。DECIMAL在数据库里没问题,一旦到Python里变成float再经过乘法,可能出现8.900000001这种结果。因此计算总价时最好用Decimal,或者至少用round包一层。下面这段是兼容现有DBHelper的写法:

from decimal import Decimal def checkout(self, order_id, paid_amount): # 使用Decimal(str())避免浮点数二进制精度误差 row = self.db.query("SELECT total_amount FROM orders WHERE id=%s", (order_id,)) if not row: raise ValueError("订单不存在") total = Decimal(str(row[0]["total_amount"])) paid = Decimal(str(paid_amount)) if paid < total: raise ValueError("支付金额不足") change = float(paid - total) self.db.execute("UPDATE orders SET pay_status=1 WHERE id=%s", (order_id,)) return float(total), change

用Decimal(str(...))而不是Decimal(0.1),是为了避开二进制浮点数误差。实战中很多系统在这里用float也没出大问题,但答辩老师一旦让输入一个12.30元,再输入20元找零7.70元,float版本有可能算出7.699999999999999,就很尴尬。金额计算在这个系统里是“面子功能”,错了必会被拍。

小票打印要不要做?我的建议是:能做但别死磕。酒店点餐的演示重点是下单和结账流程,小票是边角功能。非要打印,最稳的方案是生成文本文件再调用系统记事本打开:

def print_receipt(self, order_id): text = "欢迎光临XX酒店\n" text += f"订单号: {order_id}\n" # 这里继续拼接订单明细和总价 with open(f"receipt_{order_id}.txt", "w", encoding="utf-8") as f: f.write(text) os.startfile(f"receipt_{order_id}.txt")

用win32print直接驱动打印机,需要处理打印机名、换行符和编码,短时间搞不定的概率很高。在论文中把这一项写成“后续可扩展”反而比硬做残缺功能更合理。演示时用弹窗显示“实收100元,找零12.30元”,老师已经会认为功能完成。

4. 论文与演示视频怎么补:让交付包真正能过审

4.1 论文结构:从需求分析到系统测试,每一章写什么

源码包附带论文,通常是一份完整初稿,但学校和导师的模板各不相同。拿到手先别急着改标题,按下面这张结构对照你的模板补章节,比逐段读原文效率高得多。

论文章节内容来源篇幅建议
绪论研究背景、任务目标、国内外现状3-4页
需求分析角色定义、功能用例、业务流程图4-6页
总体设计架构图、模块划分、技术选型3-4页
数据库设计E-R图、表结构、表关系4-5页
详细设计与实现关键代码片段加界面截图10-15页
系统测试测试用例表、结果与BUG修复记录3-4页
总结与展望成果、不足、扩展方向1-2页

数据库设计这章可以直接把2.2的建表SQL和字段说明搬进去,但要补一张E-R图。画图用Visio或draw.io都行,重点是rooms、menu_item、orders、order_item之间的连线要清楚。详细设计这章别贴全部源码,贴DBHelper和submit_order两个关键片段就够了,每段代码后面写明“这一句完成什么”,论文老师要看的是你有没有理解,不是代码量。最后把测试那章写成测试用例表,比如“输入空购物车点提交,系统提示请先选菜”,比写“测试通过”四个字充实得多。

这里有一个论文专门坑:如果实测过程中你因为字段不够用执行过ALTER TABLE修改表结构,论文里不要提“后来加字段”的过程,而是把最终字段写清楚。论文描述的是交付时的系统,不是你的排错过程。另外,截图统一在分辨率1920×1080、字体无乱码时截,图片里不要出现源代码路径或测试脏数据,否则答辩老师一眼看到“D:\张三\Desktop\test”会直接质疑系统不是你自己搭的。

论文数据也不要张冠李戴,比如系统里没有会员功能,就不要在需求分析里写会员积分。答辩老师会随机挑一个你没写的功能提问,如果你把没有的东西写上去,问三句就会露馅。宁可把已有功能写细,不要靠堆名词凑篇幅。

4.2 演示视频脚本:先重置数据库再按功能点走,避免录到一半翻车

演示视频的目的是证明“系统能跑、我有操作能力”,不是展示探索过程。脚本化非常关键。按下面这个顺序录制,节奏基本覆盖全部功能:

时间操作预期画面旁白要点
0:00-0:20启动主程序进入主界面窗口打开,菜品列表加载说明系统基于Python和MySQL
0:20-0:50添加新菜品表单填写,保存后列表刷新强调数据写入数据库
0:50-1:30客房点餐,加入两道菜购物车数量变化说明一单多菜的结构
1:30-2:00提交订单弹窗显示订单号强调事务保证数据一致
2:00-2:30结账,输入实收金额显示总价与找零订单状态变为已支付
2:30-3:00按订单号查询列表显示该订单演示数据库查询

录制前一定要重置数据库,把测试订单清空,把菜品库存调回合理数字。没有这一步,视频里满屏“张三、测试、12345”的脏数据,会让人怀疑程序的可靠性。录到一半如果报错,我的经验是别急着关机重录,把错误留在画面上,关掉程序后补一句“这个问题是因为数据库没连接,下面把服务启动后再来一次”,反而显得像真实调试。当然,答辩版的视频里不要出现这种失误,最终提交版要重新录一遍。

4.3 演示视频录制的3个参数要点

视频参数不用追求高画质,但要保证评委在自己电脑上打开不卡、不模糊。第一个参数是分辨率,1920×1080足够,录制区域只圈主窗口,不要录整个桌面,否则文字和表格会被压得很小。第二个参数是帧率,25或30fps就够,60fps会让文件体积翻倍,对课设答辩没有意义。第三个参数是编码,导出H.264 + MP4,总时长控制在8分钟内,文件控制在100MB以内,超过这个体积,传到学校系统或微信里都会遇到麻烦。

音轨方面,用耳机麦克风收音,别用笔记本外放,不然键盘声和房间环境声全录进去。如果不想配音,可以不加人声,但至少加字幕,在关键操作上写一句“这里将数据写入orders表”,这样评委静音看也能理解。剪辑用剪映或Premiere都可以,导出前把音量控制在-6dB到-12dB,不要让我一打开视频就被BGM炸到。演示视频的作用是把操作链路讲清楚,语气稳一点、语速慢一点,比任何剪辑技巧都重要。文件名也值得注意,压缩包里的视频如果叫“演示视频1.mp4”,建议改成“酒店点餐系统演示视频_最终版.mp4”,交到系统里不会出现一堆1、2、3的命名。

5. 从下载到跑通:Python环境、依赖安装与Win10下的5个避坑点

5.1 Python版本怎么选:老源码别上最新版,pip安装numpy要换国内源

拿到源码包,先打开里面的.py文件扫一眼import行,确认它依赖什么。常见的依赖是PyMySQL、Pillow、openpyxl,偶尔有numpy。Python版本建议用3.8到3.10,不要一上来装最新的3.13,否则有些老代码依赖的二进制包还没跟上,报错信息会让你以为是源码问题,其实纯是版本不兼容。用命令行检查环境:

python --version # 检查解释器版本 pip --version # 检查pip是否可用

如果电脑里有多个Python,一律用python -m pip而不是裸的pip,否则装包时可能装到另一个解释器里去。安装依赖用下面这条:

python -m pip install pymysql pillow numpy openpyxl

如果你在下载numpy时慢到怀疑人生,常见做法是临时换国内镜像源,把下载地址指到清华镜像,速度能快一个量级。镜像源只做下载通道,装完的包和官方源没有区别。装完后验证一下依赖是否可用:

python -c "import pymysql, PIL, numpy; print('ok')"

这里有个经验:不要顺手把不需要的包全装一遍。依赖越少,跑到别人的答辩机上越不容易翻车。numpy在很多酒店点餐系统里其实根本用不到,只有做数据分析或报表模块才会用到。如果源码里没有import numpy,就不要装,没必要给自己增加环境变量冲突的风险。反之,如果源码用了PIL但没有装,启动时就会在import阶段直接报ModuleNotFoundError,后面的逻辑写得再好也白搭。

5.2 数据库导入与连接串配置:一开始就把SQL跑干净

数据库这步是翻车重灾区。先把交付包里的.sql文件导入MySQL,在命令行里执行:

mysql -u root -p < hotel_order.sql

如果.sql文件里没有建库语句,要先手动建库再导入:

mysql -u root -p -e "CREATE DATABASE hotel_order DEFAULT CHARSET utf8mb4;" mysql -u root -p hotel_order < hotel_order.sql

导入完别急着启动程序,先验证表和基础数据在不在:

mysql -u root -p -e "SHOW DATABASES; USE hotel_order; SHOW TABLES; SELECT COUNT(*) FROM menu_item;"

这里用到的就是mysql数据库常用命令,几分钟就能把整个状态摸清楚。如果SELECT返回0,说明.sql文件里只有建表语句没有示例数据,得自己先录几道菜再进界面,不然主界面空荡荡容易误以为程序坏了。如果.sql导入时报错,多半是字符集问题,在导入前用SET NAMES utf8mb4;再执行source。

连接串配置的检查顺序是:先搜代码里的连接参数,再把数据库的实际密码对齐。在项目目录下搜索password、host、database,Windows里可以用IDE的全局搜索或“在文件中查找”快捷键。常见做法是把连接信息集中在config.py或DBHelper的顶部,改一处即可。要特别注意MySQL 8默认认证插件是caching_sha2_password,老版本的PyMySQL连旧库没问题,但连新库时可能报Authentication plugin,解决方法是给MySQL用户换回mysql_native_password,或者在连接参数里不使用旧密码方式。这个坑很多下载包不会写,但你用新的MySQL 8时一定踩。

如果实在装不了MySQL,改成SQLite也可以。把2.2的建表SQL换成SQLite方言,再将DBHelper里的pymysql换成sqlite3即可。SQLite的数据库就是一个.db单文件,项目目录里带一个hotel.db示例文件,答辩时反而干净,不会出现“数据库服务没启动”的临场事故。但SQLite默认不开外键,要在连接后执行PRAGMA foreign_keys = ON;,否则订单明细可能插到不存在的订单上。

5.3 5个避坑点:现象、原因、解决

下面这5条不是玄学,是我帮人排这种课设源码时总结出来的血泪经验,按“现象→原因→解决”写,基本覆盖下载包跑不通的绝大多数原因。

  1. 双击.py文件闪退,窗口一闪就没。原因是程序启动时发生了异常,Tkinter主循环还没来得及显示,进程就退出了。解决方式是不要双击,在命令行里用python main.py运行,直接看traceback。也可以在main函数最外层包一层try-except,把异常弹出来:
import traceback from tkinter import messagebox def main(): # 原有启动逻辑 pass if __name__ == "__main__": try: main() except Exception: messagebox.showerror("错误", traceback.format_exc())
  1. ModuleNotFoundError: No module named 'pymysql'。原因是依赖没有安装,或者pip装到了另一个Python环境。解决方式是使用python -m pip install pymysql,并确保命令里的python和运行源码的python是同一个。验证方法是python -c "import pymysql; print(pymysql.__version__)",如果还能复现,就是环境指向不对。

  2. 界面和数据库里的中文变成问号乱码。原因是连接串没有指定charset,或建库时用了默认latin1。解决办法是连接参数里加charset="utf8mb4",并把库、表都改成utf8mb4。可以先执行ALTER TABLE menu_item CONVERT TO CHARACTER SET utf8mb4;,再查看服务端字符集:SHOW VARIABLES LIKE 'character_set_server';。

  3. 菜品图片显示空白,控制台报FileNotFoundError。原因是库里存的是绝对路径,换电脑后路径失效。解决方式是改成相对路径,用BASE_DIR拼接图片目录,并保证图片真的在源码目录的images文件夹里。如果图片文件不在压缩包里,就自己截图另存为PNG,统一放到images目录并保持数据库里的文件名一致。

  4. 下单成功但订单查询里没有新记录,重启后购物车也正常,但数据库里查不到。原因是PyMySQL默认不自动提交,execute后没有commit。解决方式是补commit,或者直接使用2.3里的DBHelper,把commit放进execute方法。如果用事务,确保begin()、commit()、rollback()三件事都在同一个try里,缺一不可。

6. 这个方向值不值得做:从单机版到Web版的升级路线

6.1 五分钟验证一套源码能不能用

拿到任何一套“源码+数据库+论文+演示视频”的压缩包,我都会先做一次后端验证:启动程序,随便点两道菜下单,然后去MySQL里执行SELECT * FROM orders;。如果表格里出现了新订单,不管界面多丑,核心链路就是通的。如果界面弹“下单成功”但数据库没有新行,那这套源码只做了界面假动作,后面的一切都别信。这个验证法比看演示视频和读论文可靠,因为视频可以剪辑,论文可以复贴,数据库不会骗你。

6.2 把Tkinter换成Flask:复用DBHelper的最小改造

如果这个项目你想拿来投实习或放简历,我建议把Tkinter版改造成Flask版,DBHelper不用重新写,原样复用就行。你只需要暴露两个接口:查询菜品、创建订单。下面是最小改造:

from flask import Flask, jsonify, request from db_helper import DBHelper app = Flask(__name__) db = DBHelper() @app.route("/api/menu", methods=["GET"]) def menu(): rows = db.query( "SELECT id, name, price FROM menu_item WHERE status=1") return jsonify(rows) @app.route("/api/order", methods=["POST"]) def create_order(): data = request.get_json() db.conn.begin() with db.conn.cursor() as cursor: cursor.execute( "INSERT INTO orders(order_no, room_id, total_amount, pay_status) VALUES(%s,%s,%s,0)", ("H" + data["order_no"], data["room_id"], data["total"])) order_id = cursor.lastrowid db.conn.commit() return jsonify({"ok": True, "order_id": order_id}) # 返回订单ID给前端 if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)

这里DBHelper仍然负责连接和通用SQL,Flask只负责接收HTTP请求和返回JSON。把app.run的host设为0.0.0.0后,同一局域网的手机和另一台电脑都能访问,这个演示效果比桌面版更有“系统”感。注意Flask的debug=True在正式演示时不要开,否则错误页面会把你内部连接串都暴露出来。

我自己的第一个课设就是酒店点餐,当年也是下载别人的包想省时间,结果卡在MySQL字符集一整个晚上,最后把表删了重建才算跑通。事后我才明白,这类项目最好的使用方法不是原样交掉,而是把数据库增删改查的链路摸熟,再把界面换成自己顺手的样子,论文和视频自然也能说清楚。希望帮到你。

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