简介:面向餐饮商家和微信小程序开发者的外卖点餐模板,以zip压缩包形式打包,共68个文件,包含png界面切图、json配置文件、js逻辑脚本、wxss样式表、wxml页面结构等类型,整体仅1.33MB,便于快速部署与二次修改。模板预设用户端点餐、菜单管理、地址填写、订单中心、门店信息等页面,并配套微信支付、配送规则等常见功能模块,商家可根据品牌风格调整颜色、布局和菜品数据,无需从零搭建即可上线基础版外卖点餐平台。预览中可见项目目录清晰,区分pages、images、utils等公共模块,适合中小餐饮企业或小程序初学者作为实战参考。至今已有136人学习浏览,适合希望节省开发成本并快速上线点餐服务的团队参考。
1. 一份“模板下载.zip”,真正值钱的是解压之后的工程判断
拿到“美食餐饮外卖点餐的微信小程序模板下载.zip”这个压缩包,大多数人第一反应是解压、拖进微信开发者工具、点编译,然后看到页面出来了就以为完事了。但实际上,模板类项目包最消耗时间的从来不是第一眼能看到的页面,而是三件容易被忽略的事:工程目录是否正确识别、appid 与合法域名是否替换干净、本地静态数据与真实后端接口之间的切换逻辑是否理顺。这个 zip 里装的不是一套“装上就能赚钱”的成品,而是一套外卖点餐业务的最小可行骨架——商品列表、购物车、下单流程、用户身份这些模块都在,但每一处都需要按你自己的部署环境做二次确认。
这篇就顺着“拿到 zip 之后”的时间线展开,从解压导入、目录结构识别,到购物车数据流和订单支付参数,最后落在品牌改造和发布前验证上。不管你是拿这套模板做毕业设计、接外包,还是自己店里想快速跑一个小程序外卖入口,下面的步骤都能直接对着操作。
2. 解压、导入与工程化:让 zip 里的模板真正跑起来
2.1 解压规范与目录识别:别把两层目录压进开发者工具
下载下来的美食餐饮外卖点餐的微信小程序模板下载.zip,首先要解决的不是代码问题,而是“哪一层才是项目根目录”。微信开发者工具导入项目时,要求你选择的目录里必须直接包含app.json、app.js、project.config.json这三个文件。很多模板包在压缩时会在外层多包一层文件夹,解压后你看到的是美食餐饮外卖点餐的微信小程序模板下载/底下又套了一个项目文件夹。
我一般的处理方式是:先解压到一个不含中文和空格的纯英文路径下,比如D:\wechat-app\takeout-template,然后进去找project.config.json。找到这个文件的那一层,才是导入开发者工具时应该选的目录。如果你直接把最外层带中文名的目录拖进去,开发者工具大概率会报“未找到 app.json”或“invalid app.json”之类的错误。
解压工具上,Windows 自带的资源管理器右键“全部解压”就能处理常规 zip 包。如果遇到压缩包损坏或者解压中途报错,先别急着怀疑工具,用 7-Zip 打开压缩包看看是不是文件列表本身就不完整——有些网盘下载的文件会截断,zip 的中央目录损坏时 7-Zip 的修复功能比换工具更有效。至于网上常说的“zip 压缩包密码破解工具”,模板包一般不会加密,如果真遇到加密包,更可能是发布者用来限制二次传播的手段,不是技术问题。
2.2 导入微信开发者工具的三个必改项
目录选对之后,导入界面里有一个 AppID 的选择。这个位置是第一个必改项,也是最容易被忽略的。
在开发者工具的导入弹窗里,AppID 有三种选择:测试号、自有 AppID、游客模式。我建议你直接选“测试号”先跑通编译,等代码改得差不多了,再换成你自己注册的小程序 AppID。原因是:测试号不需要你预先注册小程序账号,也不受“小程序名称、类目”的限制,适合前期开发调试。但要注意,测试号不支持大部分需要真实用户身份的 API,比如wx.login拿到的 code 换 openid 的流程虽然能跑,但后续云开发或自建后端的用户体系仍然需要真实 AppID 才能完成闭环。
第二个必改项在project.config.json里。用任意编辑器打开这个文件,找到"appid"字段,模板发布者一般会留一个占位符或某个特定值。把它换成你自己的 AppID。如果你用的是测试号导入,这个字段是什么其实不重要,但一旦切到真实 AppID,这里就必须一致。
第三个必改项是开发者工具右上角的“详情”面板里的本地设置。其中“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”这个开关,开发阶段务必打开。原因很简单:你本地调试时请求的后端接口大概率是http://192.168.x.x:8080或者http://localhost:3000这样的地址,小程序在真机上是不允许访问非 HTTPS 域名的,但开发者工具在勾选“不校验”之后可以放行。这个开关只影响开发者工具本地环境,真机预览时仍然会强制校验,所以它只是给开发阶段用的后门,不是上线时的解决方案。
| 设置项 | 位置 | 开发期建议值 | 发布前要求 |
|---|---|---|---|
| AppID | 导入弹窗 / project.config.json | 测试号 | 自有 AppID |
| 不校验合法域名 | 详情 → 本地设置 | 开启 | 关闭,并为所有接口配置 HTTPS 合法域名 |
| ES6 转 ES5 | 详情 → 本地设置 | 保持模板默认 | 保持模板默认即可,真机兼容性更好 |
2.3 首次编译常见的三个报错
导入完成后点击“编译”,如果页面白屏或者直接报错,大概率逃不出下面这三个问题。
第一个:app.json: 未找到。这个几乎可以断定是目录选错了层级,回到 2.1 重新定位project.config.json的位置。
第二个:appid 不合法。如果你在project.config.json里手填了一个 AppID,但开发者工具里的登录账号没有这个小程序的开发者权限,就会报这个错。解决方式是:要么用测试号,要么在导入弹窗里重新选择与你账号匹配的 AppID,要么被添加为该小程序项目的开发成员。
第三个:request:fail url not in domain list。这是开发阶段最常见也最烦人的一个。即使你已经勾选了“不校验合法域名”,在开发者工具的模拟器里有时仍会遇到——尤其是当你用wx.request请求本地 IP 时。我的经验是:确认右上角详情里的本地设置确实勾上了,然后把开发者工具整个关掉重开一次。这个开关的修改有时不会立即生效,重开后就好了。如果重开还不行,检查你请求的 URL 是否写成了https://但本地服务是http://,或者 IP 后面是否漏了端口号。
提示:模板包里的
project.config.json有时会带上发布者的"projectname"字段,这个字段只影响项目在开发者工具里显示的名字,不影响编译和运行,不必纠结要不要改。
3. 外卖点餐模板的代码骨架:页面、组件与数据流
3.1 模板的目录结构与模块划分
微信小程序的工程结构有硬性要求:pages目录下的每个页面必须包含同名的.js、.wxml、.wxss、.json四个文件,且路径必须登记在全局的app.json的pages数组里。外卖点餐类模板的页面结构高度相似,你拿到的这个 zip 里,页面数量和命名可能略有出入,但核心模块一般跑不出下面这张表:
| 模块 | 典型路径 | 职责 |
|---|---|---|
| 首页 | pages/index | 店铺信息、分类导航、商品列表展示 |
| 商品详情 | pages/goodsDetail | 单品信息、规格选择、加入购物车 |
| 购物车 | pages/cart | 已选商品列表、数量增减、结算入口 |
| 订单确认 | pages/order/confirm | 收货地址、备注、配送费计算、提交订单 |
| 订单列表 | pages/order/list | 历史订单、订单状态筛选 |
| 我的 | pages/mine | 用户信息、地址管理、设置入口 |
拿到模板之后,我建议你先做一件事:打开app.json的pages数组,对照实际文件确认哪些页面是存在的。模板包为了展示效果,有时会保留几个演示页面或者已经被注释掉的页面路径,这些冗余页面如果不清理,一是包体积变大,二是审核时可能被判定为“含有与小程序功能无关的页面”。删页面的原则是:pages数组里删掉路径,同时物理删除对应的四件套文件,再全局搜索确认没有其他页面通过wx.navigateTo跳转到这个被删的页面。
3.2 商品数据是怎么组织的:本地 JSON 到云开发的三种形态
外卖点餐模板里,商品数据层一般有三种形态,你得先搞清楚自己拿到的是哪一种,才能决定后续怎么接真实数据。
第一种是纯前端静态数据,常见做法是在utils或mock目录下放一个goods.js,里面导出一个对象数组。每个商品对象的结构大致长这样:
// utils/goodsData.js module.exports = [ { id: 1001, name: '招牌黄焖鸡米饭', categoryId: 1, price: 18.5, originalPrice: 25, image: '/assets/images/chicken.png', description: '微辣,含配菜一份', stock: 100, sales: 230, sku: [ { name: '微辣', priceDelta: 0 }, { name: '中辣', priceDelta: 0 }, { name: '特辣', priceDelta: 1 } ] } ]这里的字段含义很直白:categoryId用于首页分类侧边栏与商品列表的联动,price是基础价格,sku数组里的priceDelta是规格加价。如果模板用的是这种静态数据,你的工作就是把goodsData.js里的内容替换成后端接口返回的数据结构,或者干脆写一个请求函数动态获取。
第二种是用微信云开发。模板里会有一个cloud目录或cloudfunction目录,商品数据存放在云数据库的goods集合里,前端通过wx.cloud.callFunction或者数据库 API 直接读取。这种形态的好处是不需要自建服务器,适合没有后端开发经验的场景,但迁移到正式环境时要注意云开发环境的 ID 是在app.js里初始化的,必须换成你自己的环境 ID。
第三种是自建后端 + HTTP 接口。这需要你对模板里所有的wx.request调用点做统一替换。常见做法是在utils/request.js里封装一个全局请求函数,统一处理 baseURL、token 注入和错误码:
// utils/request.js const BASE_URL = 'https://api.yourdomain.com' function request(path, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: `${BASE_URL}${path}`, method: method, data: data, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' }, success(res) { if (res.statusCode === 200 && res.data.code === 0) { resolve(res.data.data) } else { wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }) reject(res) } }, fail(err) { reject(err) } }) }) } module.exports = { request, BASE_URL }这里的核心逻辑是:所有接口统一走这个函数,返回的Promise只 resolve 业务数据部分,错误处理和 toast 提示集中在success回调里完成。实际接后端时,你需要和后端约好三件事:一是code字段的语义(0 表示成功还是 200 表示成功,各家不一);二是 token 的传递方式(Header 还是 query 参数);三是错误信息msg的文案规则。这三件事没定清楚,后面联调时每接一个接口都要扯皮。
3.3 购物车的数据流:storage 与页面刷新
购物车是外卖点餐模板里最核心也最容易写乱的部分。常见做法是把购物车数据存到wx.setStorageSync里,每个页面启动时通过wx.getStorageSync读取。这个方案的优点是代码简单、刷新页面后数据不丢失,缺点是购物车数据存在本机,换设备或清缓存后会丢失。
模板里的购物车数据结构一般是这样的:
// 存储结构 { "1001": { "sku": "微辣", "count": 2 }, "1002": { "sku": "默认", "count": 1 } }key 是商品 id,value 里记录了选中的规格和数量。为什么这么设计?因为商品详情和价格随时可能变化,购物车里只需要存 id、规格和数量,结算时再通过 id 去商品列表里拉取最新价格。这种做法避免了购物车里存一份价格、商品列表里又存一份价格,时间长了必然出现不一致的脏数据。
往购物车里添加商品的核心代码,模板里通常长这样:
// 在商品详情页的“加入购物车”事件里 addToCart() { const cart = wx.getStorageSync('cart') || {} const key = String(this.data.detail.id) const skuName = this.data.selectedSku.name if (!cart[key]) { cart[key] = { sku: skuName, count: 1 } } else { if (cart[key].sku === skuName) { cart[key].count += 1 } else { // 同一商品不同规格,单独存一条 cart[`${key}_${skuName}`] = { sku: skuName, count: 1 } } } wx.setStorageSync('cart', cart) this.setData({ cartCount: this.getCartTotalCount() }) }这段代码有一个容易踩的坑:当同一个商品有不同的规格时,如果只以商品 id 作为 key,两个规格会被合并成一个数量。模板里常见的处理方式有两种:一种如上面代码所示,同名规格累加、不同规格追加后缀 key;另一种是把购物车整体设计成数组结构,每个元素包含id、sku、count三个字段,变更时先findIndex再更新。数组结构在“编辑购物车”场景下更灵活,但对量大的购物车来说频繁findIndex会影响性能。家用级别的外卖小程序,购物车商品种类一般不超过 20 种,数组结构完全够用。
购物车页面修改数量时的核心动作是:更新 storage 里的 count,然后重新setData当前页面的数据源。这里有个性能隐患——每次加减都调用wx.setStorageSync会频繁写入缓存,虽然小程序对 storage 的写入没有明确的频率限制,但我在实际项目里遇到过 iOS 低版本下频繁写入导致页面卡顿的问题。优化方案是:在页面卸载时才统一写入一次,或者用防抖函数把 300ms 内的多次操作合并成一次写入。
4. 把模板的静态数据换成真实接口:登录、菜单与订单
4.1 登录态与 openid:从 wx.login 到 code2Session
外卖点餐小程序需要一个身份标识来关联订单和购物车。模板里的登录逻辑通常写在app.js的onLaunch里,常见做法是:调用wx.login拿临时凭证code,发给后端,后端拿code去微信的code2Session接口换取openid和session_key。
// app.js 登录逻辑 App({ onLaunch() { this.login() }, login() { wx.login({ success: (res) => { if (res.code) { wx.request({ url: `${BASE_URL}/api/auth/login`, method: 'POST', data: { code: res.code }, success: (resp) => { if (resp.data.code === 0) { wx.setStorageSync('token', resp.data.data.token) wx.setStorageSync('userInfo', resp.data.data.userInfo) } } }) } else { console.error('登录失败', res.errMsg) } } }) } })这段逻辑里有一个常见误用:很多初学者以为wx.login返回的code可以直接当登录凭证用在小程序后续的请求里。实际上code有效期极短,且只能使用一次,正确做法是像上面这样把code传给后端换一个自签发的 token,后续所有请求都携带这个 token。模板里如果没有这段逻辑,说明登录态是伪造的或简化过的,上线前必须补上。
4.2 商品列表与分类的接口替换
模板首页通常是左右两栏:左侧是分类列表,右侧是该分类下的商品。静态数据版本里,分类和商品都来自本地变量;接接口时,你需要把onLoad或onShow里的数据加载逻辑整体替换掉。
常规的请求模式是:先请求分类列表,拿到第一个分类的 id,再请求该分类下的商品。这样做的原因是大多数外卖场景首页默认展示第一分类,用户点击分类时才触发商品刷新。
// 首页商品加载 async loadGoods() { const res = await request('/api/categories', 'GET') const categories = res const firstCategoryId = categories[0].id const goodsRes = await request(`/api/goods?categoryId=${firstCategoryId}`, 'GET') this.setData({ categories: categories, goodsList: goodsRes, activeCategoryId: firstCategoryId }) }这里有两个后端接口设计的细节需要确认:第一,分类表是独立的表还是商品表里冗余了一个categoryName字段。如果是后者,前端拿到商品列表后需要自己去重生成分类列表,这在小程序模板里会出现“分类侧边栏渲染为空”的问题。第二,商品接口是否支持categoryId作为筛选条件,如果不支持,前端只能一次拉全量商品再前端 filter。全量拉取在数据量小于 1000 条时问题不大,但餐饮店如果按天更新菜单,接口返回的包体会越来越大,首屏加载会明显变慢,所以我建议后端在接口层面就把分页和筛选做进去。
4.3 订单提交与 wx.requestPayment 的参数坑
订单提交是模板里最容易出问题的地方,因为涉及到与微信支付的对接。
先看订单提交的常规流程:用户点击“去结算” → 请求后端创建订单 → 后端返回支付参数 → 前端调wx.requestPayment。模板里如果没有真实的支付逻辑,常见的占位做法是模拟一个payType: 'mock'的开关,测试环境直接跳过支付,正式环境再启用真实流程。
承接上文,代码体现出模板改造时的核心矛盾:静态数据的写法与真实接口的差异,主要在于data的来源从require一个本地 JS 文件,变成了异步请求的返回值。这个差异会导致模板原有的一些方法执行时机出问题,典型的比如:静态数据版本里onLoad可以同步拿到商品列表并立刻渲染,而接口版本里数据是异步返回的,你必须保证所有依赖商品数据的渲染都在setData回调之后发生,否则页面会白屏或显示空数据。
改造后的商品列表按需渲染
静态数据版经常在页面的onLoad里直接赋值:
// 静态数据版本(模板原始写法) onLoad() { this.setData({ goodsList: goodsData }) }改成接口版本时,有一种常见误用是直接在onLoad里调用一个没有await的异步函数,导致setData执行时数据还没返回。我推荐的做法是:用async/await在onLoad里显式控制顺序,且在请求前先显示加载状态:
// 接口版本(推荐改写结构) async onLoad() { wx.showLoading({ title: '加载中' }) try { await this.loadCartCount() const goods = await request('/api/goods', 'GET', { categoryId: this.data.activeCategoryId }) this.setData({ goodsList: goods }) } catch (err) { wx.showToast({ title: '加载失败,请下拉重试', icon: 'none' }) } finally { wx.hideLoading() } }loadCartCount这一步不能省。因为购物车数量图标在首页和购物车页都可能需要展示,如果onLoad里不先初始化购物车数量,用户从购物车页返回首页时数量会短暂显示为 0,等别的请求触发 re-render 才恢复,观感很差。
wx.requestPayment 的参数坑
真实的微信支付调用代码如下:
// 订单支付 wx.requestPayment({ timeStamp: String(payParams.timeStamp), // 必须是字符串,且是秒级时间戳 nonceStr: payParams.nonceStr, package: payParams.package, // 格式必须是 `prepay_id=xxx` signType: 'RSA', paySign: payParams.paySign, success: (res) => { if (res.errMsg === 'requestPayment:ok') { wx.navigateTo({ url: '/pages/order/list?status=1' }) } }, fail: (err) => { if (err.errMsg && err.errMsg.indexOf('cancel') > -1) { wx.showToast({ title: '您已取消支付', icon: 'none' }) } else { wx.showToast({ title: '支付失败,请稍后重试', icon: 'none' }) } } })这里的参数有几个前置约束,模板里如果直接照搬网上代码,很容易在这个环节反复踩坑:
第一个坑是timeStamp。官方要求是字符串类型,且是秒级时间戳而不是毫秒级。如果你直接拿后端返回的毫秒时间戳Math.floor(Date.now())塞进去,微信会自动转成秒级,表面看没问题,但和后端签名用的时间戳如果不一致,就会报invalid timeStamp错误。最稳妥的方式是后端在生成签名和返回参数时就用同一个时间戳,前端不要做任何转换,后端给什么就传什么。
第二个坑是package参数的格式。它必须是prepay_id=开头,很多后端首次对接时把prepay_id单独返回,前端忘了拼前缀,报错信息会是package is invalid或直接进 fail 回调而你根本看不到具体原因。确认方式是打印payParams.package的值,看是否以prepay_id=开头。
第三个坑是signType。微信支付 API v3 默认使用RSA,而 API v2 是MD5或HMAC-SHA256。模板里写的值如果和你的后端支付服务版本不匹配,发起支付时会直接失败。如果你不确定后端用的是哪个版本,在开发者工具里打开调试器看wx.requestPayment返回的errMsg或errCode,再回查模板里的signType值,两端统一即可。
5. 品牌改造与发布前验证:让模板看不出是模板
5.1 改启动加载页的三处位置
外卖模板里的启动加载环节,通常指的是小程序冷启动时用户看到的第一个页面或加载动画。很多人拿到模板后直接改app.json里的pages数组第一项,这确实是换启动页最简单的方式,但还有两处容易漏:
第一处是app.js的onLaunch里,如果模板在启动时执行了网络请求、登录逻辑或版本检查,冷启动耗时会被拉长。如果这些逻辑不是必须的,比如有些模板为了展示效果会在启动时拉取一份远程配置,我建议先注释掉,改成手动触发,否则每次冷启动都要等一个超时才能进入首页。
第二处是启动页的 UI 本身。模板自带的启动加载页往往带有发布者的 logo、联系方式或版权信息。这个信息可能藏在pages里某个叫splash或loading的页面目录下,也可能以组件的形式被注册在首页的usingComponents里。全局搜索图片路径、品牌名和版权关键词,比手动翻目录更快。
提示:微信官方并没有提供自定义启动页的机制,小程序启动时展示的加载画面受微信客户端控制。模板里所谓的“启动加载页”通常是一个过渡页面,配合
wx.showLoading或setTimeout模拟的等待效果。改模板时不要试图去碰微信客户端底层的加载动画,那只存在于开发者工具的模拟器里,真机上你改不了。
5.2 主题色与导航栏适配:改色不如改变量
微信小程序页面顶部导航栏的高度和胶囊按钮的位置,在不同机型上并不完全一致。模板为了让页面顶部的自定义头部好看,会用到这样的取值方式:
const { statusBarHeight } = wx.getWindowInfo() const menuButton = wx.getMenuButtonBoundingClientRect() const navBarHeight = (menuButton.top - statusBarHeight) * 2 + menuButton.height把这段逻辑抽到一个公共文件里,所有需要自定义导航条的页面都来取这份高度值。改模板时最容易犯的错是:在首页把导航栏背景色改成了橙色,但二级页面的导航栏仍然是模板默认的白色,用户点击商品跳转后视觉上会觉得很突兀。排查方法是把每个页面的window配置在app.json里统一管理,模板里经常有一个全局的window配置,再在个别页面用自己的json文件覆盖全局值,你要做的是先确认哪些页面覆盖了,再把全局默认值改掉。
主题色方面,模板的wxss里通常会有一个:root或page级别的 CSS 变量定义,改这一个变量,全站颜色就跟着变。如果模板用的是硬编码的颜色值,那就需要全局替换#ff6b00之类的十六进制色值,建议用编辑器的全局搜索替换功能,替换前先搜索确认这个色值是否只用于主题色,别把商品图片里渲染出来的同类色也误伤了。
5.3 发布前验证清单:域名、支付、体检
发布前的验证环节,我按踩坑频率排个优先级:
| 检查项 | 操作方法 | 典型失败表现 |
|---|---|---|
| HTTPS 合法域名 | mp 后台 → 开发 → 开发设置 → 服务器域名 | 真机预览白屏,console 报request:fail |
| 支付参数 | 开发者工具模拟器发起真实支付 | 报invalid package或无任何响应 |
| 登录态失效 | 隔夜后再打开小程序,直接下单 | 下单时 token 过期,后端返回 401 |
| 图片资源完整性 | 开发者工具 → 资源管理 → 检查未使用的图片 | 审核时被判定为“内容不完整” |
域名检查是最容易被卡的一关。模板在开发阶段用的http://localhost或局域网 IP 接口,发布前必须全部替换成备案过的 HTTPS 域名,并在小程序管理后台把域名加入request合法域名列表。这里有个细节:开发者工具的“不校验合法域名”开关在发布审核时完全不影响真机,审核员用的是真机环境,一旦发现请求非 HTTPS 接口,直接打回。
订单状态流转的验证也不能省。模板里的订单列表页一般支持待付款 / 待取餐 / 已完成几个状态标签。真实场景里,订单状态是后端在支付回调里推进的,前端只是轮询或被动刷新。如果你接的是模拟支付,没有真实回调,订单状态会卡在“待付款”永远动不了。所以验证时要走一遍“下单 → 支付成功 → 商家接单 → 出餐完成”的完整链路,确认前端在每个状态节点都有对应的 toast 提示和跳转逻辑。
最后,用开发者工具自带的“体验评分”功能跑一遍,重点看两个指标:首屏渲染耗时和缓存大小。模板里如果加载了过多的图片资源且没有懒加载,首屏会比较慢;如果打开过多页面的onLoad都去读购物车 storage 并setData,缓存操作会比较密集。评分给出的优化建议不一定每条都改,但“请求数过多”和“setData 体积过大”这两项,值得花时间针对外卖点餐这种商品列表和购物车频繁切换的场景做一次专项优化。
本文还有配套的精品资源,点击获取