资讯详情

微信小程序餐饮点餐外卖源码:从扫码到出餐完整链路与二次开发指南

📅 2026/10/17 7:45:16 | 华诺云谱 👁 阅读
微信小程序餐饮点餐外卖源码:从扫码到出餐完整链路与二次开发指南
简介这是一套面向餐饮行业开发者与小程序学习者的微信小程序点餐外卖源码基于微信开发者工具与 WXML、WXSS、JavaScript 技术栈构建覆盖在线点餐、外卖配送、叫号排队、微信支付、用户与订单管理、数据统计分析等完整业务模块适合希望快速搭建餐饮小程序或研究小程序工程实践的初中级开发者直接复用与二次定制。资源包共 521 个文件以 137 个 js 业务逻辑脚本、108 个 wxss 样式表、104 个 json 配置、102 个 wxml 页面结构及 44 个 wxs 脚本为主另含少量图片与说明文档整体约 833KB目录结构清晰便于按模块检索阅读。目前已有 2331 人学习下载。通过研读源码读者可掌握菜品展示、购物车、订单状态流转、支付验证与退款、地图定位配送等实现思路并以此为基础按自身需求进行定制化开发。1. 餐饮点餐小程序从扫码到出餐一套能跑通的完整链路扫码点餐这件事用户端体验就三步扫码、选菜、下单。但落到代码上背后是菜单渲染、规格选择、购物车合并、订单状态流转、后厨出餐一整套链路。这份微信小程序餐饮点餐外卖源码把这条链路完整跑通了——不是那种只画了页面的空壳 Demo而是带后台管理、带订单状态机、带规格 SKU 计算的可用工程。适合谁想快速搭一套点餐系统的独立开发者、需要给中小餐饮店做定制的外包团队、以及想学小程序完整项目架构的学生。你拿到手改改菜品数据、换个配色就能上线试跑二次开发的入口也留得很清楚。下面我按「能干什么 → 怎么跑 → 怎么改」的顺序把这份源码拆一遍。2. 项目架构与技术选型前后端怎么分工2.1 整体分层小程序端 服务端 管理后台这套源码的结构是典型的三段式。小程序端负责用户交互服务端提供 API 和订单处理管理后台给商家用来看订单、改菜品、管库存。三者通过 HTTP 接口通信数据格式统一用 JSON。小程序端用的是原生微信小程序框架WXML WXSS JS没有上 uni-app 或 Taro 这类跨端方案。好处是包体积小、启动快、调用微信原生能力比如微信支付、获取手机号没有中间层损耗。服务端常见做法是 Node.js Express 或 Koa数据库用 MySQL缓存层用 Redis 存购物车和会话。管理后台一般是 Vue Element UI 或者 React Ant Design 的单页应用。为什么这么选餐饮点餐场景对实时性要求不算极端但对稳定性要求高——饭点高峰期不能崩。原生小程序 轻量 Node 服务端的组合部署简单一台 2 核 4G 的云服务器就能扛住中小型餐厅的并发。如果你要上多门店连锁那服务端得加一层网关做路由分发这是后话。2.2 目录结构拿到源码先看这几个文件夹解压之后根目录一般长这样project-root/ ├── miniprogram/ # 小程序端源码 │ ├── pages/ # 页面目录 │ │ ├── index/ # 首页菜单展示 │ │ ├── cart/ # 购物车 │ │ ├── order/ # 订单确认与支付 │ │ ├── orderList/ # 订单列表 │ │ └── mine/ # 个人中心 │ ├── components/ # 公共组件 │ │ ├── dish-card/ # 菜品卡片 │ │ ├── spec-popup/ # 规格选择弹窗 │ │ └── cart-bar/ # 底部购物车栏 │ ├── utils/ # 工具函数 │ │ ├── request.js # 网络请求封装 │ │ └── cart.js # 购物车逻辑 │ ├── app.js # 小程序入口 │ ├── app.json # 全局配置 │ └── app.wxss # 全局样式 ├── server/ # 服务端源码 │ ├── routes/ # 路由定义 │ ├── controllers/ # 业务逻辑 │ ├── models/ # 数据模型 │ ├── middlewares/ # 中间件鉴权、日志 │ └── app.js # 服务入口 ├── admin/ # 管理后台 │ ├── src/ │ │ ├── views/ # 页面 │ │ └── api/ # 接口封装 │ └── package.json └── README.md # 部署说明拿到源码第一件事不是急着跑而是把miniprogram/pages/和server/routes/这两个目录翻一遍。页面目录决定了用户能干什么路由目录决定了后端能响应什么。两边对上了说明项目结构是完整的。2.3 技术栈清单与版本注意事项层级技术选型常见版本备注小程序端微信原生框架基础库 2.20需在开发者工具中调最低版本服务端Node.js ExpressNode 16建议用 LTS 版本数据库MySQL5.7 / 8.08.0 注意密码加密方式缓存Redis6.x存购物车和 session管理后台Vue 2 Element UIVue 2.6部分老项目用 Vue 2支付微信支付 V2/V3—需商户号配置提示如果服务端用的是 MySQL 8.0连接时可能报ER_NOT_SUPPORTED_AUTH_MODE错误原因是 8.0 默认用caching_sha2_password加密方式而老版本 mysql 驱动不支持。解决办法是改用户密码加密方式为mysql_native_password或者升级 mysql 驱动包。3. 跑起来环境搭建与首次启动3.1 小程序端导入与配置修改打开微信开发者工具选择「导入项目」目录指向miniprogram/文件夹。导入后第一件事是改app.js里的全局配置// app.js App({ globalData: { baseUrl: http://localhost:3000/api, // 改成你的服务端地址 userInfo: null, cartList: [] // 全局购物车数据 }, onLaunch() { // 检查本地是否有 token有则自动登录 const token wx.getStorageSync(token); if (token) { this.globalData.token token; } } })baseUrl是唯一必须改的地方。本地调试时填http://localhost:3000/api但注意微信开发者工具需要勾选「不校验合法域名」才能请求 localhost。上线前必须换成备案过的 HTTPS 域名并在小程序后台配置 request 合法域名。cartList放在 globalData 里是为了让购物车数据在多个页面之间共享——菜单页加菜、购物车页改数量、订单页结算都读同一份数据。这是小程序开发里很常见的做法比用状态管理库轻量。3.2 服务端启动数据库建表和依赖安装服务端启动分三步。第一步装依赖cd server npm install第二步建数据库。源码里一般会带一个init.sql文件直接导入mysql -u root -p your_database_name init.sql这个 SQL 文件会创建几张核心表dishes菜品、categories分类、orders订单、order_items订单明细、users用户。如果源码没带 SQL 文件你需要根据models/目录下的模型定义手动建表。第三步改数据库配置。找到server/config/db.js或类似文件// server/config/db.js module.exports { host: localhost, port: 3306, user: root, password: your_password, // 改成你的数据库密码 database: restaurant, // 改成你建的库名 connectionLimit: 10 // 连接池大小按并发量调 }connectionLimit这个参数值得说一下。默认 10 在开发环境够用但饭点高峰期如果同时有几十个用户下单连接池会被打满后面的请求排队等连接表现为接口响应变慢甚至超时。中小餐厅调到 20 到 30 比较稳妥但也要看数据库服务器的承载能力不是越大越好。然后启动npm run dev # 或者 node app.js看到Server running on port 3000就说明服务端起来了。3.3 管理后台启动与联调验证管理后台通常是独立的前端项目cd admin npm install npm run serve启动后访问http://localhost:8080用默认管理员账号登录常见是 admin / 123456具体看 README。登录后先做一件事添加几个菜品分类和菜品数据。因为小程序端的菜单是从服务端拉的数据库里没数据小程序首页就是空的。联调验证的顺序是管理后台加菜品 → 小程序端刷新看菜单 → 加购物车 → 下单 → 管理后台看订单。这条链路走通了说明前后端对接没问题。注意微信支付在开发阶段没法真实调起源码里一般会做一个「模拟支付」的开关。在server/controllers/order.js里找到支付回调部分把mockPay设为true下单后直接标记为已支付跳过微信支付流程。上线前记得改回来。4. 核心模块拆解购物车、规格 SKU 与订单状态机4.1 购物车逻辑本地存储与服务端同步的取舍购物车是点餐系统里最容易出 bug 的地方。这份源码的做法是未登录时购物车存在本地 Storage登录后同步到服务端 Redis。先看本地购物车的核心逻辑// utils/cart.js const CART_KEY cart_list; // 添加菜品到购物车 function addToCart(dish, spec, count 1) { let cart wx.getStorageSync(CART_KEY) || []; // 用 dishId specId 作为唯一标识同菜品不同规格算两条 const key ${dish.id}_${spec ? spec.id : default}; const existIndex cart.findIndex(item item.key key); if (existIndex -1) { // 已存在则累加数量 cart[existIndex].count count; } else { cart.push({ key, dishId: dish.id, name: dish.name, price: spec ? spec.price : dish.price, specName: spec ? spec.name : , count, image: dish.image }); } wx.setStorageSync(CART_KEY, cart); return cart; } // 计算购物车总价 function calcTotal(cart) { return cart.reduce((sum, item) sum item.price * item.count, 0); }关键点在key的生成规则dishId specId。同一道菜如果选了不同规格比如大份/小份必须算两条记录不能合并。我见过有人只用dishId做 key结果用户选了大份又选小份数量直接叠加价格算错这就是血泪教训。calcTotal用reduce累加注意价格单位。如果数据库存的是「分」前端展示要除以 100如果存的是「元」浮点数累加会有精度问题比如 0.1 0.2 0.30000000000000004。常见做法是全程用「分」做整数运算展示时再转「元」。4.2 规格 SKU 计算多规格组合的价格与库存处理餐饮菜品和电商商品不一样规格通常只有一两个维度份量、辣度、加料不像衣服有颜色尺码版型那么多组合。但逻辑是一样的每个规格组合对应一个独立的价格和库存。// 规格选择弹窗中的价格计算 Page({ data: { dish: null, specs: [], // 规格列表 selectedSpec: null, // 当前选中的规格 finalPrice: 0 }, onLoad(options) { const dishId options.id; this.fetchDishDetail(dishId); }, fetchDishDetail(dishId) { wx.request({ url: ${app.globalData.baseUrl}/dishes/${dishId}, success: (res) { const { dish, specs } res.data; this.setData({ dish, specs, finalPrice: dish.price, // 默认显示基础价 selectedSpec: null }); } }); }, // 用户点击某个规格 onSelectSpec(e) { const spec e.currentTarget.dataset.spec; this.setData({ selectedSpec: spec, finalPrice: spec.price // 切换规格时更新价格 }); } });这里有个细节finalPrice初始值是dish.price也就是基础价。如果菜品有必选规格基础价应该设为最低规格价或者干脆不显示等用户选完规格再显示最终价。否则用户看到 9.9 元点进去选完规格变成 15.9 元会有被套路的感觉。库存扣减的时机也要注意。常见做法是下单时扣库存而不是加入购物车时扣。因为购物车里的东西用户可能不结算如果加购就扣库存会出现「库存被占着但没人买」的情况。下单时扣库存也要加锁防止超卖——用 Redis 的DECR原子操作或者数据库的UPDATE ... WHERE stock count都能实现。4.3 订单状态机从待支付到已完成的流转订单状态是整个系统里最需要严谨对待的部分。这份源码的订单状态定义如下状态码状态名可执行操作触发条件0待支付支付、取消用户下单1已支付/待接单接单、拒单支付成功回调2已接单/制作中出餐商家接单3已出餐/待取餐完成后厨出餐4已完成—用户取餐-1已取消—用户取消或商家拒单状态流转的核心代码在服务端// server/controllers/order.js const ORDER_STATUS { PENDING_PAY: 0, PAID: 1, ACCEPTED: 2, READY: 3, COMPLETED: 4, CANCELLED: -1 }; // 状态流转白名单定义每个状态能转到哪些状态 const STATUS_TRANSITIONS { [ORDER_STATUS.PENDING_PAY]: [ORDER_STATUS.PAID, ORDER_STATUS.CANCELLED], [ORDER_STATUS.PAID]: [ORDER_STATUS.ACCEPTED, ORDER_STATUS.CANCELLED], [ORDER_STATUS.ACCEPTED]: [ORDER_STATUS.READY], [ORDER_STATUS.READY]: [ORDER_STATUS.COMPLETED], [ORDER_STATUS.COMPLETED]: [], [ORDER_STATUS.CANCELLED]: [] }; async function updateOrderStatus(orderId, newStatus) { const order await OrderModel.findById(orderId); if (!order) throw new Error(订单不存在); const allowed STATUS_TRANSITIONS[order.status] || []; if (!allowed.includes(newStatus)) { throw new Error(订单状态不允许从 ${order.status} 变更为 ${newStatus}); } await OrderModel.updateStatus(orderId, newStatus); return { success: true }; }STATUS_TRANSITIONS这个白名单是防止状态乱跳的关键。没有它可能出现「已完成的订单又被取消」或者「待支付的订单直接变成已出餐」这种玄学问题。每次状态变更前先查白名单不合法就抛错日志里记下来排查问题时一目了然。5. 避坑与排查上线前必须处理的几个问题5.1 微信支付回调收不到现象用户支付成功但订单状态还是「待支付」管理后台看不到新订单。原因通常有三个一是回调地址没配好微信服务器 POST 不到你的接口二是回调接口没有做签名验证被微信判定为不安全三是服务端用了 HTTPS 但证书链不完整微信服务器握手失败。解决先在微信支付商户平台检查回调地址是否填对必须是公网可访问的 HTTPS 地址。然后在服务端加日志把微信回调的原始请求体打印出来确认是否收到请求。如果收到了但验签失败检查 API 密钥是否配置正确。证书问题用openssl s_client -connect yourdomain.com:443检查证书链是否完整。5.2 购物车数据在页面切换后丢失现象用户在菜单页加了菜切到个人中心再切回来购物车空了。原因购物车数据存在页面的data里页面卸载后数据就没了。小程序页面切换时非 tabBar 页面会被销毁。解决把购物车数据存在globalData或本地 Storage 里页面onShow时重新读取。这份源码用的是 Storage globalData 双写写的时候两边都写读的时候优先读 globalData没有再从 Storage 读。这样既保证页面切换不丢数据又保证小程序被杀掉后重新打开数据还在。5.3 菜品图片加载慢导致页面卡顿现象菜单页滚动时卡顿图片半天加载不出来。原因菜品图片没有压缩原图可能好几 MB或者图片存在本地服务器带宽不够。解决上传菜品图片时做压缩宽度控制在 750px 以内格式用 WebP。图片存储建议用对象存储比如云厂商的 OSS/COS不要直接放应用服务器上。小程序端用lazy-load属性开启图片懒加载只加载可视区域内的图片。5.4 订单号重复或并发下单出错现象两个用户同时下单订单号一样或者库存扣成负数。原因订单号生成用了时间戳同一毫秒内多个请求会重复库存扣减没有加锁并发时超卖。解决订单号用「时间戳 用户ID后四位 随机数」的组合或者用数据库自增ID加前缀。库存扣减用UPDATE dishes SET stock stock - ? WHERE id ? AND stock ?靠数据库的行锁保证原子性。返回影响行数为 0 就说明库存不足直接回滚。5.5 开发者工具能跑真机预览白屏现象微信开发者工具里一切正常扫码真机预览打开是白屏。原因最常见的是baseUrl用了localhost真机访问不到你电脑的本地服务其次是用了开发者工具的「不校验合法域名」选项真机没有这个选项。解决真机调试时把baseUrl改成电脑的局域网 IP比如http://192.168.1.100:3000/api确保手机和电脑在同一个 WiFi 下。上线前必须换成备案的 HTTPS 域名。另外检查小程序后台的「开发设置」里request 合法域名有没有配好。6. 二次开发换数据、加功能、改界面的实操思路拿到源码后最省力的改造路径是「先换数据再加功能最后改界面」。换数据最简单——管理后台加菜品、改分类、调价格不用动代码。加功能就要看你的需求落在哪一层了。比如要加一个「预约取餐」功能。小程序端需要在订单确认页加一个时间选择器服务端需要在订单表加一个pickup_time字段管理后台的订单列表要显示预约时间。改动涉及三个端但每个端的改动都不大。具体做法先在orders表加字段ALTER TABLE orders ADD COLUMN pickup_time DATETIME然后在server/controllers/order.js的创建订单逻辑里接收并存储这个字段小程序端用picker modetime组件选时间管理后台在表格里加一列。再比如要改配色。小程序的全局样式在app.wxss里主题色一般定义在page或view的 CSS 变量里。找到--primary-color这个变量改一个值全站生效。但注意有些组件可能硬编码了颜色需要全局搜索替换。我一般改别人源码的习惯是先跑通再在本地建一个 git 分支每改一个功能提交一次。这样改崩了随时能回滚不会出现「改到一半忘了原来什么样」的情况。另外改服务端代码之前先把数据库备份一份尤其是改表结构的时候——我有次加字段忘了加默认值导致老数据查询报错排查了半天。还有一个技巧如果源码的某个模块你暂时用不上比如外卖配送不要删掉用配置开关关掉就行。在app.json里把对应的页面注释掉服务端路由里把配送相关的接口挂起。这样以后要启用的时候改回来就行不用重新写。从那以后我每次拿到新源码都强制走一遍「跑通 → 备份 → 小步改 → 验证」的流程再急也不跳步。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑