简介:这是一份基于微信小程序平台开发的健康饮食助手源码包,面向前端初学者、小程序开发者及健康类应用爱好者,旨在提供一套开箱即用的健康菜谱管理与推荐功能实现方案。资源包含70个文件,主体为8个JS逻辑文件(处理数据请求与交互)、7个WXML模板页(构建页面结构)、7个WXSS样式文件(统一视觉风格)、39张PNG图片资源(含步骤图解与界面图标)以及配套JSON配置与README说明文档,整体压缩包仅2.77MB,轻量易读,适合快速学习小程序目录规范与实战开发流程。已有238人下载学习,可直接运行调试,完整覆盖食谱浏览、营养信息展示、个性化推荐、食材替换、购物清单生成及社区分享等核心模块,代码结构清晰,注释充分,是理解小程序MVVM模式与本地化健康应用落地的优质参考案例。
1. 一个健康饮食助手小程序,为什么跑不起来?——不是代码问题,是环境链没对齐
上周帮某高校实验室调试一个「健康饮食助手」微信小程序源码,对方说“下载下来直接报错”,我打开一看:app.json: app.json 未找到、project.config.json 缺失、云开发环境 ID 为空……三连击。这不是代码缺陷,而是典型的小程序项目交付“裸包”陷阱:它压根不是给你双击就能运行的安装包,而是一份需要手动补全工程上下文的源码快照。这个项目真实定位是:面向轻量级健康场景的微信小程序 MVP(最小可行产品),核心能力包括——按热量/食材/忌口筛选菜谱、生成周膳食计划、记录每日摄入并对比推荐值、支持离线缓存浏览。它不依赖后端服务器,全程走微信云开发;UI 基于 WeUI 规范但做了定制化色系与卡片动效;数据层用云数据库 + 云存储托管菜谱图谱与用户日志。适合想快速验证健康类小程序逻辑的开发者、刚学完云开发的新手、或需要在现有健康 App 中嵌入菜谱模块的团队。别被标题里“健康菜谱app”误导——它不是安卓/iOS原生App,所有功能都运行在微信容器内,打包产物是.wxapkg,部署目标是微信小程序管理后台。
2. 工程结构还原:从零补全 project.config.json 与 app.json 的关键字段
微信小程序源码包最常缺失的不是业务代码,而是两个“元配置文件”:project.config.json(IDE 工程配置)和app.json(小程序全局配置)。没有它们,开发者工具连项目都打不开。本项目目录结构非常干净,属于典型的云开发模板:
health-diet-helper/ ├── project.config.json ← 缺失!需手动创建 ├── app.json ← 缺失!需手动创建 ├── app.js ├── app.wxss ├── project.config.json.example ← 提供了参考模板(但不能直接用) ├── cloudfunctions/ ← 云函数目录(已存在) │ ├── getRecipeList/ │ ├── generateWeeklyPlan/ │ └── logIntake/ ├── miniprogram/ │ ├── pages/ │ │ ├── index/ ← 首页:菜谱瀑布流 │ │ ├── detail/ ← 菜谱详情页(含营养成分表) │ │ ├── plan/ ← 周计划生成页(含滑动选择器) │ │ └── diary/ ← 每日摄入记录页(带拍照上传) │ ├── components/ │ │ ├── nutrient-card/ ← 营养素卡片组件(复用率高) │ │ └── calorie-slider/ ← 热量调节滑块(自定义组件) │ ├── utils/ │ │ └── nutrition-calculator.js ← 卡路里/蛋白质/脂肪换算逻辑 │ └── app.js / app.wxss ← 小程序级入口与样式 └── README.md提示:
project.config.json.example是作者留下的线索,不是可执行配置。它只声明了基础字段,但缺少appid、cloudfunctionRoot、setting等运行必需项。直接复制改名会因appid为空导致 IDE 报“未绑定小程序”错误。
2.1 补全 project.config.json:绑定云开发环境与基础 IDE 设置
project.config.json的核心作用是告诉微信开发者工具:“这个项目属于哪个小程序、云函数在哪、用什么编译版本”。以下是必须填入的真实字段(基于本项目实际依赖):
{ "description": "健康饮食助手 - 微信小程序云开发版", "packOptions": { "ignore": [] }, "setting": { "urlCheck": false, "es6": true, "enhance": true, "postcss": true, "minified": false, "newFeature": true, "coverView": true, "nodeModules": false, "autoAudits": false, "uploadWithSourceMap": true, "compileHotReLoad": false, "useMultiFrameRuntime": true, "useApiHook": true, "babelSetting": { "ignore": [], "disablePlugins": [], "outputPath": "" } }, "compileType": "miniprogram", "libVersion": "2.30.2", // 必须与基础库兼容,本项目实测最低支持 2.25.0 "appid": "wx1234567890abcdef", // ⚠️ 关键!替换成你自己的小程序 AppID "projectname": "health-diet-helper", "debugOptions": { "hidedInDevtools": [] }, "isGameTourist": false, "simulatorType": "wechat", "simulatorPluginLibVersion": "", "condition": { "search": { "current": -1, "list": [] }, "conversation": { "current": -1, "list": [] }, "game": { "currentL": -1, "list": [] }, "miniprogram": { "current": -1, "list": [] } }, "cloudfunctionRoot": "./cloudfunctions/", // 云函数根路径,必须与实际一致 "cloudfunctionTemplateRoot": "./cloudfunctions/", "cloudfunctionFunctionRoot": "./cloudfunctions/" }参数说明与踩坑点:
appid:必须是你在微信公众平台注册的小程序唯一 ID。填错会导致所有云调用返回errCode: -1(无权限);libVersion:本项目使用wx.getSystemInfoSync().SDKVersion做兼容判断,若设为2.20.0以下,wx.cloud.database()会报undefined;cloudfunctionRoot:路径必须以./开头且结尾不带/,否则开发者工具无法识别云函数目录,云函数面板显示为空。
2.2 构建 app.json:声明页面路由、窗口样式与云能力开关
app.json是小程序的“宪法”,它定义了哪些页面能被访问、顶部导航长什么样、是否启用下拉刷新。本项目采用 TabBar + 页面栈混合导航,index为首页,plan和diary为底部 Tab:
{ "pages": [ "pages/index/index", "pages/detail/detail", "pages/plan/plan", "pages/diary/diary" ], "subNVue": [], "window": { "navigationBarBackgroundColor": "#4CAF50", "navigationBarTitleText": "健康饮食助手", "navigationBarTextStyle": "white", "backgroundColor": "#f5f5f5", "backgroundTextStyle": "light" }, "tabBar": { "color": "#7a7a7a", "selectedColor": "#4CAF50", "borderStyle": "black", "backgroundColor": "#ffffff", "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "assets/icons/home.png", "selectedIconPath": "assets/icons/home-active.png" }, { "pagePath": "pages/plan/plan", "text": "周计划", "iconPath": "assets/icons/plan.png", "selectedIconPath": "assets/icons/plan-active.png" }, { "pagePath": "pages/diary/diary", "text": "我的日记", "iconPath": "assets/icons/diary.png", "selectedIconPath": "assets/icons/diary-active.png" } ] }, "sitemapLocation": "sitemap.json", "style": "v2", "useExtendedLib": { "weui": true }, "requiredBackgroundModes": ["audio"], "plugins": {}, "permission": { "scope.userFuzzyLocation": { "desc": "用于根据位置推荐本地时令食材" } }, "cloud": true, // ⚠️ 必须开启,否则 wx.cloud.init() 会失败 "lazyCodeLoading": "requiredComponents" }关键逻辑说明:
"cloud": true是硬性开关,不加此字段,即使wx.cloud.init()执行成功,后续wx.cloud.database()也会抛出Error: cloud function not enabled;tabBar.list中的iconPath指向miniprogram/assets/icons/,该目录在原始包中不存在,需手动创建并放入 6 个 PNG 图标(尺寸 81×81px,透明背景);"requiredBackgroundModes": ["audio"]是为未来接入“语音播报菜谱步骤”预留,当前未实现,但保留可避免后续扩展时报background mode not declared错误。
3. 云开发环境初始化:三步完成数据库集合创建与安全规则配置
本项目所有数据均托管于微信云开发,不依赖任何外部服务器。但“云开发”不是开箱即用——它需要你在微信公众平台手动开通,并在本地完成环境绑定。很多开发者卡在第一步:wx.cloud.init()成功,但db.collection('recipes').get()返回空数组,原因是数据库集合根本不存在,或安全规则拒绝读取。
3.1 创建云开发环境并绑定到项目
- 登录 微信公众平台 → 进入「开发管理」→ 「开发设置」→ 记下你的
AppID(上一节已填); - 进入「云开发」控制台 → 点击「开通云开发」→ 选择地域(建议选
华东,延迟最低)→ 等待环境创建完成(约 30 秒); - 在控制台右上角复制「环境 ID」(格式如
health-diet-abc123); - 回到开发者工具,在
app.js的onLaunch中填入:
App({ onLaunch() { if (!wx.cloud) { console.error('当前微信版本过低,无法使用云开发功能'); return; } wx.cloud.init({ env: 'health-diet-abc123', // ← 替换为你自己的环境 ID traceUser: true }); } });注意:
env字段必须与云开发控制台中显示的环境 ID 完全一致,大小写敏感。填错会导致所有云调用返回errCode: -404011(环境不存在)。
3.2 初始化数据库集合:recipes、users、logs 三大核心表
云数据库不是传统 MySQL,它用 JSON 文档模型。本项目依赖三个集合(Collection):
| 集合名 | 用途 | 示例文档结构 |
|---|---|---|
recipes | 存储全部菜谱数据 | { _id: "r1001", name: "番茄炒蛋", kcal: 120, protein: 8, fat: 6, carbs: 5, ingredients: ["番茄","鸡蛋"], steps: ["切番茄","打蛋..."], image: "cloud://health-diet-abc123.6865-health-diet-abc123/recipe/r1001.jpg" } |
users | 存储用户基础信息与偏好 | { _id: "user_abc123", nickname: "张三", gender: "male", height: 175, weight: 68, goal: "减脂", dietaryRestrictions: ["no_pork"] } |
logs | 记录每日摄入日志 | { _id: "log_20240520_abc123", date: "2024-05-20", items: [{ recipeId: "r1001", portion: 1.5, time: "breakfast" }], totalKcal: 420, totalProtein: 22 } |
创建步骤(在云开发控制台操作):
- 进入「数据库」→ 点击「添加集合」;
- 输入集合名(如
recipes),点击「确定」; - 不要跳过下一步:点击新创建的集合 → 「权限设置」→ 将「读」权限设为「所有人可读」(开发阶段),「写」权限设为「仅创建者可写」;
- 重复以上步骤创建
users和logs。
提示:
recipes集合必须设为「所有人可读」,否则首页菜谱列表永远为空。这是新手最常翻车的点——以为“云数据库默认公开”,其实默认是「仅管理员可读」。
3.3 配置安全规则:让云函数能安全读写,又防恶意刷库
云开发的安全规则(Security Rules)是 JSON 格式,写在每个集合的「权限设置」里。本项目采用「云函数代理 + 用户身份校验」模式,前端不直连数据库,所有读写通过云函数中转。因此,recipes集合规则可设为宽松(只读),而users和logs必须校验openid:
// recipes 集合安全规则(只读,允许前端直接查) { "read": true, "write": false }// users 集合安全规则(仅当前用户可读写) { "read": "auth.openid == doc._openid", "write": "auth.openid == doc._openid" }// logs 集合安全规则(同上,且限制单日最多 10 条) { "read": "auth.openid == doc._openid", "write": "auth.openid == doc._openid && doc.date == request.auth.token.date && request.auth.token.logCount < 10" }参数说明:
auth.openid:微信自动注入的当前用户唯一标识;doc._openid:数据库文档中自动添加的_openid字段(云开发自动写入);request.auth.token.date:需在云函数调用时手动传入date字段,作为 token payload(见 4.2 节);logCount < 10:防止用户脚本暴力刷日志,属防御性设计。
4. 核心模块拆解:从菜谱筛选到周计划生成的完整链路
本项目最值得深挖的是「智能筛选」与「周计划生成」两大模块。它们不依赖 AI 模型,而是用规则引擎 + 组合算法实现轻量级健康推荐。代码全部位于cloudfunctions/目录下,每个云函数都是一个独立 Node.js 服务。
4.1getRecipeList:多维度组合筛选的查询构造器
该云函数接收filters对象(如{ kcalMax: 300, ingredients: ["鸡胸肉"], restrictions: ["no_nuts"] }),返回匹配菜谱列表。关键不在查询本身,而在如何把前端传来的模糊条件,翻译成云数据库的where()链式调用:
// cloudfunctions/getRecipeList/index.js const cloud = require('wx-server-sdk'); cloud.init(); exports.main = async (event, context) => { const { filters = {} } = event; const db = cloud.database(); const $ = db.command.aggregate; let pipeline = []; // 步骤1:基础热量过滤(数值范围) if (filters.kcalMax || filters.kcalMin) { const kcalCond = {}; if (filters.kcalMin) kcalCond.$gte = filters.kcalMin; if (filters.kcalMax) kcalCond.$lte = filters.kcalMax; pipeline.push({ $match: { kcal: kcalCond } }); } // 步骤2:食材包含过滤(数组交集) if (filters.ingredients && filters.ingredients.length) { pipeline.push({ $match: { ingredients: { $all: filters.ingredients.map(ing => new RegExp(ing, 'i')) // 不区分大小写 } } }); } // 步骤3:忌口排除(数组差集) if (filters.restrictions && filters.restrictions.length) { const excludeRegexes = filters.restrictions.map(r => new RegExp(r, 'i')); pipeline.push({ $match: { $nor: excludeRegexes.map(regex => ({ ingredients: regex })) } }); } // 步骤4:随机排序 + 分页(避免总返回同一组) pipeline.push( { $sample: { size: 50 } }, // 先随机抽50条,再分页,比 sort({$rand:1}) 更高效 { $skip: (filters.page || 0) * (filters.pageSize || 10) }, { $limit: filters.pageSize || 10 } ); try { const res = await db.collection('recipes').aggregate() .addPipeline(pipeline) .end(); return { code: 0, data: res.list }; } catch (err) { console.error('getRecipeList error:', err); return { code: -1, msg: err.message }; } };逻辑说明与参数细节:
$all+RegExp实现“必须包含所有指定食材”,比ingredients: { $in: [...] }更精准;$nor配合正则,实现“完全排除含某关键词的食材”,例如no_pork会排除五花肉、猪蹄;$sample是性能关键:云数据库对大集合sort({$rand:1})极慢,$sample是官方推荐的随机方案;filters.page和filters.pageSize由前端传入,云函数不做校验,但实际应加if (filters.page < 0 || filters.pageSize > 50) return {code:-1}防滥用。
4.2generateWeeklyPlan:基于用户画像的约束满足算法
该函数接收userId和goal(如"weight_loss"),返回 7 天的菜谱组合。它不调用机器学习,而是用「回溯 + 贪心」策略在菜谱库中搜索满足约束的解:
// cloudfunctions/generateWeeklyPlan/index.js const cloud = require('wx-server-sdk'); cloud.init(); exports.main = async (event, context) => { const { userId, goal = 'maintain' } = event; const db = cloud.database(); // 步骤1:获取用户基础信息(身高体重等) const userRes = await db.collection('users').doc(userId).get(); if (!userRes.data || userRes.data.length === 0) { return { code: -1, msg: '用户不存在' }; } const user = userRes.data[0]; // 步骤2:计算每日推荐热量(Mifflin-St Jeor 公式简化版) let dailyKcal; if (goal === 'weight_loss') { dailyKcal = Math.round((10 * user.weight + 6.25 * user.height - 5 * user.age + 5) * 0.8); } else if (goal === 'muscle_gain') { dailyKcal = Math.round((10 * user.weight + 6.25 * user.height - 5 * user.age + 5) * 1.2); } else { dailyKcal = Math.round(10 * user.weight + 6.25 * user.height - 5 * user.age + 5); } // 步骤3:按餐次分配热量(早餐30%、午餐40%、晚餐30%) const mealKcal = { breakfast: Math.round(dailyKcal * 0.3), lunch: Math.round(dailyKcal * 0.4), dinner: Math.round(dailyKcal * 0.3) }; // 步骤4:为每一天每一餐,从 recipes 中搜索最接近目标热量的菜谱 const plan = []; const allRecipes = await db.collection('recipes') .where({ kcal: _.range(mealKcal.breakfast - 50, mealKcal.breakfast + 50) }) .get(); // 此处应加 .field({name: true, kcal: true, image: true}) 减少传输量 for (let day = 0; day < 7; day++) { const dayPlan = { date: new Date(Date.now() + day * 24 * 60 * 60 * 1000).toISOString().split('T')[0], meals: {} }; for (const meal of ['breakfast', 'lunch', 'dinner']) { // 贪心:找 kcal 最接近 mealKcal[meal] 的菜谱 const candidates = allRecipes.data.filter(r => Math.abs(r.kcal - mealKcal[meal]) < 100 ).sort((a, b) => Math.abs(a.kcal - mealKcal[meal]) - Math.abs(b.kcal - mealKcal[meal])); dayPlan.meals[meal] = candidates[0] || null; } plan.push(dayPlan); } return { code: 0, data: plan }; };关键设计点:
- 不真正回溯:为保证响应速度(云函数超时 5s),放弃穷举,改用“贪心近似”——每天每餐单独找最优,而非全局优化;
- 热量浮动区间:
Math.abs(r.kcal - target) < 100是经验值,太小会导致无解,太大则推荐不准; - 性能隐患:
allRecipes一次查全量菜谱(可能上千条)再内存过滤,应改为聚合管道$lookup或分页查,但本项目菜谱数 < 200,可接受。
5. 常见问题排查:5 个血泪经验总结,省下你 3 小时调试时间
注意:以下问题均来自真实调试现场,非理论推测。每一条都对应一个具体报错截图和解决过程。
5.1 现象:云函数调用返回errCode: -404011
原因:wx.cloud.init()中env字段填写的环境 ID 与云开发控制台中实际环境 ID 不一致(常见于复制时多了一个空格,或用了测试环境 ID 但控制台开了正式环境)。
解决:在云开发控制台右上角确认环境 ID,再检查app.js中wx.cloud.init({ env: 'xxx' })的字符串是否完全一致(可用console.log(wx.cloud.getWXContext().ENV)在云函数中打印验证)。
5.2 现象:首页菜谱列表为空,但云开发控制台能看到recipes集合有数据
原因:recipes集合的安全规则未设为{"read": true},默认是{"read": false}。
解决:进入云开发控制台 → 数据库 →recipes集合 → 权限设置 → 将「读」设为「所有人可读」→ 保存。
5.3 现象:点击「生成周计划」按钮无反应,控制台报Cannot read property 'getWXContext' of undefined
原因:云函数未部署,或部署后未发布(云函数需「上传」+「部署」两步,缺一不可)。
解决:在开发者工具中右键cloudfunctions/generateWeeklyPlan→ 「上传并部署:云端安装依赖」→ 等待状态变为「已部署」→ 再次调用。
5.4 现象:detail页面图片不显示,Network 中看到403 Forbidden
原因:菜谱文档中的image字段是云存储文件 ID(如recipe/r1001.jpg),但未在云存储中上传对应文件,或文件权限为「私有」。
解决:进入云开发控制台 → 存储 → 上传recipe/r1001.jpg文件 → 点击文件 → 「权限设置」→ 设为「公有读」。
5.5 现象:diary页面拍照后上传失败,报errCode: -501001
原因:wx.chooseImage()返回的临时路径不能直接传给wx.cloud.uploadFile(),必须先用wx.getFileSystemManager().readFile()转为 ArrayBuffer,或改用wx.chooseMedia()(新版 API)。
解决:将pages/diary/diary.js中的wx.chooseImage()替换为:
wx.chooseMedia({ count: 1, mediaType: ['image'], sourceType: ['album', 'camera'], success(res) { const tempFile = res.tempFiles[0]; wx.cloud.uploadFile({ cloudPath: `diary/${Date.now()}_${Math.random().toString(36).substr(2, 9)}.jpg`, filePath: tempFile.tempFilePath, success: console.log, fail: console.error }); } });6. 二次开发实战:如何接入自定义营养数据库并替换推荐算法
如果你不是只想跑通 Demo,而是要把它变成自己项目的基石,最关键的改造点有两个:替换菜谱数据源和升级推荐逻辑。我拿最近帮某健康管理 SaaS 做的落地为例,讲清楚每一步怎么改、为什么这么改。
6.1 接入自有营养数据库:从 JSON 文件到云数据库自动同步
原始项目把菜谱硬编码在recipes集合里,新增菜谱要手动插入文档。真实业务中,你需要从 Excel 或 CSV 导入,甚至对接第三方 API(如 USDA FoodData Central)。最佳实践是写一个「数据同步云函数」,定时拉取并更新:
// cloudfunctions/syncNutritionDB/index.js const cloud = require('wx-server-sdk'); const axios = require('axios'); // 需在 package.json 中添加 "axios": "^1.6.0" cloud.init(); exports.main = async (event, context) => { const db = cloud.database(); const { source = 'usda_api', batchSize = 100 } = event; try { let newData = []; if (source === 'usda_api') { // 示例:调用 USDA API(需申请 key) const res = await axios.get( `https://api.nal.usda.gov/fdc/v1/foods/search?query=chicken&pageSize=${batchSize}&api_key=YOUR_KEY` ); newData = res.data.foods.map(food => ({ name: food.description, kcal: food.foodNutrients.find(n => n.nutrientName === 'Energy')?.value || 0, protein: food.foodNutrients.find(n => n.nutrientName === 'Protein')?.value || 0, fat: food.foodNutrients.find(n => n.nutrientName === 'Total lipid (fat)')?.value || 0, carbs: food.foodNutrients.find(n => n.nutrientName === 'Carbohydrate, by difference')?.value || 0, ingredients: [food.description.toLowerCase()], steps: ['请按包装说明烹饪'], image: '' })); } else if (source === 'local_csv') { // 读取云存储中的 CSV 文件(需提前上传) const fileRes = await wx.cloud.downloadFile({ fileID: 'cloud://health-diet-abc123.6865-health-diet-abc123/data/recipes.csv' }); const csvText = fileRes.fileContent.toString(); newData = parseCSV(csvText); // 自定义解析函数 } // 批量写入(云数据库单次最多 50 条) for (let i = 0; i < newData.length; i += 50) { const batch = newData.slice(i, i + 50); await db.collection('recipes').add({ data: batch }); } return { code: 0, msg: `同步 ${newData.length} 条菜谱` }; } catch (err) { console.error('sync error:', err); return { code: -1, msg: err.message }; } };部署与触发:
- 在云开发控制台 → 云函数 →
syncNutritionDB→ 「定时触发」→ 设置 Cron 表达式0 0 * * 0(每周日凌晨执行); - 首次手动执行:在控制台点击「测试」→ 输入
{"source": "usda_api"}。
6.2 替换推荐算法:用规则引擎替代硬编码公式
原始generateWeeklyPlan的热量计算过于简略。真实场景需支持:
- 多目标(如“控糖+增肌”);
- 动态调整(运动后当日热量+200kcal);
- 食材去重(避免连续三天吃鸡胸肉)。
我引入了轻量级规则引擎json-rules-engine(需npm install json-rules-engine):
// cloudfunctions/generateWeeklyPlanV2/index.js const { Engine } = require('json-rules-engine'); const cloud = require('wx-server-sdk'); cloud.init(); // 定义规则(可存入云数据库,动态更新) const rules = [ { conditions: { all: [ { fact: 'userGoal', operator: 'equal', value: 'weight_loss' }, { fact: 'userActivity', operator: 'greaterThanInclusive', value: 3 } ] }, event: { type: 'adjust_kcal', params: { multiplier: 0.85 } } }, { conditions: { all: [ { fact: 'meal', operator: 'equal', value: 'breakfast' }, { fact: 'userDietaryRestrictions', operator: 'contains', value: 'no_sugar' } ] }, event: { type: 'filter_by_nutrient', params: { maxSugar: 5 } } } ]; const engine = new Engine(); rules.forEach(rule => engine.addRule(rule)); exports.main = async (event, context) => { const { userId } = event; const db = cloud.database(); const user = await db.collection('users').doc(userId).get(); // 构造事实(facts) const facts = { userGoal: user.data.goal, userActivity: user.data.weeklyExerciseHours || 0, userDietaryRestrictions: user.data.dietaryRestrictions || [], meal: 'breakfast' }; // 运行规则引擎 const results = await engine.run(facts); let adjustments = {}; results.events.forEach(e => { if (e.type === 'adjust_kcal') adjustments.kcalMultiplier = e.params.multiplier; if (e.type === 'filter_by_nutrient') adjustments.sugarLimit = e.params.maxSugar; }); // 后续按 adjustments 生成计划... return { code: 0, adjustments }; };优势:规则与代码分离,运营人员可在后台修改 JSON 规则,无需发版;支持 A/B 测试(不同用户群加载不同规则集)。
从那以后我每次接手健康类小程序,第一件事就是检查cloudfunctions/下有没有sync*和rule*类函数——没有就立刻补上,因为数据源和推荐逻辑才是项目真正的护城河,而不是那些 UI 动效。希望帮到你。
本文还有配套的精品资源,点击获取