Files
toor 76a5d71252 refactor: 只保留 Web,移除 Capacitor 原生壳
删除 android/、ios/ 两个 Capacitor 原生工程与 capacitor.config.ts、
ionic.config.json,以及 scripts/android、scripts/ios.sh 构建脚本;
根与 apps/web 的 @capacitor/* 依赖和相关 npm scripts 一并移除。

Web 侧去掉原生分支:登录只走云乐坊容器授权面板或顶层重定向,
暗色模式不再改原生状态栏,移除 Android 物理返回键处理。

保留 @nuxtjs/ionic(Web UI 框架)、云乐坊 window.ylf 桥接与 PWA。

决策记录见 docs/decisions/0004-web-only.md。
2026-10-21 12:27:50 +08:00

10 KiB
Raw Permalink Blame History

name, description, metadata
name description metadata
add-recipe 给 Cook(食用手册)新增菜式。当用户说「增加菜式」「加几道菜」「添几个菜谱」「补点新菜」「增加 XX 类菜」「加个 XX 做的菜」时使用。按「先摸清现有数据 → 上网找数据里没有的新菜 → 拆解成食材/做法/厨具/菜系类别 → 写入并校验」的流程执行。
author repository
YunYouJun https://github.com/YunYouJun/cook

新增菜式

你在给 Cook(食用手册)补充菜谱数据。每次运行把若干道新菜写进 packages/data/recipe.csv,跑完校验后向用户汇报。

数量规则

  • 用户明确说了数量(「加 10 道」「补 5 个」),就按用户说的来。
  • 用户没说数量,默认加 5 道菜。
  • 用户指定了主题或范围(「加几道川菜」「补点用砂锅的菜」),数量仍按上一条判断。

数据落点

只有一个文件要改:packages/data/recipe.csv。

列为 name,stuff,bv,difficulty,tags,methods,tools,id,单元格内多个值用顿号「、」分隔(不能用逗号、斜杠、分号)。

name,stuff,bv,difficulty,tags,methods,tools,id
青椒炒肉,青椒、猪肉,,普通,家常菜,炒,一口大锅,637
电饭煲版广式腊肠煲饭,腊肠、米,BV1NE411Q7Jj,简单,广式,煲,电饭煲,0
列 规则
name 必填。菜名要能让人一眼看出做法,习惯写成「厨具版+做法+主料」或「主料+做法」
stuff 必填。只能填 packages/data/food.json 里的食材标准名,具体品种(虾仁、鲈鱼)要写成标准名(虾、鱼)
bv B 站 BV 号或 av 号。可以留空——应用里点菜走的是 B 站搜索链接,不是这个视频号;填了就必须是真实可播放的视频
difficulty 简单 / 普通 / 困难,或留空
tags 菜系类别 + 其它标签(快手菜、下饭、汤、杂烩、辣…),见下表
methods 做法(炒、煎、蒸、煮、煲、烤、炸…),见下表
tools 只能填 packages/data/food.json 里的厨具名
id 必填,非负整数且唯一。新菜用当前最大 id + 1(现在是 637、638…)

id 一旦发布就不能修改、不能复用。 收藏、反馈、筛选信号都按 id 记录。新菜一律追加到文件末尾,id 递增。

取值白名单

食材(35 个,来自 packages/data/food.json)

  • 蔬菜:土豆、胡萝卜、花菜、白萝卜、西葫芦、番茄、芹菜、黄瓜、洋葱、莴笋、菌菇、茄子、豆腐、包菜、白菜、青椒、韭菜、南瓜
  • 肉蛋水产:午餐肉、香肠、腊肠、鸡肉、猪肉、鸡蛋、虾、牛肉、骨头、鱼、蟹
  • 主食:面食、面包、米、方便面、玉米、红薯

需要列表里没有的食材时,先在 packages/data/food.json 里加,再加别名到 packages/data/aliases.json(如「柿子椒」→「青椒」),然后才写进菜谱。别名映射用户输入的写法到标准名。

厨具(6 个)

烤箱、空气炸锅、微波炉、电饭煲、一口大锅、砂锅

菜系类别(tags 里的菜系标签)

必须从下表里选标签值写进 tags,选了哪个菜系筛选就只认哪个标签:

菜系 可用标签
川菜 川菜
湘菜 湘菜
粤菜 粤菜、广式
苏菜 苏菜
东北菜 东北菜
西北菜 西北特色
日式 日式、日式菜
意式 意式
法式 法式、法餐
西式 西式
国宴菜 国宴菜

每道新菜都尽量打上菜系标签。 应用里一旦选了菜系,只有带该标签的菜会出现——标签不全会导致选了菜系就几乎没结果。

做法分组(methods + tags 的组合)

用户在应用里选的「做法」是分组,按下面的规则命中:

做法分组 怎么填 备注
炒菜 methods 填「炒」
汤羹 tags 填「汤」,methods 填煮/煲/炖 靠标签识别,不是靠 methods
炖煮 methods 填炖、煮、煲、卤、焖、烧、灼
蒸 methods 填「蒸」
炸煎 methods 填炸、煎、烙
烤 methods 填烤、烘、焗
凉拌 methods 填拌、凉拌、腌、腌制、泡菜、油泼
微波 methods 填「微波加热」
火锅 tags 填「杂烩」,且食材 ≥10 种 靠标签识别;食材少于 10 种不算大杂烩

「辣」标签同样靠 tags 识别,选了「不吃辣」时会排除带此标签的菜。

步骤

1. 摸清现有数据

先搞清楚「已经有什么、缺什么」,不要凭印象加菜。

# 全部菜谱(638 行,含表头)
cat packages/data/recipe.csv

# 菜谱总数
wc -l packages/data/recipe.csv

# 现有菜系分布:每种菜系各有多少道
node -e "const fs=require('fs');const l=fs.readFileSync('packages/data/recipe.csv','utf8').trim().split(/\r?\n/).slice(1);const c={};l.forEach(r=>{const t=r.split(',')[4]||'';t.split('、').filter(Boolean).forEach(x=>{if(/菜|式|广式|法餐/.test(x))c[x]=(c[x]||0)+1})});console.log(c)"

# 某个食材出现在多少道菜里(找出只有 1-2 道菜的冷门食材)
node -e "const fs=require('fs');const l=fs.readFileSync('packages/data/recipe.csv','utf8').trim().split(/\r?\n/).slice(1);const c={};l.forEach(r=>r.split(',')[1].split('、').filter(Boolean).forEach(x=>c[x]=(c[x]||0)+1));console.log(Object.entries(c).sort((a,b)=>a[1]-b[1]))"

# 某个厨具下有多少道菜
node -e "const fs=require('fs');const l=fs.readFileSync('packages/data/recipe.csv','utf8').trim().split(/\r?\n/).slice(1);const c={};l.forEach(r=>r.split(',')[6].split('、').filter(Boolean).forEach(x=>c[x]=(c[x]||0)+1));console.log(c)"

同时读 packages/data/food.json(食材/厨具词典)和 packages/data/aliases.json(别名)。

重点找这几类空洞,补菜时优先填:

  • 菜系空洞:某个菜系只有 1-2 道菜,用户一选就几乎没结果。
  • 冷门食材:某个食材只出现在 1-2 道菜里,选它必然匹配不到。
  • 厨具 × 食材交叉:某个食材配某个厨具一道菜都没有(比如砂锅只有十来道菜,覆盖的食材很窄)。
  • 做法 × 菜系交叉:某菜系里缺某类做法(比如粤菜没有凉拌、日式没有汤)。

2. 上网找新菜

  • 围绕要补的空洞找菜:「XX 家常做法」「XX 砂锅」「XX 快手菜」这类关键词。
  • 优先选常见、做法明确、食材在词典里的家常菜。冷门菜、分子料理、需要特殊设备的不要。
  • 判重:菜名和已有菜重复的、或同名同做法的,不加。先用步骤 1 的命令或直接搜 CSV 确认。
  • 不要编造菜谱或视频号。找不到真实信息就换一道菜,别硬凑。
  • 找视频(可选):搜索时带上 bilibili.com/video 关键词,从结果里拿 BV 号。

3. 拆解并写入

对每一道新菜,逐列拆开:

  1. 菜名:青椒炒肉
  2. 食材:拆成词典里的标准名 —— 青椒、猪肉
  3. 厨具:这道菜实际用什么锅做 —— 一口大锅
  4. 做法:methods 填具体动作 —— 炒
  5. 菜系类别:tags 填菜系标签 —— 川菜、家常菜
  6. 难度:简单 / 普通 / 困难,拿不准留空
  7. id:从当前最大 id + 1 开始,逐个递增

然后追加到 packages/data/recipe.csv 末尾(不要改已有行)。

4. 校验

按顺序跑完,全绿才算完成:

pnpm convert      # CSV → JSON,必须先跑
pnpm validate     # 有错误时退出码为 1
pnpm fixtures     # 重新生成 recommend.json(改了数据必须重跑)
pnpm test -- --run
pnpm lint

常见问题:

  • pnpm validate 报「食材/厨具不在词典中」 —— 名称不对,改成标准名;词典里确实没有就先加进 food.json。
  • pnpm test 失败且是 test/recommendations.test.ts 里的 counts 断言 —— 新菜改变了推荐结果的分组数量,属于预期变化,更新这些数字即可。
  • pnpm test 报 fixtures 不一致 —— pnpm fixtures 没跑或跑漏了。
  • pnpm lint 报 md 文件格式错 —— 文档类改动注意行尾和代码块内不要留多余空行。

5. 汇报

告诉用户:

  • 加了几道菜,每道的:菜名、食材、厨具、做法、菜系、难度、id
  • 补的是哪些空洞(比如「补了砂锅 + 香菇 3 道」「粤菜凉拌 2 道」)
  • 校验结果(pnpm validate 错误数、pnpm test 通过情况)
  • 哪些地方拿不准、需要用户定夺(比如新加的食材名、没找到合适视频的菜)

注意事项

  • 不要删除或修改已有行。发现已有数据有问题(重复、失效视频)时,在汇报里指出,让用户决定。
  • 不要改 packages/cook/src/utils/recommend.ts 里的筛选逻辑,也不要改 test/ 下的断言来「凑」结果——断言变化只允许是数据量自然变化导致。
  • packages/data/recipe.json 和 packages/data/fixtures/recommend.json 是生成物,不要手写,改 CSV 和用例输入后用 pnpm convert / pnpm fixtures 重新生成。