☰
微信小程序餐饮点餐外卖源码拆解:从架构到支付闭环
2026/10/20 4:24:47 网站建设 项目流程

简介:这是一份面向餐饮商家及微信小程序开发者的餐饮点餐外卖完整源代码方案,覆盖在线点餐、外卖配送、叫号排队、微信支付与订单管理等核心业务场景,可直接作为基础进行定制化二次开发。资源包共521个文件,以JavaScript逻辑、WXML页面结构、WXSS样式及JSON配置为主,另有WXS脚本与PNG图片等辅助资源,压缩包仅833KB,目录结构清晰,便于在微信开发者工具中快速导入运行。已有2331人学习下载。代码实现了从用户注册登录、菜品展示与实时库存更新、购物车与订单确认,到配送范围计算、排队叫号同步、支付校验与退款处理等完整链路;同时提供商家后台的订单管理、菜品维护与数据分析功能,覆盖前后端交互的主要开发实践。各功能模块划分明确,便于按需裁剪或扩展,适合希望快速上手小程序开发、了解餐饮O2O业务逻辑或直接搭建运营原型的初中级开发者学习参考。

1. 微信小程序餐饮点餐外卖源码:从目录结构到上线闭环

做餐饮小程序外包这几年,我拆过的点餐外卖项目源码不下二十套,最深的感受是:demo 和能上线的成品之间,隔着一整条“业务逻辑 + 鉴权 + 支付回调 + 消息推送”的护城河。这份开箱即用的《微信小程序-餐饮点餐外卖-源代码》,不是那种只有几个页面跳来跳去的教学稿,而是把点餐、购物车、下单、外卖配送、商家接单、订单状态流转、微信支付预下单这些环节全部串起来的完整工程。它适合三类人:刚入门想找个完整项目练手的新手、接了餐饮小程序单子但不想从零写业务逻辑的开发者、以及想研究微信小程序电商闭环的从业者。它的价值不在于代码量多少,而在于“整套链路齐全且能直接跑起来”——我这篇就把目录结构、页面渲染、后端接口联调、以及那些最容易翻车的坑全部拆开讲。

2. 项目全景:前后端分离架构与四个核心模块的职责划分

2.1 从 app.json 看全局配置:页面注册、导航栏与 TabBar 的隐藏细节

拿到这份源码,我习惯先看app.json,它相当于小程序的“总开关”,直接决定哪些页面参与编译、底部导航长什么样、窗口怎么渲染。这份工程的配置里有几个关键点值得注意:页面列表里除了常见的pages/index/index、pages/cart/cart、pages/user/user之外,还挂了一个pages/order/detail,说明订单详情页是独立的,不是用弹窗硬凑;TaBar 有三个 tab(首页、购物车、我的),图标用的是本地图片而不是网络图,这比较稳——网络图标一旦 CDN 资源挂掉,tab 就显示空白。

{ "pages": [ "pages/index/index", "pages/order/list", "pages/order/detail", "pages/cart/cart", "pages/user/user", "pages/address/list" ], "window": { "backgroundTextStyle": "dark", "navigationBarBackgroundColor": "#FF6B35", "navigationBarTitleText": "餐饮点餐外卖", "navigationBarTextStyle": "white" }, "tabBar": { "color": "#999999", "selectedColor": "#FF6B35", "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/cart/cart", "text": "购物车" }, { "pagePath": "pages/user/user", "text": "我的" } ] }, "permission": { "scope.userLocation": { "desc": "用于外卖配送地址的定位获取" } } }

逻辑说明:pages数组的第一个元素是启动页,也就是用户打开小程序最先看到的页面;tabBar的pagePath必须是pages数组里已注册的路径,否则编译直接报错;permission字段用于声明需要用户授权的地理位置——做外卖项目这个必须有,不然wx.getLocation和wx.chooseAddress在部分基础库版本下会静默失败。

参数说明:navigationBarBackgroundColor用的十六进制色值建议跟商家的品牌主色一致,而不是直接在index.wxss里硬写背景色;如果后续加了会员中心页面,记得把pages/user/member同步注册进pages数组,否则跳转会白屏。另外注意selectedColor和color的对比度,我见过一个项目把选中色设成浅灰色,用户在真机上根本分不清当前是哪个 tab。

这份工程的页面比多数教学 Demo 多出订单列表和地址管理两个模块,恰好是外卖场景的刚需。教学稿通常只做“点餐 → 下单 → 支付”的假闭环,但它把配送地址的增删改查也当成一等公民来处理,这点值得称赞。

2.2 WXML 模板语法:数据绑定、列表渲染与事件冒泡的实战姿势

pages/index/index.wxml是首页的模板文件,点餐页的菜品列表、分类导航、数量加减都在这层处理。这份代码在 WXML 的使用上没有一味追求wx:for套wx:for,而是用模板片段拆出了菜品卡片,这样做的好处是索引列表和分类切换可以共用一套渲染逻辑。

<view class="dish-card" wx:for="{{dishList}}" wx:key="id">onImgError(e) { const { index } = e.currentTarget.dataset; this.setData({ [`dishList[${index}].cover`]: '/assets/img/placeholder.png' }); }

逻辑说明:用模板字符串精确更新数组中某一项的字段,而不是setData({ dishList: newList })整个数组替换。小程序setData的性能瓶颈在于数据量,整数组替换在菜品上百个时会明显卡顿,按索引更新能将传输数据量降到最小。

参数说明:index必须通过>// 购物车核心数据结构 cartData: { shopId: 'shop_001', // 当前购物车所属门店 items: { 'dish_101': { id: 'dish_101', name: '招牌黄焖鸡', price: 28, count: 2, spec: '中辣', cover: '/assets/img/dish_101.jpg' }, 'dish_102': { /* ... */ } }, totalCount: 0, // 总件数(用于 tabBar 角标) totalPrice: 0 // 结算总价 }

逻辑说明:用items对象的 key 存菜品 ID,value 存菜品详情和数量。加菜的时候直接cartData.items['dish_101'].count++,不用遍历数组找菜品;减到 0 的时候delete cartData.items['dish_101'],天然做到了“没量就不占位”。计算总额时用Object.values(this.data.cartData.items)做归约,如果计算量大,可以放到computePrice()方法里统一处理。

参数说明:shopId的设计很关键——做外卖必须判断购物车里的商品是否来自同一家门店,跨店结算在微信小程序的餐饮场景里是不允许的(微信支付接口不支持跨商户号合并支付)。下单前检查shopId与当前所选门店是否一致,如果不一致要弹窗提示“清空当前购物车,切换到新门店?”这是我拆过的项目里最常见的业务遗漏点。

菜单更新(this.setData)的时候,totalCount和totalPrice必须同步刷新,而且要跟 tabBar 的角标联动。有一个小技巧我一直在用:把金额计算独立成纯函数,放进utils/cart.js,比如单位转换在函数内部做,页面里传的是分还是元在函数开头统一转换,这样即使后端改了金额精度,只改一处就够了,不用满项目找price/100这种魔法数字。

3. 业务流程串联:从菜品列表到外卖订单的完整操作链

3.1 点餐页的联动逻辑:左侧分类、右侧菜品与滚动监听

餐饮小程序的点餐页几乎都是“左侧竖排分类、右侧菜品列表”的布局,这份源码也不例外,但它处理两栏联动的方案干净利落:左侧点击分类右侧滚动到对应的分组顶部,右侧滑动时左侧分类高亮跟随,两边互不抢事件。

<!-- 左侧分类栏 --> <scroll-view scroll-y class="category-sidebar"> <view wx:for="{{categoryList}}" wx:key="id" class="category-item {{currentCategoryId === item.id ? 'active' : ''}}" >onListScroll(e) { const scrollTop = e.detail.scrollTop; let activeId = this.data.categoryList[0].id; for (let i = 0; i < this.data.groupOffsets.length; i++) { if (scrollTop >= this.data.groupOffsets[i].offset) { activeId = this.data.groupOffsets[i].categoryId; } else { break; } } if (activeId !== this.data.currentCategoryId) { this.setData({ currentCategoryId: activeId }); } }

逻辑说明:groupOffsets里存的是每个分类在右侧scroll-view里的累计偏移量,因为菜品列表是纵向排布的,只要scrollTop超过某个分组的 offset,就说明该分组已经滚动到视口顶部附近,这时左侧相应项变高亮。这里的break是性能优化:offset 数组是从小到大排序的,一旦遇到比自己大的就停止循环,不用遍历全部分类。

参数说明:所有取偏移量的计算必须在图片加载完成之后进行——如果菜品封面图是懒加载的,图片没加载完时offsetTop是被撑开前的高度,会跟实际滚动位置对不上,表现为“右侧滚动到红烧肉区域,左侧高亮却还停在素菜区”。解决方法是wx.createSelectorQuery().selectAll('.dish-group').boundingClientRect()在onReady里执行一次,并在首页图加载完成事件里再执行一次补偿。

3.2 微信支付流程:预下单、签名校验与回调处理的外卖链路

餐饮外卖的支付和普通商城有个显著差异:订单里包含配送费、打包费、优惠减免等多个金额维度,微信支付预下单接口需要的是最终实付金额,所以前端要在提交订单之前把金额算好,并且以分为单位传给后端。这份源码的支付链路完整地实现了小程序端wx.requestPayment的全流程。

// 提交订单并拉起支付 submitOrder() { const { cartData, addressInfo } = this.data; if (!addressInfo || !addressInfo.phone) { wx.showToast({ title: '请先选择配送地址', icon: 'none' }); return; } wx.showLoading({ title: '提交订单中' }); wx.request({ url: `${apiBase}/order/create`, method: 'POST', data: { shopId: cartData.shopId, addrId: addressInfo.id, items: Object.values(cartData.items), totalPrice: cartData.totalPrice, deliveryFee: 3, // 配送费 packFee: 1.5, // 打包费 discount: 0, // 优惠金额,这里前端不做计算,由后端算 remark: this.data.remark }, success: (res) => { if (res.data.code === 0) { this.doPayment(res.data.data.payParams); } else { wx.showToast({ title: res.data.msg, icon: 'none' }); } }, complete: () => wx.hideLoading() }); } doPayment(payParams) { wx.requestPayment({ timeStamp: payParams.timeStamp, nonceStr: payParams.nonceStr, package: payParams.package, signType: 'RSA', paySign: payParams.paySign, success: () => { this.redirectToOrderDetail(payParams.orderId); }, fail: (err) => { // 用户取消支付不等于订单失效,要在后端将订单标记为“待支付” wx.showToast({ title: '支付未完成', icon: 'none' }); } }); }

逻辑说明:wx.requestPayment的参数(timeStamp、nonceStr、package、paySign)必须全部由后端签名生成,前端绝对不能自己拼。package的格式是prepay_id=xxx,这个prepay_id是后端用订单号到微信支付统一下单接口换来的。前端要做的只是把后端返回的payParams原样塞进wx.requestPayment。

参数说明:signType在微信支付 v3 版本下是RSA,老代码里常写MD5,那是 v2 的协议,现在新申请的小程序商户号已经不支持了。如果你拿到的源码里写死MD5,大概率是老项目,需要后端配合升级——这也是我拆代码时判断项目新旧的一个指标。

支付成功后的动作是redirectToOrderDetail,跳转到订单详情页而不是重定向到首页,这符合用户心智:支付完成后的第一诉求是确认订单状态、看到配送信息。如果直接跳首页,用户会以为订单没提交成功。

下单接口传输的items直接把整个购物车数组传给后端,没有做签名或加密。这里有个安全漏洞:用户修改totalPrice为 0.01 或负数,后端如果不重新根据菜品单价计算金额,就会产生“0 元购”。生产环境一定要让后端在接单时按数据库里的菜品价格重新核算,前端传的totalPrice只作展示用。

3.3 订单状态机:待支付、待接单、配送中、已完成的关键流转

外卖订单的状态流转比线下点餐复杂:用户支付后订单进入“待接单”,商家确认后变“制作中”,制作完成再由骑手接单变“配送中”,最后用户确认收货才到“已完成”。这份源码用了一个order_status字段,并在前端页面里对每个状态做了不同的按钮组和倒计时逻辑。

状态值状态含义用户端操作商家端操作前端按钮组
0待支付立即支付/取消订单无操作取消订单、去支付
1待接单催单接单催单、取消订单
2制作中无标记完成联系商家
3配送中确认收货无查看骑手位置、确认收货
4已完成评价/再来一单无再来一单、评价
5已取消无无删除订单

逻辑说明:这里最容易被粗心实现搞错的点是“催单”和“取消订单”的可用状态——待支付状态的订单用户随时能取消,但一旦进入待接单(即商家已接单),用户就不能直接取消,只能联系商家协商。前端的cancelBtn显隐要跟order_status严格对应,如果直接在“配送中”的状态下还展示取消按钮,用户点下去后端就要处理“配送中订单取消”的逆向流程,牵扯退款、骑手通知等多个环节,复杂度指数级上升。

参数说明:订单列表的倒计时是一个高频需求——用户想知道“我的外卖还有多久到”,最稳妥的实现是后端在订单详情里返回estimated_delivery_time(时间戳),前端用setInterval每秒计算剩余秒数并更新 UI,同时在下一次页面onShow时重新拉取一次订单详情,以防止时间漂移。不要在本地用Date.now()减下单时间推算,因为用户切后台再回来,本地时间不保证准确。

订单取消的操作需要在后端调用微信支付的“关闭订单”接口,如果用户已经付款则需要走“申请退款”流程。这份源码在前端只做了 UI 层的取消按钮交互,真正关单的逻辑在服务端,前端只需要在点取消后轮询订单状态,直到后端确认订单变为已取消。这种现象级问题是外包项目的高频翻车点——前端以为取消成功了,后端其实没收到请求,结果用户收到了餐。

4. 后端接口联调与鉴权:从 wx.login 到 token 刷新的完整链路

4.1 wx.login 静默登录与自定义登录态维护

餐饮小程序的登录体系和网页完全不同:微信不允许小程序直接拿到用户的手机号、身份证等敏感信息,只能通过wx.login拿到一个临时凭证code,再向后端换取openid和session_key。这份源码的登录流程没有用微信官方推荐的wx.getUserProfile弹窗,而是直接调wx.login静默登录——这是外卖场景正确的取舍,因为用户还没点餐就要弹授权窗,转化率会断崖式下跌。

login() { wx.login({ success: (res) => { if (res.code) { wx.request({ url: `${apiBase}/auth/login`, method: 'POST', data: { code: res.code }, success: (resp) => { const { token, userInfo } = resp.data.data; wx.setStorageSync('token', token); wx.setStorageSync('userInfo', userInfo); } }); } } }); }

逻辑说明:wx.login的code有效期只有五分钟,而且只能使用一次。后端拿这个code去微信的jscode2session接口换取openid,再生成自定义的token返回给前端。前端把它存进Storage,后续所有需要鉴权的接口都在header里带Authorization: Bearer token。

参数说明:token的过期策略建议设为 24 小时,因为餐饮 App 的使用频次比社交软件低得多,太短会让用户频繁掉线,太长又增加被盗用的风险。这份源码在前端做了一层“401 拦截器”——响应码是 401 时,删掉本地 token,自动调用一次login()重新登录,然后重放刚才的请求。这个小机制虽然简单,但能避免用户在结账页被突然踢回登录页的尴尬体验。

代码里还要注意wx.login如果失败(比如用户关闭了微信授权),res.code是 undefined,此时不能发起登录请求,否则后端的code判空会直接抛异常。

4.2 request 请求封装:拦截器、错误码统一处理与超时重试

一个生产级小程序的wx.request必须封装成统一的方法,不能每个页面裸调。这份源码在utils/request.js里做了一个简易封装,把基础 URL、header 拼接、错误码提示、token 失效跳转都集中到了一起。

const request = (url, method = 'GET', data = {}, options = {}) => { const token = wx.getStorageSync('token'); return new Promise((resolve, reject) => { wx.request({ url: `${apiBase}${url}`, method, data, header: { 'Authorization': `Bearer ${token}` }, timeout: 10000, success: (res) => { if (res.data.code === 0) { resolve(res.data.data); } else if (res.data.code === 401) { // token 失效,重新登录后再执行原请求 wx.removeStorageSync('token'); login().then(() => resolve(request(url, method, data, options))); } else { wx.showToast({ title: res.data.msg || '服务异常', icon: 'none' }); reject(res.data); } }, fail: (err) => { if (options.retry && options.retry > 0) { setTimeout(() => { resolve(request(url, method, data, { ...options, retry: options.retry - 1 })); }, 1000); } else { wx.showToast({ title: '网络连接失败', icon: 'none' }); reject(err); } } }); }); };

逻辑说明:Promise 化的wx.request让页面的async/await调用成为可能,避免回调地狱。401 时的“重新登录并重放原请求”是这套封装里最有价值的部分——用户在点“结算”时恰好 token 过期,如果直接让用户重新登录再回来操作,购物车内容可能已经变了,用户体验极差;自动重放则无感续期。

参数说明:timeout: 10000是给弱网场景的兜底,不要设太短,餐饮店里的公共 Wi-Fi 经常 3G 速度都不到。失败重试retry只对 GET 请求建议开启,POST 请求(尤其支付下单)不要自动重试,因为 POST 不具备幂等性,重试会重复创建订单。支付类接口要做幂等控制——这也是后端的事,但前端至少要避免“自己重发请求”放大问题。

4.3 配送地址管理:wx.chooseAddress 与地图定位的坑

地址管理是外卖场景的核心模块,这份源码直接调用了wx.chooseAddress获取微信用户自带的收货地址,又用wx.chooseLocation让用户在地图上拖拽定位。这里有个不能忽略的坑:wx.chooseAddress和wx.chooseLocation都必须用户主动点击触发,而且前者返回的地址结构是provinceName、cityName而不是常见的province、city。

chooseAddress() { wx.chooseAddress({ success: (res) => { const address = { province: res.provinceName, city: res.cityName, county: res.countyName, detail: `${res.detailInfo} ${res.streetName}`, phone: res.telNumber, name: res.userName }; this.setData({ addressInfo: address }); } }); }

逻辑说明:wx.chooseAddress返回的detailInfo只包含街道和门牌号,streetName是额外的街道名称字段,两者可能需要拼接才能得到完整的详细地址。另外当用户授权的是“仅使用期间允许”,wx.chooseLocation的success回调拿到的经纬度是latitude和longitude,需要转换成地址字符串时,得靠后端或腾讯地图 API 反查——这份源码没有内置反查逻辑,而是把经纬度直接传给后端,由后端调用地图服务。

参数说明:wx.chooseAddress在部分安卓机型上存在“重复弹窗”问题——第一次调用能返回地址,第二次调用会静默失败,必须让用户先进入设置页开启“收货地址”权限。这个问题的排查耗时很长,因为它不是必现的,而且只在真机上能复现。我当时的解决方式是做一个权限检测的辅助函数,用wx.getSetting获取scope.address授权状态,如果为false就直接引导用户去设置页,而不是反复调wx.chooseAddress。

5. 避坑指南:餐饮点餐小程序最常见的六个翻车现场

5.1 页面真机白屏:ES6 语法与基础库版本不兼容

现象:开发者工具里一切正常,一上真机,部分页面白屏,Console 报错显示Cannot read property 'map' of undefined。

原因:小程序的基础库对 ES6 的支持程度参差不齐,低版本基础库不认Object.entries()或者Array.flatMap()。这份源码在购物车金额计算里用了Object.values(cartData.items),在 iOS 低版本上直接报错。

解决:在所有用到Object.values()、Object.entries()的地方手动替换成Object.keys().map()的兼容写法,或者在app.js顶部引入polyfill。我更推荐后者,一劳永逸,代码不用改。

5.2 微信支付拉起失败:package 格式被前端二次编码

现象:点击支付按钮后,wx.requestPayment的fail回调返回requestPayment:fail [payment:fail: Missing parameter package],但后端确认返回了package字段。

原因:前端从res.data.data.payParams取出package后,被 JSON 解析或encodeURIComponent二次处理,导致prepay_id=xxx的格式被破坏。package的值必须是prepay_id=xxxx这种原始字符串,前面不能加"package="前缀——很多人会想当然地自己拼一遍,结果双写了参数名。

解决:后端返回什么就原样塞给wx.requestPayment,不要做任何字符串拼接或 URL 编码。如果后端返回的不是prepay_id=xxx而是别的格式,检查后端统一下单时trade_type是否设置成了JSAPI,以及openid是否正确获取。

5.3 购物车角标与真实数据不一致:setData 的异步回调问题

现象:用户把商品从 3 件减到 1 件,tabBar 角标仍显示 3;杀掉小程序重新进来,角标又正常了。

原因:setData是异步的,更新cartData.items和更新totalCount被放在两个setData调用里,第二个setData执行时读到的cartData是旧值。尤其在性能较差的安卓机型上,两个相邻setData的间隔可能达到几十毫秒,用户快速操作时就会出现脏读。

解决:所有金额和数量的重算必须放在同一个setData里执行,只调用一次setData。可以先把cartData拷出来在内存里算好,再把计算结果整体setData进去。如果数据量太大必须分次,也要在成功的callback里做第二段更新,而不要直接写第二行setData。

5.4 定位权限拒绝后,配送地址列表为空

现象:用户首次打开小程序,系统弹出定位授权框,用户点了“不允许”,进入“配送地址”页面时列表空白,点击“新增地址”也没有反应。

原因:wx.chooseLocation和wx.getLocation在用户拒绝授权后,不会进入fail回调,而是直接返回 success 但latitude和longitude为 0。如果前端没做latitude === 0的兜底判断,就会把“空坐标”当成有效地址传给后端,后端存了一条没有实际位置的记录,列表当然显示不出来。

解决:在拿到wx.chooseLocation的回调后,先判断经纬度是否为空或全 0,如果是就提示用户“定位失败,请手动输入地址”。同时用wx.getSetting检测scope.userLocation的授权状态,如果是false,跳转wx.openSetting让用户手动打开设置页授权。

5.5 订单列表滑不动:scroll-view 高度设置不当

现象:订单详情页里的商品列表可以上下滑动,但切换到底部的“配送信息”区域后,整个页面无法滚动。

原因:外层scroll-view设置了scroll-y,但它没有固定高度——scroll-view在小程序里高度自适应时,scroll-y是不生效的,内容溢出后页面卡死不动。常见做法是在外层scroll-view上写height: 100vh,这个值在页面有自定义导航时会超出屏幕,导致底部出现白边或无法看到最后的按钮。

解决:用flex: 1加min-height: 0的方式让scroll-view自动填充剩余空间,而不是写死100vh。页面如果有底部操作栏(比如“提交订单”按钮),要给顶部内容区域设padding-bottom,否则按钮会盖住最后一条订单记录。

5.6 优惠券金额计算有误:后端不校验前端金额,导致超卖

现象:用优惠券下单,前端展示实付金额 25 元,但后端订单记录实付金额 28 元,用户投诉“多扣钱了”。

原因:前端提交订单时把discount字段直接传给后端,后端在计算实付金额时直接信任了前端的优惠金额,没有在服务端重新计算优惠规则(最低消费门槛、适用范围、是否已用)。结果用户用了一张满 30 减 3 的券,但订单金额只有 28,前端把discount写死为 3,后端也照单全收,扣了用户 28 元。

解决:防止这类问题的唯一可靠办法是前端不传任何金额字段给后端,后端根据订单明细单价、数量、配送费、打包费和用户实际可用的优惠券重新计算实付金额。前端展示的金额仅供用户预览,后端返回的最终金额才作为支付依据。这份源码在前端逻辑里遗留了一个方便测试的“前端算金额”开关,上线时务必关闭。

6. 上线前的最后一道工序:用体验版二维码与真机调试验证完整链路

源码跑通是一回事,能过微信审核、能稳定跑在用户手机上又是另一回事。我建议你按照我下面这个顺序把整条链路做一遍端到端验证,不要跳过,这些坑我全踩过。

第一步是准备一个体验版二维码。开发者工具右上角点“预览”,会生成一个有时效的二维码,用手机微信扫码进入体验版。这一步能过滤掉大部分开发环境和真机的差异问题。扫码进去后,先用新账号走一遍完整流程:进入首页 → 选菜品 → 加购物车 → 选地址 → 提交订单 → 微信支付(用测试金额 0.01 元) → 商家端接单 → 配送中 → 确认收货。每走一步就切到开发工具的 Network 面板,看请求是否全部成功、状态码是否 200、响应数据是否符合预期。

第二步是验证支付回调。用体验版完成一笔 0.01 元的支付后,让后端模拟微信支付的回调通知,确认订单状态从“待支付”变更到“待接单”,并给用户的订阅消息推送“商家已接单”的通知。不要直接用真钱测试,否则退款流程会让你在财务对账时痛苦不堪。

第三步是真机调试。开发工具里点“真机调试”,手机微信打开后,重点测这几个场景:弱网环境(开发者工具的网络模拟调到 Weak Network)下购物车加减菜是否卡顿;安卓低端机上页面滚动的帧率是否掉到每秒 30 帧以下(用wx.getPerformance()可以拿到帧率数据);从后台切回小程序时,订单状态是否需要手动下拉刷新才能更新。

// 切后台再回来,重新拉取活跃订单状态 onShow() { const token = wx.getStorageSync('token'); if (token && this.data.currentOrderId) { this.fetchOrderDetail(this.data.currentOrderId); } }

逻辑说明:onShow是页面每次从后台切回前台都会触发的生命周期钩子。这里判断currentOrderId是否存在再重新拉取订单状态,是为了处理用户切到微信聊天页回来看外卖到没到——后端状态可能已经变成配送中,本地 UI 还停留在制作中。这是餐饮小程序体验上的一个隐形得分项。

参数说明:fetchOrderDetail内部需要控制请求频率,不能每次onShow都全量刷新。配合throttle函数:两次请求间隔少于 3 秒就直接用本地缓存渲染,避免用户快速切前后台时出现多次网络请求、界面闪烁。

第四步是做一次静态代码审查。检查app.json里有没有未注册的页面路径;wx.request的 URL 是否全部是 HTTPS 且已经加入小程序的 request 合法域名白名单;所有wx:key是否给到了唯一值。最后一个才是打包发版。

上线这件事,我吃过最大的亏是在真实门店跑通时没验证“门店切换”这个场景——两个门店的商品在同一个购物车里,后端直接拒单,用户骂了十分钟我才反应过来。从那以后我每次做餐饮小程序都会强制走一遍“跨店加购 → 清空购物车 → 换店重选”这个用例,再确认按钮文案是“确认切换”而不是“确认清空”,避免用户手滑把购物车删了。这份源码我在本地也专门验证了这条链路,异常处理做得很稳。

希望这份拆解能帮你在餐饮小程序项目的路上少走几段弯路。如果你在部署时遇到代码层面的问题,欢迎按我上面提到的排查路径逐一对照——大概率是基础库版本或权限配置的差异,而不是业务逻辑本身的坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询