
简介这是一份面向微信小程序初学者的外卖点餐类完整源码与演示截图包适合正在学习小程序页面布局、交互逻辑与前后端数据绑定的开发者参考。资源以微信小程序原生框架实现涵盖点餐列表、购物车、个人中心、订单管理等典型模块代码结构清晰便于直接导入开发者工具运行调试。压缩包共八十九个文件包含三十一张png界面演示图、十七个js逻辑脚本、十五个wxml页面结构、十二个json配置及十二个wxss样式文件约六十KB体量轻量适合快速阅读核心代码。目前已有四百零二人学习下载说明该资源在同类教程中具备一定参考价值。通过源码与截图对照可直观理解外卖点餐场景下的页面跳转、数据传递和状态切换实现方式帮助初学者缩短从理论到实践的距离。1. 微信小程序外卖点餐源码包接手后的第一件事是拆结构拿到一个标注着“外卖点餐”的小程序压缩包很多人第一时间解压、双击、看预览图然后卡在第一步这个项目该怎么跑起来。外卖点餐类小程序是所有电商类目里最适合练手的形态因为它的业务闭环足够完整——菜单分类、加购、购物车、下单、订单流转、个人中心全都有而且前后端边界非常清晰。你不需要具备很强的后端能力只需要弄清楚微信小程序的路由、状态管理和请求封装就能把一个带真实点餐流程的小程序跑通。这篇内容面向两类人一类是准备做毕业设计或外包项目、需要快速改出一版可用源码的开发者另一类是已经在做电商小程序、但想从外卖场景里抠出可复用的订单状态机和缓存策略的从业者。我按接手源码包的顺序来讲先看目录结构再对接口然后打通购物车与订单最后改掉启动加载页让它真正像一个能上线的产品。2. 微信小程序外卖点餐的页面结构源码包里的第一张地图2.1 项目目录里先找 view 层和 data 层解压后先不急着打开app.js而是先看根目录里有哪些文件夹。一个正统的外卖点餐小程序目录结构会分成两条线pages下放页面utils下放请求和数据工具components下放可复用组件。如果你拿到的压缩包里还有server或cloudfunctions目录那说明它是带后端实现的完整项目如果只有纯小程序端大概率走的是 mock 数据或第三方后端。用app.json判断页面关系是最快的方式。第一项pages数组的起始页就是小程序冷启动时加载的页面外卖项目通常会把首页放在首位。看这个文件时我习惯同时确认三点页面路径是否全部存在、tabBar配置是否指向真实存在的页面、window里的导航栏标题是否按商户名称命名。许多源码包下载后白屏都是因为页面路径写错或tabBar指向了不存在的pagePath。{ pages: [ pages/index/index, pages/menu/menu, pages/cart/cart, pages/order/order, pages/user/user ], window: { navigationBarTitleText: 外卖点餐, navigationBarBackgroundColor: #FF6A00, navigationBarTextStyle: white }, tabBar: { list: [ { pagePath: pages/index/index, text: 点餐 }, { pagePath: pages/order/order, text: 订单 }, { pagePath: pages/user/user, text: 我的 } ] } }navigationBarTitleText是顶部导航文字外卖场景下建议替换成实际店铺名navigationBarBackgroundColor决定导航栏底色和店铺主色调保持一致tabBar的list最多配置 5 项点餐、订单、我的三栏是外卖项目的下限加购物车和优惠券页也可以但没必要刻意凑数。页面多不代表体验好外卖用户真正高频入口只有菜单和订单。2.2 页面文件的三件套与组件拆分每个页面文件夹里有同名四件套.js控制逻辑、.wxml写结构、.wxss写样式、.json做页面级配置。外卖项目里最典型的复用组件是商品卡片、加减号控件和订单状态标签。如果你发现components目录是空的而商品卡片在每个页面里被复制了三四遍那这个源码包的可维护性就打折了接手后第一件事就是把这些重复模板抽成product-card。看商品的.wxml时重点观察两个数据绑定写法商品列表用什么数据结构循环加减操作是直接修改本地数组还是调用了setData的完整路径。很多源码包在加购时写this.data.list[index].count 1这种直接赋值不会触发视图更新会导致数量变了但界面不刷新。正确的做法是重建数组再整体setData或者用setData({ list[index].count: 1 })这种路径写法更新单一字段。2.3 app.js 里的全局状态和登录态打开app.js看globalData里放了什么。合理的设计是只放与用户身份和全局配置相关的数据比如userInfo、shopId、API_BASE_URL。商品列表和购物车数据不该长期放在globalData里因为它不持久化杀进程后丢失而且多页面共享时容易产生脏数据。外卖项目的商品和购物车应该走本地缓存登录态走wx.getStorageSync(token)。App({ globalData: { API_BASE_URL: https://api.example.com, shopId: 1001, userInfo: null }, onLaunch() { const token wx.getStorageSync(token); if (!token) { wx.redirectTo({ url: /pages/login/login }); } } })API_BASE_URL建议提成常量不要在十个页面里各写一遍完整域名否则后端换域名时你会改到崩溃。shopId对应商户维度外卖 SaaS 项目会用它区分不同店铺的菜单和订单源码包里如果写死编号说明它原本是单商户的演示项目。onLaunch里做登录跳转时要注意时机冷启动首屏还没渲染完就跳转容易闪过一块白屏解决办法放在后面加载页部分讲。3. 外卖点餐的数据来源与接口对接从 mock 到真实下单3.1 接口清单先定下来外卖点餐小程序的核心接口不会超过七个菜单分类列表、分类下商品列表、购物车提交订单、订单详情、订单列表、支付状态查询、用户登录。不管源码包里沿用的是什么后端你接手后第一项工作是把接口地址替换成自己的并把返回结构统一。下面这张表是单商户外卖项目最常见的接口设计字段名不是标准但结构符合绝大多数后端实现。接口路径请求方式用途关键参数/menu/categoriesGET获取左侧分类shopId/menu/productsGET获取分类下商品categoryId,page,pageSize/order/submitPOST创建订单商品数组、地址、备注/order/payPOST发起支付orderId,payType/order/detailGET查询订单状态orderId/order/listGET拉取历史订单page,status/auth/loginPOST微信登录换 tokencode,nickname,avatar重点检查/order/submit和/order/pay是分离的还是合一的。外卖项目里这两个接口最好分开因为先创建订单再调起微信支付可以处理“支付失败但订单已生成”的边界场景而且取消订单时需要独立的订单号。如果源码包里只有一个submitAndPay接口建议你拆开否则订单状态机少一个节点后面做退款和超时关单会非常别扭。3.2 本地开发先跑 mock别等后端后端接口还没就绪时用本地 mock 数据把页面跑通是最省事的推进方式。在utils/mock.js里导出菜单数据页面请求时先判断useMock开关再决定走wx.request还是直接返回本地数组。这样后端联调时改一个布尔值就能切换不需要重写页面逻辑。// utils/mock.js const mockProducts [ { id: 1, name: 招牌黄焖鸡, price: 18.8, categoryId: 1, stock: 99, image: /images/chicken.png }, { id: 2, name: 番茄牛腩饭, price: 22.5, categoryId: 1, stock: 50, image: /images/beef.png } ]; function getProducts(categoryId) { return mockProducts.filter((item) item.categoryId categoryId); } module.exports { getProducts };mock 数据里的stock字段别省外卖场景库存是硬需求。价格字段用数字类型不要用字符串因为购物车加法运算会依赖数字类型字符串拼出来的结算金额会在支付时多出想不到的精度问题。mock 数据和后端返回结构必须一致这样切到真实接口时页面不用改。3.3 request 封装与 wx.login 的配合真实接口联调时所有请求走同一个封装函数统一带token、统一处理401、统一解包数据。外卖项目的鉴权流程是小程序端调wx.login拿到临时code后端用这个 code 换openid并返回自定义 token小程序把 token 存到本地storage后续请求通过Authorization头传递。// utils/request.js function request(url, method, data, needAuth true) { return new Promise((resolve, reject) { const header { Content-Type: application/json }; if (needAuth) { header.Authorization Bearer ${wx.getStorageSync(token)}; } wx.request({ url: ${getApp().globalData.API_BASE_URL}${url}, method, data, header, success(res) { if (res.statusCode 401) { wx.removeStorageSync(token); wx.redirectTo({ url: /pages/login/login }); reject(new Error(未登录)); return; } if (res.data.code 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message, icon: none }); reject(new Error(res.data.message)); } }, fail(err) { wx.showToast({ title: 网络异常请稍后重试, icon: none }); reject(err); } }); }); } module.exports { request };接口统一约定code 0表示业务成功这是最普遍的返回结构。wx.showToast放在request封装里而不是每个页面重复写能减少重复代码。注意fail回调里不要再弹一次后端错误信息因为success分支已经弹过了避免重复提示。想要确认订单请求带上了正确的参数最直接的办法是打开开发者工具的 Network 面板查看请求链路逐个核对 header 里的 token 和 data 里的商品 ID。3.4 真机联调的三个坑第一个坑是域名白名单。开发者工具里勾选了“不校验合法域名”能跑通真机上会把请求直接拦掉。解决方式是后端上线 HTTPS 域名后在微信公众平台配置 request 合法域名或者开发阶段在真机上打开调试模式两者要分清不能混用。第二个坑是超时时间。外卖项目请求菜单图时图片资源多timeout默认 60 秒够用但提交订单的 POST 请求在弱网下容易卡住建议在wx.request里显式设置timeout: 10000并在fail回调中区分超时和断网。第三个坑是wx.login的 code 只能使用一次。登录接口失败重试时后端会提示 code 已失效前端必须重新调wx.login拿新 code而不是缓存旧 code 反复提交。4. 购物车与订单状态流外卖点餐业务的骨架逻辑4.1 购物车数据必须走本地缓存外卖购物车的核心特点是跨页面保留用户在菜单页加了三个菜滑到购物车页要能看到同样内容刷新后也得在。globalData只活在当前小程序进程里所以购物车数据要落在wx.setStorageSync(cart)。每次加购、减购、清空后必须同步更新缓存页面读取时统一走wx.getStorageSync(cart)。// pages/menu/menu.js addToCart(item) { const cart wx.getStorageSync(cart) || []; const found cart.find((line) line.id item.id); if (found) { found.count 1; } else { cart.push({ id: item.id, name: item.name, price: item.price, count: 1, specs: item.specs || 默认 }); } wx.setStorageSync(cart, cart); const totalCount cart.reduce((sum, line) sum line.count, 0); this.setData({ cartTotalCount: totalCount }); }加购时用find判断购物车里是否已经存在同 ID 商品存在就增加数量不存在就推入新行。存储结构里保留name和price快照是为了幂等下单接口返回前如果商品信息变了购物车展示的仍是用户勾选时的信息。购物车里的count永远是整数reduce统计总数后setData到角标上这一步别省否则菜单页右下角购物车图标不显示数字。减购时要注意count减到 0 时把这一行从数组里splice掉不要保留count: 0的僵尸行否则结算金额不刷新。4.2 订单状态机与页面权限的映射订单状态是外卖项目里最容易写乱的部分。单商户外卖的标准状态流转是待支付、已支付、商家接单、配送中、已完成、已取消。部分平台把“待支付”和“待接单”拆成两个状态实际业务里待支付 15 分钟超时自动关单支付成功后才会推给商家确认。状态码设计建议用两位数10 起步递增为后续插入退款和售后状态留空间。状态码状态名前端展示动作可操作项10待支付倒计时 支付按钮继续支付 / 取消订单20待接单等待商家确认无30配送中展示预计送达时间拨打电话40已完成弹评价入口再来一单50已取消退款提示无订单列表页的筛选 Tab 按照状态码过滤而不是按字符串匹配订单状态名因为后端返回的状态码是数字状态名的文案可能随地区调整。订单详情的“再来一单”本质是读原订单的items数组把每个商品重新执行一次addToCart然后跳转到购物车页。注意这里要清空原购物车再导入避免新旧餐品混在一起。4.3 下单参数与幂等设计提交订单时参数至少要包含门店 ID、商品明细、配送地址、期望送达时间和备注。餐具数量这类选项会被聚合成一个tableware字段前端做选择器时需要注意微信小程序里 group 组件的值传递推荐用单选框绑定value不用字符串拼接。{ shopId: 1001, items: [ { productId: 1, quantity: 2, price: 18.8 }, { productId: 2, quantity: 1, price: 22.5 } ], address: { name: 张伟, phone: 13800001111, detail: 海淀区中关村大街 1 号 }, expectedTime: 2025-06-01T12:00:0008:00, remark: 不要辣, tableware: 2, source: miniapp }price字段在订单请求里必须由前端带上但后端要以自己查到的商品表单价为准重新算一遍总价这是防止篡改金额的基本防线。source字段标记订单入口是miniapp还是h5外卖 SaaS 项目后期要区分渠道转化率这个字段在订单落库时就该统一写死。幂等键可以借clientToken实现每次进入结算页生成一个唯一字符串下单失败重试时复用同一个 token后端检测到重复 token 直接返回上一次的订单结果防止用户手滑连点两次提交按钮产生两笔订单。5. 自定义启动加载页修改刚进入的加载页面并验证启动流程5.1 为什么外卖小程序必须有自己的加载页默认情况下小程序冷启动时第一帧是首页页面数据还没渲染出来用户会先看到导航栏和空白 body体验很差。外卖项目尤其明显菜单数据量大首屏要同时拉分类和商品两批数据白屏时间可能超过一秒。常见的做法是新增一个pages/loading/loading页面把它放进app.json的pages数组第一项加载逻辑放在这个页面的onLoad里数据准备好后再跳转到实际首页。这样“修改刚进入的加载页面”就成了控制启动体验的入口而不是简单地把首页空着等数据。5.2 加载页的最小可用实现加载页的职责是并行完成两件事检查登录态、预拉取菜单缓存。两者都不阻塞主流程最多等 2 秒强制跳转避免网络故障时用户卡死在加载页。进度条用setInterval模拟推进真实数据加载完成后直接置满。// pages/loading/loading.js Page({ data: { progress: 0 }, onLoad() { this.timer setInterval(() { let progress this.data.progress 20; if (progress 80) progress 80; this.setData({ progress }); }, 150); this.bootstrap(); }, bootstrap() { const token wx.getStorageSync(token); if (token) { getApp().globalData.token token; this.enter(/pages/index/index); } else { this.enter(/pages/login/login); } }, enter(url) { clearInterval(this.timer); this.setData({ progress: 100 }); setTimeout(() { wx.redirectTo({ url }); }, 200); } })progress最多推进到 80剩余 20 留给页面切换动画时间视觉上不会出现跳变的割裂感。redirectTo替代switchTab是因为加载页本身不是 tabBar 页面跳转目标是首页时用redirectTo会把加载页从页面栈中移除用户返回时不会回到加载页再经历一次重复跳转。token 写入globalData是为了让首页的onLoad直接读取不用再同步走一次getStorageSync。5.3 验证加载页是否生效改完app.json后在开发者工具中点击“编译”模拟器首屏显示的应该是pages/loading/loading而不是首页。接着用另一种方式验证真实冷启动在真机预览模式下杀掉小程序进程重新从最近使用记录进入观察首屏是否先出现加载页再平滑切换到首页。如果直接闪现首页说明app.json里pages数组的首项没有改成加载页或者编译器还在走旧缓存。要清理登录态测试未登录跳转可以清除小程序缓存后重新进入要测试已登录用户直接进首页要保证本地 storage 里的 token 未过期。最后一步检查启动页各环节耗时在加载页的onLoad和首页的onLoad里各放一个console.time两者差值就是用户实际体感启动耗时。在加载页请求接口时不要处理业务失败所有异常统一丢给目标页面的onLoad处理否则加载页会变成第二层错误处理中心。确认当前页首字节渲染来自加载页而不是首页再把白屏时间压在 800ms 以内这套启动流程才算真正收工。本文还有配套的精品资源点击获取