CSDN 版本说明:原文是偏评测口径,这里改成技术拆解口径。重点放在需求建模、页面组织、云函数设计和功能边界上。
1. 为什么要把情侣、个人、家庭分开看
菜谱管理看起来只是"记菜名",但不同人群的实际问题不一样。
个人用户关心的是长期积累。自己会做什么、常吃什么、外面吃到什么想复刻,这些内容要能搜、能分类、能重新拿出来用。
情侣用户关心的是共同决策。两个人都说随便,最后还是要有人决定吃什么。工具如果能随机抽菜、生成双人菜单、让对方参与点单,就能减少沟通成本。
家庭用户关心的是协作和稳定。谁爱吃什么,谁不吃辣,周末来客人要提前点哪几道菜,这些都不适合只放在一个人的脑子里。
所以一个免费菜谱管理小程序顺不顺手,不是看它能不能展示很多公开菜谱,而是看它有没有围绕这三类场景设计数据和流程。
2. 功能入口先按工作流拆,而不是按页面好看拆
示例项目的核心页面都能在 miniprogram/app.json 找到。
json
{
"pages": [
"pages/index/index",
"pages/add-menu/index",
"pages/menu-detail/index",
"pages/menu-cooking/index",
"pages/draw/index",
"pages/menu-poster/index",
"pages/my-orders/index",
"pages/order-detail/index",
"pages/food-diary/cup/index",
"pages/food-diary/meal/index"
]
}
这些页面基本覆盖了一个家庭菜单的闭环。
录入菜品,进入 add-menu。
查看和复用菜品,进入 menu-detail 和 menu-cooking。
不知道吃什么,进入 draw。
要发给别人看,进入 menu-poster。
要让别人点,进入 my-orders、order-detail、share。
要记录外食和饮品,进入 food-diary 相关页面。
这个页面拆法比较适合 C 端小程序。用户不需要理解"数据管理",只要按生活动作进入对应页面。
3. 菜品数据模型是基础
在 pages/add-menu/index.js 里,表单数据不是一个简单字符串,而是一个结构化对象。
js
formData: {
name: '',
categoryId: '',
remark: '',
imageUrl: '',
ingredients: [],
steps: [],
}
菜名、分类、备注、图片是最小可用字段。用料和做法则解决"这道菜我家到底怎么做"的问题。
项目里用 miniprogram/utils/menuRecipe.js 抽出了统一限制。
js
const MENU_RECIPE_LIMITS = {
maxIngredients: 12,
maxSteps: 10,
ingredientNameMaxLength: 20,
ingredientAmountMaxLength: 15,
stepDescMaxLength: 120,
};
这些限制对用户体验有帮助。菜谱不是越长越好,尤其是家庭菜谱。如果一个步骤可以写到几千字,详情页、烹饪模式和菜单导出都会受到影响。
服务端也会在 cloudfunctions/dataManager/index.js 做同样的 normalize 和校验。这样即使前端出 bug,云数据库里也不会轻易出现过长或畸形数据。
4. 分类和搜索解决"找得到"
菜品记录工具最怕的是前几天很新鲜,录了几十道菜,后面找不到。
示例项目在首页维护 categories、menuItems、activeCategoryId、searchKeyword 这些状态。
js
data: {
categories: [],
menuItems: [],
activeCategoryId: 'all',
searchKeyword: '',
page: 1,
pageSize: 20,
hasMore: true
}
这里有两个细节。
一是分页,避免首页一次性拉太多菜品。
二是分类缓存。add-menu 页面加载分类时,会把默认分类和用户自定义分类合并,并使用本地缓存减少重复请求。
js
const CACHE_DURATION = 30 * 60 * 1000;
wx.setStorageSync(cacheKey, { time: Date.now(), data: dbCategories });
小程序里这类缓存很有必要。分类变动不算高频,每次进页面都请求云函数,会让页面显得慢,也会增加云调用次数。
5. 随机抽菜要基于私有菜单
情侣和家庭场景里,"今天吃什么"不是随机生成一个陌生菜名。
更合理的逻辑是从自己已经录入的菜品里抽。因为这些菜默认是会做、吃过、可接受的。
pages/draw/index.js 里会读取当前用户的 menus。
js
const res = await CloudAPI.list('menus', {
orderBy: { field: 'createTime', order: 'desc' },
limit: 1000
});
然后根据用户选择的数量做随机。
js
const finalResults = this.pickRandom(this.data.allMenus, drawCount, true);
这个设计对情侣和家庭很关键。
两个人纠结晚餐时,不需要从互联网上重新发现菜谱,只需要在两个人都接受的菜单里做一次低成本选择。
家庭聚餐也是一样。随机结果来自自家菜单,实际落地概率更高。
6. 情侣、个人、家庭三种菜单导出不是换个标题
pages/menu-poster/index.js 里把菜单风格抽成了 MENU_STYLES。
js
const MENU_STYLES = {
family: {
id: 'family',
label: '家庭',
exportTitle: '家庭私享菜单'
},
couple: {
id: 'couple',
label: '双人',
exportTitle: '双人小馆菜单'
},
personal: {
id: 'personal',
label: '个人',
exportTitle: '我的常做菜'
}
};
它的意义不只是视觉主题。
家庭菜单适合展示"家常菜、拿手菜、招牌菜"。
双人菜单适合展示"两个人常吃的菜、今晚从这里挑"。
个人菜单适合展示"我的常做菜、工作日快手菜、健身期常吃菜"。
如果这些都用同一套标题和文案,功能也能跑,但使用感会弱很多。C 端工具经常差在这里,技术上能实现,场景上没有贴合。
菜单导出还涉及分页和免费额度。
js
const DIRECT_EXPORT_MAX = 18;
const EXPORT_PAGE_SIZE = 12;
const DAILY_FREE_EXPORT_LIMIT = 2;
菜品少时可以直接导出整张长图,菜品多时分页。每日免费导出次数限制也放在本地状态中管理。
这类能力会消耗 Canvas 渲染、图片临时链接和保存链路,适合做额度控制,而不是无限制开放。
7. 邀请点单本质是轻量订单系统
很多家庭菜单工具会停在"分享菜谱"。
但"邀请别人点菜"需要更完整的服务端模型。示例项目用 cloudfunctions/orderService/index.js 单独处理。
它支持读取邀请人的分类和菜品,也支持创建订单、多人单、查询今日点单。
js
case 'listCategoriesByOwner':
return await listCategoriesByOwner(event.inviterOpenId);
case 'listMenusByOwner':
return await listMenusByOwner(event.inviterOpenId, event.query || {});
case 'createOrder':
return await createOrder(wxContext.OPENID, event);
case 'createGroup':
return await createGroup(wxContext.OPENID, event.mealPeriod);
创建点单时,会写入邀请人、被邀请人、菜品明细、用餐时段、状态和日期索引。
js
const doc = {
inviterOpenId,
inviteeOpenId,
groupId: groupId || null,
mealPeriod: mealPeriod || null,
items: items.map(x => ({
dishId: x.dishId,
dishName: x.dishName,
image: x.image || '',
count: Number(x.count || 1)
})),
status: 'created',
createdAt: new Date(),
dateKey: todayKey()
};
多人单还会校验 24 小时过期,避免旧链接一直可写。
这个实现给同类项目一个提醒:只要涉及"别人对我的数据发起操作",就不能只靠前端页面状态,最好单独建服务端流程。
8. 免费策略应围绕成本设计
免费工具最容易出问题的地方,是把所有功能都说成免费,但资源消耗没有边界。
示例项目在 dataManager 里对新增菜品和修改图片做了每日限制。
js
const LIMIT_CONFIG = {
menus: {
dailyAddLimit: 35
},
menuImageUpdate: {
dailyUpdateLimit: 10
}
};
这里的重点是,它限制的是每日新增速度,不是长期菜品总量。
对家庭菜谱管理来说,长期菜品库是用户最核心的数据。如果总量被卡得太低,用户一旦认真使用,很快就会被打断。
但图片修改、菜单导出、抠图、分享图生成这些能力确实会消耗资源。给这类能力设置每日额度,工程上更可控,也更符合成本结构。
9. 外食和饮品记录为什么能放进菜谱工具
从传统菜谱 App 的角度看,奶茶、咖啡、堂食记录好像不属于菜谱。
但从"个人口味管理"的角度看,它们是同一类数据。
pages/food-diary/cup/index.js 里,饮品记录字段比较细。
js
function createEntryDraft(entryDate = getTodayDateString()) {
return {
entryDrinkName: '',
entryBrandName: '',
entryPrice: '',
selectedCupType: '',
selectedRating: 0,
selectedIngredient: '',
selectedTemperature: '常温',
selectedSweetness: '标准糖',
entryRemark: '',
};
}
这不是公开点评,而是私人口味档案。
一个人经常忘记的不是品牌,而是少冰、三分糖、加什么小料、上次到底喜不喜欢。把这些字段结构化,后面才能搜索和复盘。
food-diary/meal 则更偏餐食照片和日历记录,适合记录外面吃过但以后还想复刻或再点的餐食。
这也是"免费菜谱管理"比"公开菜谱搜索"更宽的地方。它记录的是人的饮食偏好,不只是菜谱文本。
10. 面向不同人群的功能映射
| 人群 | 主要问题 | 对应功能 | 关键实现 |
|---|---|---|---|
| 个人 | 常做菜和外食偏好容易散 | 菜品录入、分类、搜索、个人菜单、记一杯 | menus、categories、food-diary/cup |
| 情侣 | 每天互相问吃什么,决策成本高 | 随机抽菜、双人菜单、邀请点单 | draw、menu-poster 的 couple 风格、orderService |
| 家庭 | 多人偏好和聚餐点菜难同步 | 家庭菜单、多人点单、今日订单提醒 | order_groups、orders、首页 todayOrderCount |
| 厨房新手 | 不知道怎么做 | 用料、步骤、烹饪模式 | ingredients、steps、menu-cooking |
| 长期用户 | 数据越来越多,维护成本上升 | 分页、缓存、分类管理、数据清理 | pageSize、分类缓存、purgeUserRecipeData |
这张表也可以反过来作为产品需求清单。
如果一个功能找不到对应人群和问题,先不要急着做。
如果一个高频问题没有对应功能,优先级就应该提高。
11. 如果做同类小程序,可以参考的实现顺序
第一步,先把菜品数据模型定下来。菜名、分类、备注、图片、用料、步骤足够构成 MVP。
第二步,做数据隔离。所有云函数查询都要带当前用户 openid,更新和删除要校验文档归属。
第三步,做分类、搜索和分页。只要菜品超过二三十道,没有这些功能就会很快失控。
第四步,做随机抽菜。这个功能开发成本不高,但对"今天吃什么"这个场景很直接。
第五步,做邀请点单。这里要上服务端模型,不能只靠分享参数。
第六步,做菜单导出和日记记录。它们能提升完成度,但也要注意图片和 Canvas 资源成本。
12. 小结
免费菜谱管理小程序是否顺手,不取决于它能展示多少公开菜谱。
更关键的是,它能不能把个人、情侣、家庭这三类真实场景串起来。
从这个示例项目看,一套比较完整的设计应该包括:结构化菜品、分类搜索、私有菜单随机抽菜、三种菜单导出、邀请点单、外食和饮品记录、每日额度控制。
对开发者来说,这类小程序的难点不在单个页面,而在功能边界。公开菜谱学习、私人口味管理、多人点单、图片导出,这几件事成本和数据模型都不同。先把边界拆清楚,再写页面,后面会少很多返工。