微信小程序外卖点餐系统从0到1实战开发指南

发布时间:2026/9/30 6:20:42
微信小程序外卖点餐系统从0到1实战开发指南 1. 为什么我选择做一个外卖点餐小程序先说结论如果只挑一个练手项目来吃透微信小程序开发外卖点餐系统比绝大多数项目都合适。原因有三点餐购物车是典型的页面状态管理场景订单流转考验网络请求与接口设计支付环节能完整体验微信生态的登录、支付、订阅消息闭环。这个项目的标题叫微信小程序 #项目笔记# | 从0到1实现外卖点餐系统小程序看这个名字就知道这不是一个只做静态展示的页面而是一个从账号注册到正式上线全流程的完整项目。我做完这套系统之后最大的感受是表面上是写了一个点餐页面实际上是把微信小程序的页面框架、组件通信、全局状态、后端交互、异步接口、上线审核全部过了一遍。这套系统能做什么一句话概括用户端打开小程序看到附近商家点进店铺选菜品加购物车提交订单微信支付商家后台收到新订单处理出餐状态用户实时看到进度。如果你正在学习小程序开发或者想自己做一个能真实上线的商业项目作为作品集这套系统可以作为很好的参考。适合谁来看这篇笔记刚学完小程序基础语法、想做整合项目的人以及后端开发想了解小程序端完整实现方式的人。我会把每一步是怎么想、为什么这样选、踩过哪些坑都记录下来尽量还原真实开发过程而不是给一份百度都能搜到的代码汇总。2. 技术选型与整体架构设计2.1 前端框架原生小程序还是uni-app开发小程序第一步要做的决定就是用什么框架来写前端。我当时的候选有三个微信原生语法、uni-app、Taro。先说结论我选了原生语法。原因其实很简单这个项目要调的微信API很多登录、支付、订阅消息、地图定位。用原生框架可以少一层编译转换报错排查时直接对应官方文档不至于出现uni-app 里写的代码编译成小程序后行为不一致这类问题。很多做外包的朋友喜欢用 uni-app因为能一把梭多端但纯小程序项目上原生方案更稳维护也直观。还有一点在做决定时要考虑进去你的队友技术栈是什么样的。如果团队是 Vue 或 React 背景选用 uni-app 或 Taro 确实能让上手难度降低一截。但若是我这样以小程序为唯一交付目标的小项目原生的 WXML/WXSS/JS 三件套是官方第一优先支持的坑最少资料最多。2.2 后端方案选型自建接口还是云开发外卖点餐系统必须要后端至少要能保存商品、订单、用户信息。后端方案我当时认真纠结过两条路也推荐你根据自己情况来选。第一条路是微信云开发。云端数据库、云函数、存储一条龙不用自己买服务器不需要域名备案对新手极其友好。如果你点开微信开发者工具发现云开发按钮不见了那可能是因为你的小程序账号没开通云开发服务需要在云开发控制台创建环境才行。云开发的坑在于项目做大了之后集合之间的关联查询、事务操作会变得别扭调试也相对受限。第二条路是自己写接口服务。我用的是轻量级 Node.js 加 Express配一个 MySQL 数据库部署在一台小服务器上。这样做的好处是接口逻辑完全可控订单状态机、库存扣减、支付回调这些业务希望在服务端自己写起来更顺手。我最终选了自建接口。原因是外卖点餐系统的核心是订单流转服务端逻辑占大头我希望把控制权留在自己手里。云开发适合业务逻辑简单、以数据存取为主的应用而加购物车、下单、扣库存、改订单状态这种带事务的操作自建接口做起来更清晰。2.3 整体架构与目录结构规划技术方案确定后第一步要画的是数据流图。我最担心的点有两个购物车数据要在小程序端共享服务端数据库要在订单状态变更时第一时间通知小程序端。端的整体架构是这样的小程序端原生 ├── pages/home // 首页·商家列表 ├── pages/shop // 店铺页·菜品列表与分类 ├── pages/cart // 购物车页 ├── pages/order-confirm // 确认订单页 ├── pages/order-list // 订单列表页 ├── pages/order-detail // 订单详情页 ├── pages/user // 个人中心 ├── components // 自定义组件商品卡片、购物车栏、SKU弹窗 ├── utils // 登录、请求封装、支付工具 └── store // 购物车全局状态管理 服务端Node.js Express MySQL ├── routes // 路由层用户、商家、商品、订单、支付 ├── controllers // 控制层参数校验、业务分发 ├── services // 业务层下单事务、状态机流转 ├── models // 数据模型 └── utils // 微信登录、支付签名、回调验签为什么页面要分成这几层因为外卖系统的用户路径其实是固定的先逛店、再选菜、下单、付款、等餐、收到餐。每个环节对应页面页面之间是强流程关系不适合用 tab 页到处乱跳。另外购物车那一栏在店铺页底部常驻需要依赖全局状态所以我把 store 单独拎出来避免每个页面各存一份导致数据不同步。3. 登录、点餐与购物车核心实现3.1 用户登录与openid的获取流程微信小程序与传统网页最大的不同是它没有一个显式的账号密码登录框而是以微信身份作为用户体系。用户打开小程序后我们调用 wx.login 拿到一个临时 code然后把 code 传给后端后端带着 code、appid、appsecret 去微信接口换 openid。有朋友问过这个 code 是不是就是 token不是。code 有效期很短就这么一次使用机会用完即焚。拿到 openid 后后端应当用自己的逻辑生成 session/token 返回给小程序端后续所有需要登录的请求都带这个 token而不是每次都拿 code 去换 openid。服务端实现大概是这样// 小程序端 wx.login({ success: (res) { // 拿到 code 后发给后端 wx.request({ url: https://api.example.com/auth/login, data: { code: res.code }, success: (res) { const { token, userInfo } res.data.data; wx.setStorageSync(token, token); // 保存登录态 } }); } });// 服务端 Node.js 实现 const appid 你的appid; const secret 你的appsecret; const url https://api.weixin.qq.com/sns/jscode2session?appid${appid}secret${secret}js_code${code}grant_typeauthorization_code; const res await axios.get(url); const { openid, session_key } res.data; // 用自己的逻辑生成 token存到 session 表或数据库 const token generateToken(openid);很多新手在这一步容易掉进一个坑把 appsecret 直接写在代码里。小程序的代码包本质上是公开的用开发者工具就能反编译出来一旦 appsecret 被提取别人就能冒充你的小程序操作接口。正确的做法是 appsecret 只放在服务端环境变量里小程序端永远不涉及这个敏感信息。关于登录态还有一个要注意的点现在微信官方也在推手机号快捷登录、头像昵称填写能力但外卖点餐对用户信息要求没那么严格只要有个 openid 能区分用户是谁就够用了。如果你想让用户体验更好可以把 wx.getUserProfile 得到的头像昵称上传到自己的数据库展示在个人中心。我是做到二期才加的这个功能前期主流程优先级更高。3.2 商家与菜品数据懒加载外卖点餐的首页是商家列表一般的实现是进入首页直接请求商家列表接口然后把所有商家和菜品一次性拉回来。这种实现方式简单但有个问题菜品一多数据包体积很大弱网场景下拉取时间非常久。我后来把回到首页的请求改成了懒加载首页只拉商家基础信息和评分、起送价这些概览数据等用户点进某个商家再根据 shopId 拉取这个商家的完整菜品列表。这样首次进入页面的速度快很多商家框架渲染出来之后菜品数据异步到达用户感知不到明显卡顿。懒加载的实现并不复杂在店铺页 onLoad 时根据路径参数带上 shopId 去请求即可Page({ data: { shopId: , categories: [], products: [], currentCategoryId: , loading: true }, onLoad(options) { if (options.shopId) { this.setData({ shopId: options.shopId }); this.fetchShopDetail(); this.fetchMenu(); } }, async fetchMenu() { const res await request.get(/api/shop/${this.data.shopId}/products); const products res.data; // 把菜品按分类整理供左侧分类栏使用 this.setData({ categories: buildCategories(products), products, loading: false }); } });这里有个细节我想专门说一下菜品数据的结构组织。外卖菜单是左侧分类、右侧菜品的经典布局如果前端只按一个扁平的数组来存菜品分类筛选每条 select 时都要重新遍历过滤性能在小店场景能接受但分类一多会明显卡顿。我自己是把数组预处理好按 categoryId 分组成一个对象切换分类时直接取对象里的数组复杂度降成 O(1)。3.3 购物车状态管理最简单也最麻烦购物车在外卖点餐系统里是个非常核心的状态管理问题。它和电商购物车不太一样外卖购物车是要显示在店铺页底部的点开之后有商品明细、有加号减号、有清空按钮而且每家店的购物车是独立的。这意味着购物车数据必须在页面之间、组件之间共享不能只存在某一个页面的 data 里。微信原生的全局状态管理有几种做法globalData、storage、自定义事件总线。我最后用的是 globalData 搭配事件订阅的模式。购物车的数据放 globalData操作购物车时更新 globalData同时触发一个自定义事件店铺页底部购物车栏和侧滑购物车面板都会监听这个事件去刷新自己。// store/cart.js const cart { // 购物车数据{ shopId: { productId: { product, count } } } data: {}, listeners: [], setItem(shopId, product, count) { if (!this.data[shopId]) this.data[shopId] {}; if (count 0) { delete this.data[shopId][product.id]; } else { this.data[shopId][product.id] { product, count }; } this.emit(); }, subscribe(callback) { this.listeners.push(callback); }, emit() { this.listeners.forEach(fn fn(this.data)); } };为什么不用 storage 每次存因为 storage 是异步读写的高频加购操作容易产生读写竞争也可能影响性能。globalData 是内存态操作即时生效等用户最终确认订单时再一次性写入 storage作为数据兜底。购物车还有一个隐藏逻辑跨店清空。外卖不能同时在两家店下单用户从 A 店加了菜品再进 B 店加菜A 店的购物车应该被清空。这个逻辑如果不做用户等下单时才发现混乱。我是在 setItem 时判断传入的 shopId 和当前购物车里的 shopId 不一致就先清空原来的。3.4 菜品SKU和规格选择弹窗点餐系统里常见的规格需求就是一杯奶茶可以选择大杯、中杯、小杯或者加冰、去冰。这个功能看起来简单但实现时要注意的细节很多。你可能在小程序里见过那些点弹窗后菜品区分变形的界面多半是弹窗组件没有处理滚动穿透。我处理的方式是每个配置项存一个规格分组用户每次点击某个选项就把这个分组的值记录下来选完后把多个分组的值联合生成一个 skuId。之后加购物车都是针对这个确定的 skuId比如 specName大杯去冰。服务端在生成订单详情时直接用 skuId 查找对应的价格、库存。这里要提醒一下规格弹窗的层级和滚动问题在 iOS 上特别明显。弹窗打开后底层的页面内容还会跟着手指滚动这就产生了滚动穿透。解决办法是给弹窗组件加 catchtouchmove或者设置 page-meta 的 page-style overflow: hidden。只给弹窗单独设 fixed 定位是不够的必须处理底层页面的事件冒泡。具体代码可以这样写view classsku-mask catchtouchmovenoop wx:if{{show}}/view view classsku-panel catchtouchmovenoop wx:if{{show}} !-- 规格选项 -- /viewnoop() {}这样弹窗区域内的滚动不会传导到底层页面。苹果手机在微信小程序里不能进行滑动滚动这个问题很多时候都和这种事件冒泡机制有关后面我会专门在问题排查里再展开。4. 下单、支付与订单状态流转4.1 从购物车到确认订单页购物车侧滑面板、店铺页底部购物车栏这些交互做完之后用户点结算接下来进入确认订单页。这个页面主要做三件事展示商品清单、选择收货地址、计算配送费和优惠。最后把这三块数据组织成一个订单请求体发送到后端。确认订单页的数据处理有一个要小心的地方商品信息、价格这类数据不能直接信任小程序端传来的值后端必须重新根据商品的 skuId 去数据库核对价格。原因很简单小程序的页面和接口是可以被篡改的一个熟练的用户完全可以通过抓包工具修改菜品价格。外卖点餐的体量黑产未必盯上但从一开始就养成服务端校验所有价格数据的习惯总没有错。我在这个页面做了金额的实时计算展示因为涉及到配送费、打包费直接在 data 里维护一个 totals 对象每次商品数量变化或配送方式变化时调用 computed 方法重新计算。注意 setData 的粒度不要一整块大对象刷局部更新 totals.xxx 就行。确认订单页还需要处理的一个是备注输入框。外卖系统中用户经常需要备注不要辣餐具多一份等这个字段在下单时随订单保存下来商家端展示订单时会看到。这块逻辑不复杂主要是我建议把备注字数控制在 50 字以内超出部分截断避免后期给商家打印小票带来格式问题。4.2 后端统一下单与微信支付对接微信支付可以算得上外卖点餐系统中小程序端最复杂的模块。不是代码本身难而是流程繁琐、坑多涉及小程序端、后端、微信支付服务端三方交互。支付流程的核心链路是这样的小程序端把订单号发给后端请求预下单接口。后端查到订单调微信支付的统一下单接口传入 appid、mch_id、openid、订单金额、回调地址等参数。微信支付返回预支付交易会话标识prepay_id。后端把这个 prepay_id 和签名信息返回给小程序端。小程序端拿这些参数调 wx.requestPayment拉起支付面板。用户输入密码支付成功微信支付服务端回调后端通知的接口。回调接口收到支付通知验签成功将订单状态改为已支付。第二步里涉及一个重要概念签名。微信支付的所有接口都需要用商户密钥对参数做 MD5/HMAC-SHA256 签名微信在收到请求后会验签。如果签名算法写的顺序不对、字符编码不对或者 URL 编码的 key 没按字典序排列都会报通信参数错误。我在写签名工具时踩过一个很经典的坑参数拼接时数组空值字段的处理。按照微信官方要求参与签名的参数值不能为空所以要先过滤掉空数组项再按 key 的字典序升序排列拼成 query string末尾加上key商户密钥然后做 MD5转大写。这一步细节很多我建议直接用一个封装好的函数别每次都手写。function getSign(params, key) { const keys Object.keys(params) .filter(k params[k] ! params[k] ! null params[k] ! undefined) .sort(); const str keys.map(k ${k}${params[k]}).join(); return crypto.createHash(md5).update(${str}key${key}).digest(hex).toUpperCase(); }支付回调的地址必须是外网可访问的 HTTPS 地址不能带端口。而且回调地址所在域名要求是已经备案的这一点在测试阶段就很容易卡住。如果你只是本地开发调试支付几乎不可能我就是因为这个原因在开发阶段先把支付逻辑留了 debug 开关模拟支付成功的回调等部署到测试服务器后再走真实支付流程。4.3 订单状态机设计点餐系统上线之后订单状态的变更会比开发时想象中复杂得多。我第一次做的时候直接在 order 表加了一个 status 字段各种 if else 去判断越到后面越乱。后来重构的时候才认真设计了一套状态机。我的订单状态分这几个关键节点状态含义可触发事件目标状态PENDING_PAYMENT待支付用户支付成功 / 超时未支付PAID / 自动关闭订单PAID已支付商家接单PREPARINGPREPARING制作中商家出餐DELIVERINGDELIVERING配送中用户确认收货 / 骑手标记送达COMPLETEDCOMPLETED已完成无终态CANCELLED已取消无终态状态流转的原则是每一步都只允许从一个状态到另一个确定的状态其他非法跳转直接拒绝。比如已支付订单不能直接改为已完成必须经过 PREPARING、DELIVERING。这个约束在服务端做了一层校验防止前端在接口层乱传状态。超时未支付自动关闭订单这个功能最靠谱的实现方式不是用定时任务去轮询所有订单而是利用微信支付的关闭订单能力和数据库里的时间判断。在用户下单时记录 created_at前端轮询或后端定时任务发现超过 15 分钟未支付就将状态改为 CANCELLED同时调微信支付关闭订单接口防止用户再支付。这种需求和微信支付限时接口是配套的。4.4 订阅消息通知用户外卖点餐的一个重要体验是用户下单后商家接单、出餐这些状态变化最好通过微信订阅消息通知用户不然用户得一直盯着小程序页面刷新。订阅消息在小程序端的实现流程是用户在小程序端授权接收订单状态通知。后端在下单成功后调用微信的 subscribeMessage.send 接口给用户发送订阅消息。用户收到消息点击可跳转到小程序对应页面。这里需要注意订阅消息模版需要在微信公众后台申请。不同行业类目可用的模版不一样餐饮类目下一般有订单状态提醒商家接单通知这些可用模版。申请时要填具体参数名比如订单号、订单状态、提醒内容等。我在开发阶段经常遇到一个问题订阅消息发送成功了但用户收不到。排查后发现是授权次数的问题用户每授权一次只意味着可以给用户发一次订阅消息。如果业务里有多个状态变更都要发消息必须让用户多次确认授权或者使用总是保持以上选择的长效订阅。这块逻辑要在产品层面设计好不然一路发到第几条就断了。5. 后端接口设计与数据库建模5.1 数据表结构设计外卖点餐系统的数据库核心表可以分成几组用户相关、商家与商品相关、订单相关。我给出的具体表结构比较简洁尽量不做过度设计但每一张表都有它存在的理由。用户表至少要有openid、昵称、头像、手机号、创建时间。这里的 openid 是用户全局唯一的标识配合用户自己在系统内维护的 token 使用。商家表商家名称、logo、评分、起送价、配送费、营业状态。现实中商家信息是商家后台维护的个人开发可以先用 SQL 脚本初始化一批模拟店铺。商品表商家ID、分类ID、名称、价格、图片、月售量、库存、上下架状态。如果商品有规格单独建一张 sku 表关联商品 ID、规格描述、价格、库存。订单表和订单明细表是外卖系统的重头戏。订单表放订单号、用户ID、商家ID、总金额、配送费、打包费、优惠金额、实付金额、收货地址、备注、订单状态、支付时间、完成时间。订单明细表放订单ID、商品ID、SKU信息、单价、数量、小计。我建表的习惯是所有核心业务表都加 created_at 和 updated_at 两个时间字段这在排查数据和做统计报表时会非常有用。还有订单号一定不要用自增 ID 暴露给用户订单号可以拼上时间戳和随机数方便客服按订单号搜索。5.2 订单接口与防并发处理外卖点餐最典型的并发场景是同一个 SKU 库存只剩 1 件两个用户同时下单。如果不做任何控制可能出现两个订单都扣掉了 1 件库存数据账不平。处理办法主要是两种乐观锁和悲观锁。乐观锁是在商品表加一个 version 字段更新库存时带上 version 条件如果 version 对不上就说明数据被改过了重新查库存逻辑再来。悲观锁是直接在数据库行上取锁但在高并发外卖出单场景下锁粒度太大会造成性能瓶颈。我实际用的是预扣库存方案下单时先锁定库存支付超时释放库存。在服务端逻辑上这里必须把检查库存、扣减库存、创建订单放在同一个数据库事务里任何一步失败都整体回滚保证数据一致性。这段伪代码可以说明思路await db.transaction(async (tx) { // 1. 查 SKU 库存 const sku await tx.select().from(sku).where({ id: skuId }).forUpdate(); if (sku.stock count) throw new Error(库存不足); // 2. 扣库存 await tx.update(sku).set({ stock: sku.stock - count }).where({ id: skuId }); // 3. 生成订单主表和明细 await tx.insert(order, { orderNo, userId, ... }); await tx.insert(order_item, { orderNo, skuId, count, ... }); });forUpdate()是把这个事务内选中的行锁住事务提交前其他修改操作会阻塞这是比较保险的方式。这里有一个进阶建议如果后续要做秒杀类高并发场景要引入 Redis 库存预扣异步队列但外卖点餐的场景一般用不着。5.3 RESTful 接口设计API 设计我遵循的是 RESTful 风格资源和操作分开。核心接口如下方法路径说明GET/api/shops获取商家列表GET/api/shops/:id/products获取商家菜品POST/api/orders创建订单GET/api/orders获取当前用户的订单列表GET/api/orders/:id获取订单详情POST/api/orders/:id/pay发起支付预下单POST/api/orders/:id/confirm用户确认收货每个接口在我的服务端实现里都有统一的返回结构{ code: 0, message: success, data: {} }code 为 0 表示成功非 0 表示错误。在message里带上用户能看懂的提示比如库存不足。小程序端用一个统一的 request 封装处理 token 注入、错误码拦截、loading 展示这样每个页面请求接口时就不用重复处理这些逻辑了。在微信小程序里所有请求域名都必须配置到小程序后台的 request 合法域名里而且要求 HTTPS。开发调试阶段可以在开发者工具里勾选不校验合法域名但真机预览和上线前必须把域名配置好。我踩过一个坑本地开发用了 localhost 后端开发者工具没问题一用真机调试发现所有请求都失败了。查了半天发现真机根本访问不了电脑的 localhost需要把后端接口地址改成电脑在同一局域网下的 IP。如果你也是这种情况把http://localhost:3000改成http://192.168.x.x:3000并且保证手机和电脑连接同一个 Wi-Fi就能解决。5.4 地址管理模块订单收货地址我做成用户在小程序端手动填写和维护。地址字段至少包括联系人、手机号、所在地区、详细地址。微信小程序提供了wx.chooseAddress接口可以直接获取用户在微信里保存的收货地址这个功能实现起来很简单而且用户体验很好用户不用在小程序里再填一遍地址。要注意的是wx.chooseAddress获取的地址格式和自家系统里存储的字段不一定完全吻合需要做一层适配映射。用户在微信里维护的地址可能不一定更新所以拿到地址后还是要允许用户在确认订单页编辑。外卖配送还有一个常被忽略的需求定位当前经纬度。用wx.getLocation获取用户当前位置再把经纬度传给后端由后端或地图服务计算配送范围。如果你接入的是高德地图从微信小程序跳转到高德 App 进行导航也是常见需求这需要用到wx.openLocation如果直接在小程序里调用地图组件则不需要跳出去。这里常见的问题是很多人在开发工具里拿到的定位是正常的一到苹果真机上位置就偏了。这种情况通常是因为没在 app.json 里声明permission中的scope.userLocation用途说明以及 iOS 上定位权限弹窗没正常弹出导致。在 app.json 里加上{ permission: { scope.userLocation: { desc: 你的位置信息将用于配送范围校验 } } }6. 实操中遇到的高频问题与排查记录6.1 真机调试请求无法到达后端这个问题的表现形式是在微信开发者工具里一切正常但用预览扫码后在真机上打开所有请求全部失败Network 面板显示请求直接报错。排查思路按优先级来确认后端地址是否为局域网 IP而不是 localhost。确认手机和电脑是否在同一局域网并可以互通。确认小程序后台是否已配置 request 合法域名并在开发者工具里详情-本地设置把不校验合法域名勾上用于调试预览。确认后端接口允许跨域因为小程序请求默认要求后端开启 CORS 响应头。我在这个坑上花了一个下午。最后发现是后端 Express 应用没配 CORS 响应头浏览器端和小程序开发者工具对跨域的限制策略不一样开发者工具对部分情况容忍度较高但真机上是严格按照配置执行的。6.2 苹果手机上不能滑动滚动页面卡死这个问题在小程序里很常见尤其表现在 iOS 真机上。出现苹果手机在微信小程序不能进行滑动滚动的情况通常原因有几类页面上某个元素意外获得了 focus比如 input 组件键盘弹起后页面滚动被锁。某些 CSS 属性如overflow: hidden误用在了page层。自定义弹窗的catchtouchmove触发了事件上浮底层页面无法滚动。排查时先确认是不是所有页面都卡死还是只有特定页面。如果是特定页面检查这页的page元素 CSS style 是否被设置过度滚动限制。弹窗组件要确保关闭时清理掉position: fixed的遮罩层。还有一种隐蔽情况scroll-view 作为外层容器时高度没有设置好内容超出后 iOS 上的滚动惯性触发不了。给 scroll-view 设置固定高度并且使用enable-flex属性配合 flex 布局时更要注意。6.3 开发者工具里没有云开发入口很多人第一次打开微信开发者工具发现界面上找不到云开发按钮以为是工具坏了。出现这种情况通常是当前小程序项目不是云开发模板创建的项目或者当前账号没有开通云开发环境。云开发是按环境计费的需要先在小程序公众平台开通云开发服务再在开发者工具里点云开发图标按提示创建环境。如果你不是非要云开发这套自建后端方案完全不受这个功能影响。不过顺带说一句我在开发阶段确实遇到一个情况开发者工具更新版本之后云开发的入口位置变了。如果在工具栏的缩略图标里找不到可以试试顶部菜单的工具选项有些版本把它收进了二级菜单。6.4 基础库版本与兼容性问题小程序基础库版本直接决定某些 API 和组件是否可用。微信开发者工具默认会使用最新基础库版本但真机的用户不一定都升级了最新版。所以开发时要留意项目里用了哪些较新的 API然后到小程序公众平台把最低基础库版本设置到一个合适的值。我设置最低基础库版本时比较谨慎如果某个 API 只在 2.20.x 以后才支持而我的用户群里还有相当一部分人使用旧版本微信那我宁愿换一种实现方式也不盲目调高最低版本避免用户一进来就白屏。兼容性问题是隐形的它不会在开发者工具里暴露。我用过最笨但有效的方式就是借几台不同年代的老手机装体验版实际跑一遍主流程。这东西比读文档效率高得多。还有一个技巧在 app.json 的debug模式打开后控制台会打印详细的页面跳转和 setData 日志定位基础库相关报错会有帮助。6.5 小程序审核与上线注意事项小程序做完之后不是直接就能上线微信公众平台的审核是很多初学者的噩梦。审核失败的原因常见的有这么几类涉及支付功能但类目选择不对。外卖点餐必须选择餐饮服务类目上传对应的资质文件。个人主体不能开通微信支付这一点如果做商业项目就很痛实际开发前一定要确认主体类型。页面存在测试字样、未完善的占位内容。审核员会截图反馈我第一次因为首页放了一个调试用的 banner 图片被判为页面功能不完整。涉及用户隐私如果使用了用户手机号、位置信息必须在小程序后台配置《用户隐私保护指引》否则真机调试或审核时会被拦截。隐私弹窗、公告等也要在页面里体现。小程序名称和实际功能不符外卖点餐的小程序名称里如果包含官方旗舰这类词容易被打回。审核被拒别慌重点是看拒绝原因截图和文字描述。我第一次被打回是因为没有隐私政策页面后来在个人中心里加了一个静态的隐私协议页面问题就解决了。审核时长一般是半天到两天类目资质越清晰通过越快。6.6 常见问题速查表最后整理一个我在开发全过程中遇到问题的速查表方便你开发时快速定位问题现象可能原因处理办法真机预览请求失败后端地址使用了 localhost改为局域网 IP 或真实域名请求报跨域错误后端未配置 CORS服务端加响应头系统提示 appsecret 泄露前端代码中混入密钥移除敏感信息换成环境变量重置密钥购物车数量不同步页面引用了局部变量改用 globalData 事件监听弹窗滚动穿透未阻止底层事件冒泡遮罩层和弹窗加 catchtouchmoveiOS 无法滚动page 样式或 scroll-view 高度问题检查样式和容器高度支付失败报签名错误参数或签名算法有问题用官方签名校验工具比对订单支付成功但状态未更新回调地址不可达或验签失败检查回调公网可达性订阅消息收不到授权次数已用完或模版参数错误重新检查授权逻辑和模版字段审核页面被拒有测试字样或隐私协议缺少补充完善页面和隐私声明7. 项目的后续扩展方向到这里从 0 到 1 的外卖点餐小程序核心流程就完整了。但我每次做完一个项目都会复盘这个项目做到什么程度算完整什么方向还能继续深挖外卖点餐系统也不例外后面可以做的方向还挺多的。第一个方向是商家端。用户端只是外卖的一半真正运营需要商家能管理菜品、上下架商品、接单改状态。商家端可以做成独立的小程序或者用微信公众号内的 H5 页面来管理。我在开发时把商家接口都留好了后面套一个管理后台模板就能用。第二个方向是接单通知与打印小票。商家收到新订单后如果能通过订阅消息收到通知甚至对接云打印机自动出单整个闭环会更接近商业系统。这块我还没深入做但技术上无非是整合第三方打印服务商的接口。第三个方向是营销玩法。满减活动、优惠券、新客立减、会员积分这些玩法在外卖平台里几乎是标配。数据模型上要加优惠活动表、用户优惠券表下单时先计算优惠金额再算出实付金额逻辑会复杂一些但项目含金量会明显提升。第四个方向是数据分析。点餐系统天然会产生订单数据按时间维度统计商家销量、菜品排行、用户复购率等数据是非常实用的功能。后端可以做一些聚合查询的接口前端画折线图展示趋势。如果你对数据可视化感兴趣可以在小程序里用 canvas 手写简单的折线图或者引入第三方图表组件库。我在做这个小程序的过程中最大的体会是写页面只是表象真正花时间的地方都在数据流转和状态管理上。外卖点餐系统恰好把这些问题都覆盖了一遍做完之后你再看其他行业的小程序业务大多能互相迁移。如果你正在纠结用什么项目练手这个选题不会亏。