微信小程序外卖点餐系统全流程开发实践与踩坑指南

发布时间:2026/9/30 5:38:34
微信小程序外卖点餐系统全流程开发实践与踩坑指南 前阵子帮朋友的小餐馆做了一个微信小程序外卖点餐系统从注册账号、搭项目骨架到核心点餐流程跑通整个过程踩了不少坑。这篇文章就是那次实践的项目笔记把从0到1实现一个外卖点餐系统小程序的完整思路和关键代码整理出来——包括项目该怎么做技术选型、登录和token怎么处理、商品和购物车数据怎么设计还有真机调试和上线审核阶段容易踩的那些雷。想做微信小程序项目练手、或者给实体店做点餐系统的同学可以直接照着这套思路来。1. 动手之前先搞懂外卖小程序到底要做什么1.1 一份外卖作业的完整功能清单我在开始写代码之前先花了半天时间把外卖点餐的用户流程走了一遍。用户进店之后要干什么看菜品、选菜、加购物车、下单、付钱、等外卖。如果把这个流程拆成可以实现的模块大概是这样首页店铺信息、公告、热销推荐承担门面的角色菜单分类左侧分类、右侧菜品列表这是点餐效率的核心购物车加购、减购、清空、实时计算金额结算下单确认订单信息、填写收货地址、提交订单订单管理待支付、制作中、配送中、已完成等状态查看个人中心用户信息、地址管理、订单记录、客服与售后入口。这还只是用户端。站在商家角度还需要一个后台来维护菜品、接收订单、修改订单状态。但做从0到1的项目一定要做减法先做用户端这六个模块商家端用一个简单的后台或者数据库直接改数据来过渡。我见过很多人一开始就想做商家管理后台、骑手端、大数据分析最后连点餐主流程都没走通——这是项目失败最常见的原因。1.2 技术选型原生微信小程序、uni-app 还是云开发先说结论我建议如果只是学小程序、做单端项目直接用原生微信小程序开发不要一上来就上跨端框架。当时我做一个简单的对比方案适用场景我在意的点原生微信小程序只做微信端、想深入理解小程序机制调试直接、无编译层、文档最好查uni-app / Taro需要同时出微信、支付宝、H5、App多端复用但多一层框架转换遇到问题要会区分框架bug还是小程序bug第二个关键选择是后端。对于外卖点餐来说小程序前端只是半个系统没有后端就没有菜品数据、没有订单存储。两种主流方案云开发和自建后端。新手和做产品原型推荐用云开发云函数写接口、云数据库存数据免运维域名都不用买。如果你想锻炼全栈能力或者项目后面要接商家端、要自由扩展就可以用 Node.js 或者 PHP 自建后端。我当时选了原生小程序加 Node.js 自建后端原因很简单这个项目的目标不是最快上线而是把前后端交互链路完整吃透。如果你最终选了云开发也不影响这篇文章后续对登录、商品、购物车、订单这些数据链路的理解只是把后端接口换成了云函数。1.3 项目边界先跑通核心闭环再考虑支付和配送外卖点餐绕不开两件事支付和配送。但是这两个功能在MVP阶段都不要做。微信支付要求小程序主体必须是企业、个体工商户等非个人主体个人开发者无法开通就算有企业资质支付接入还需要商户号、证书、回调地址这已经是一个独立工程。配送也一样接第三方配送平台要商务合作和费用自配送又要做骑手端。所以第一个版本我把闭环定义为用户浏览菜单 → 加入购物车 → 提交订单 → 订单写入数据库 → 后台看到订单。用户订单状态先停留在待支付/已提交后续再对接支付和配送。2. 项目初始化注册、工具链与目录分层的正确姿势2.1 注册小程序账号与开发者工具打开微信公众平台注册小程序。注册时有两点要注意一是主体类型个人和企业的权限差异很大个人主体无法开通微信支付也无法上架部分类目二是邮箱不能重复注册。注册好后在开发-开发设置里拿AppID。后续在微信开发者工具里新建项目时选小程序填入AppID。工具建议下载稳定版别追beta版我遇到过beta版自带一堆插件兼容问题。还有一个经常被问的问题基础库版本从哪设置——开发者工具右上角详情-本地设置-调试基础库可以切换调试基础库后台的设置-基本设置里可以设置最低基础库版本。平时调试用新版没问题但上线前最好把最低版本按官方建议设置好避免用户基础库过低导致API不生效。2.2 目录分层别把代码全堆在 pages 里很多新手项目所有页面放在pages、所有方法写在各页面的index.js里两三百行还能看五百行就开始痛苦了。外卖小程序页面多、交互多我的做法是预先分层目录结构大概是这样的miniprogram/ ├── app.js // 全局逻辑、登录态初始化 ├── app.json // 全局配置页面路径、tabBar、窗口样式 ├── app.wxss // 全局样式变量 ├── pages/ │ ├── menu/ // 点餐页左侧分类、右侧商品 │ ├── cart/ // 购物车页或点餐页内侧滑面板 │ ├── order/ // 订单列表 │ └── mine/ // 个人中心 ├── components/ │ ├── goods-card/ // 商品卡片组件 │ └── number-box/ // 加购减购数量组件 ├── api/ │ └── request.js // 统一请求封装 ├── utils/ │ └── format.js // 价格格式化、时间格式化等 └── assets/ └── images/这样分层的核心好处是页面只负责页面逻辑和交互数据请求在api层可复用的UI封装成组件。特别是goods-card和number-box菜单页、购物车、订单详情都可能用到抽成组件后一处修改、处处生效。注意components里每个组件四个文件js/json/wxml/wxss需要在组件的json里声明component: true页面使用前再在页面的json里用usingComponents引用。2.3 app.json 全局配置tabBar、窗口与第一屏小程序启动后读的第一个文件就是app.json页面路径、窗口样式、tabBar都在这里配置。我配了三个tab点餐、订单、我的。配置代码大致是这样的{ pages: [ pages/menu/index, pages/order/index, pages/mine/index ], window: { navigationBarTitleText: xx外卖, navigationBarBackgroundColor: #ff6b35, navigationBarTextStyle: white, backgroundColor: #f7f7f7 }, tabBar: { color: #999, selectedColor: #ff6b35, list: [ { pagePath: pages/menu/index, text: 点餐 }, { pagePath: pages/order/index, text: 订单 }, { pagePath: pages/mine/index, text: 我的 } ] } }注意两个细节一是pages数组第一项是启动首页小程序页面路径都必须在这里注册二是tabBar的pagePath必须在pages数组中并且tabBar页面建议不用自定义导航栏否则会出现胶囊和导航栏重叠计算的问题。navigationBarTextStyle只支持black/white两种值背景色要跟导航栏文字颜色搭配好否则状态栏会糊成一片。3. 登录链路code 换 token 的完整闭环3.1 登录不是一个获取用户名密码的过程很多第一次做小程序的人会习惯性地想登录嘛做一个账号密码输入页。在小程序里主流方案是微信授权登录。用户打开小程序微信就能作为身份提供方不需要用户输入用户名密码。核心API是wx.login。整个链路如下前端调用wx.login()微信返回一个临时code前端把code发给自己的后端后端拿code appid appsecret调用微信的接口code2Session换取 openid 和 session_key后端用 openid 在数据库里找到或创建用户生成自己的登录凭证token后端把token返回给前端前端把token存到storage之后所有请求都带上这个token。这里有一个新手最容易踩的坑code2Session 的调用必须有 appsecret而 appsecret 一旦出现在前端代码里就相当于把账号密码贴在了门口。所以这个接口只能在后端调用前端永远只拿code。3.2 openid、session_key、token 各管什么openid是用户在当前小程序里的唯一ID拿到它你就能把订单、购物车、地址跟用户关联起来session_key是微信会话密钥主要用来解密手机号等敏感数据它不应该下发到前端。token则是你自己后端发给小程序的通行证里面可以带user_id和过期时间也可以做成无状态的JWT。我的做法是后端收到code后如果查不到openid就创建一个用户如果查到就直接取用户然后生成token返回。前端不关心openid是谁它只需要把token管好。注意session_key 是会过期的如果你将来要解密手机号不能缓存旧session_key必须从新的 code 换取。另外手机号快速填写的API现在也需要企业认证个人主体用不了所以第一版地址管理就让用户手动填写。这一点在项目规划时就要考虑到否则做到后面才发现能力受限就得返工。3.3 请求封装token、状态码、loading 统一管起来为了让所有页面不重复写网络请求的样板代码我封装了一个request函数const BASE_URL https://your-api.example.com const request (url, method GET, data {}) { return new Promise((resolve, reject) { const token wx.getStorageSync(token) wx.request({ url: ${BASE_URL}${url}, method, data, header: { Content-Type: application/json, Authorization: token ? Bearer ${token} : }, success: (res) { if (res.statusCode 401) { handleTokenExpired() return } if (res.data res.data.code 0) { resolve(res.data.data) } else { wx.showToast({ title: res.data.msg || 请求失败, icon: none }) reject(res.data) } }, fail: () { wx.showToast({ title: 网络异常, icon: none }) reject(new Error(network error)) } }) }) } module.exports { request }接口返回报文统一用{ code, data, msg }code为0表示成功。这样前端处理逻辑会非常清爽。handleTokenExpired 里做静默重新登录先调用 wx.login 换新 code再请求后端换新 token然后重新执行失败的请求。外卖场景下用户可能使用很久token过期时不要让用户重新走一遍登录体验会好很多。这里还要说一个实践细节不要把 wx.showToast 放在每个页面都写一遍。封装层统一处理错误提示页面只关心数据这样能少写很多重复代码。对于需要loading的接口也可以在request里加一个可选参数自动管理showLoading和hideLoading页面不用自己去配对调用。4. 商品与购物车点餐主流程的数据设计4.1 菜单数据结构与两种加载策略点餐主流程的数据主要是分类和商品。我的表结构大致是category: id, name, sortproduct: id, category_id, name, price, image, stock, sales, status前端页面的核心数据结构是一个数组menuData: [ { id: 1, name: 热销, products: [ { id: 101, name: 招牌卤肉饭, price: 22, image: , stock: 50 } ]} ]两种加载策略一次性加载所有分类和商品前端本地做筛选。适合菜品几十个以内的小店用户切分类时秒开无loading闪烁点击分类时按category_id请求适合上百个菜品的餐厅但每次切换都有网络等待需要做loading状态。外卖点餐MVP我建议用第一种。后续菜品多了再改成懒加载改动的点集中不会推翻整体设计。4.2 购物车本地状态管理 setData 性能优化购物车在小程序里怎么存我的建议是未提交订单之前购物车完全维护在前端本地用storage做持久化。这个设计有一个很实际的理由用户可能加了几样菜退出小程序再回来购物车还在体验会好很多。数据结构用对象而不是数组data: { cart: { 101: 2, // 商品id: 数量 102: 1 } }对象的好处是按商品id更新数量时不需要遍历数组改起来快。这里分享一个setData的性能细节。新手容易写成每次加购都把整个menuData重新setData一遍菜品一多就会卡顿、掉帧。正确的做法是精确更新路径handleAdd(e) { const { id } e.currentTarget.dataset const current this.data.cart[id] || 0 this.setData({ [cart.${id}]: current 1 }) }利用数据路径语法只更新一个键值。设计number-box组件时点击加号从外层的自定义事件往上抛不要在子组件里直接修改全局数据这样数据流是单向的好排查问题。购物车底部栏的金额也可以通过计算属性在页面里统一算不要在多个方法里各算一遍否则很容易出现数字对不上的bug。4.3 订单状态机与下单接口设计下单时前端把购物车清单传给后端后端做三件事校验库存、用服务端价格重新计算金额、生成订单。这里必须强调永远不要信任前端传过来的金额。页面金额是给用户看的真正的金额必须用服务端商品表里的价格来算否则用户改一下请求数据就能低价下单。订单表核心字段order_id主键/订单号user_id下单用户goods_list冗余商品快照商品名、单价、数量、图片total_amount服务端计算的总额status状态码create_time / pay_time / finish_time订单状态我习惯用数字方便后端排序和计算状态值含义可执行操作0待支付用户取消、支付1已支付/制作中商家接单或出餐2配送中查看配送状态3已完成评价、再来一单-1已取消无前端只需要一个状态映射对象显示时把数字翻译成文案。设计状态机时只定义合法的流转路径例如0到1到2到3非法跳转会直接报错这样后端就不会出现状态混乱的问题。商品快照字段尤其重要因为商家改价或下架菜品后历史订单仍然要展示当时的价格和名称快照就是把下单那一刻的商品信息固定下来。5. 踩坑与上线真机调试、适配与审核5.1 真机调试请求不到后端完整的排查链路这个坑几乎人人都会遇到。后台接口在开发者工具里能通预览到手机上就失败。我是按这个顺序排查的一步步来看报错提示。开发者工具的Network面板如果显示fail多半是域名校验或网络层问题如果有statusCode比如404/500说明请求已经到了后端问题在后端逻辑。排查域名。正式环境小程序要求后端必须HTTPS并且要在小程序后台配置request合法域名。本地调试时可以临时在开发者工具里勾选不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书但注意这只能解决工具里的问题真机预览还需要在手机上打开调试模式。排查地址。如果你用本地电脑后端调试真机访问不到localhost。手机和电脑必须连同一个局域网而且接口地址要写你电脑的内网IP比如192.168.x.x:3000不能用127.0.0.1。这一步卡了我一下午。排查防火墙。电脑防火墙没放行对应端口手机依然访问不到。Windows上把Node.js或对应端口设为允许入站即可。排查跨域小程序wx.request不存在浏览器里的跨域限制如果报错url not in domain list那是域名校验问题不是CORS问题。很多前端同学在这里被误导。按这个顺序排查基本十分钟定位。我当时遇到的问题就出在第三步接口地址写成了localhost换成局域网IP立刻就好了。5.2 顶部导航栏与安全区不同机型的适配小程序默认导航栏是系统渲染的有胶囊按钮标题居中。但如果要自定义导航栏比如点餐页想要沉浸式头图就要自己计算导航栏高度。导航栏总高度等于状态栏高度加菜单按钮高度加上下间隙。可以通过wx.getWindowInfo()和wx.getMenuButtonBoundingClientRect()拿到const windowInfo wx.getWindowInfo() const menuRect wx.getMenuButtonBoundingClientRect() const statusBarHeight windowInfo.statusBarHeight const navHeight (menuRect.top - statusBarHeight) * 2 menuRect.height这个navHeight就可以用来设置自定义导航栏的占位高度。底部安全区也一样iPhoneX之后的机型有Home条要让购物车结算按钮避开底部在wxss里写.settle-bar { padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); }这两行代码对点餐页的底部购物车尤其重要否则结算按钮会被Home条挡住一半。真机调试时多拿几台不同机型的手机试一下尤其是有横条的老款全面屏。5.3 基础库版本与API兼容性开发工具里跑得好好的API到了用户手机上没反应多半是基础库版本太低。基础库相当于小程序的运行时不同版本支持的API不一样。我建议在后台设置最低基础库版本时先看微信官方的版本占比数据选一个能覆盖95%以上用户的最低版本。代码里对较新的API用wx.canIUse()做判断例如if (wx.canIUse(getWindowInfo)) { const info wx.getWindowInfo() } else { const info wx.getSystemInfoSync() }注意wx.getSystemInfoSync在2022年底已经被标记为废弃新项目直接用getWindowInfo但为了兼容低版本还是要做能力判断。另外不要在线上版本贸然把最低基础库版本调到最新能覆盖绝大多数用户比用上最新API更稳妥。5.4 审核提交最容易被拒的几个点小程序提审时最怕的不是功能简陋而是触犯平台规则。结合这次经历几个高频风险点类目与内容不符。做外卖点餐需要选择符合平台规范的类目个人主体没有食品经营许可类资质的话上餐饮服务类目会被拒。可以先选工具-信息查询这类允许的服务类目提交审核但项目说明要写清楚是演示用途否则会被驳回。要有可用数据。审核人员打开小程序至少要能看到真实的商品和分类不能是空页。我提审前专门造了一批完整测试数据包括菜品图、价格、分类、库存。隐私协议。现在小程序收集用户信息包括头像、昵称、手机号必须要有隐私弹窗和《隐私政策》。不弹窗审核不通过。这个要在开发初期就加上不要拖到提审前一天再补。不要诱导分享。外卖小程序里常见的分享好友得红包很容易被判为诱导分享MVP阶段先别做。页面可用性比功能多少重要。审核最怕遇到点了半天没反应的死链宁可功能少而精别放占位按钮。最后分享一个实际感受。做这个外卖点餐小程序最难的不是某个具体API而是把用户流程、数据结构、状态流转统一想清楚。如果时间重来我会更早地把订单状态机画出来更早接真机调试而不是在模拟器里自我感觉良好。后续想扩展的话可以加商家后台、订阅消息通知、优惠券系统甚至对接支付后把订单闭环真正跑完。希望这篇笔记能帮你少走一些弯路。