
简介这是一套面向微信小程序初学者与前端开发者的点餐系统实战源码包围绕菜单展示、分类浏览、购物车、订单管理、微信支付、用户登录与后台管理等核心模块展开帮助读者在真实项目中理解小程序从原型设计、数据库规划到接口对接与上线审核的完整链路。压缩包共438个文件约1.44MB以117个js业务逻辑脚本、86个md说明文档、82个json配置、67个png界面素材为主另含wxss样式、wxml结构、ts类型文件及eslint、yml等工程配置目录层次清晰便于按模块检索与二次修改。目前已有13762人学习下载适合希望借助现成demo快速跑通点餐流程、对照源码补齐数据绑定、网络请求、本地存储与支付集成等关键知识点的开发者参考实践。1. 微信小程序点餐系统从扫码到后厨出单一条能跑通的链路长什么样周末去一家新开的快餐店桌上贴着一张二维码扫码后直接进小程序点餐、支付后厨打印机同步出单。这套流程背后就是微信小程序点餐系统在跑。它解决的核心问题很具体把「顾客看菜单、下单、付款」和「后厨接单、出餐」这两段原本靠服务员口头传递的链路压缩成一次扫码加一次点击。适合谁做中小餐饮门店想省人力、想接外卖平台之外的私域订单或者开发者想拿一个完整项目练手微信小程序开发实战。这篇文章不讲空泛概念只讲一条能跑通的链路前端页面怎么搭、登录和手机号怎么拿、订单怎么落到后端、后厨怎么收到通知以及我踩过的那些坑。读完你至少能判断这个方向值不值得投入以及第一步该动哪里。2. 点餐系统的小程序端页面结构、登录与手机号获取2.1 先定页面骨架再谈交互点餐小程序的页面数量不多但每个页面的职责必须切干净。常见做法是拆成五个页面首页门店信息加扫码入口、菜单页分类加菜品列表、购物车页已选菜品加数量加减、订单确认页桌号、备注、优惠、订单详情页状态流转。页面之间用wx.navigateTo跳转购物车数据用全局状态管理不要在每个页面里各存一份。我一般会在app.json里先把tabBar配好只留「点餐」和「我的订单」两个入口。菜单页的分类用左侧竖排、右侧菜品列表的经典布局滚动联动用scroll-view的scroll-into-view属性实现。这里有个容易忽略的点菜品图片一定要走 CDN 并压缩到 200KB 以内否则菜单页首屏加载会明显卡顿用户等三秒就可能退出。{ pages: [ pages/index/index, pages/menu/menu, pages/cart/cart, pages/order/order, pages/orderDetail/orderDetail ], tabBar: { list: [ { pagePath: pages/menu/menu, text: 点餐 }, { pagePath: pages/order/order, text: 我的订单 } ] }, window: { navigationBarTitleText: 点餐, navigationBarBackgroundColor: #ffffff } }这段配置决定了小程序的页面注册顺序和底部导航。pages数组第一项是启动页通常放首页tabBar的list至少两项每项要有pagePath和text。注意tabBar页面必须出现在pages数组里否则真机上底部导航会直接不显示这个坑我见过不止一次。2.2 登录换 code手机号要单独授权微信小程序的登录流程分两步先wx.login拿临时 code后端用 code 换openid和session_key再通过button的open-typegetPhoneNumber获取手机号。很多新手会把这两步混在一起结果发现拿不到手机号。原因是手机号获取需要用户主动点击按钮授权不能静默调用。// 第一步静默登录拿 code 换 openid wx.login({ success: (res) { if (res.code) { wx.request({ url: https://your-api.com/auth/login, method: POST, data: { code: res.code }, success: (resp) { // 后端返回自定义登录态 token存本地 wx.setStorageSync(token, resp.data.token); } }); } } }); // 第二步用户点击按钮授权手机号 // WXML 中button open-typegetPhoneNumber bindgetphonenumberonGetPhone onGetPhone(e) { if (e.detail.code) { // 把 e.detail.code 发给后端后端换真实手机号 wx.request({ url: https://your-api.com/auth/phone, method: POST, header: { Authorization: wx.getStorageSync(token) }, data: { code: e.detail.code } }); } }逻辑说明wx.login的 code 只能用一次五分钟内有效后端换完openid后要生成自己的 token 返回给前端。手机号授权返回的e.detail.code也是临时凭证同样要后端去换。参数上token建议放header的Authorization字段不要拼在 URL 里。注意如果用户拒绝授权手机号要有降级方案比如允许先下单、后补手机号否则转化率会掉一截。2.3 购物车与订单确认的数据流购物车不要只存在页面 data 里因为用户可能从菜单页加菜后直接跳订单页。我一般用getApp().globalData.cart存一份同时用wx.setStorageSync做持久化防止小程序被系统回收后数据丢失。订单确认页提交前必须做三件事校验桌号是否为空、校验购物车是否为空、把菜品列表和总价重新算一遍再发给后端。前端算价只是展示后端必须再算一次这是防止篡改的基本要求。// 提交订单 const app getApp(); const cart app.globalData.cart; if (!cart.length) { wx.showToast({ title: 请先选菜, icon: none }); return; } wx.request({ url: https://your-api.com/order/create, method: POST, header: { Authorization: wx.getStorageSync(token) }, data: { tableNo: this.data.tableNo, items: cart.map(i ({ dishId: i.id, count: i.count })), remark: this.data.remark }, success: (res) { // 清空购物车跳订单详情 app.globalData.cart []; wx.redirectTo({ url: /pages/orderDetail/orderDetail?id${res.data.orderId} }); } });参数说明tableNo来自扫码时带的参数或用户手输items只传菜品 ID 和数量单价和总价由后端查库计算。这样即使前端被改后端也不会算错钱。常见错误是把单价也传给后端并信任它这是典型的翻车点。3. 后端订单服务接口设计、状态机与后厨通知3.1 订单接口的最小集合一个能跑的点餐系统后端至少要有五个接口登录换 openid、手机号解密、菜单查询、创建订单、查询订单状态。菜单查询要支持按分类返回创建订单要返回订单号和支付参数。订单状态机建议用数字枚举0 待支付、1 已支付、2 制作中、3 已完成、4 已取消。状态流转只能单向不能从已完成跳回制作中否则后厨会重复出单。# Django 示例订单模型与状态枚举 from django.db import models class Order(models.Model): STATUS_CHOICES [ (0, 待支付), (1, 已支付), (2, 制作中), (3, 已完成), (4, 已取消), ] order_no models.CharField(max_length32, uniqueTrue) openid models.CharField(max_length64) table_no models.CharField(max_length16) total_price models.DecimalField(max_digits10, decimal_places2) status models.IntegerField(choicesSTATUS_CHOICES, default0) created_at models.DateTimeField(auto_now_addTrue) class OrderItem(models.Model): order models.ForeignKey(Order, related_nameitems, on_deletemodels.CASCADE) dish_id models.IntegerField() dish_name models.CharField(max_length64) price models.DecimalField(max_digits10, decimal_places2) count models.IntegerField()这段模型定义了两个表订单主表和订单明细表。order_no要唯一建议用时间戳加随机数生成。status用整数枚举方便前端判断和数据库索引。注意total_price用DecimalField而不是FloatField金额计算用浮点会出现 0.10.2 不等于 0.3 的经典问题对账时就是血泪经验。3.2 支付回调与状态推进微信支付的回调是订单状态从「待支付」变「已支付」的唯一可靠来源。不要依赖前端支付成功的回调来改状态因为用户可能支付后直接杀进程。后端收到支付回调后先验签再查订单是否已处理最后把状态改成已支付并触发后厨通知。这里必须做幂等同一笔回调可能来多次用order_no加transaction_id做唯一约束。# 支付回调处理伪代码验签逻辑按微信官方文档实现 def pay_callback(request): data parse_xml(request.body) if not verify_sign(data): return xml_response(FAIL, 签名错误) order_no data[out_trade_no] order Order.objects.filter(order_noorder_no).first() if not order: return xml_response(FAIL, 订单不存在) if order.status ! 0: # 已处理过直接返回成功保证幂等 return xml_response(SUCCESS, OK) order.status 1 order.save() notify_kitchen(order) # 触发后厨通知 return xml_response(SUCCESS, OK)逻辑说明先验签防止伪造回调再查订单状态做幂等最后改状态并通知后厨。参数上out_trade_no就是我们的order_notransaction_id是微信侧流水号建议存到订单表里方便对账。注意回调必须返回 XML 格式的SUCCESS否则微信会持续重试把服务器打满。3.3 后厨通知的三种落地方式后厨怎么收到新订单常见做法有三种一是后厨放一台平板页面轮询订单接口二是用 WebSocket 长连接推送三是接小票打印机通过云打印服务出单。轮询最简单但延迟高、请求多WebSocket 实时性好但需要维护连接云打印最省事但依赖第三方服务。我一般会先上轮询间隔五秒等单量上来再换 WebSocket。// 后厨平板轮询示例 setInterval(() { wx.request({ url: https://your-api.com/kitchen/pending, header: { Authorization: kitchenToken }, success: (res) { if (res.data.length) { // 有新订单播提示音并刷新列表 playNoticeSound(); this.setData({ orders: res.data }); } } }); }, 5000);参数说明kitchenToken是后厨账号的独立 token和顾客 token 分开权限只允许查待制作订单和改状态。轮询间隔不要低于三秒否则后端压力大。注意后厨页面要加自动重连和错误提示网络断了要能看出来不然漏单了都不知道。4. 避坑与排查点餐系统上线前必须过的五道坎4.1 手机号获取失败提示「该小程序不支持」现象用户点击获取手机号按钮弹窗提示不支持。原因手机号快速验证需要小程序主体为企业认证个人主体没有这个权限。解决个人开发者可以先用「手机号验证组件」的替代方案比如让用户手动输入手机号加短信验证码或者只保留 openid 登录手机号作为可选字段。4.2 支付回调没收到订单一直待支付现象用户明明付了钱订单状态还是 0。原因回调地址配置错误或者服务器防火墙拦了微信的请求。解决检查微信支付后台的回调地址是否公网可达用curl从外网访问一下再看服务器日志有没有收到请求。如果验签失败检查密钥和证书是否匹配。4.3 菜单页图片加载慢首屏白屏现象进入菜单页后图片一张张慢慢出来用户等不及就退了。原因图片没压缩、没走 CDN、没设懒加载。解决图片统一压到 200KB 以内上传到对象存储并开 CDNimage标签加lazy-load属性。首屏只加载前两个分类的图片后面的滚动到再加载。4.4 购物车数据丢失用户重新选菜现象用户加完菜切到微信聊天再回来购物车空了。原因小程序被系统回收页面 data 重置。解决购物车数据同时存globalData和wx.setStorageSync页面onShow时从本地恢复。注意存储有 10MB 上限购物车数据很小不用担心。4.5 后厨重复出单同一订单打印两次现象后厨小票机同一单出了两张。原因支付回调重复触发或者轮询接口没有做去重。解决回调处理加幂等判断轮询接口返回的订单列表要带printed标记后厨端打印后调接口标记已打印。更稳妥的做法是后端在状态变成「已支付」时只发一次通知用消息队列保证。5. 进阶技巧用订阅消息把「出餐提醒」做成闭环前面讲的链路到后厨出单就结束了但顾客还不知道餐好了。微信小程序的订阅消息可以补上这一环用户下单时授权「出餐提醒」后厨点「已完成」时后端调订阅消息接口给用户推一条「您的餐已准备好」。这个功能不难但有几个细节决定成败。首先订阅消息必须由用户主动点击授权且每次授权只能发一条。所以要在订单确认页放一个「接收出餐提醒」的勾选框用户勾了才调wx.requestSubscribeMessage。其次模板要提前在微信公众平台申请字段和内容要匹配否则发送会报错。最后后端发送时要带page参数让用户点消息直接跳到订单详情页。// 下单前请求订阅授权 wx.requestSubscribeMessage({ tmplIds: [YOUR_TEMPLATE_ID], success: (res) { if (res[YOUR_TEMPLATE_ID] accept) { // 用户同意把授权状态传给后端记录 wx.request({ url: https://your-api.com/order/subscribe, method: POST, header: { Authorization: wx.getStorageSync(token) }, data: { orderId: this.data.orderId, subscribed: true } }); } } });参数说明tmplIds是模板 ID 数组一次最多三个。res里对应模板 ID 的值为accept表示同意reject表示拒绝。注意这个接口必须在用户点击事件里调用不能放在onLoad里静默调否则会失败。后端发送订阅消息的代码大致如下# Django 调用微信订阅消息发送接口 import requests def send_ready_notice(openid, order_no): url https://api.weixin.qq.com/cgi-bin/message/subscribe/send params {access_token: get_access_token()} body { touser: openid, template_id: YOUR_TEMPLATE_ID, page: fpages/orderDetail/orderDetail?orderNo{order_no}, data: { thing1: {value: 您的餐已准备好}, character_string2: {value: order_no} } } resp requests.post(url, paramsparams, jsonbody) return resp.json()逻辑说明access_token要缓存不能每次现取否则会触发频率限制。data里的字段名要和模板定义一致thing类型限 20 个字符以内character_string限 32 位。注意发送失败要记日志常见错误是用户没授权或授权次数用完这时候不要重试重试也没用。验证方法很简单下单时勾选提醒后厨点完成后看手机有没有收到消息。如果没收到先查access_token是否有效再查模板 ID 和字段名是否匹配最后看用户授权次数是否已耗尽。我一般会在后端加一个「补发」按钮万一漏发可以手动补但补发也要消耗授权次数所以别滥用。这套点餐系统从扫码到出餐提醒核心链路就这些。我自己的习惯是先把登录和下单跑通再补支付和后厨通知最后加订阅消息。每加一个环节就用真机测一遍不要等全做完再联调否则问题堆在一起很难定位。希望帮到你。本文还有配套的精品资源点击获取