尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

微信小程序外卖源码二次开发:购物车、订单状态与支付回调拆解

微信小程序外卖源码二次开发:购物车、订单状态与支付回调拆解 简介云贝餐饮外卖O2O v1.6.9 开源小程序.zip是一款面向微信小程序开发者和餐饮商家的源码模板基于微信小程序框架构建覆盖菜单展示、在线下单、支付、订单管理、配送跟踪等核心外卖闭环适合快速搭建或二次定制餐饮外卖应用。压缩包约65.84MB文件总数与类型明细暂时缺失不过源码类模板通常包含页面WXML/WXSS、逻辑JS、项目配置及说明文档可导入微信开发者工具查看。目前已有1240人学习下载适用于熟悉或希望进阶微信小程序开发的个人与团队。源码开放原始代码支持根据业务需求调整界面、增加功能、优化性能对商家而言也可借助模板快速上线一套符合行业习惯的外卖小程序降低从零开发的成本。持续关注v1.6.9版本的迭代更新与技术社区能帮助使用者保持应用竞争力。1. 一份 v1.6.9 外卖小程序源码怎么拆才不算浪费拿到云贝餐饮外卖 O2O v1.6.9 这套开源微信小程序模板时大多数人第一反应是直接导入微信开发者工具看到首页出来就以为完事了。实际跑一遍你会发现这套源码的价值不在那个能浏览的菜单页而在你改第一行代码之前对它的理解订单状态机怎么流转、购物车数据怎么持久化、支付回调怎么和本地订单对齐这三个问题不搞清楚后续每一次二次开发都会在联调时返工。适合读这篇文章的人是手里已经有小程序基础、想拿一套完整业务源码做改造的开发者或者是餐饮商家侧的技术负责人想评估这套模板能不能支撑真实门店的外卖订单。本文会从目录结构讲起逐步落到下单链路、接口层封装、版本升级排查最后给一个实际切换线上接口的改造技巧。2. 源码目录与服务层结构先定位业务入口再谈改代码2.1 微信小程序原生工程的标准骨架解压 zip 之后第一件事不是看页面而是看根目录下的app.json。它是整个小程序的注册中心pages 数组里第一个元素就是启动页。云贝这套模板的 pages 顺序通常是pages/index/index优先也就是首页作为冷启动入口。这里有一个容易忽略的细节tabBar配置决定了底部导航是否生效如果tabBar.list里的pagePath和 pages 数组里的路径不一致编译期不会报错但点击底部 tab 会白屏。收到这种二手模板第一步应当做一致性校验。{ pages: [ pages/index/index, pages/order/order, pages/cart/cart, pages/user/user ], tabBar: { list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/order/order, text: 订单 }, { pagePath: pages/cart/cart, text: 购物车 }, { pagePath: pages/user/user, text: 我的 } ] } }这段配置的核心逻辑是让四个主页面平级注册tab 切换时不会重新触发 onLoad而是走 onShow。对 O2O 外卖场景来说购物车页必须保持内存状态否则用户选了几个菜切到首页再切回来购物车被清空体验直接崩掉。参数说明pagePath必须写相对路径不带.js后缀text是 tab 下显示的文字长度建议控制在 4 个汉字以内iconPath和selectedIconPath是可选字段如果不配tab 上就只有文字。2.2 utils 与 api 目录接口层是所有二次开发的起点云贝这个版本的utils/request.js封装了 wx.request 的 Promise 化处理api/目录下按业务域拆分了接口函数。看源码时优先读这个文件因为它决定了你后续接真实后端时改动范围有多大。如果模板里所有请求都直接调用wx.request那说明接口层没有收敛接真实接口时得全局搜索替换工作量大得多。// utils/request.js 核心片段 const request (url, method GET, data {}, header {}) { return new Promise((resolve, reject) { wx.request({ url: baseUrl url, method, data, header: Object.assign({ content-type: application/json }, header), success: (res) { if (res.statusCode 200 res.statusCode 300) { resolve(res.data); } else if (res.statusCode 401) { wx.navigateTo({ url: /pages/login/login }); } else { reject(res); } }, fail: (err) reject(err) }); }); };这段代码的要点在于把状态码判断收敛到一处2xx 直接 resolve401 统一踢到登录页其余错误抛给调用方。实际业务里需要再补一层业务码判断因为不少后端接口即使 HTTP 200返回体里code也可能是非零错误码。我一般会在 resolve 之前加一个if (res.data.code ! 0)的分支统一 Toast 错误信息。参数说明baseUrl在config.js里维护切换环境只改这个文件header的默认 content-type 对 GET 请求也生效如果后端不接收 JSON 格式的 GET可以按请求类型动态调整。2.3 模板页面的组件拆分逻辑看components/目录云贝把商品卡片、数量步进器、订单卡片做成了自定义组件。这种拆分的好处是首页、搜索页、分类页可以复用同一套商品展示逻辑。每个组件由.js、.json、.wxml、.wxss四个文件组成组件的properties定义了外部传入的参数。改组件时特别注意properties里的字段名如果和组件内部 data 里的字段名冲突会出现属性覆盖后显示异常的问题排查时可以先看组件 wxml 里绑定的字段到底是来自 properties 还是 data。// components/goods-card/index.js Component({ properties: { goods: { type: Object, value: {} }, showStepper: { type: Boolean, value: true } }, methods: { handleAddToCart() { this.triggerEvent(addtocart, { goods: this.properties.goods }); } } });这里的核心事件机制是triggerEvent子组件不直接操作全局购物车数据而是向上抛事件交给页面来处理。这样设计的好处是组件可以在不同页面复用而不污染数据流坏处是如果页面的 bind 事件没写点击加号按钮会毫无反应而且不报错。排查这类问题时看页面 wxml 里有没有bind:addtocartonAddToCart这样的绑定。3. 购物车与下单链路状态管理的关键路径3.1 购物车为什么不能直接存在组件里拿到这个模板你会发现购物车数据没有用全局状态库而是在pages/cart/cart.js里用getApp().globalData.cartList存储。这是一个很务实的做法外卖场景购物车字段少、拼单复杂度低引入 MobX 或 Redux 反而增加理解成本。globalData 的生命周期和小程序实例一致冷启动后首次访问是空数组需要在app.js的 onLaunch 里从 storage 恢复。// app.js 片段 onLaunch() { const cart wx.getStorageSync(cartList); this.globalData.cartList cart || []; }, addToCart(goods) { const list this.globalData.cartList; const idx list.findIndex(item item.id goods.id); if (idx -1) { list[idx].count 1; } else { list.push(Object.assign({ count: 1 }, goods)); } this.globalData.cartList list; wx.setStorageSync(cartList, list); }这个实现的关键在于同 ID 商品合并数量而不是重复插入。实际改造时还需要考虑规格维度同一道菜选「微辣」和「中辣」应当视为不同购物车项判断条件不能只看 goods id要把 sku 标识一起拼接。参数说明wx.setStorageSync每次调用都会全量写入购物车列表大时会有性能损耗但外卖场景几十个条目完全没问题findIndex是 ES6 方法基础库版本高于 2.0 都没问题。3.2 下单页的数据组装与校验下单页pages/confirm/confirm.js在整个链路里承担的是「把购物车数据变成订单数据」的角色。它干的事有三件从 globalData 读取购物车分门店分组如果支持多门店计算总价并校验起送价提交订单到后端或本地模拟接口。很多模板在本地演示模式下没有真实后端提交按钮走的是 wx.cloud 或 setTimeout 模拟成功这在实际联调时是第一个坑。// 下单前的校验逻辑 const canSubmit (cartList, shopInfo) { if (!cartList.length) return { ok: false, msg: 购物车为空 }; const total cartList.reduce((sum, item) sum item.price * item.count, 0); if (total shopInfo.minPrice) { return { ok: false, msg: 未达起送价 ¥${shopInfo.minPrice} }; } return { ok: true, total }; };这段代码覆盖了两种最常见的下单失败原因空购物车和未达起送价。真实场景还需要补配送费计算、满减活动判断、优惠券抵扣三个模块建议顺着total的累加逻辑打断点看每步数值是否符合预期。参数说明reduce的初始值必须传0不传的话数组为空时会报错shopInfo.minPrice如果来自接口返回的字符串类型要先Number()转换再做比较。3.3 模拟支付与真实支付的回调差异模板里的支付多半是一个wx.requestPayment调用参数从后端下单接口返回。但本地演示时没有服务端签名常见做法是先走一个mockPay()直接把订单置为已支付。这里有个隐患模拟支付跳过了金额校验如果后续要切真实支付必须把下单接口、支付参数获取、支付回调三段逻辑全部替换。建议先读api/order.js里 submitOrder 函数看它的返回结构是否包含timeStamp、nonceStr、package、signType、paySign这五个字段缺任何一个wx.requestPayment都会直接 fail。wx.requestPayment({ timeStamp: res.data.timeStamp, nonceStr: res.data.nonceStr, package: res.data.package, signType: RSA, paySign: res.data.paySign, success: (payRes) { // 支付成功跳转订单详情 wx.redirectTo({ url: /pages/order-detail/order-detail?id${orderId} }); }, fail: (err) { // 用户取消支付保留订单为待支付状态 this.setData({ paying: false }); } });关键逻辑在 fail 回调用户主动取消和支付异常都走这里但业务含义不同。取消支付应该允许用户回到订单页继续付款而支付异常需要弹错误码。判断方式是看err.errMsg是否包含requestPayment:fail cancel包含则是用户主动取消。参数说明package的写法是prepay_idxxx不能只传xxxsignType必须和商户平台配置一致用MD5还是RSA以后端签名为准建议让后端同学在接口文档里标注。4. 订单管理与配送跟踪状态机是业务的核心4.1 订单列表的类型切换实现订单页通常有「全部 / 待付款 / 待配送 / 已完成」四个 tab云贝模板用currentType控制列表筛选。实现方式有两种前端本地过滤全部订单数据或者每次切换 tab 重新请求接口。本地过滤适合订单量少的场景接口分页查询则适合真实部署。看代码时注意请求参数里的status对应关系0 待付款、1 待接单、2 配送中、3 已完成不同版本可能用字符串连调时要和后端确认枚举值表。// 订单列表请求示例 fetchOrders(status) { request(/api/order/list, GET, { status }).then((res) { this.setData({ orderList: res.data.list }); }).catch(() { wx.showToast({ title: 订单加载失败, icon: none }); }); }这里的请求函数没有写 loading 状态实际使用中要在请求前wx.showLoading、请求后wx.hideLoading否则弱网环境下用户反复点击 tab 会顺序错乱。参数说明status不传或传空字符串时后端应返回全部订单wx.showToast的icon只有success、error、loading、none四个值想要自定义图标需要image字段。4.2 配送跟踪如何用 map 组件承接模板中配送页通常是 web-view 内嵌 H5 地图或者 map 组件加 marker 标记。map 组件是小程序内置组件里少数不推荐频繁 setData 的因为经纬度数据高频更新会导致渲染卡顿。云贝的做法是定时器每 10 秒拉一次骑手位置用wx.createMapContext的translateMarker做平滑移动。const mapCtx wx.createMapContext(map, this); setInterval(() { request(/api/order/rider-location, GET, { orderId }).then((res) { mapCtx.translateMarker({ markerId: 1, destination: { latitude: res.data.latitude, longitude: res.data.longitude }, duration: 500, animationEnd: () {} }); }); }, 10000);这段代码的要点是translateMarker替代直接改 marker 的经纬度动画过渡比硬跳体验好得多。实际项目中注意两点定时器要在页面 onUnload 里 clearInterval否则页面销毁后还在请求接口每 10 秒一次请求可以用wx.stopLocationUpdate配合后台推送降低耗电但地图精度要求高的场景还是轮询稳妥。参数说明duration单位是毫秒值设太短会看起来像瞬移建议和轮询间隔保持 1/20 左右的比例markerId是地图上 marker 的唯一标识多个骑手时每个骑手对应不同 id。4.3 订单状态被跳过时怎么排查外卖业务里最怕的状态问题是「已支付但商家没收到」也就是订单状态没有从「待接单」流转到「已接单」。用这套模板自测时可以在支付成功回调里加一行日志输出订单 id然后去接单端手动确认。如果后端接口正常但前端页面不刷新多半是onShow里没有重新拉订单详情页面从后台切回时仍显示旧状态。onShow() { const orderId this.options.id; if (orderId) { this.fetchOrderDetail(orderId); } }onShow 和 onLoad 的执行时机差异是这类问题的根源onLoad 只在页面创建时执行一次onShow 每次页面从后台恢复都会触发。支付成功后跳转订单详情页新页面 onLoad 会执行但如果用户切到微信聊天再切回来只有 onShow 触发不加这个逻辑就看不到状态更新。参数说明this.options拿到的参数类型是字符串直接拼进请求 URL 没问题但用来比较数字类型时要先转换。5. v1.6.9 升级差异与兼容性排查换版本前必须做的事5.1 从旧版本升级的差异对比如果你之前用过 v1.5 或 v1.6 的云贝模板v1.6.9 主要的改动集中在三个方面订单模块从本地 mock 数据改成接口驱动、WXS 过滤器替代了部分 JS 端的价格格式化、以及组件库公共样式的抽离。直接覆盖源码文件会有残留文件问题旧版本有而新版本删除了的页面会在app.json里报找不到路径编译直接失败。正确做法是保留project.config.json的 appid 配置其余全部用新版文件覆盖再用微信开发者工具的「代码质量」面板跑一遍静态检查。# 在项目根目录执行检查无引用文件 grep -r pages/order/order app.json这条命令的作用是确认 app.json 里的页面路径在磁盘上真实存在。实战中覆盖升级后最常见的报错是module utils/util.js is not defined原因是新版把工具函数拆到了utils/index.js老页面还在 import 旧路径。处理方式是用全局搜索替换把utils/util批量改成utils/index然后逐个页面跑编译。5.2 基础库版本与 API 兼容矩阵云贝 v1.6.9 用到的部分 API 有基础库最低版本要求比如wx.requestPayment的最低版本是 1.2.0wx.createMapContext是 1.0.0而wx.getUserProfile要求 2.10.4 以上。如果线上用户的基础库版本偏低这些 API 调用会直接失败。排查手段是在app.json里配置requiredBackgroundModes和 lazyCodeLoading同时掌握wx.canIUse来做 API 存在性检测。if (wx.canIUse(getUserProfile)) { wx.getUserProfile({ desc: 用于完善会员资料, success: (res) {}, fail: (err) {} }); } else { // 降级方案直接弹窗让用户填写昵称 wx.showModal({ title: 提示, content: 请手动输入昵称 }); }这段代码解决的是不同系统版本微信对授权接口支持不一致的问题。老版本微信里wx.getUserProfile不存在不判断直接调用会报TypeError: wx.getUserProfile is not a function。参数说明wx.canIUse的参数格式是API名.参数.返回值也可以只写 API 名做粗粒度判断降级方案里弹窗收集用户昵称的方式虽然体验差但至少保证功能可用。5.3 微信开发者工具中常见的编译告警处理拿到源码导入工具时常见的 warning 有两类一类是Some selectors are not allowed in component wxss这是因为组件 wxss 里写了标签选择器或 ID 选择器小程序组件样式隔离默认不允许另一类是property is not supported对应的是 WXSS 里写了不兼容的 CSS 属性。处理方法并不复杂把标签选择器全部改成 class 选择器把不支持的属性用标准属性替代。/* 错误写法 */ view { margin: 10rpx; } /* 正确写法 */ .goods-item { margin: 10rpx; }样式选择器的隔离规则是组件化开发必须遵守的约束标签选择器会穿透组件边界导致页面其他部分被意外影响。调试时可以用工具右上角的样式隔离开关临时关闭隔离来看效果但发布前必须改回 class 写法。参数说明组件 json 文件里styleIsolation: apply-shared可以允许页面样式影响组件但会引入命名冲突风险不建议默认打开。6. 接入真实后端接口的最小改造套路最后说一个实际接真实后端时最省事的做法保留前端模板的请求封装只替换baseUrl和登录态管理。云贝这套模板里所有接口都走utils/request.js所以接后端时只需要改两个文件第一是config.js里的baseUrl指向你们的服务端地址第二是在 request 的 header 里注入 token。token 的获取方式通常是先调微信登录接口换 code再把 code 传给后端换取 openid 和 session_key模板自带的 mock 登录不会做这一步需要自己补。// config.js module.exports { baseUrl: https://api.yourdomain.com, tokenKey: token };// request.js 里注入 token const token wx.getStorageSync(config.tokenKey); if (token) { header[Authorization] Bearer token; }这里最关键的是登录时序页面 onLoad 时如果发现本地没有 token不能直接跳登录页要先静默调用wx.login拿 code再用 code 请求后端换取 token。这个过程可能在用户看到首页之前就要完成所以建议在app.js的 onLaunch 里做一次并且用 Promise 包起来页面请求接口时等登录态就绪。参数说明Authorization的 Bearer 前缀是后端约定的格式有的后端用token字段而不是 header 头对接时以接口文档为准wx.login拿到的 code 五分钟内有效且只能使用一次不能缓存复用。有一点容易被忽略模板内置的地址管理、商品分类这些数据在上线前必须把假数据清理干净。假数据通常写死在data里或者在onLoad里直接赋值排查时全局搜索mock、demo、test关键词找到后全部切到接口调用。最后在开发者工具里打开「真机调试」用一个测试微信号走一遍选餐、加购、下单、支付、查看骑手位置的完整流程订单状态和金额每个环节都要核对这套模板才算真正落地到你的项目里。本文还有配套的精品资源点击获取
返回列表