简介:这套菜谱微信小程序源码基于云开发模式构建,适合美食类创业者、小程序开发初学者以及需要快速落地菜谱应用的开发者。无需自备域名和服务器,导入微信开发者工具即可运行并提交审核,大幅降低搭建门槛。压缩包共包含182个文件,以100个PNG图片、23个JS逻辑文件、20个JSON配置、18个WXSS样式和17个WXML结构为主,另有4个JPG封面图,整体体积仅1.79MB,结构紧凑、便于整理与二次开发。目前已有991人学习下载,适合用于学习云开发接口调用、小程序页面布局以及数据分类展示思路。源码仿照京细菜谱的内容组织方式,覆盖八大菜系、特色食品、特殊场合、热门功效、人群细分、烘焙甜品、口味和食材等详细分类,同时提供用户页、列表页、详情页等完整交互流程,开发者可直接沿用其设计语言和模块划分,快速生成专属菜谱小程序。
1. 菜谱微信小程序源码:云开发版直接导入就能跑,省掉服务器那层麻烦
想给餐饮店做一个菜谱小程序,问了一圈服务器最便宜也要几十块一个月,还要备案。这份仿京细菜谱的小程序源码走的是微信云开发路线,不需要域名、不需要服务器,导入微信开发者工具就能跑。我拆了一遍,它的分类做得很细:八大菜系、人群、场合、口味、功效全都有索引,适合做毕业设计、接外包底子,也适合想研究云数据库在小程序里怎么落地的开发。下面我会从文件结构、导入配置、数据流和踩坑几个角度,把它完整摊开,照着操作可以快速复现。
2. 先拆源码:文件职责、云开发原理和集合设计
2.1 从 JS 文件名推断页面职责
拿到压缩包后先别急着导入。我看到的项目正文列出的文件有ascf.jpg、share.jpg、cover_null.jpg和一批 JS:index.js、list.js、recipe.js、user.js、mypage.js、mypage1.js。从这个文件分布看,基本可以确定是微信小程序原生项目,不是 uniapp 或 taro。uniapp 项目通常会有pages.json、App.vue、main.js,而原生小程序是每个页面四个同后缀文件。
按命名习惯推测:
index.js对应首页,负责展示分类导航和推荐菜谱;list.js对应分类菜谱列表页,会接收categoryId参数,做分页读取;recipe.js对应菜谱详情页,一般用options.id查询单个菜谱;user.js负责用户信息相关逻辑,比如读取头像昵称、本地缓存;mypage.js和mypage1.js是个人中心,可能是不同版本的页面,实际生效路由看app.json的pages数组。
这里有个判断技巧:不要只看 JS 名字,要打开app.json确认pages数组的顺序。数组第一项是首页,后面依次是其他页面。如果mypage1.js不在pages数组里,说明它只是被mypage.js引用的公共模块,或者已经废弃的旧页面。做二次开发时,删除文件前先看路由,避免删掉还在使用的页面导致编译报错。
share.jpg是转发分享时用的封图,cover_null.jpg应该是菜谱没有封面时的兜底图,ascf.jpg从名字看可能是广告位或头部轮播图。图片资源不多的话,要留意 image 标签的路径,如果引用的是本地相对路径,在分包或发布前别乱移动。
2.2 云开发替代了传统后端的三层结构
很多第一次接触云开发的人把它当成“小程序的数据库”,不够准确。微信云开发其实是一个 BaaS,提供了数据库、云存储、云函数三件套。对这份菜谱源码来说:
- 菜谱记录和分类信息放在云数据库;
- 菜谱图片放在云存储;
- 用户身份和收藏逻辑可以不写云函数,直接用内置的
_openid字段区分。
也就是说,常规小程序开发里需要自己买服务器、写接口、处理文件上传下载、申请 HTTPS 域名和备案的那一层,云开发全部接管了。这也是摘要里强调“不需要域名和服务器即可搭建”的原因。
不用云开发、用传统后端的话,流程是这样的:购买轻量服务器、部署接口、申请 HTTPS 证书并备案、小程序后台配置 request 合法域名、联调接口。每一步都可能卡几天,尤其备案要等审核。云开发版本把这些步骤压缩成两步:创建云环境、初始化app.js。对个人开发者来说,省去的不只是钱,还有时间成本。
但要注意,云开发不是完全没有服务器概念,它只是把底层运维隐藏了。你的数据仍然存在云服务商的服务器上,底层资源有免费额度,超过后按量付费。个人菜谱小程序的访问量不大,通常不会超,但上线后要关注控制台的用量指标,避免月底收到超额账单。
2.3 初始化代码与数据库集合字段设计
云开发环境初始化一般在app.js的onLaunch里。下面是一段兼容写法:
// app.js App({ onLaunch() { if (!wx.cloud) { console.error('请使用 2.2.3 以上基础库以支持云开发'); return; } wx.cloud.init({ env: 'cloud1-3g9t0abc', // 替换成你自己的云环境 ID traceUser: true }); } });代码里env的值决定所有数据库、存储访问落在哪个环境。环境 ID 在云开发控制台首页可以看到,格式通常是cloud1-xxxxxxxx。如果你不填env,默认使用第一个创建的环境,但多人协作时很容易连到别人的环境,所以我建议每次都显式写上。
接下来说集合设计。菜谱类小程序至少需要两张表:dishes和categories。dishes表的核心字段我整理如下:
| 字段 | 类型 | 说明 |
|---|---|---|
_id | string | 云数据库自动生成,详情页传参用 |
name | string | 菜名,用于列表展示和搜索 |
categoryId | string | 分类 ID,关联categories表 |
image | string | 云存储 fileID,也可以是 https 链接 |
ingredients | array | 食材列表,比如["五花肉 500g", "冰糖 30g"] |
steps | array | 步骤列表,按顺序排列 |
heat | number | 热量或制作难度,可选 |
createTime | date | 入库时间,分页排序用 |
为什么步骤和食材都不用单独建表?因为云开发数据库是文档型,存数组拆取很方便,详情页一次.get()就能拿到所有渲染数据。如果像 MySQL 那样拆成ingredients表、steps表,小程序端还得分两次查询再拼接,完全没必要。这也是一线开发里的常见误区:把后端数据库思维直接搬进小程序会导致性能差、代码啰嗦。
2.4 数据权限:先想清楚谁能读、谁能写
在云开发控制台新建集合时,默认权限是“仅创建者可读写”。对于菜谱这种公共内容,这个权限会让非创建者看到空数据。你需要手动改成“所有用户可读,仅创建者可写”。这样游客打开小程序能读到所有菜谱,但数据库中的文档不会被随意篡改。
这里有一个细节:如果要做收藏功能,收藏表favorites应该继续用“仅创建者可读写”。因为云数据库在写入时会自动给每条记录打上_openid字段,表示创建者身份。用户 A 收藏一条菜,生成一条带 A 的 openid 的文档;用户 B 收藏是他自己的文档,互相不冲突。查询收藏列表时用.where({ _openid: '{openid}' })就能拿到当前用户的数据。这段逻辑不需要自己调用登录云函数,是云开发默认行为。
如果你以后加了管理员修改菜谱的需求,管理员操作通常要走云函数,因为小程序端受权限限制,无法修改其他人的文档。云函数端拿到管理员 openid 后,可以绕过权限校验对任意文档做更新。这个放在后面进阶部分讲。
3. 落地:导入开发者工具、开通云环境并导入数据
3.1 解压后先做三件事
第一件事就是解压,不要用在线压缩工具,因为里面可能有中文文件和嵌套目录,解压不完整会导致导入报错。第二件事检查文件完整性。一个原生小程序项目至少要有的文件是app.js、app.json、project.config.json。缺少project.config.json时,开发者工具无法识别项目类型,会提示“请选择正确的项目目录”。第三件事看project.config.json里的appid。如果里面写的是touristappid或别人的 AppID,导入后云开发功能用不了,必须改成你自己的。
不要直接双击打开文件,用 VS Code 打开整个项目,按 Ctrl+Shift+F 搜索appid,替换成自己的。注意有些模板会把 AppID 也写在app.js里,要一起替换。
3.2 注册小程序并开通云开发
在小程序公众平台注册一个小程序账号,个人主体也可以注册。注册成功后拿到 AppID。然后打开微信开发者工具,点击“导入项目”,选择解压后的目录,AppID 填自己刚申请的。后端服务选择“小程序·云开发”,点确定。
导入成功后,工具栏会出现“云开发”按钮。首次点开会要求创建环境。环境 ID 形如cloud1-xxxx,随便起名,但记住它,后面代码要用。计费模式建议选“按量付费”,个人项目即使有量也很低,按量付费比包月划算。如果暂时没开通,创建免费额度环境也可以,但要注意免费环境有时效和资源限制。
3.3 在云开发控制台创建集合
云环境创建好后,进入“数据库”页面,新建两个集合,集合名必须和代码一致:
dishes:菜谱主表categories:分类表
如果源码里用的集合名不是这两个,你在控制台的新建名字要和源码里的.collection('xxx')对应。我拿到一个模板时,会先在代码里搜索collection(,把出现过的集合名列出来,再去控制台建。这是最快的方式。
集合权限设置:dishes和categories都选“所有用户可读,仅创建者可写”。不要选“仅创建者可读写”,否则非管理员用户打开就是空白。这个是菜谱类的公共数据场景,跟收藏表不一样。
3.4 导入菜谱数据:JSON 文件格式注意
数据库支持导入 JSON 或 CSV。最常见的格式是一个 JSON 数组,每个对象是一条记录。下面这个示例可以导入dishes:
[ { "name": "西红柿炒鸡蛋", "categoryId": "home", "image": "cloud://cloud1-xxxx.636c-cloud1-xxxx-1300000000/recipe/tomato.jpg", "ingredients": ["西红柿 2个", "鸡蛋 3个", "盐 5g", "糖 3g"], "steps": ["西红柿切块", "鸡蛋打散炒熟", "下西红柿翻炒", "调味出锅"], "createTime": "2025-01-01T00:00:00+08:00" }, { "name": "红烧肉", "categoryId": "re_cai", "image": "cloud://cloud1-xxxx.636c-cloud1-xxxx-1300000000/recipe/hongshao.jpg", "ingredients": ["五花肉 500g", "冰糖 30g", "生抽 20ml", "姜 3片"], "steps": ["五花肉切块焯水", "炒糖色", "下肉块上色", "加调料炖40分钟"], "createTime": "2025-01-02T00:00:00+08:00" } ]导入时有几个注意点:
- 数组最外层别忘了方括号,文件编码保持 UTF-8,不要带 BOM。
_id字段不要手动写,导入时会自动生成;导入后再从控制台复制_id去关联分类。createTime建议用 ISO 字符串,方便后面orderBy排序和startAfter分页。- 如果图片还没上传云存储,可以先填空字符串,后面用
update方法补上去。
导入完成后,在集合里看到这些记录,基本就成功一半了。
3.5 云存储传图并回填 fileID
接下来把菜谱图片上传到云存储。进入云开发控制台“存储”页面,创建recipe目录,批量上传图片。上传完后,在文件列表点“复制文件ID”,会得到类似cloud://cloud1-xxxx.636c-cloud1-xxxx-1300000000/recipe/tomato.jpg的路径,把这个值填到dishes记录里的image字段。
这里有个容易搞混的概念:云存储 fileID 和外链 URL 的区别。fileID 是云开发内部的引用,小程序端<image>标签可以直接用。而外链 URL 需要在微信后台配置 downloadFile 合法域名,如果是http://或非 https 还会被拦截。所以能传云存储就传云存储,不要转外链,省去域名白名单配置。
如果数据较多,不希望手动回填,可以在控制台用导出、修改再导入的方式批量操作。但建议先小批量试通,再全量操作。
3.6 真机预览前要做的检查
打开开发者工具的“编译”按钮,模拟器如果能显示首页但不显示菜谱,先打开 Console 看报错。常见问题:集合名不存在、环境 ID 不对、集合权限太严格。确认无误后,点击“预览”,用微信扫码真机调试。真机和模拟器的差异通常体现在图片和网络请求,多准备一台安卓和一台 iPhone 测试。
到这里,一个可跑的菜谱小程序就搭起来了。下一步,我把列表页和详情页的核心代码逻辑讲透,方便你按自己需求改。
4. 核心逻辑:列表分页、详情跳转与用户页背后的数据边界
4.1 列表页用 skip+limit 加载更多,注意 20 条上限
list.js是菜谱列表页,它的核心是加载对应分类下的一页菜谱。一段常见的基础实现如下:
// list.js const db = wx.cloud.database(); const PAGE_SIZE = 10; let page = 0; let isFetching = false; let hasMore = true; Page({ data: { dishes: [], categoryId: '' }, onLoad(options) { this.setData({ categoryId: options.categoryId || '' }); this.loadList(); }, loadList() { if (isFetching || !hasMore) return; isFetching = true; wx.showLoading({ title: '加载中' }); const query = db.collection('dishes'); if (this.data.categoryId) { query.where({ categoryId: this.data.categoryId }); } query.skip(page * PAGE_SIZE) .limit(PAGE_SIZE) .get() .then(res => { const newList = this.data.dishes.concat(res.data); this.setData({ dishes: newList }); page++; if (res.data.length < PAGE_SIZE) { hasMore = false; } wx.hideLoading(); }) .catch(err => { console.error(err); wx.hideLoading(); }) .finally(() => { isFetching = false; }); }, onReachBottom() { this.loadList(); } });这段代码里有几个参数说明:
PAGE_SIZE是每页数量,这里写 10,实际可以调大到 20,不要超过 20。云开发数据库在普通小程序端单次get()最多返回 20 条,设置 50 也没用,后台会截断。isFetching是并发锁,防止 onReachBottom 在数据还没返回时又触发一次,导致重复请求。hasMore用于判断是否还有下一页,如果返回条数小于 PAGE_SIZE,说明已经到最后一页。where({ categoryId })是精确匹配,菜谱的categoryId必须和分类表_id一致。
这个写法对数据总量不超过 1000 条的菜谱项目够用。如果以后数据量大了,skip会因为扫描偏移量过大而变慢,到时改成orderBy('createTime', 'desc')加startAfter(res.data[res.data.length - 1].createTime)的方式。这个我们放到第 5 章避坑里详细展开。
4.2 触底加载更多:onReachBottom 与页面配置
“微信小程序页面列表加载更多”这个热搜问题,本质上就是两件事:触发条件和翻页逻辑。触发条件除了onReachBottom,还需要在页面的.json里开启:
{ "onReachBottomDistance": 50, "enablePullDownRefresh": true }onReachBottomDistance是距离底部多少像素时触发,默认 50,不用改也行。enablePullDownRefresh是下拉刷新,如果你不需要下拉刷新可以直接删掉。
然后下拉刷新的处理函数:
onPullDownRefresh() { page = 0; this.setData({ dishes: [], hasMore: true }); this.loadList().finally(() => { wx.stopPullDownRefresh(); }); }注意:onPullDownRefresh里必须先重置page和dishes,否则你会看到旧数据拼接新数据,列表越来越长。这个细节做外包时经常被我拿来调 bug。
4.3 首页分类点击跳转与参数接收
首页index.js里通常有一段wx.navigateTo:
goList(e) { const categoryId = e.currentTarget.dataset.id; wx.navigateTo({ url: '/pages/list/list?categoryId=' + categoryId }); }dataset.id是 WXML 里><view class="category-item">// recipe.js const db = wx.cloud.database(); Page({ data: { dish: {} }, onLoad(options) { const id = options.id; if (!id) return; db.collection('dishes').doc(id).get() .then(res => { this.setData({ dish: res.data }); }) .catch(err => console.error(err)); } });
doc(id)是指定记录 ID 直接读取,比where({_id: id})更高效。拿到记录后,dish.steps是一个数组,WXML 里用wx:for渲染步骤:
<view class="steps"> <view wx:for="{{dish.steps}}" wx:key="index" class="step-item"> <text>{{index + 1}}. {{item}}</text> </view> </view>wx:key="index"用于列表复用时的 key,这里因为 steps 数组的元素可能重复,用index做 key 是可以接受的。注意不要写成wx:key="*item"以免字符串重复导致警告。
4.5 用户页与云开发的 openid 机制
user.js和mypage.js最常做的操作是展示用户登录信息和收藏列表。云开发下识别用户的默认方式不是自己写登录,而是读取云数据库记录里的_openid字段。小程序端调用collection.add时,云平台会自动把当前用户的openid写入_openid。查询时用:
db.collection('favorites') .where({ _openid: '{openid}' }) .get() .then(res => { this.setData({ favorites: res.data }); });这里'{openid}'是云开发的特殊语法,在服务端或小程序端查询时会被自动替换成当前用户 openid。因此,收藏功能完全不用自己写云函数。但如果需要把收藏与用户的其他信息关联,比如昵称头像,可能需要在用户第一次授权时保存一份 user 文档。此时注意wx.getUserProfile在最新基础库上返回的昵称是“微信用户”默认昵称,头像也是灰色默认头像,真实头像需要用户上传或使用开放数据,这个不是源码问题,是微信平台策略。
这一段主要讲了:云开发数据库的分页限制、页面跳转参数、详情读取、用户 openid 自动注入。理解这四点,修改模板就有方向了。
5. 常见坑与排查:环境、分页、图片和权限
5.1 数据空白类:环境 ID 和集合名不一致
现象:模拟器打开首页,分类能显示,但点进列表加载不出任何数据,console 提示collection not exists或Collection not found,或者干脆没有报错。
原因:一种是app.js里的env没有改成自己的云环境 ID,代码访问的是别人创建的环境,那个环境里没有你的集合。另一种是项目里写的集合名是recipe,但你在控制台建的集合叫dishes。我在处理资源时,见过把.collection('recipe')写成.collection('dishes')的,就是复制时没改名称。
解决:打开开发者工具的 Console,定位到报错信息里的集合名,去云开发控制台创建同名集合;再确认env正确。如果env显示undefined,回到app.js里显式写上环境 ID。改完重启编译。
5.2 分页失效类:skip 超过记录数导致空白
现象:列表第一页正常,下拉加载更多后,第二页偶尔有数据,到第三页或更后直接空白,甚至报错Error: errCode: -502005 database request fail。
原因:skip分页在记录总数超过 1000 条时,数据库会拒绝请求;另外PAGE_SIZE如果设置为 50,而数据库单次最多返回 20 条,第二页的skip就会跳过 40 条,结果只返回后面 10 条,看起来就像缺数据。或者hasMore判断逻辑写错了,导致一直请求。
解决:把PAGE_SIZE改回 20 以内;将分页方式改为基于游标。游标分页示例:
let lastCreateTime = null; function loadMore() { let query = db.collection('dishes').orderBy('createTime', 'desc').limit(20); if (lastCreateTime) { query = query.startAfter(lastCreateTime); } query.get().then(res => { if (res.data.length) { lastCreateTime = res.data[res.data.length - 1].createTime; this.setData({ dishes: this.data.dishes.concat(res.data) }); } else { this.setData({ hasMore: false }); } }); }这里的startAfter接收排序字段的值,必须是上次返回的最后一条记录的createTime,且排序字段必须已经建索引。索引在控制台数据库中创建,字段选择createTime,排序方式选“降序”。
5.3 图片裂图类:云存储 fileID 被当网络链接处理
现象:模拟器上图片显示正常,真机预览一部分图片显示空白,或控制台出现url not in domain list。
原因:如果image字段填的是https://外链,微信小程序要求下载域名必须加入白名单,且必须是备案过的 HTTPS。如果填的是云存储 fileID,默认是可以直接显示的,不需要白名单,但如果使用了旧环境产生的 fileID,新环境里无法识别,就会裂图。
解决:排除法:先看image值开头是不是cloud://,如果开头是http,去小程序后台配置 downloadFile 合法域名,或者把图传到云存储换成 fileID。如果已经是cloud://还是显示不了,大概率是环境不匹配,重新上传一次图片并替换image字段。还有一个小坑:云存储目录名有中文或空格,复制 fileID 后路径会被编码,建议目录名全英文。
5.4 编译失败类:基础库版本过低和找不到 appid
现象:导入后编译直接报wx.cloud is undefined,或Cannot read property 'init' of undefined,点云开发按钮没反应。
原因:微信开发者工具的基础库版本低于 2.2.3,wx.cloudAPI 不存在;或者 AppID 是测试号,测试号无法开通云开发。
解决:在开发者工具“详情-本地设置”中,把调试基础库切到最新稳定版。AppID 换成正式小程序的,测试号必须替换成自己的。个人主体注册的小程序也可以开通云开发,不需要企业资质。注意基础库设置只影响当前项目,不要全局更改。
5.5 权限太严类:数据导入后其他用户看到空白
现象:自己在控制台导入的菜谱数据,自己用开发者工具能看到,但用其他微信号扫码预览,列表是空的。
原因:集合权限是“仅创建者可读写”,控制台导入的数据创建者是管理员,普通用户没有读权限。云开发数据库权限是集合级别的,不是每条记录单独设置。
解决:把dishes和categories两个集合的权限改成“所有用户可读,仅创建者可写”。这一步必须在控制台手动操作,不是改代码。改完后,让同事或另一个微信号扫码测试。注意,如果之后又用控制台重新导入数据,权限不会重置;但如果新建集合,默认权限又变成“仅创建者可读写”,这是最容易漏的地方。
这五条基本覆盖了这个模板从导入到真机预览的高频问题。按“现象-原因-解决”排查,大多数情况 10 分钟内能定位。
6. 进阶:把菜谱模板改成自己风格的三个小技巧
第一个:搜索功能。菜谱数据量不大时,用模糊查询:
const db = wx.cloud.database(); db.collection('dishes') .where({ name: db.RegExp({ regexp: keyword, options: 'i' }) }) .limit(20) .get()正则查询无法建索引,几万条以内没问题,再大就要用云函数接入搜索服务。
第二个:默认导航栏够用就别自定义。这个模板用的是系统导航栏,在app.json的window里改navigationBarTitleText、navigationBarBackgroundColor就行。如果你非要自定义,设置"navigationStyle": "custom"后要自己处理wx.getMenuButtonBoundingClientRect算胶囊高度,容易在安卓和 iOS 上出现偏差。我的经验是:不是被产品逼到那份上,别动导航栏。
第三个:分享图已经准备好,share.jpg可以直接用于onShareAppMessage:
onShareAppMessage() { return { title: '这几道家常菜,厨房新手也能做', imageUrl: '/images/share.jpg', path: '/pages/index/index' }; }注意imageUrl要写本地路径,如果写网络链接需要配域名白名单。
最后说一句自己的教训。我以前接外包用类似模板,就是第 5 章那个分页坑,客户上线后数据量到 200 条,用户在列表页划到一半就空白,我排查了一整天才定位到是limit超过云开发限制和skip超界。从那以后,我每次做云开发列表页都会先看数据量上限再决定用哪种分页,然后强制在真机上把列表翻到底。如果你准备下载这份源码来改成自己的菜谱小程序,建议先在自己账号下按第三章流程跑通,再开始改界面,能少折腾一个下午。希望帮到你。
本文还有配套的精品资源,点击获取