相信不少刚入门Python的朋友都面临过同一个困惑:语法学了、爬虫会写了Linux系统安装Python也折腾过好几遍,但一到“做个完整的项目”就卡住了。做点菜系统,恰恰是综合程度比较高、离实际业务又近的练手项目。
本文要聊的,就是一套基于Python的电子点菜系统从零搭建过程。它能解决餐饮店纸质菜单更新慢、前台下单靠吼、营业数据难统计的痛点,特别适合学完Python基础想做完整Web项目的人参考,也适合餐饮行业有定制需求的技术爱好者。我会把架构设计、数据库表结构、核心接口逻辑、还有实际踩过的坑一并讲透,不是那种只给demo不给理由的文章。
1. 为什么电子点菜系统适合用Python来实现
1.1 从一沓手写菜单引发的需求梳理
先交代一下背景。2023年底,一个开小龙虾馆的朋友找我聊,说他店里高峰期服务员要跑前跑后记菜名,经常出现“菜上重了”“客人催了半天发现单没传后厨”的情况。他想让我帮忙搞个微信里能扫码点餐的玩意儿,预算几乎为零。
那时候我第一反应是这需求不难,但真正动工前我理了一遍业务流程,发现“电子点菜”根本不是做一个“能下单的网页”那么简单。它背后至少牵扯四类角色:顾客(浏览菜品、加购、下单)、前台收银(核验订单、处理优惠)、后厨(查看已下单菜品、标记出餐)、店长(维护菜品、查看营业报表)。
这四类角色的诉求完全不同:顾客要快、要直观;后厨要稳定、不能漏单;收银要能处理异常;店长要数据。所以技术方案不能只搞一个“能跑就行”的小网页,得把权限、流程、异常处理都设计进去。
1.2 技术选型:为什么是Python而不是Node.js或Java
说句实在话,如果给大厂做高并发外卖系统,我会选Java或Go。但朋友的小龙虾馆,日单量峰值也就几百单,后端逻辑主要围绕“菜单管理+订单流转+简单的统计报表”转,这时候Python的优势就非常明显:
- 开发效率高。同样是搭一套REST API,用Flask或FastAPI几十行就能把路由和参数校验写完,Java要配一堆依赖和配置文件。
- 生态契合数据分析。餐饮店后续要做“哪些菜卖得好”“一周营业额趋势”,Python的pandas、matplotlib直接无缝衔接,不用另起一套技术栈。
- 招人或自己维护成本低。Python人才基数大,坏代码找人接手也容易。
我最终选型是:后端用Flask + SQLAlchemy + SQLite(开发环境)/ PostgreSQL(生产),前端用Jinja2模板 + Bootstrap,扫码点餐页用轻量Vue.js(CDN引入)。这套组合的好处是,单文件就能启动整个系统,朋友店里的老电脑跑起来也毫无压力。
提示:如果你只想做个课程设计或者demo,SQLite完全够用,别一上来就整MySQL/PostgreSQL,那是给多用户并发环境准备的。课程设计答辩时老师问你“为什么用SQLite”,你可以理直气壮地回答:单机版系统不需要独立的数据库服务,部署成本更低、文件即数据、备份直接复制文件即可。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构与数据库表设计,先把地基打牢
2.1 三端一API的整体结构
系统的物理结构并不复杂,一个Flask应用同时充当Web服务端和API服务端。但逻辑上要把三端彻底分开:
| 端 | 使用对象 | 主要页面/接口 | 技术形态 |
|---|---|---|---|
| 顾客端 | 微信扫码的食客 | 菜单浏览、购物车、提交订单 | H5页面(手机优先) |
| 商户端 | 收银员、后厨、店长 | 订单管理、菜品管理、报表 | PC网页 |
| 管理API | 未来第三方对接 | 订单状态查询、菜品同步 | JSON接口 |
共享同一个用户体系,用role字段区分三类后台用户(cashier、kitchen、admin)。顾客不登录,靠session_id维持购物车状态,下单时留手机号便于通知取餐/上菜。
这种设计的核心思路是:不让顾客感知到“后台”的存在,顾客端页面是纯展示+交互的薄客户端,所有校验和状态流转都在服务端完成。这样即使前端被绕过,直接调API,也拿不到越权数据。
2.2 数据表:少一张表,后面每个功能都得打补丁
数据库我设计成六张核心表,每张表的取舍都经历过实际业务的“毒打”,这里直接给大家看最终版:
sql复制-- 菜品分类表
CREATE TABLE category (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(50) NOT NULL,
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE
);
-- 菜品表
CREATE TABLE dish (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER NOT NULL REFERENCES category(id),
name VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL,
image_url VARCHAR(255),
monthly_sales INTEGER DEFAULT 0, -- 月售量,列表页展示用
is_active BOOLEAN DEFAULT TRUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 桌台表
CREATE TABLE table_seat (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_no VARCHAR(20) NOT NULL UNIQUE,
qr_code VARCHAR(255),
status VARCHAR(20) DEFAULT 'idle' -- idle/occupied
);
-- 订单主表
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_no VARCHAR(32) NOT NULL UNIQUE,
table_id INTEGER REFERENCES table_seat(id),
customer_phone VARCHAR(20),
total_amount DECIMAL(10,2) NOT NULL,
status VARCHAR(20) DEFAULT 'pending', -- pending/confirmed/cooking/served/completed/canceled
remark TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
paid_at DATETIME
);
-- 订单明细表
CREATE TABLE order_item (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL REFERENCES orders(id),
dish_id INTEGER NOT NULL REFERENCES dish(id),
dish_name VARCHAR(100
