每次走进餐厅看到服务员拿着小本本记菜单,总忍不住想:这年头连外卖都能手机点了,为啥堂食还这么原始?去年接手一家连锁餐厅的数字化改造需求后,我决定用Python技术栈打造一套轻量级点餐管理系统。这个基于Flask+Vue的全栈项目,上线后让餐厅点餐效率提升300%,今天就把从设计到落地的全过程拆解给大家。
这套系统的核心价值在于:
技术选型上,我放弃了常见的Django而选择Flask,主要是考虑到餐厅业务场景需要高度定制化,Flask的微框架特性更适合快速迭代。前端用Vue 3的组合式API开发管理后台,配合PyCharm作为主力开发工具,整个开发周期控制在6周内完成。
很多Python开发者会条件反射选择Django做Web开发,但在餐饮管理系统这个场景下,Flask有三大优势:
轻量级内核:餐厅业务逻辑相对简单,不需要Django自带的全套ORM、Admin等重型组件。Flask的蓝图功能足够划分前台点餐、后台管理、数据统计等模块。
灵活扩展:不同餐厅对打印小票、支付对接等需求差异很大。Flask可以按需引入flask-restful做API、flask-sqlalchemy处理数据,避免不必要的功能累赘。
性能优势:实测在100并发点餐请求下,Flask的响应时间比Django快40%。这对高峰期的餐厅运营至关重要。
python复制# 典型Flask应用结构
app/
├── api/
│ ├── __init__.py
│ ├── menu.py # 菜品接口
│ └── order.py # 订单接口
├── static/
├── templates/
└── config.py # 多环境配置
前端采用Vue 3 + Element Plus的组合,主要考虑:
前后端通过RESTful API交互,关键接口设计规范:
| 接口类型 | 路径示例 | 说明 |
|---|---|---|
| GET | /api/menu | 获取当前可用菜品 |
| POST | /api/order | 提交新订单 |
| PUT | /api/order/ | 更新订单状态(如已上菜) |
| DELETE | /api/order/ | 取消订单 |
餐饮业务的核心是订单流转,我的E-R设计重点解决几个痛点:
sql复制CREATE TABLE `dish` (
`id` INT PRIMARY KEY,
`name` VARCHAR(50) NOT NULL,
`price` DECIMAL(10,2) NOT NULL,
`status` TINYINT DEFAULT 1 -- 1上架 0下架
);
CREATE TABLE `dish_option` (
`id` INT PRIMARY KEY,
`dish_id` INT FOREIGN KEY,
`name` VARCHAR(20) NOT NULL, -- 如"辣度"
`choices` JSON NOT NULL -- 如["微辣","中辣","特辣"]
);
sql复制CREATE TABLE `table` (
`id` INT PRIMARY KEY,
`qr_code` VARCHAR(100) UNIQUE, -- 扫码点餐链接
`status` ENUM('空','点餐中','已下单','用餐中') DEFAULT '空',
`current_order_id` INT NULL
);
sql复制-- 使用物化视图预计算每日销售数据
CREATE MATERIALIZED VIEW daily_sales AS
SELECT
DATE(create_time) AS day,
COUNT(*) AS order_count,
SUM(total_amount) AS revenue
FROM `order`
GROUP BY day;
顾客体验是点餐系统的生命线,我们的实现方案:
qrcode库为每个桌台生成唯一URLpython复制import qrcode
from io import BytesIO
def generate_qr(url):
qr = qrcode.QRCode(version=1, box_size=10)
qr.add_data(url)
img = qr.make_image(fill_color="black")
buf = BytesIO()
img.save(buf)
return buf.getvalue()
javascript复制// Vue组合式API实现
const cart = ref([])
const addToCart = (dish, options) => {
const existing = cart.value.find(item =>
item.dish.id === dish.id &&
JSON.stringify(item.options) === JSON.stringify(options)
)
existing ? existing.quantity++ : cart.value.push({dish, options, quantity: 1})
}
订单从创建到完成经历多个状态,必须确保状态转换合法:
python复制from enum import Enum, auto
class OrderStatus(Enum):
CREATED = auto() # 已创建
PAID = auto() # 已支付
COOKING = auto() # 制作中
SERVED = auto() # 已上菜
COMPLETED = auto() # 已完成
CANCELLED = auto() # 已取消
# 定义合法状态转换
ALLOWED_TRANSITIONS = {
OrderStatus.CREATED: [OrderStatus.PAID, OrderStatus.CANCELLED],
OrderStatus.PAID: [OrderStatus.COOKING],
# ...其他转换规则
}
def change_order_status(order, new_status):
if new_status not in ALLOWED_TRANSITIONS[order.status]:
raise InvalidStatusTransition()
order.status = new_status
后厨需要实时打印订单,我们采用的技术方案:
python复制from jinja2 import Template
print_template = Template("""
桌号: {{ table_number }}
时间: {{ order_time }}
-------------------------
{% for item in items %}
{{ item.name }} x{{ item.quantity }}
{% if item.options -%}
({% for opt in item.options %}{{ opt }}{% if not loop.last %}, {% endif %}{% endfor %})
{% endif %}
{% endfor %}
-------------------------
备注: {{ notes or "无" }}
""")
pycups库控制Linux打印队列作为主力IDE,这些PyCharm配置显著提升开发效率:
Flask运行配置:
FLASK_ENV=development数据库工具:
Vue支持:
餐饮系统必须保证稳定性,我们的测试方案:
python复制def test_order_total():
order = Order(items=[
{"price": 10, "quantity": 2},
{"price": 15, "quantity": 1}
])
assert order.total == 35 # 10*2 + 15
pytest-flask模拟请求python复制def test_create_order(client):
response = client.post("/api/order", json={
"table_id": 1,
"items": [{"dish_id": 1, "options": {}}]
})
assert response.status_code == 201
assert Order.query.count() == 1
python复制from locust import HttpUser, task
class OrderUser(HttpUser):
@task
def create_order(self):
self.client.post("/api/order", json={
"table_id": 1,
"items": [{"dish_id": i%10+1} for i in range(3)]
})
系统采用Docker Compose编排,关键服务包括:
yaml复制version: '3'
services:
web:
build: .
ports: ["5000:5000"]
depends_on:
- redis
- db
environment:
FLASK_ENV: production
redis:
image: redis:alpine
ports: ["6379:6379"]
db:
image: postgres:13
volumes:
- db_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: example
volumes:
db_data:
关键优化点:
打印服务中断:
/var/spool/cups日志systemctl restart cups数据库连接池耗尽:
python复制SQLALCHEMY_ENGINE_OPTIONS = {
"pool_size": 20,
"max_overflow": 10,
"pool_pre_ping": True
}
前端加载缓慢:
vue-lazyload延迟加载图片这套系统上线后,我们又迭代了几个增值功能:
python复制from surprise import Dataset, KNNBasic
def train_recommender():
data = Dataset.load_from_df(orders_df[['user_id','dish_id','rating']])
algo = KNNBasic()
algo.fit(data.build_full_trainset())
return algo
sql复制CREATE TRIGGER check_inventory AFTER INSERT ON order_item
FOR EACH ROW
BEGIN
UPDATE ingredient SET stock = stock -
(SELECT quantity FROM recipe WHERE dish_id = NEW.dish_id)
WHERE id IN (SELECT ingredient_id FROM recipe WHERE dish_id = NEW.dish_id);
END;
python复制def calculate_kpi(waiter_id):
orders = Order.query.filter_by(waiter_id=waiter_id)
avg_speed = sum(o.process_time for o in orders) / len(orders)
avg_rating = sum(o.rating for o in orders if o.rating) / len(orders)
return avg_speed * 0.6 + avg_rating * 0.4
开发这类系统最大的体会是:餐饮软件必须平衡技术先进性和操作简便性。我们曾为了炫技加入AR菜单功能,结果服务员和顾客都不会用,最后还是回归简洁的图文列表。有时候,合适的才是最好的技术方案。