diff --git a/CHANGELOG.md b/CHANGELOG.md index 6de3722..3bc4766 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,73 @@ All notable changes to this project will be documented in this file. +## [0.92.4] - 2026-04-13 + +### 📋 å…³äºŽé¡µé¢ + 文档更新 + +#### 新增功能 +- 📋 **关于页é¢** — 新增AboutPage,展示应用信æ¯ã€ç‰ˆæœ¬å·ã€ç”¨æˆ·åé¦ˆå…¥å£ +- 💬 **用户å馈入å£** — å…³äºŽé¡µé¢æ–°å¢ž"用户å馈"选项,点击跳转æ„è§å馈页 + +#### 页é¢ç»“æž„ +- 📱 应用信æ¯ï¼šç‰ˆæœ¬å·ã€æ›´æ–°æ—¥æœŸã€æž„建版本 +- 📞 è”系我们:用户å馈ã€è¯„价应用ã€è”系邮箱 +- 📜 法律信æ¯ï¼šç”¨æˆ·åè®®ã€éšç§æ”¿ç­– + +#### 路由更新 +- 新增 `/about` 路由指å‘AboutPage +- profile_settings.dart"关于"按钮跳转改为 `/about` + +#### 文档更新 +- 📄 **UNFINISHED_FEATURES.md** — 新增阶段三å七/三å八记录,更新总体进度96% +- 📄 **PAGE_STRUCTURE_ANALYSIS.md** — 新增收è—页é¢å’Œå…³äºŽé¡µé¢ç»“æž„æè¿°ï¼Œæ›´æ–°å·¥å…·ä¸­å¿ƒå¸ƒå±€è¯´æ˜Ž + +## [0.92.3] - 2026-04-13 + +### 📚 文档更新 + UI布局优化 + +#### 文档更新 +- 📄 **PAGE_STRUCTURE_ANALYSIS.md** — 更新全局功能缺失汇总状æ€ï¼ˆç›¸å…³æŽ¨è/过æ•原警示/è¥å…»å¯è§†åŒ–标记为✅v0.92.0) +- 📄 **PAGE_STRUCTURE_ANALYSIS.md** — æ›´æ–°API能力分æžéƒ¨åˆ†ï¼ˆå·²ä½¿ç”¨/未使用接å£çжæ€åŒæ­¥ï¼‰ +- 📄 **UNFINISHED_FEATURES.md** — æ›´æ–°API接å£ä½¿ç”¨çжæ€ä¸€è§ˆï¼ˆæ–°å¢žapi_check_duplicate.phpã€é™æ€æ•°æ®ä½¿ç”¨çжæ€ï¼‰ + +#### UI布局优化 +- â¤ï¸ **æ”¶è—页é¢** — 布局改为æ¯è¡Œ2个å¡ç‰‡ï¼ˆGridView.builder替代ListView.separated) +- ðŸ› ï¸ **工具中心页é¢** — 新增"使用工具"按钮,点击直接跳转工具使用页é¢ï¼ˆéžè¯¦æƒ…页) + +#### ä»£ç æ”¹è¿› +- 🎨 æ”¶è—å¡ç‰‡æ”¹ä¸ºåž‚直布局,图标居中显示 +- 🎨 工具å¡ç‰‡æ–°å¢ž"使用工具"æŒ‰é’®ï¼ˆæ¬¡è¦æ ·å¼ï¼Œè¾¹æ¡†é£Žæ ¼ï¼‰ + +## [0.92.2] - 2026-04-13 + +### 📚 çƒ¹é¥ªæŠ€å·§æ¨¡å— â€” HowToCookå¼€æºé¡¹ç›®é›†æˆ + +#### 新增功能 +- 📖 **烹饪技巧列表页** — 分类展示18篇烹饪技巧文章(基础/进阶技巧/学习烹饪) +- 📠**Markdown渲染页é¢** — 使用flutter_markdown_plusæ¸²æŸ“ï¼Œæ”¯æŒæ ‡é¢˜/列表/代ç å—/表格等 +- 🔠**æœç´¢åŠŸèƒ½** — æ”¯æŒæŒ‰å称和分类æœç´¢çƒ¹é¥ªæŠ€å·§ +- 🠠**本地Assets存储** — 18个MD文件打包到应用内,无需网络请求 + +#### æ•°æ®æ¥æº +- æ•°æ®æ¥æºäºŽ [HowToCook](https://github.com/Anduin2017/HowToCook) å¼€æºé¡¹ç›® +- 包å«åŽ¨æˆ¿å‡†å¤‡ã€é£Ÿæç›¸å…‹ã€æ²¹æ¸©åˆ¤æ–­ã€å­¦ä¹ çƒ¹é¥ªç­‰å®žç”¨æŠ€å·§ + +#### 文件结构 +- `assets/md/tips/` — 基础技巧MD文件 +- `assets/md/tips/advanced/` — 进阶技巧MD文件 +- `assets/md/tips/learn/` — 学习烹饪MD文件 +- `assets/json/cooking_tips.json` — 技巧元数æ®ç´¢å¼• + +#### ä¾èµ–æ›´æ–° +- 新增 `flutter_markdown_plus: ^1.0.7` ä¾èµ– + +#### Bugä¿®å¤ +- 🛠修å¤QRç ç»˜åˆ¶ä½¿ç”¨qr 3.0 API(isDark方法移至QrImage类) +- 🛠修å¤recipe_detail_page.dart中steps字段ä¸å­˜åœ¨é—®é¢˜ +- 🛠修å¤cooking_mode_page.dartå’Œdaily_menu_page.dart缺少material导入 +- 🛠修å¤ToastServiceè°ƒç”¨æ–¹å¼ + ## [0.92.1] - 2026-04-13 ### ðŸ“ ç›®å½•ç»“æž„é‡æž„ — 所有文件夹ä¸è¶…过8个文件 diff --git a/android/.kotlin/sessions/kotlin-compiler-18170880723584643219.salive b/android/.kotlin/sessions/kotlin-compiler-18170880723584643219.salive deleted file mode 100644 index e69de29..0000000 diff --git a/assets/json/cooking_tips.json b/assets/json/cooking_tips.json new file mode 100644 index 0000000..b570fc1 --- /dev/null +++ b/assets/json/cooking_tips.json @@ -0,0 +1,128 @@ +[ + { + "id": "kitchen_prep", + "name": "厨房准备", + "category": "基础", + "assetPath": "assets/md/tips/厨房准备.md", + "icon": "ðŸ " + }, + { + "id": "what_to_eat", + "name": "如何选择现在åƒä»€ä¹ˆ", + "category": "基础", + "assetPath": "assets/md/tips/如何选择现在åƒä»€ä¹ˆ.md", + "icon": "🤔" + }, + { + "id": "food_taboo", + "name": "食æç›¸å…‹ä¸Žç¦å¿Œ", + "category": "基础", + "assetPath": "assets/md/tips/食æç›¸å…‹ä¸Žç¦å¿Œ.md", + "icon": "âš ï¸" + }, + { + "id": "oil_temp", + "name": "油温判断技巧", + "category": "进阶技巧", + "assetPath": "assets/md/tips/advanced/油温判断技巧.md", + "icon": "🌡ï¸" + }, + { + "id": "sugar_color", + "name": "糖色的炒制", + "category": "进阶技巧", + "assetPath": "assets/md/tips/advanced/糖色的炒制.md", + "icon": "ðŸ¬" + }, + { + "id": "auxiliary", + "name": "辅料技巧", + "category": "进阶技巧", + "assetPath": "assets/md/tips/advanced/辅料技巧.md", + "icon": "🧂" + }, + { + "id": "pro_terms", + "name": "高级专业术语", + "category": "进阶技巧", + "assetPath": "assets/md/tips/advanced/高级专业术语.md", + "icon": "📚" + }, + { + "id": "remove_fishy", + "name": "去腥", + "category": "学习烹饪", + "assetPath": "assets/md/tips/learn/去腥.md", + "icon": "ðŸŸ" + }, + { + "id": "cold_dish", + "name": "学习凉拌", + "category": "学习烹饪", + "assetPath": "assets/md/tips/learn/学习凉拌.md", + "icon": "🥗" + }, + { + "id": "fry_pan", + "name": "学习炒与煎", + "category": "学习烹饪", + "assetPath": "assets/md/tips/learn/学习炒与煎.md", + "icon": "ðŸ³" + }, + { + "id": "blanch", + "name": "学习焯水", + "category": "学习烹饪", + "assetPath": "assets/md/tips/learn/学习焯水.md", + "icon": "💧" + }, + { + "id": "boil", + "name": "学习煮", + "category": "学习烹饪", + "assetPath": "assets/md/tips/learn/学习煮.md", + "icon": "ðŸ²" + }, + { + "id": "marinate", + "name": "学习腌", + "category": "学习烹饪", + "assetPath": "assets/md/tips/learn/学习腌.md", + "icon": "🥩" + }, + { + "id": "steam", + "name": "学习蒸", + "category": "学习烹饪", + "assetPath": "assets/md/tips/learn/学习蒸.md", + "icon": "♨ï¸" + }, + { + "id": "microwave", + "name": "微波炉", + "category": "学习烹饪", + "assetPath": "assets/md/tips/learn/微波炉.md", + "icon": "📻" + }, + { + "id": "air_fryer", + "name": "空气炸锅", + "category": "学习烹饪", + "assetPath": "assets/md/tips/learn/空气炸锅.md", + "icon": "🔥" + }, + { + "id": "food_safety", + "name": "食å“安全", + "category": "学习烹饪", + "assetPath": "assets/md/tips/learn/食å“安全.md", + "icon": "🛡ï¸" + }, + { + "id": "pressure_cooker", + "name": "高压力锅", + "category": "学习烹饪", + "assetPath": "assets/md/tips/learn/高压力锅.md", + "icon": "🔒" + } +] diff --git a/assets/md/tips/advanced/油温判断技巧.md b/assets/md/tips/advanced/油温判断技巧.md new file mode 100644 index 0000000..a15bbe9 --- /dev/null +++ b/assets/md/tips/advanced/油温判断技巧.md @@ -0,0 +1,35 @@ +# 油温判断技巧åŠå¸¸è§æ¸©åº¦å’Œå•使¢ç®—表 + +* 油温在 120°C-140°C 之间:适åˆè½¯ç‚¸[^1]ã€æ»‘ç‚’[^2], æŠŠç­·å­æ”¾å…¥æ²¹é”…中,周围基本ä¸èµ·æ³¡æ³¡ï¼Œæ— é’çƒŸã€æ— å“å£°ã€æ²¹æ¸©å¹³é™ã€‚ +* 油温在 150°C-160°C ä¹‹é—´ï¼šæœ€ä½³çƒ¹é¥ªæ¸©åº¦ï¼ŒæŠŠç­·å­æ”¾å…¥æ²¹é”…中,周围会冒出少许油泡,略有é’烟,油从四周往中间翻动。 +* 油温在 160°C-180°C 之间:适åˆä¸Šè‰²ç‚¸é…¥ï¼ŒæŠŠç­·å­æ”¾å…¥æ²¹é”…中,大é‡é’烟上å‡ï¼Œæ²¹é¢å而较平é™ã€‚ +* (注) 最好买把油温枪,谨慎使用温度计。 + +> 网络视频教程中所谓的 â€œå‡ æˆæ²¹æ¸©â€ æŒ‡çš„æ˜¯ç›¸å¯¹äºŽåæˆæ²¹æ¸©ï¼Œä¸º 300°C。 +> å³ næˆæ²¹æ¸© T_n = 30n [degree Celsius] +> 温度æ¢ç®—å…¬å¼ +> C = 5/9 *(F - 32) +> 或者 +> F = 9/5*C + 32 + +## 附油温对照表 + +æ ¹æ®ä¸Šè¿°æè¿°åˆ¶è¡¨ã€‚误差为éžä¸“业数æ®ã€‚ + +| æ‘„æ°åº¦ °C | 常è§åç§° | åŽæ°åº¦ °F | +| :----: | :----: | :----: | +| -18±4 | 急冻(冷冻) | -0.4±4 | +| 4±2 | ä¿é²œ | 40±4 | +| 30±10 | 常温 | 86±18 | +| 60±10 | äºŒæˆ | 140±18 | +| 90±10 | ä¸‰æˆ | 194±18 | +| 120±10 | å››æˆ | 248±18 | +| 150±10 | äº”æˆ | 302±18 | +| 180±10 | å…­æˆ | 356±18 | +| 210±10 | ä¸ƒæˆ | 410±18 | +| 240±10 | å…«æˆ | 464±18 | +| 270±10 | 乿ˆ | 518±18 | +| 300±10 | åæˆ | 572±18 | + +* ^1: 软炸是将å°å—ã€ç‰‡æˆ–æ¡å½¢çš„ææ–™æŒ‚糊,放入油锅,炸æˆä¸ƒå…«æˆç†Ÿçš„æ²¹ç‚¸æ–¹æ³• +* ^2: 滑炒,选用质嫩的动物性原料ç»è¿‡æ”¹åˆ€åˆ‡æˆä¸ã€ç‰‡ã€ä¸ã€æ¡ç­‰å½¢çŠ¶ï¼Œç”¨è›‹æ¸…ã€æ·€ç²‰ä¸Šæµ†ï¼Œç”¨æ¸©æ²¹æ»‘散,倒入æ¼å‹ºæ²¥åŽ»ä½™æ²¹ diff --git a/assets/md/tips/advanced/糖色的炒制.md b/assets/md/tips/advanced/糖色的炒制.md new file mode 100644 index 0000000..d255c8c --- /dev/null +++ b/assets/md/tips/advanced/糖色的炒制.md @@ -0,0 +1,48 @@ +# 糖色的炒制 + +原ç†ï¼š ç³–é‡é«˜æ¸©èžåŒ–,且在加热ä¸åŒæ—¶é—´åŽå‘ˆä¸åŒæ€§çж + +以炒制 200ml 糖色为例 + +## æ°´ç‚’ + +1. å–**锅底有弧度**或**平底é¢ç§¯å 1/3以下**的锅 +2. å‘锅中加入 8g 冰糖ã€ç™½ç ‚糖或绵白糖,以冰糖为佳,如果希望糖色甜味更明显å¯ä»¥å¢žåŠ  2g å·¦å³ +3. å‘锅中加入 50ml 70°C 热水 +4. 将锅置于ç¶å°ä¸Š +5. 如使用燃气ç¶ï¼Œåº”å°†ç«åŠ›è°ƒæ•´ç”±å¤§å‘å°è°ƒæ•´è‡³æœ€å°ï¼Œä½¿ç«ç„°èŒƒå›´ä¸è¶…过水é¢é¢ç§¯ï¼Œå¦‚使用电ç£ç‚‰å…·ï¼Œå¼€è‡³æœ€å°ï¼Œå¹¶æ—¶åˆ»å‡†å¤‡å…³é—­ç”µæº +6. ä½¿ç”¨é”…é“²ä¸æ–­æ…拌糖水混åˆç‰©ï¼Œå¦‚果为冰糖此时å¯ä»¥ä½¿ç”¨é”…é“²èƒŒé¢æˆ–边缘轻轻敲打至粉碎以加速èžåŒ–,在此过程中如果出现糖尚未èžåŒ–而已ç»å¼€å§‹å˜è‰²çš„现象,则需è¦ç»§ç»­åŠ å…¥çƒ­æ°´è‡³ 50ml +7. 待糖全部èžåŒ–åŽï¼Œç³–æ°´æ··åˆç‰©å‘ˆçŽ°ç²˜ç¨ çŠ¶æ€è€Œé¢œè‰²å‘ˆé€æ˜Žå白色,如云æ¯é¢œè‰²ï¼Œæ­¤æ—¶åº”ç»§ç»­æ…æ‹Œ +8. ç»§ç»­æ…æ‹Œç³–æ°´æ··åˆç‰©ï¼Œç³–æ°´æ··åˆç‰©é¢œè‰²é€æ¸å‘ˆçŽ°ç™½è‰² +9. ç»§ç»­æ…æ‹Œç³–æ°´æ··åˆç‰©ï¼Œç³–æ°´æ··åˆç‰©é¢œè‰²å¼€å§‹å‡ºçŽ°æµ…æµ…çš„æ£•è‰² +10. ç»§ç»­æ…æ‹Œç³–æ°´æ··åˆç‰©ï¼Œç³–æ°´æ··åˆç‰©é¢œè‰²æ£•色å分明显 +11. ç»§ç»­æ…æ‹Œç³–æ°´æ··åˆç‰©ï¼Œç³–æ°´æ··åˆç‰©é¢œè‰²å‘ˆæ£•色并出现绵密的气泡 +12. ç»§ç»­æ…æ‹Œç³–æ°´æ··åˆç‰©ï¼Œç³–æ°´æ··åˆç‰©é¢œè‰²å‘ˆæ£•è‰²å¹¶å‡ºçŽ°ç»µå¯†çš„æ°”æ³¡ï¼Œç»µå¯†çš„å°æ°”泡中出现了较大的气泡 +13. å‘锅中加入 150ml 70°C çƒ­æ°´ï¼Œæ­¤æ—¶é”…ä¸­çš„æ¶²ä½“å‘ˆçŽ°å‡ºæ·±çº¢æ£•è‰²ï¼Œé¢œè‰²é€æ˜Žæ¸…澈,有淡淡焦糖味 +14. 倒入碗中,获得糖色 200ml + +> [!Note] +> 第13步补充: +>ä¸å»ºè®®ä½¿ç”¨å†·æ°´ä»£æ›¿ 70°C 热水,因为低温会导致糖å‡å›ºç²˜é”… + +## 油炒(Recommanded) + +1. åŒæ°´ç‚’ +2. åŒæ°´ç‚’ +3. å‘锅中加入 10ml 常温食用油 +4. åŒæ°´ç‚’,糖水混åˆç‰©æ”¹ä¸ºç³–油混åˆç‰©ï¼Œæœ€åŽåŠ å…¥ 190ml 热水,其余略 + +## 附糖浆状æ€åŠå…¶ç”¨é€”说明 + +|状æ€|制备方法|用途(例)| +|:--:|:--:|:--:| +|未完全èžåŒ–糖浆|水(油)炒8|挂霜山楂| +|完全èžåŒ–糖浆|水(油)炒9|冰糖葫芦| +|焦糖|水(油)炒11åŽè‡ªç„¶å†·å´|焦糖| +|糖色|水(油)炒13|糖色| + +* 当使用糖色烹饪如红烧肉等å•ä½ä½“积较å°çš„食物时,å¯ä»¥åœ¨ç¬¬ 13 步之å‰ç›´æŽ¥åŠ å…¥é£Ÿæå¿«é€Ÿç¿»ç‚’至锅底无明显液体堆积,此时å†åŠ å…¥æ°´ï¼Œè¿™æ ·å¯ä»¥æ›´å¿«ä¸Šè‰² + +## 附加内容 + +如果您éµå¾ªæœ¬æŒ‡å—的制作æµç¨‹è€Œå‘现有问题或å¯ä»¥æ”¹è¿›çš„æµç¨‹ï¼Œè¯·æå‡º Issue 或 Pull request 。 diff --git a/assets/md/tips/advanced/辅料技巧.md b/assets/md/tips/advanced/辅料技巧.md new file mode 100644 index 0000000..4072290 --- /dev/null +++ b/assets/md/tips/advanced/辅料技巧.md @@ -0,0 +1,15 @@ +# 辅料技巧 + +* 辅料的放入顺åºåŸºæœ¬ä¸ºä¸‹ï¼šå…ˆæ”¾å§œã€åŽæ”¾è‘±å’Œè’œã€è¾£æ¤’ã€å†æ”¾å¹²æ–™ï¼ˆå…«è§’/花椒/麻椒)ã€å†æ”¾å¹²è¾£æ¤’。以上æ¯ä¸€æ­¥éª¤æ ¹æ®æ‰€åšèœçš„ä¸åŒï¼Œå¯ä»¥æŠŠä¸éœ€è¦çš„辅料从队列中移除。 +* å§œçš„å«æ°´é‡æ˜¯æœ€å¤§çš„,这æ„å‘³ç€æˆ‘ä»¬éœ€è¦æ›´å¤šçš„æ—¶é—´å°†å§œçš„æ±æ°´ç…¸å‡ºã€‚ +* 如果你使用的是葱段(葱段最好使用èœåˆ€æ‹ä¸¤ä¸‹ï¼‰ï¼Œé‚£ä¹ˆæˆ‘推èä½ å…ˆæ”¾å…¥è‘±æ®µï¼Œå†æ”¾å…¥è’œç¢Žï¼Œå¦‚果你使用的是葱花,那么å¯ä»¥å°†è¿™ä¸¤ç§è¾…æ–™ä¸€èµ·ä¸‹é”…ã€‚æ³¨ï¼šè‘±æ®µä¸­çš„æ±æ°´æ›´éš¾è¢«ç‚’出。 +* 如果你åšçš„æ˜¯ç‚’èœï¼Œé‚£ä¹ˆæˆ‘更推è你在没放姜之å‰å…ˆæ”¾å…¥å¹²æ–™ï¼Œè¿™å¯ä»¥è®©æ²¹å˜å¾—更有味é“,以至于炒出æ¥çš„èœæ›´é¦™ã€‚为什么炖èœã€ç„–èœä¸è¿™æ ·åšï¼Œæ˜¯å› ä¸ºä½ å¯èƒ½éœ€è¦ç¿»ç‚’很多辅料,以至于辅料翻炒时间过长导致干料å˜é»‘ã€å˜è‹¦ã€‚ +* å°†å¹²è¾£æ¤’æ”¾åœ¨æœ€åŽæ˜¯å› ä¸ºå¹²è¾£æ¤’很容易因为锅的温度而å˜é»‘,干辣椒ç¨å¾®ç¿»ç‚’几秒钟å³å¯ã€‚ +* 注:ä¸è®ºä½ å–œæ¬¢åšä»€ä¹ˆèœç³»ï¼Œå°ç«å°†è¿™äº›è¾…料炒至金黄,都å¯ä»¥å°†æ•´é“èœå˜å¾—更有香味,这是调料所ä¸èƒ½ç»™äºˆçš„。 + +## æ”¾ç›æ—¶æœºä¸Žç›é‡æŽ§åˆ¶ + +* å¿«ç‚’æ–™ç†å…ˆåŠ ç›ï¼Œç›é‡=é£Ÿææ€»é‡é‡ x0.9%ï¼› +* 肉食料ç†å…«æˆç†Ÿæ—¶åŠ ç›ï¼Œç›é‡=ï¼ˆé£Ÿææ€»é‡é‡+30mL æ±æ°´ï¼‰x(1~1.2%); +* æ±¤æ–™ç†æœ€åŽæ—¶å†åŠ ç›ï¼Œç›é‡=æœ€å¼€å§‹çš„æ°´é‡ x0.8%。 +* 一天的总ç›é‡ä¸å»ºè®®è¶…过 5g,å‚考[WHO](https://www.who.int/zh/news-room/fact-sheets/detail/salt-reduction) diff --git a/assets/md/tips/advanced/高级专业术语.md b/assets/md/tips/advanced/高级专业术语.md new file mode 100644 index 0000000..d859c15 --- /dev/null +++ b/assets/md/tips/advanced/高级专业术语.md @@ -0,0 +1,171 @@ +# åšèœä¸“业术语 + +åšèœå’Œå­¦ä¹ ç¼–ç¨‹ä¸€æ ·ï¼Œé¦–å…ˆå¾—è®¤è¯†ä¸“ä¸šæœ¯è¯­å°±å¥½æ¯”å­¦ä¹ åŸºç¡€è¯­æ³•ï¼Œé€šè¿‡æœ¯è¯­ä¹‹é—´çš„ç»„åˆæ–¹èƒ½å®Œæˆä¸€é“èœã€‚ + +ã€ç‚’(chÇŽo)】å¤å†™ä½œâ€œç…¼(chÇŽo)â€ï¼Œæ˜¯ç›®å‰æœ€åŸºæœ¬çš„烹调方法之一;å³å°†é£Ÿç‰©åˆ‡æˆå°ä»¶ï¼Œæ”¾å…¥çƒ§çŒ›æ²¹çš„é“镬(huò)(锅)中迅速翻æ…致熟åŽåŠ è°ƒæ–™è°ƒå‘³çš„æ‰‹æ³•ã€‚ + +ã€ç‚(qiàng)】食物切好åŽï¼Œç»æ²¸æ°´æˆ–热油的“ç¼â€æˆ–“泡â€ç­‰å¤„ç†åŽï¼Œå†åœ¨çƒ§é•¬ï¼ˆé”…)中爆入干辣椒和花椒油拌匀的烹调方法。 + +ã€ç‚Š(chuÄ«)】å³åˆ©ç”¨è’¸ã€ç…®ç­‰å°†é£Ÿç‰©è‡´ç†Ÿçš„æ–¹æ³•。多è§å† å在潮州èœä¸­ã€‚ + +ã€ç…®(zhÇ”)】最简å•的烹调方法之一;在镬(锅)中用适é‡çš„æ²¸æ°´æˆ–汤水以åŠè°ƒå‘³æ–™å°†é£Ÿç‰©è‡´ç†Ÿçš„烹调方法。 + +ã€ç…Ž(jiÄn)】烧热é“镬(锅),放入少许生油,然åŽå°†é£Ÿç‰©å¹³æ»©ç´§è´´åœ¨é•¬ä¸­ï¼Œåˆ©ç”¨æ…¢ç«çƒ­æ²¹ä½¿é£Ÿç‰©çš„表é¢å‘ˆé‡‘黄色åŠè‡´ç†Ÿçš„烹调方法。 + +ã€çˆ†(bào)】利用热镬(锅)热油,攒入适é‡è°ƒå¥½çš„æ±é…±æˆ–æ±¤æ°´ï¼Œä½¿é•¬ä¸­çš„å°ä»¶é£Ÿç‰©å¿«é€Ÿè‡´ç†Ÿåˆèµ‹å…¥é¦™æ°”的烹调方法。 + +ã€ç‚¸(zhá)】å¤å†™ä½œâ€œç… (zhá)â€ï¼Œæœ€å¸¸ç”¨çš„烹调方法之一;指将食物放入大é‡çš„热油中致熟至脆的烹调手法。 + +ã€çƒš(xiá)】å¤å†™ä½œâ€œç… â€ï¼Œåˆ©ç”¨å¤§é‡çš„æ²¸æ°´å°†è‚‰è´¨è¾ƒéŸ§çš„食物在炉ç«ä¸Šç‚Šè½¯ç‚Šç†Ÿçš„加工方法。 + +ã€æ»š(gÇ”n)】利用大é‡çš„æ²¸æ°´çš„æ¶ŒåŠ¨å°†é£Ÿç‰©çª³(yÇ”)味带出的加工方法。 + +ã€æ±†(cuÄn)】北方烹调术语,å¤ä¸ºâ€œå·â€ï¼›è¿‘乎粤èœçš„“渌(lù)â€ï¼Œå³å°†åŠ å·¥æˆä¸¸çŠ¶æˆ–ç‰‡çŠ¶çš„é£Ÿç‰©åœ¨æ²¸æ°´ä¸­è‡´ç†ŸåŽï¼Œæžèµ·å…¥ç¢—ä¸­ï¼Œå†æ·»å…¥æ²¸æ±¤çš„烹调方法。 + +ã€ç¼(zhuó)】北方写作“焯(chÄo)â€ï¼›æ˜¯æŒ‡é£Ÿç‰©åˆ‡æˆè–„片等,利用沸水迅速至熟å†è˜¸(zhàn)上酱料而åƒçš„烹调方法。 + +ã€ç‚Ÿ(dá)ã€‘å°†è”¬èœæ”¾å…¥æ·»æœ‰æž§(jiÇŽn)水或生油的沸中用慢ç«ç…®é€ï¼Œä½¿æˆå“è½¯å¹¶ä¿æŒç¿ ç»¿çš„加工方法。 + +ã€æ¶®(shuàn)】北方烹调术语;将切æˆè–„片的食物放入辣汤中致熟å†è˜¸ä¸Šé…±æ–™è€Œåƒçš„烹调方法。 + +ã€ç…€(kuò)ã€‘å¤æ—¶å†™ä½œâ€œçˆ©(yù)â€ï¼›æŒ‡å°†é£Ÿç‰©ç›´æŽ¥æ”¾å…¥é•¬ï¼ˆé”…)中或瓦罉(煲)中,加入大é‡å§œè‘±ç­‰é¦™æ–™æ–™å¤´ï¼Œç›–上盖,利用大é‡çš„香料料头至香åŠè¾¾åˆ°æˆç†Ÿçš„烹调方法。 + +ã€ç„—(jú)】利用ç¼çƒ­çš„ç²—ç›ç­‰å°†ç”¨é”¡çº¸æˆ–玉扣纸等包å°å¥½çš„食物在密å°çš„æ¡ä»¶ä¸‹è‡´ç†Ÿçš„çƒ¹è°ƒæ–¹æ³•ã€‚ + +ã€ç„–(mèn)】北方烹调法;指质韧的食物放入镬(锅)中,加入适é‡çš„æ±¤æ°´ï¼Œç›–上盖并利用文ç«ç‚Šè½¯åŠè‡´ç†Ÿçš„烹调方法。 + +ã€ç‚†(wén)】近乎北方烹调法的“烧â€ï¼Œæ•…有“å—炆北烧â€ä¹‹è¯´ï¼›æŒ‡è´¨éŸ§çš„食物放入镬(锅)中,加入适é‡çš„æ±¤æ°´ï¼Œåˆ©ç”¨æ–‡ç«ç‚Šè½¯åŠè‡´ç†Ÿçš„烹调方法。 + +ã€çƒ©(huì)】用适é‡çš„æ±¤æ°´å°†å¤šç§è‚‰æ–™å’Œè”¬èœä¸€åŒç‚Šç…®çš„烹调方法。 + +ã€è’¸(zhÄ“ng)】利用水蒸汽的热力使食物致熟的烹调方法。 + +ã€ç‚–(dùn)】食物加入清水或汤水,放入有盖的容器中,盖盖,å†åˆ©ç”¨æ°´è’¸æ±½çš„热力致熟并得出汤水的烹调方法。北方èœç³»æ˜¯æŒ‡ç”¨å¤§é‡æ±¤æ°´åŠæ–‡ç«å°†é£Ÿç‰©ç‚Šè½¯ç‚Šç†Ÿçš„烹调方法。 + +ã€æ‰£(kòu)】食物ç»è°ƒå‘³åŠé¢„加工åŽï¼Œæ•´é½æŽ’放入扣碗之中隔水蒸熟,然åŽä¸»æ–™è¦†æ‰£å…¥ç¢Ÿä¸­å†æ³¼ä¸Šç”¨åŽŸæ±å‹¾å¥½çš„ç‰ç’ƒçš„烹调方法。 + +ã€ç…²(bÄo)】将食物放入大é‡çš„æ¸…水,置在炉ç«ä¸Šæ…¢ç«ç‚Šç†Ÿå¹¶å¾—出汤水的烹调方法。 + +ã€ç†¬(áo)】利用慢ç«é•¿æ—¶é—´åœ°å°†è‚‰æ–™é²œå‘³èžå…¥æ±¤æ°´ä¸­å¹¶ä½¿æ±¤æ°´æµ“缩的加工方法。 + +ã€é (kào)】利用浓味的原料和鲜汤,利用文ç«å’Œé€šè¿‡è¾ƒé•¿çš„æ—¶é—´å°†é²œå‘³èµ‹å…¥å¦ä¸€ç§ä¹å‘³ä¸»æ–™ä¸­çš„加工或烹调方法。 + +ã€ç…¨(wÄ“i)】å¤ä½œåŸ‹å…¥ç‚­ç°è‡´ç†Ÿæ–¹æ³•。今指利用姜葱和汤水使食物入味åŠè¾ŸåŽ»é£Ÿç‰©æœ¬èº«çš„å¼‚å‘³çš„åŠ å·¥æ–¹æ³•ã€‚åŒ—æ–¹èœç³»åˆæŒ‡é£Ÿç‰©è¿žåŒæ±¤æ°´æ”¾å…¥å¯†å°çš„瓦å›ä¸­ï¼Œåœ¨æ–‡ç«ä¸­è‡´ç†Ÿçš„烹调方法。 + +ã€ç„(wù)】替代“煨â€çš„夿„,指食物ç»è…Œåˆ¶åŽï¼Œç”¨è·å¶ç­‰åŒ…裹,å†ç”¨æ¹¿æ³¥æˆ–é¢å›¾è£¹å°ï¼Œç½®å…¥ç‚­ç«ä¸­è‡´ç†Ÿçš„烹调方法。 + +ã€çƒ˜(hÅng)】点心或食物调好味或加工好åŽç½®å…¥çƒ˜ç‚‰ä¸­è‡´ç†Ÿçš„烹调方法。 + +ã€ç…¸(biÄn)】åŒç…(bì)ï¼Œæ—§è®¹å†™ä½œâ€œéž­â€æˆ–“ç«ä¾¿â€ï¼Œè¿‘乎“熯(rÇŽn)â€ï¼Œæ˜¯æŒ‡å°†é£Ÿç‰©æ”¾å…¥çƒ­é•¬ï¼ˆé”…ï¼‰ä¸­ï¼Œä¸æ–­åœ°ç¿»ç‚’ï¼Œä½¿é£Ÿç‰©ä¸­æ°´åˆ†ç•¥ç†¯å¹²è€Œè¾Ÿé™¤çª³å‘³çš„åŠ å·¥æ–¹æ³•ï¼›æˆ–ç»æ­¤è€Œæ”¶æµ“鲜味而åƒçš„烹调方法。 + +ã€æºœ(liÅ«)】北方烹调术语,近乎粤èœçš„“打芡(qiàn)â€ï¼Œå³é…¸ç”œçš„æ±æ°´ç”¨ç”Ÿç²‰å‹¾èŠ¡ä»¤é…¥ç‚¸è¿‡çš„é£Ÿç‰©æ»‘å«©å¯å£çš„烹调方法。 + +ã€ç¾¹(gÄ“ng)】å¤è€çš„烹调法之一,是指切制æˆä¸çš„食物用沸汤煮åŽï¼Œé™¤é™¤åŠ å…¥æ¹¿ç”Ÿç²‰ï¼Œä½¿æ±¤æ°´æºœæˆç³ŠçŠ¶çš„çƒ¹è°ƒæ–¹æ³•ã€‚ + +ã€æ”’(zÇŽn)ã€‘æ›¾å†™ä½œâ€œæº…â€æˆ–“ç’(zàn)â€ç­‰ï¼Œåˆ†â€œæ”’æ²¹â€æˆ–“攒酒â€ï¼›å‰è€…是指将烧沸的热油泼洒在蒸熟的食物上以辟腥增滑的手法;åŽè€…是指将ç»é…’泼洒入正在烹煮的食物上,令食物更有“镬(huò)æ°”â€çš„æ‰‹æ³•。 + +ã€çƒ«(tàng)ã€‘æŒ‡ç”¨æ²¸æ°´æ”¶ç´§è‚‰æ–™è¡¨çš®çš„åŠ å·¥æ–¹æ³•ã€‚åŒ—æ–¹å¸¸è§æ˜¯æŒ‡å°†åˆ‡ç‰‡ã€åˆ‡ä»¶çš„原料在沸汤或辣汤中致熟的烹调方法。 + +ã€çƒ§(shÄo)ã€‘å¤æ—¶çš„“炙(zhì)â€ï¼Œç²¤èœæ˜¯æŒ‡å°†é£Ÿç‰©æ”¾åœ¨ç‚­ç«æˆ–明ç«ä¸Šè‡´ç†Ÿçš„烹调方法。现北方èœç³»æ˜¯æŒ‡é€šè¿‡æ…¢ç«å°†æ±æ°´ç•¥æ”¶å¹²å¹¶å°†é£Ÿç‰©ç‚Šç†Ÿçš„烹调方法。 + +ã€çƒ¤(kÇŽo)】北方èœç³»ç”¨æ¥æ›¿ä»£â€œçƒ§â€çš„æ—§æ„,故有“å—烧北烤â€ä¹‹è¯´ã€‚是指食物置在明ç«ä¸Šè‡´ç†Ÿçš„烹调方法。 + +ã€å¤(lÇ”)ã€‘åˆ©ç”¨ç”ŸæŠ½ä¸Žé¦™æ–™è¯æè°ƒå¥½çš„â€œå¤æ°´æ±â€ä½¿é£Ÿç‰©è‡´ç†Ÿæˆ–令其入味的烹调方法。 + +ã€é…±(jiàng)】利用大é‡çš„æ±é…±æˆ–ç”ŸæŠ½å…¥å‘³æˆ–è‡´ç†Ÿçš„çƒ¹è°ƒæ–¹æ³•ã€‚ + +ã€æµ¸(jìn)】利用大é‡çš„æ²¸æ°´æˆ–汤水以“èŠèŠ±å¿ƒâ€ä¸ºåº¦çš„热力在一定时间内将食物致熟的烹调方法。类似北方的“氽(tÇ”n)â€ï¼Œå³ç‰©æ–™ç¼ç†ŸåŽï¼Œå†èˆ€(yÇŽo)入过é¢çš„æ±¤æ°´è€Œé£Ÿçš„烹调方法。 + +ã€é£Ž(fÄ“ng)ã€‘å¸¸å¹´å°†è…Œåˆ¶å¥½çš„é£Ÿç‰©åŠæŒ‚在通风的地方,让其自然阴干或风干的加工方法。 + +ã€è…Š(là)】在农历å二月å‰åŽå°†è…Œå¥½çš„é£Ÿç‰©åŠæŒ‚在通风的地方,让其自然阴干或风干的加工方法。 + +ã€çƒŸ(yÄn)ã€‘èŒ¶å‘³æˆ–é¦™æ–™è¯æåœ¨å¯†å°æƒ…况下点燃,让食物赋入其香喷烟味的烹调方法。 + +ã€ç†(xÅ«n)】旧写作“ç†â€ï¼Œæœ‰â€œå¹²ç†â€ä¸Žâ€œæ¹¿ç†â€ä¹‹åˆ†ï¼Œâ€œå¹²ç†â€ç±»ä¼¼â€œçƒŸâ€ï¼›â€œæ¹¿ç†â€æ˜¯é£Ÿç‰©ç”¨é²œèŠ±æˆ–ç»é…’等赋入香味的烹调方法。 + +ã€ç³Ÿ(zÄo)】将食物放入酒糟之中入味或致熟的烹调方法。 + +ã€é†‰(zuì)】利用大é‡çš„烧酒入味或致熟的烹调方法。 + +ã€ç”‘(zèng)ã€‘å¤æ—¶çš„“蒸â€ï¼›å°†é£Ÿç‰©æ–©ä»¶è°ƒå‘³åŽæ”¾å…¥ç“¦é’µ(bÅ)之中,å†åˆ©ç”¨è¾ƒå¼ºçš„蒸气致熟的烹调方法。 + +ã€å†»(dòng)】åˆç§°â€œæ°´æ™¶â€ï¼Œæ˜¯æŒ‡å°†ç…®çƒ‚的食物加入ç¼è„‚或猪皮等å†ç…®æˆç¾¹ï¼Œç„¶åŽç½®å…¥å†°ç®±å¾…其冰冻å‡ç»“而åƒçš„烹调方法。 + +ã€é£žæ°´ã€‘将食物投入沸水中过一过水致åŠç†Ÿè€Œè¿…速æžèµ·ï¼Œä¸ºç»§åŽçš„烹调æä¾›è‰¯å¥½å‰æ²¿åŸºç¡€çš„加工方法。 + +ã€å†°æµ¸ã€‘食物切æˆä¸åŽï¼Œè¿…速投入冰水之中,令食物有爽脆效果的一ç§åŠ å·¥çƒ¹è°ƒæ–¹æ³•ã€‚æ­¤æ³•æºäºŽæ—¥æœ¬ã€‚ + +ã€æ‹¨ä¸ã€‘食物上浆油炸åŽï¼Œæ”¾å…¥ç…®æº¶çš„糖浆中拌匀,使食物夹起时能拉出细ä¸çš„烹调方法。 + +ã€æŒ‚éœœã€‘é£Ÿç‰©ç»æ²¹ç‚¸åŽï¼Œæ”¾å…¥ç…®æº¶çš„糖浆中拌匀打散或直接洒入糖粉的烹调方法。 + +ã€æ¤’ç›ã€‘é£Ÿç‰©ç»æ²¹ç‚¸åŽè‡´ç†Ÿå’Œå¹²èº«åŽï¼Œå†ç”¨äº‹å…ˆç”¨æ¤’米和精ç›é…好的“椒ç›â€ç¿»ç‚’拌匀的烹调方法。 + +ã€æ²¹æ³¡ã€‘利用大é‡çš„热油,迅速地将食物致熟的烹调方法。 + +ã€èµ°æ²¹ã€‘åˆç§°â€œæ‹–æ²¹â€â€œèµ°æ²¹â€â€œè·‘æ²¹â€ï¼›æ˜¯æŒ‡å°†åŠ å·¥å¥½çš„åŽŸæ–™æ”¾å…¥æ»šæ²¹ä¹‹ä¸­è¿…é€Ÿæ‹–è¿‡ï¼Œä¸ºç»§åŽçš„烹调æä¾›å‰æ²¿åŸºç¡€çš„加工方法。 + +ã€ç«ç„°ã€‘将生猛新鲜的海鲜放入玻璃器皿内,利用点燃高度数的白酒产生的热力致熟的烹调方法。 + +ã€å•«(zé)å•«(zé)】食物åŠå§œè‘±ç­‰æ”¾å…¥çƒ§è‡³æžçƒ­çš„瓦罉(煲),使食物å‘出“啫啫â€å£°éŸ³å’Œå–·å‡ºé¦™æ°”的烹调方法。 + +ã€ä¸²çƒ§ã€‘肉料切片腌制好åŽï¼Œç”¨ç«¹ç­¾ä¸²èµ·ï¼Œæ”¾å…¥çƒ­æ²¹ä¸­â€œæ³¡â€è€Œé£Ÿçš„烹调方法。或肉料切片åŽï¼Œç”¨é“钎串起,放入炭ç«ä¸Šçƒ§ç†Ÿï¼Œå†æ’’上孜(zÄ«)然等味料的烹调方法。 + +ã€é“æ¿ã€‘原是西å¼çƒ¹è°ƒæ–¹æ³•ï¼›å³æŒ‡é£Ÿç‰©â€œèµ°æ²¹â€åŽï¼Œè¿žåŒä»¥æ´‹è‘±ä¸ºä¸»çš„香料料头和æ±é…±ï¼Œæ”¾å…¥çƒ§è‡´æžçƒ­çš„铿¿ä¸­è‡´ç†Ÿå’Œè‡´ä»¤é£Ÿç‰©å–·é¦™çš„烹调方法。 + +ã€æ¡‘拿】åˆç§°â€œçŸ³çƒ¹â€ç­‰ï¼›é£Ÿç‰©ç»æ‹–æ²¹åŽï¼ŒæŠ•入烧至ç¼çƒ­çš„石å­ï¼ˆå¤šæ˜¯é›¨èŠ±çŸ³ï¼‰ä¸Šï¼Œå†æ”’入调好的æ±é…±æˆ–汤水,利用蒸气将食物致熟或喷出香气的烹调方法。 + +ã€ç…Žå°ã€‘北方åˆç§°â€œç…Žçƒ¹â€ï¼Œä¸€èˆ¬é€‚åˆäºŽé±¼ç±»è¾ƒå¤šï¼›å³å°†é±¼ç±»ç”¨è°ƒå‘³å“腌过åŽï¼Œç”¨çƒ­æ²¹æ…¢ç«ç…Žé€ï¼Œå†å°ä¸Šæ–™å¤´èŠ¡(qiàn)使é€å‘³çš„烹调方法。 + +ã€çªè´´ã€‘属“åŠç…Žç‚¸æ³•â€ï¼Œå³å°†è…Œè¿‡çš„肉料上好“çªè´´æµ†â€è´´åœ¨è‚¥è‚‰ä¸Šï¼Œåˆ©ç”¨â€œçŒ›é•¬é˜´æ²¹â€ä»¤è‚‰æ–™ä¸€é¢é…¥è„†è€Œä¸€é¢è½¯æ»‘的烹调方法 。 + +ã€çªå¡Œ(tÄ)】将腌好的食物上好“蛋粉浆â€ï¼Œåˆ©ç”¨å…ˆç…ŽåŽç‚¸çš„æ‰‹æ³•,使食物煎熟,然åŽå†åŠ å…¥è°ƒå¥½å‘³çš„é²œæ±¤å†ç…®é€çš„烹调方法。 + +ã€è½¯ç…Žã€‘属“åŠç…Žç‚¸æ³•â€ï¼Œå³å°†è…Œè¿‡çš„肉料拌上“蛋粉浆â€ï¼Œåˆ©ç”¨å…ˆç…ŽåŽç‚¸çš„æ‰‹æ³•使肉料致熟,然åŽåˆ‡ä»¶æ·‹ä¸Šé…±æ±çš„烹调方法。 + +ã€è›‹ç…Žã€‘è‚‰æ–™å…ˆç”¨â€œé£žæ°´â€æˆ–“油泡â€çš„æ–¹æ³•é¢„ç†Ÿï¼Œå†æ”¾å…¥è°ƒå¥½å‘³çš„鸡蛋浆内拌匀,然åŽç”¨æ–‡ç«å°†è‚‰æ–™è›‹æµ†åº•é¢ç…Žè‡³é‡‘黄色的烹调方法。 + +ã€å‰åˆ—】为英文 CUTLET 的译音;å³å°†é£Ÿç‰©ä¸Šè›‹æµ†åŽï¼Œç²˜ä¸Šé¢åŒ…糠,å†ç”¨çƒ­æ²¹æµ¸ç‚¸çš„çƒ¹è°ƒæ–¹æ³•ã€‚æ­¤åšæ³•æºäºŽè¥¿åŽ¨ã€‚ + +ã€é…¥ç‚¸ã€‘食物用调味å“腌过åŽï¼Œå…ˆä¸Šæ¹¿ç²‰æµ†ï¼Œå†æ‹ä¸Šå¹²ç”Ÿç²‰ï¼Œå†ç”¨çƒ­æ²¹ç‚¸ç†Ÿï¼Œç„¶åŽæžå…¥é…±æ±çš„烹调方法。 + +ã€ç«é”…】åˆç§°â€œæ¶®é”…â€ï¼Œå¹¿ä¸œç§°â€œæ‰“边炉â€ï¼Œå³å°†æ–°é²œè‚‰æ–™â€œç‰‡â€ã€â€œåˆ‡â€æˆè–„片,或肉料挞æˆä¸¸ã€çƒã€é¦…等,连åŒè”¬èœç­‰é€åˆ°å®¢äººè¾¹ï¼Œè®©å®¢äººè‡ªè¡Œæ”¾å…¥æ»šæ°´æˆ–滚汤中烹熟的食法。 + +ã€æ±½é”…】将肉料腌制åŽï¼Œè¿žåŒè¯æï¼Œæ”¾å…¥ç…®æ»šè°ƒå‘³æ±¤æ°´çš„一ç§ç‰¹åˆ¶çš„“气锅â€ä¸­ï¼Œç»†ç†¬è€Œé£Ÿçš„烹调方法。 + +ã€å‡‰æ‹Œã€‘将熟食食物或蔬果改切好åŽï¼ŒåŠ å…¥è°ƒå‘³æ–™å’Œæ‹Œå‡åŒ€çš„烹调方法。 + +ã€é±¼ç”Ÿã€‘将新鲜生猛水产去血åŽï¼Œæ”¹åˆ‡è–„片,拌上姜ä¸ã€è‘±ä¸ã€è–„è„†ã€æŸ æª¬ä¸ç­‰ï¼Œå†è˜¸ä¸Šç”ŸæŠ½è€Œåƒçš„烹调方法。 + +ã€åˆºèº«ã€‘原是日本料ç†çš„åšæ³•,原指生食肉片,ç»ä¸­å›½èœå¼•用指将鲜活的水产或海产去鳞净血,薄切æˆç‰‡ï¼Œæ»´å…¥æŸ æª¬æ±ï¼Œè˜¸ä¸Šæ—¥æœ¬èŠ¥(jiè)辣而åƒçš„烹调方法。 + +ã€ç«¹ç­’】å¤ç§°â€œç†·(zÄ“ng)â€ï¼ŒæŒ‡ç”¨ç«¹ç­’为器皿,å†ç»â€œçƒ¤â€â€œçƒ§â€â€œè’¸â€â€œç‚–(dùn)â€ç­‰æ–¹æ³•将食物致熟的烹调方法。 + +ã€èœœæ±ã€‘指将白糖ã€èœ‚蜜ã€éº¦èŠ½ç³–ç­‰åŒ–æˆæµ“æ±ï¼Œæ”¾å…¥åŠ å·¥å¥½çš„åŽŸæ–™ï¼Œç»â€œç†¬â€ã€â€œè’¸â€ç­‰æ–¹æ³•使质地软糯(nuò)ã€ç”œå‘³æ¸—é€ã€æ¶¦é€ç³–æ±çš„烹调方法。 + +ã€ç„¯(chÄo)水】åˆç§°â€œå‡ºæ°´â€ï¼Œæ˜¯å°†åŽŸæ–™ç½®äºŽå¼€æ°´æˆ–å†·æ°´é”…ä¸­è¿›è¡Œåˆæ­¥ç†Ÿå¤„ç†çš„ä¸€ç§æ–¹æ³•。 + +ã€è¿‡æ²¹ã€‘ç”¨æ²¹ä¸ºä¼ çƒ­ä»‹è´¨å¯¹çƒ¹é¥ªåŽŸæ–™è¿›è¡Œåˆæ­¥ç†Ÿå¤„ç†çš„æ–¹æ³•。å°åž‹åŽŸæ–™ä»Žæ¸©æ²¹èµ°è¿‡åˆç§°â€œæ»‘æ²¹â€ï¼›å¤§åž‹åŽŸæ–™ä»Žæ—ºæ²¹ä¸­èµ°è¿‡åˆç§°â€œèµ°æ²¹â€ã€‚ + +ã€æŒ‚糊】烹饪å‰å°†åŽŸæ–™å‡åŒ€è£¹ä¸Šä¸€å±‚糊液的工艺。 + +ã€ä¸Šæµ†ã€‘用淀粉ã€é¸¡è›‹ã€ç›ç­‰ä¸ŽåŽŸæ–™ä¸€èµ·è°ƒæ‹Œï¼Œä½¿åŽŸæ–™å¤–å±‚è£¹ä¸Šä¸€å±‚è–„è–„æµ†æ¶²çš„å·¥è‰ºã€‚ + +ã€ä¸ŠåŠ²ã€‘å°†åŠ å·¥æˆèŒ¸(róng)æ³¥(ní)末(mò)的动物性原料加精ç›ã€æ°´ã€æ·€ç²‰åŠå…¶ä»–è¾…æ–™åŽå夿…拌,使之达到色泽å‘亮ã€è‚‰è´¨ç»†å«©ã€å…¥æ°´ä¸æ²‰ã€ä¸æ•£çжæ€çš„一ç§åŠ å·¥æ–¹æ³•ã€‚ + +ã€å‹¾(gÅu)芡(qiàn)】在烹饪过程中å‘锅中加入淀粉水溶液,使èœè‚´æ±¤æ±å…·æœ‰ä¸€å®šæµ“稠度的工艺。åˆç§°â€œç€è…»â€ã€â€œç€èŠ¡â€ã€â€œæ‹¢(lÇ’ng)芡(qiàn)â€ã€‚ + +ã€æ¸©æ²¹ã€‘俗称三至四æˆï¼Œæ¸©åº¦ä¸€èˆ¬åœ¨ 70℃~100℃。 + +ã€çƒ­æ²¹ã€‘俗称五至六æˆï¼Œæ¸©åº¦ä¸€èˆ¬åœ¨ 110℃~170℃。 + +ã€æ—ºæ²¹ã€‘俗称七至八æˆï¼Œæ¸©åº¦ä¸€èˆ¬åœ¨ 180℃~220℃。 + +ã€æ»‘锅】将锅烧热,淋少许油把锅滑é,å†å€’å‡ºæ²¹çš„ä¸€ç§æ–¹æ³•。 + +ã€ç‚锅】åˆç§°â€œç‚¸é”…â€ï¼Œæ˜¯æŒ‡å°†å§œã€è‘±ã€è¾£æ¤’末或其他带有香味的调料放入烧热的底油,锅中煸炒出香味,å†åŠæ—¶ä¸‹èœæ–™çš„ä¸€ç§æ–¹æ³•。 + +ã€é«˜æ±¤ã€‘åˆç§°â€œæ¸…汤â€ã€â€œä¸Šæ±¤â€ã€â€œé¡¶æ±¤â€ï¼Œæ˜¯æŒ‡ç”¨çŒªéª¨ã€é¸¡éª¨ã€é¸­æž¶ã€ç¢Žè‚‰å¤´ç­‰åŽŸæ–™ç†¬åˆ¶å¥½çš„æ¯›æ±¤ï¼ŒåŠ å…¥å’Œæˆç¨€ç³ŠçŠ¶çš„ã€æœªåŠ ç›çš„鸡茸或肉茸处ç†ï¼Œä½¿ä¹‹æ¸…澈如水ã€å‘³æµ“è€Œé²œçš„ä¸€ç§æ±¤æ–™ã€‚ + +ã€å¥¶æ±¤ã€‘åˆç§°â€œç™½æ±¤â€ï¼ŒåŽŸæ–™åŠ æ¸…æ°´ç…¨åˆ¶è€Œæˆçš„色泽乳白的汤。 diff --git a/assets/md/tips/learn/去腥.md b/assets/md/tips/learn/去腥.md new file mode 100644 index 0000000..a830de4 --- /dev/null +++ b/assets/md/tips/learn/去腥.md @@ -0,0 +1,43 @@ +# 去腥 + +去腥是åšèœè¿‡ç¨‹ä¸­çš„一é“å·¥åºã€‚ + +去腥指通过包括但ä¸é™äºŽæ·»åŠ è°ƒæ–™ã€ç„¯æ°´ç­‰æ‰‹æ®µåŽ»é™¤è‚‰ç±»ã€æ°´äº§ç­‰é£Ÿç‰©ä¸­è…¥è†»å‘³ã€‚ + +**腥膻味是æŸäº›é£Ÿç‰©çš„é£Žå‘³æ¥æºï¼Œè¿‡åº¦åŽ»è…¥å¯èƒ½å¯¼è‡´é£Ÿç‰©ä¸§å¤±é£Žå‘³ã€‚** + +去腥的手段多ç§å¤šæ ·ï¼Œåœ¨çƒ¹é¥ªå·¥ç¨‹ä¸­è¦çµæ´»é€‰æ‹©ã€‚ + +## 手段 + +### 添加调料 + +在食æä¸­æ·»åŠ è°ƒæ–™æ˜¯æœ€ç®€å•的去腥手段。比如对于大部分使用鸡蛋液的èœè‚´ï¼ˆ[鸡蛋羹](../../dishes/vegetable_dish/鸡蛋羹/鸡蛋羹.md),[西红柿炒鸡蛋](../../dishes/vegetable_dish/西红柿炒鸡蛋.md)),å¯ä»¥åœ¨åˆ¶ä½œè›‹æ¶²çš„过程中加入ç›ã€æ–™é…’ã€é£Ÿé†‹ç­‰è°ƒæ–™æ¥åŽ»è…¥ã€‚ + +烹饪æŸäº›è‚‰ç±»æ—¶ï¼Œå¯ä»¥åœ¨æ±¤åº•中加入花椒ã€å…«è§’ã€é¦™å¶ã€æ¡‚çš®ã€å°èŒ´é¦™ã€è¾£æ¤’等香料æ¥åŽ»è…¥ã€‚ + +æˆå“麻辣ç«é”…底料具有æžå…¶æµ“éƒçš„香味,å¯ä»¥åœ¨çƒ¹é¥ªæ—¶é€‚釿·»åŠ ï¼Œè¶³ä»¥è¦†ç›–ç»å¤§å¤šæ•°è‚‰ç±»çš„腥味。 + +### 蘸料 + +æŸäº›é£Ÿç‰©åœ¨çƒ¹é¥ªä¹‹åŽä»ç„¶è…¥å‘³ä¸¥é‡ã€‚å¯ä»¥è°ƒé…蘸料æ¥åœ¨é£Ÿç”¨æ—¶æŽ©ç›–腥味。 + +常è§çš„蘸料原料有:食醋ã€é…±æ²¹ã€é¦™æ²¹ã€è±†ç“£é…±ã€ç”œé¢é…±ã€èŠéº»é…±ã€èŠ±ç”Ÿé…±ã€è±†è…ä¹³ã€é£Ÿç›ã€å¤§è’œã€ç”Ÿå§œç­‰ã€‚ + +å„ç§è˜¸æ–™æ­é…è§ä»è§æ™ºï¼Œè¿™é‡Œä¸åšä¸¾ä¾‹ã€‚ + +### ç‚é”… + +ç‚’èœè¿‡ç¨‹ä¸­ï¼Œå¯ä»¥åœ¨è¿‡ç¨‹ä¸­ä½¿ç”¨è‘±ã€å§œã€è’œã€å¹²è¾£æ¤’等香料ç‚锅。香料中的香味物质在高温的作用下挥å‘出æ¥ï¼Œä¸€å®šç¨‹åº¦ä¸Šèƒ½è¦†ç›–腥味并且增加æˆèœçš„风味。 + +### 冷水锅焯水 + +æŸäº›åŠ¨ç‰©æ€§åŽŸæ–™ä¸­æ®‹ç•™æœ‰è¡€æ¶²ï¼Œå¦‚ï¼šé¸¡è‚‰ã€çŒªè¹„ã€æŽ’éª¨ç­‰ã€‚æ®‹ç•™çš„è¡€æ¶²å¦‚æžœä¸åŽ»é™¤ä¼šå¯¼è‡´æˆèœæœ‰ä¸€å®šçš„腥味。 + +冷水下锅时,残留的血液会分散到水中;éšç€æ¸©åº¦å‡é«˜ï¼Œè¡€æ¶²ä¸­çš„蛋白质å‡å›ºï¼ŒåŽŸæœ¬åˆ†æ•£åœ¨æ°´ä¸­çš„è¡€æ¶²å½¢æˆæµ®æ²«é£˜åœ¨æ°´é¢ä¸Šã€‚这时åªéœ€ç”¨å‹ºæ’‡åŽ»æµ®æ²«å³å¯å®ŒæˆåŽ»è…¥ï¼Œå‰©ä¸‹çš„æ¸…æ±¤å¯ä»¥ç”¨ä½œç‚–ç…®èœçš„æ±¤åº•继续烹饪。 + +### 注æ„事项 + +- 焯水时往往在锅中加入一些调料如:花椒ã€å…«è§’ã€æ–™é…’ã€å¤§è‘±ç­‰ï¼Œè¿›ä¸€æ­¥å¼ºåŒ–去腥的力度 +- 八角香味浓éƒï¼Œåº”适釿·»åŠ  +- 花椒和麻椒体积å°è€Œæ·»åŠ é‡å¤§ï¼Œæ·»åŠ åŽå¯èƒ½ä¼šæ®‹ç•™åœ¨é”…中甚至残留至æˆèœï¼Œå¯ä»¥ä½¿ç”¨çº±å¸ƒåŒ…裹一个调料包或者使用食å“级ä¸é”ˆé’¢è°ƒæ–™ç›’,方便在æˆèœå‰æŒ‘出 diff --git a/assets/md/tips/learn/学习凉拌.md b/assets/md/tips/learn/学习凉拌.md new file mode 100644 index 0000000..8c430e4 --- /dev/null +++ b/assets/md/tips/learn/学习凉拌.md @@ -0,0 +1,155 @@ +# 凉拌 + +## 凉拌是什么 + +凉拌是一ç§å°†ä¸»é£Ÿæä¸Žè¾…æ–™é€šè¿‡æ…æ‹Œæ··åˆä»¥æˆèœçš„æ–¹å¼ + +### å‡‰æ‹Œçš„å½¢æ€ + +凉拌å¯åšæˆé£Ÿæä¸Žè¾…æ–™åœ¨ç©ºé—´ä¸Šäº¤æ··çš„å½¢æ€ +凉拌å¯åšæˆé£Ÿæä¸Žè¾…料在空间上分立的形æ€ï¼Œæ­¤æ—¶è¾…料被称为蘸料 + +### 为什么凉拌 + +* 部分凉拌æˆèœæ—¶ä¸éœ€è¦çƒ­æº +* 部分凉拌能å‡å°‘æ´—é”…çš„æµç¨‹ï¼ˆä¸æ´—或仅过水å³å¯ï¼‰ +* 凉拌能ä¿ç•™é£Ÿæçжæ€ï¼Œæ­¤ç‚¹ç‰¹åˆ«å±•现在蔬èœã€ç”Ÿè‚‰ä¸Š + +### 凉拌的目的 + +* å‡‰æ‹Œçš„ç›®çš„åœ¨äºŽå¯¹æ— å‘³æˆ–å‘³æ·¡é£Ÿææ·»åР味é“,例如鸡肋 + +### 凉拌能放什么 + +包括但ä¸é™äºŽï¼š + +* 主食æ +* è¾…æ–™ +* 腌制酱料 +* 调味料 + +### 注æ„事项 + +* 凉拌时应该注æ„食æå®‰å…¨ï¼Œåœ¨ä¸ç¡®è®¤é£Ÿææ˜¯å¦å®‰å…¨æ—¶ï¼Œè¯·å‹¿å‡‰æ‹Œå¯¹åº”食æï¼Œåœ¨ç¡®è®¤é£Ÿæä¸å®‰å…¨æ—¶ä¸åº”凉拌对应食æ +* 凉拌应尽å¯èƒ½åŠ å¤§ä¸»é£Ÿèœçš„æŽ¥è§¦é¢ç§¯ï¼Œæ•…凉拌时推è刀花ã€åˆ‡ç‰‡ã€æ‹ç¢Žç”šè‡³æ…碎 +* 凉拌èœå¯¹è‚ èƒƒæå‡ºäº†åŸºæœ¬è¦æ±‚,请在确认ä¸ä¼šå–·å°„或存有喷射时间时采用凉拌 +* 文件撰写时处于新冠疫情状æ€ä¸‹ï¼Œå»ºè®®å°†æ‰€æœ‰é£Ÿæå‡åœ¨ 100 æ‘„æ°åº¦ä»¥ä¸Šçš„环境中加热 15 秒以上以图心ç†å®‰æ…°ï¼Œè‹¥æƒ³æ±‚得安全请尽é‡é¿å…凉拌 + +## 器具 + +å¯ä»¥ä½¿ç”¨ä»»ä½•容器,从瓷缸到食å“级塑料袋å‡å¯ + +### 注æ„事项 + +* ä¸ºæ–¹ä¾¿æ…æ‹Œæ—¶é£Ÿæä¸æº…出,使用容积在所有食æä¸¤å€ä»¥ä¸Šçš„硬质容器较为åˆé€‚ +* 为ä¿è¯é£Ÿå“安全,在塑料袋或塑料碗中腌制åŽè¯·å°½å¿«å°†é£Ÿæç§»è‡³ç“·å®¹å™¨æˆ–金属质容器中 +* 为ä¿è¯é£Ÿå“安全,请在æ´å‡€çš„ç §æ¿ä¸Šå¤„ç†ç”Ÿé£Ÿé£Ÿæä¸Žè¾…æ–™ + +## æµç¨‹ + +### 片状蔬èœç±»ä¸»é£ŸæåŠ å·¥ï¼ˆæ­¤æµç¨‹å¯é€‰ï¼‰ï¼ˆé€‰é¡¹å•选或多选) + +用例:包èœã€ç”Ÿèœã€ç™½èœå¿ƒã€æ´‹è‘±ç­‰ + +* å°†é£Ÿææ’•碎为 4cm * 4cm çš„å°å— +* 将食æåˆ‡æˆ 0.5cm é•¿çš„æ¡çж +* 将食æè£åŽ»ä¸ä½¿ç”¨éƒ¨åˆ†åŽæ•´ç‰‡ä½¿ç”¨ +* 将处ç†åŽçš„食æç„¯æ°´ + +#### 注æ„事项 + +对部分食æå¯ä¸ç”¨å‰¥å¼€ï¼Œç›´æŽ¥å»¶è½´çº¿ç»™åˆ€åŽåž‚直轴线切段å³å¯ï¼Œå¯¹åˆ€æ³•有信心者å¯ç›´æŽ¥åž‚直轴线切段 + +### å—状蔬èœç±»ä¸»é£ŸæåŠ å·¥ï¼ˆæ­¤æµç¨‹å¯é€‰ï¼‰ï¼ˆé€‰é¡¹å•选或多选) + +用例:马铃薯,è¸è ï¼Œé»„瓜ã€åœŸè±†ç­‰ + +* 将食æåˆ‡æˆ 0.5cm * 0.5cm 截é¢é•¿æ¡çж +* 将食æåˆ‡æˆåŽšåº¦å°äºŽ 0.5cm çš„ 4cm * 4cm 片状 +* 将食æç”¨åˆ€é¢æ‹ç¢Žæˆ–压碎(犹适用于黄瓜) +* 将食æç›´æŽ¥ä½¿ç”¨ï¼ˆçŠ¹é€‚ç”¨äºŽæœ¬èº«ä¸ºå°å—的食æï¼‰ +* 将处ç†åŽçš„食æç„¯æ°´ + +#### 注æ„事项 + +对食æä½¿ç”¨æ‹ç¢Žæ—¶å¯èƒ½å¯¼è‡´é£Ÿæé£žæº…,å¯ä»¥ä½¿ç”¨é£Ÿå“çº§å¡‘æ–™è¢‹åŒ…è£¹åŽæ‹ç¢Ž + +### å—状èŒç±»ä¸»é£ŸèœåŠ å·¥ï¼ˆæ­¤æµç¨‹å¯é€‰ï¼‰ï¼ˆé€‰é¡¹å•选或多选) + +用例:å„类蘑è‡ã€å„类木耳等 + +* å°†é£Ÿææ³¡å‘ +* 将食æåˆ‡æˆ 0.5cm * 0.5cm 截é¢é•¿æ¡çж +* 将食æåˆ‡æˆåŽšåº¦å°äºŽ 0.5cm çš„ 4cm * 4cm 片状 +* 将食æç›´æŽ¥ä½¿ç”¨ï¼ˆçŠ¹é€‚ç”¨äºŽæœ¬èº«ä¸ºå°å—的食æï¼‰ +* 将处ç†åŽçš„食æç„¯æ°´ + +#### 注æ„事项 + +所有èŒç±»éœ€è¦ä¸¥æ ¼ç¡®è®¤å®‰å…¨ï¼Œæ¿æ¿ä¸Šä¸€èººå°±å•¥éƒ½æ²¡äº† + +### å—状肉类主食èœåŠ å·¥ï¼ˆæ­¤æµç¨‹å¯é€‰ï¼‰ï¼ˆé€‰é¡¹å•选或多选) + +ç”¨ä¾‹ï¼šé±¼è‚‰ã€æµ·èœ‡å¤´ã€ç†ŸçŒªè‚‰ã€ç†Ÿç¦½ç±»ç­‰ + +* 将食æé€šè¿‡è’¸ç…®çƒ¤ç‚¸ç­‰æ–¹å¼ç†Ÿåˆ¶ +* 将食æåœ¨å‡‰æ°´ä¸­æ³¡ä¸Šäº›è®¸æ—¶é—´ï¼ˆçŠ¹é€‚ç”¨äºŽæµ·äº§ï¼‰ +* å°†é£Ÿææ’•æˆè‚‰æ¡ +* 将食æåˆ‡æˆè–„片(犹适用于煮熟åŽçš„猪肉) +* 将食æåˆ‡æˆ 0.8cm * 0.8cm 截é¢é•¿æ¡çж +* 将食æç›´æŽ¥æŒ‰éƒ¨ä½æ’•碎或切大å—(犹适用于整åªç†Ÿç¦½ï¼‰ + +#### 注æ„事项 + +* çŒªè‚‰ä¸Žç¦½è‚‰æ²¡æœ‰ä¾‹å¤–ï¼Œå¿…é¡»åæˆç†Ÿï¼Œå¿…é¡»å®Œå…¨ç†Ÿåˆ¶ï¼Œå¿…é¡»ä¸è§ä»»ä½•血水 +* 部分牛肉ã€é±¼è‚‰ã€æµ·é²œç±»åœ¨ç¡®è®¤å®‰å…¨åŽå¯ç”Ÿé£Ÿ + +### 俺寻æ€è¿™ä¸ªä¹Ÿæˆç±»é£ŸæåŠ å·¥ï¼ˆæ­¤æµç¨‹å°½å¯èƒ½ä¸é€‰ï¼‰ï¼ˆé€‰é¡¹å¿…选) + +ç”¨ä¾‹ï¼šé¢æ¡ã€ç±³é¥­ã€æžœç±»ã€å«©æ ‘å¶ç­‰ + +* 确认食æå®‰å…¨ +* 将食æå¤„ç†æˆå¯é£Ÿç”¨çŠ¶æ€ +* 将食æå¤„ç†æˆå¯æ…æ‹ŒçŠ¶æ€ + +#### 注æ„事项 + +* 请确认食æå®‰å…¨ï¼Œå¯ä»¥å°†å°‘é‡é£Ÿææ…碎åŽåœ¨ä¸Šè‡‚糊上 30 分钟以检测过æ•å应,有异常情况必须弃用 +* 大多数树å¶ã€é’è‰å«æœ‰ä¼šä»¤äººä¸é€‚çš„æˆåˆ†ï¼Œå³ä½¿åœ¨ç†Ÿåˆ¶åŽä¾ç„¶å¦‚此,请确认安全 +* 大多数谷类在熟制å‰ä¸åˆ©äºŽæ¶ˆåŒ–叿”¶ï¼Œå¯èƒ½å¯¹è‚ èƒƒäº§ç”Ÿä¸è‰¯å½±å“ + +### 辅料加工(此æµç¨‹å¯é€‰ï¼‰ï¼ˆé€‰é¡¹å•选或多选) + +用例:指天椒ã€è’œç“£ã€ç”Ÿå§œã€å¹²è¾£æ¤’ç­‰ + +* 将指天椒去蒂,洗净åŽåˆ‡åŽ»è’‚ç«¯ 0.5cm 长部分åŽåˆ‡ç¢Žæˆ–切段 +* 将蒜瓣æ‹ç¢ŽåŽåŽ»çš®ï¼Œåˆ‡åŽ»è’‚ç«¯ 0.5cm 长部分åŽåˆ‡ç¢Žæˆ–切段 +* 将生姜去皮åŽåˆ‡ç¢Žæˆ–åˆ‡ä¸æˆ–切片 +* 将干辣椒碾碎 +* 将准备好的食æè£…å…¥å°ç¢—或å³å°†ç”¨äºŽæ…拌的容器中 +* 在容器中加入:å„类粉剂ã€è°ƒå‘³æ–™æˆ–è…Œåˆ¶é…±æ–™ï¼Œæ…æ‹Œå‡åŒ€ + +#### 注æ„事项 + +* 辅料的ç§ç±»ï¼ŒåŠ å·¥ï¼Œæ–¹æ³•æžä¸ºå®½æ³›ï¼Œè¯·ä¸è¦å±€é™æ‚¨çš„æ€ç»´ï¼Œä½†è¯·å°å¿ƒæ±‚è¯ï¼Œé€‚度适é‡ï¼Œè°¨è®°å®‰å…¨ + +### æ··åˆé£Ÿæï¼ˆæ­¤æµç¨‹å¯é€‰ï¼‰ï¼ˆé€‰é¡¹å•选或多选) + +* 将嫿°´é‡é«˜çš„è‚‰ç±»é£ŸææŒ¤å‡ºæ°´åˆ†åŽæ»¤å¹² +* 将嫿°´é‡é«˜çš„蔬èœç±»é£Ÿææ”¾å…¥å®¹å™¨ä¸­åŠ å…¥çº¦ 200g : 5g çš„ç›å¹¶æ…拌åŽé™ç½® 5 分钟,将水分滤干 +* 将食æä¸Žè¾…æ–™åŠ å…¥æ…æ‹Œå®¹å™¨ +* 用筷å­ã€å‹ºå­æˆ–æ‰‹æ´—å‡€åŽæ…拌 +* 将容器密å°åŽæ‘‡æ™ƒå‡åŒ€ +* 将容器倾斜至ä¸ä¼šæœ‰ææ–™æ´’出的角度,以轴心为轴旋转容器 + +#### 注æ„事项 + +* 嫿°´é‡é«˜çš„食æç›´æŽ¥åœ¨åŠ å…¥åŽå¯èƒ½æžå‡ºè¿‡å¤šæ°´åˆ†æ·¡åŒ–调料 +* æ…æ‹Œæ—¶å‘现水é‡ä¸è¶³æˆ–æ…æ‹Œä¸åŒ€å¯é€‚é‡åŠ ç™½å¼€æ°´ï¼Œè‹¥æ— æ³•ç¡®å®šç”¨é‡æ¯æ¬¡ 15mL 为佳 +* éƒ¨åˆ†å¸æ°´çŽ‡é«˜çš„é£Ÿæä¸å»ºè®®æ…拌,å¯èƒ½å¯¼è‡´è…Œåˆ¶åŽçš„食æå‘³é“è¿‡é‡ + +### 食用(此æµç¨‹å¿…选) + +* å°†æ…æ‹ŒåŽçš„食æç›´æŽ¥é£Ÿç”¨ +* å°†æœªæ…æ‹Œçš„主食æè˜¸å–蘸料åŽé£Ÿç”¨ +* 将食æä¸Žè˜¸æ–™åŠ å…¥ä¸»é£Ÿä¸­é£Ÿç”¨ diff --git a/assets/md/tips/learn/学习炒与煎.md b/assets/md/tips/learn/学习炒与煎.md new file mode 100644 index 0000000..f8087c0 --- /dev/null +++ b/assets/md/tips/learn/学习炒与煎.md @@ -0,0 +1,51 @@ + +# ç‚’/ç…Ž + +## 器具 + +å¯ä½¿ç”¨æ™®é€šé‡‘属制(é“/ä¸é”ˆé’¢/é“)炒/煎锅或ä¸ç²˜é”…。 + +ä¸å»ºè®®ä½¿ç”¨é“制容器, 原因详è§é£Ÿå“安全一节 + +### 注æ„事项 + +* 使用普通锅炒èœä¸ç²˜çš„æ–¹æ³•: + +#### 先炒鸡蛋法 + +* ä¸ç®¡ç‚’什么èœä¹‹å‰éƒ½ç‚’个鸡蛋,炒完ä¸åˆ·é”…,å†ç‚’ä¸‹ä¸ªèœæ—¶å°±ä¸ç²˜ã€‚ + +#### 热锅凉油法 + +* è®°ä½ä¸€å®šè¦æ˜¯çƒ­é”…凉油,首先热锅 + * å¹²å‡€çš„é”…ä»€ä¹ˆéƒ½ä¸æ”¾ï¼Œå¹²çƒ§ï¼Œä½¿å…¶å—热å‡åŒ€ï¼Œçƒ§çƒ­ + * 放入凉油,旋转锅å­ï¼Œä½¿æ²¹æ²¾æ»¡æ•´ä¸ªé”…(å¯ä»¥æ¥å›žæ—‹è½¬ä½¿å…¶å—热å‡åŒ€ï¼‰ + * 看到有气体从锅中å‘出时,就表示锅å­çš„æ²¹å·²ç»çƒ§çƒ­äº† + * 把油倒出æ¥ï¼Œå€’出æ¥åŽä¸è¦åˆ·é”… + * å¯ä»¥é‡å¤ä¸Šè¿°æ­¥éª¤ 2-3 é以得到更好的ä¸ç²˜æ•ˆæžœ + * 注æ„:如果是燃气,å¯èƒ½ä¼šå–·ç«ï¼Œæ³¨æ„安全 + +#### çƒ­é”…åŒæ²¹æ³• + +* 首先热锅 + * å¹²å‡€çš„é”…ä»€ä¹ˆéƒ½ä¸æ”¾ï¼Œå¹²çƒ§ï¼Œä½¿å…¶å—热å‡åŒ€ï¼Œçƒ§çƒ­ + * 放入“少é‡å‡‰æ²¹â€ï¼Œæ—‹è½¬é”…å­ï¼Œä½¿æ²¹æ²¾æ»¡æ•´ä¸ªé”…(å¯ä»¥æ¥å›žæ—‹è½¬ä½¿å…¶å—热å‡åŒ€ï¼‰ + * 看到有气体从锅中å‘出时,就表示锅å­çš„æ²¹å·²ç»çƒ§çƒ­äº† + * å†ç»§ç»­æ”¾å…¥å‡‰æ²¹ï¼Œå¼€å§‹ç‚’èœ + * 注æ„:如果是燃气,å¯èƒ½ä¼šå–·ç«ï¼Œæ³¨æ„安全。 + +补充: + +* 目的是使油挂满锅底,所有市é¢ä¸Šçš„家用锅都适用,挂油åŽç§’å˜ä¸ç²˜é”…。 +* 使用ä¸ç²˜é”…煎炒食物ä¸ä¼šç²˜é”…。ä¸ç²˜é”…çš„åŠŸèƒ½æ¥æºäºŽå…¶å†…å£ä¸Šçš„æ¶‚层。**金属锅铲会划伤涂层。使用ä¸ç²˜é”…时应使用木制或硅胶锅铲以é¿å…æŸå涂层。** + +### æµç¨‹ + +å¼€ç«â€”—直接将锅平放于ç«ä¸Šï¼Œçƒ§çƒ­â€”—将油倒入锅中,烧热——放入èœå“,翻炒——出锅å‰è®°å¾—放调料 + +### 注æ„事项 + +* 判断锅/油是å¦çƒ§çƒ­æ—¶ï¼Œå¯å°†æ‰‹å¹³æ”¾äºŽé”…的上方感å—热é‡ï¼›æ²¹çƒ­åŽæ–¹å¯æ”¾å…¥é£Ÿæã€‚ +* 倒油入锅å‰ï¼ŒåŠ¡å¿…ç¡®è®¤é”…çš„å†…éƒ¨æ²¡æœ‰æ®‹ä½™æ°´ä»½ã€‚**水会导致热油飞溅,造æˆå±é™©ã€‚** +* 接上æ¡ï¼Œé£Ÿææ”¾å…¥æ²¹é”…å‰ï¼Œåº”当沥干水份(蛋液没事);åŒç†ï¼Œä¸å¯å°†æœªè§£å†»çš„é£Ÿææ”¾å…¥æ²¹é”…,以å…冰化åŽé€ æˆå±é™©ã€‚ +* **若油锅起ç«ï¼Œåˆ‡ä¸å¯å€’æ°´ç­ç«**。这样åšä¼šä½¿ç«åŠ¿æ‰©å¤§ã€‚ç«åˆšèµ·æ—¶ï¼Œå¯è¿…速关ç«ï¼Œç›–上锅盖。 diff --git a/assets/md/tips/learn/学习焯水.md b/assets/md/tips/learn/学习焯水.md new file mode 100644 index 0000000..7f8ecf4 --- /dev/null +++ b/assets/md/tips/learn/学习焯水.md @@ -0,0 +1,49 @@ +# 焯水 + +焯水是åšé¥­çš„一é“å·¥åºï¼Œè¯»ä½œ chÄo shuÇ。 + +ç„¯æ°´æŒ‡å°†åˆæ­¥åŠ å·¥çš„åŽŸæ–™æ”¾åœ¨å¼€æ°´é”…ä¸­åŠ çƒ­è‡³åŠç†Ÿæˆ–全熟,å–出以备进一步烹调或调味。 + +焯水是烹调中特别是冷拌èœä¸å¯ç¼ºå°‘的一é“å·¥åºã€‚ 对èœè‚´çš„色ã€é¦™ã€å‘³ï¼Œç‰¹åˆ«æ˜¯è‰²èµ·ç€å…³é”®ä½œç”¨ã€‚ + +大部分蔬èœå’Œå¸¦æœ‰è…¥ç¾¶æ°”味的肉类原料都需è¦ç„¯æ°´ã€‚ + +## æ“作 + +### 开水锅焯水 + +开水锅焯水,就是将锅内的水加热,然åŽå°†åŽŸæ–™ä¸‹é”…ã€‚ä¸‹é”…åŽåŠæ—¶ç¿»åŠ¨ï¼Œæ—¶é—´è¦çŸ­ï¼Œä¸è¦è¿‡ç«ã€‚ + +è¿™ç§æ–¹æ³•多用于æ¤ç‰©æ€§åŽŸæ–™ï¼Œå¦‚ï¼šèŠ¹èœã€è èœã€èŽ´ç¬‹ç­‰ã€‚ 焯水时è¦ç‰¹åˆ«æ³¨æ„ç«å€™ï¼Œæ—¶é—´ç¨é•¿ï¼Œé¢œè‰²å°±ä¼šå˜æ·¡ï¼Œè€Œä¸”也ä¸è„†ã€å«©ã€‚ 因此放入锅内åŽï¼Œæ°´å¾®å¼€æ—¶å³å¯æžå‡ºæ™¾å‡‰ã€‚ + +- å¶ç±»è”¬èœåŽŸæ–™åº”å…ˆç„¯æ°´å†åˆ‡ç‰‡ï¼Œä»¥å…è¥å…»æˆåˆ†æŸå¤±è¿‡å¤šã€‚ +- ç„¯æ°´æ—¶åº”æ°´å®½ç«æ—ºï¼Œä»¥ä½¿æŠ•入原料åŽèƒ½åŠæ—¶å¼€é”…;焯制绿å¶è”¬èœæ—¶ï¼Œåº”ç•¥æ»šå³æžå‡ºã€‚ +- 蔬èœç±»åŽŸæ–™åœ¨ç„¯æ°´åŽåº”ç«‹å³æŠ•å‡‰æŽ§å¹²ï¼Œä»¥å…因余热而使之å˜é»„ã€ç†Ÿçƒ‚的现象å‘生。 +- 蔬èœç„¯æ°´å¯ä»¥æ”¾å…¥é€‚é‡è‰²æ‹‰æ²¹å¦‚花生油ã€çŽ‰ç±³æ²¹ã€å¤§è±†æ²¹ä»¥ä¿æŒç¿ ç»¿ã€‚ + +### 冷水锅焯水 + +å†·æ°´é”…ç„¯æ°´æ˜¯å°†åŽŸæ–™ä¸Žå†·æ°´åŒæ—¶ä¸‹é”…。 æ°´è¦æ²¡è¿‡åŽŸæ–™ï¼Œç„¶åŽçƒ§å¼€ï¼Œç›®çš„æ˜¯ä½¿åŽŸæ–™æˆç†Ÿï¼Œä¾¿äºŽè¿›ä¸€æ­¥åŠ å·¥ã€‚ + +土豆ã€èƒ¡èåœç­‰å› ä½“ç§¯å¤§ï¼Œä¸æ˜“æˆç†Ÿï¼Œéœ€è¦ç…®çš„æ—¶é—´é•¿ä¸€äº›ã€‚ + +有些动物性原料,如:白肉ã€ç‰›ç™¾é¡µã€ç‰›è‚šé¢†ç­‰ï¼Œä¹Ÿæ˜¯å†·æ°´ä¸‹é”…加热æˆç†ŸåŽå†è¿›ä¸€æ­¥åŠ å·¥çš„ã€‚æœ‰äº›ç”¨äºŽç…®æ±¤çš„åŠ¨ç‰©æ€§åŽŸæ–™ä¹Ÿè¦å†·æ°´ä¸‹é”…,在加热过程中使è¥å…»ç‰©è´¨é€æ¸æº¢å‡ºï¼Œä½¿æ±¤å‘³é²œç¾Žï¼Œå¦‚用热水锅,则会造æˆè›‹ç™½è´¨å‡å›ºã€‚ + +- 锅内的加水é‡ä¸å®œè¿‡å¤šï¼Œä»¥æ·¹æ²¡åŽŸæ–™ä¸ºåº¦ã€‚ +- åœ¨é€æ¸åŠ çƒ­è¿‡ç¨‹ä¸­ï¼Œå¿…é¡»å¯¹åŽŸæ–™å‹¤ç¿»åŠ¨ï¼Œä»¥ä½¿åŽŸæ–™å—热å‡åŒ€ï¼Œè¾¾åˆ°ç„¯æ°´çš„目的。 + +## é¢å¤–注æ„事项 + +- 焯水有时也会使原料内的一些ä¸ç¨³å®šã€å¯æº¶æ€§è¥å…»ç‰©è´¨æº¢å‡ºï¼Œç‰¹åˆ«æ˜¯æ–°é²œè”¬èœä¸­çš„æ°´æº¶æ€§ç»´ç”Ÿç´ æ›´å®¹æ˜“å—到æŸå¤± +- 动物类原料与æ¤ç‰©ç±»åŽŸæ–™è¦åˆ†åˆ«ç„¯æ°´ï¼›è‰²å‘³è¾ƒé‡çš„与色味较轻的è¦åˆ†åˆ«ç„¯æ°´ï¼›å—状大的è¦ä¸Žå—状å°çš„分别焯水,以防彼此串味 +- 焯制动物性原料åŽï¼Œæ±¤æ±å¯åœ¨æ’‡æ²«æ¾„清åŽä½œä¸ºé²œæ±¤ä½¿ç”¨ + +### 肉的焯水 + +- 肉类原料ç»è¿‡å¼€æ°´ç„¯è¿‡åŽå˜è‰²å³å¯ï¼Œæžå‡ºæ²¥å¹²æ°´åˆ†åŽå¯ä»¥è¿›è¡Œä¸‹ä¸€æ­¥çš„烹调。 +- 肉类焯水åŽéœ€è¦æ´—去沾附的血沫污æ¸ï¼Œè®°å¾—用温水清洗,å¦åˆ™è‚‰çƒ­èƒ€å†·ç¼©ä¼šå¸é™„污æ¸ï¼Œå¯¼è‡´æ— æ³•洗净血沫。 + +### é’èœçš„焯水 + +- æ´—é’èœæ—¶ï¼Œåœ¨æ¸…水里撒一些ç›ï¼Œè¿™æ ·å¯ä»¥æŠŠé’èœé‡Œçš„è™«å­æ¸…æ´—å‡ºæ¥ +- 焯过åŽçš„é’èœåº”ç«‹å³æµ¸å…¥å†·æ°´ä¸­ï¼Œä»¥ä¿æŒé¢œè‰²å’Œå£æ„Ÿã€‚如果ä¸ç”¨å†·æ°´æµ¸ï¼Œé’èœä¼šå› ä¸ºå¼€æ°´çš„余温å˜çš„ä¸å†æ¸…脆,而出现烂烂的感觉 diff --git a/assets/md/tips/learn/学习煮.md b/assets/md/tips/learn/学习煮.md new file mode 100644 index 0000000..2619a9e --- /dev/null +++ b/assets/md/tips/learn/学习煮.md @@ -0,0 +1,12 @@ +# ç…® + +## æµç¨‹ + +倒水入锅——开ç«ï¼Œå°†é”…放于ç«ä¸ŠåŠ çƒ­â€”â€”æ°´å¼€ï¼ˆæ°´ç¿»æ»šï¼Œæœ‰å¤§é‡æ°”æ³¡å†’å‡ºï¼‰åŽæ”¾å…¥é£Ÿæ + +### 注æ„事项 + +* 加热时盖上锅盖å¯ä»¥åŠ å¿«å—热。**ä½†è¿™æ ·åšæœ‰æº¢é”…的风险**。æŒç»­åŠ çƒ­åŽï¼Œè¿‡æ¸¡ç¿»è…¾çš„æµä½“å¯èƒ½ä¼šå†’出锅外,这就是溢锅。 +* **è‹¥å³å°†æº¢é”…,立刻关å°ç«å¹¶æ‰“开锅盖å³å¯ã€‚** +* 想è¦åŠ å¿«å—热åˆé¿å…溢锅,å¯ä»¥åŠå¼€é”…盖,留出气体出å£ï¼›ä¹Ÿå¯åœ¨åŽæœŸå…³å°ç«ï¼Œå¹¶æ—¶æ—¶æ³¨æ„锅中情况。 +* æ ¹æ®çƒ¹é¥ªéœ€è¦ï¼Œé£Ÿæä¹Ÿå¯å†·æ°´ä¸‹é”…。ä¸è¿‡è¿™æ ·æ°´çƒ§å¼€éœ€è¦çš„æ—¶é—´æ›´ä¹…。 diff --git a/assets/md/tips/learn/学习腌.md b/assets/md/tips/learn/学习腌.md new file mode 100644 index 0000000..90e4b3a --- /dev/null +++ b/assets/md/tips/learn/学习腌.md @@ -0,0 +1,122 @@ +# 腌(肉) + +## æ³¨æ„ + +此处所æè¿°çš„è…Œæ¸æ˜¯é£Ÿæçƒ¹é¥ªå‰å¤„ç†çš„æ­¥éª¤ï¼Œå¹¶éžåˆ¶ä½œå’¸è‚‰æˆ–腌制香肠等æˆå“ + +## è…Œæ¸ + +在烹饪å‰è…Œåˆ¶è‚‰ç±»æ˜¯è®©è‚‰ç±»é¢„先入味的常用方法。一般腌æ¸çš„对象是生肉。根æ®èœå“的需求,å¯ä»¥è‡ªè¡Œç¡®å®šè‚‰ç±»æ”¹åˆ€çš„大å°ã€‚ + + 例如炸鸡米花,鸡胸肉是在改刀为骰å­å¤§å°çš„å°å—åŽæ”¾å…¥ç¢—ä¸­è…Œæ¸ + + ä¾‹å¦‚çƒ¤å…¨ç¾Šï¼Œç¾Šè…¿ï¼ŒåŠæ‰‡æˆ–整扇羊肉ä¸å¿…改刀å³å¯ç”¨å¤§é‡è°ƒå‘³æ–™æ¶‚抹在表é¢ä»Žè€Œè…Œæ¸å…¥å‘³ + +æ ¹æ®èœå“çš„ä¸åŒï¼Œè…Œæ¸æ‰€é€‰çš„调味料ã€è¾…æ–™å¯ä»¥æ˜¯ä»»ä½•ç§ç±»ã€‚有时候为了ä¸åŒçš„å£å‘³ï¼Œè¾…料也å¯èƒ½éœ€è¦é¢„先处ç†ã€‚ + +## è…Œæ¸åŸºæœ¬æ¦‚念 + +此处介ç»çš„æ˜¯æ­£å¸¸å£å‘³çš„è…Œæ¸è¿‡ç¨‹ã€‚ + +- 一般æ¥è¯´ï¼Œè‚‰é‡è¶Šå¤§ï¼ˆæ¯”å¦‚ä¸€æ¬¡æ€§è…Œæ¸ 5kg 鸡翅),体积越大(比如一整个羊腿),å£å‘³è¶Šé‡ï¼Œåˆ™éœ€è¦è°ƒå‘³æ–™å’Œè¾…料越多 +- 一般æ¥è¯´ï¼Œè®¡åˆ’è…Œæ¸çš„æ—¶é—´è¶Šé•¿ï¼Œä½¿ç”¨çš„调味料和辅料越少 +- è…Œæ¸æ—¶åº”使用料å‡åŒ€è¦†ç›–在所有的表é¢ã€‚如果是肉片ã€è‚‰ä¸ï¼Œåº”è¯¥ç”¨æ‰‹å°½é‡æŠ“åŒ€ã€æ…匀。如果是整个羊腿,应该用手或刷å­åœ¨è¡¨é¢åˆ·åŒ€ +- 一般炒肉ã€ç‚¸è‚‰éœ€è¦æå‰è…Œæ¸ã€‚炒肉应该ä¿è¯è‚‰é²œå«©çš„壿„Ÿï¼Œçƒ¹è°ƒå¾€å¾€éœ€è¦å¤§ç«ä¸”时间较短。短时间烹饪ä¸å®¹æ˜“入味时,æå‰è…Œæ¸å°±èƒ½å¼¥è¡¥å£å‘³çš„ä¸è¶³ + +## è…Œæ¸æ‰‹æ³• + +- 细肉ä¸ã€è–„肉片:由于肉质较脆弱,需è¦å°½é‡è½»æŸ”。手指呈娃娃机钳å­çš„形状,轻微抓匀腌料。然åŽå‘一个方å‘轻轻æ…匀å³å¯ +- 肉ä¸ã€è‚‰ç‰‡ã€è‚‰å—:手法åŒä¸Šï¼Œä½†æ˜¯åŠ›é‡å¯ä»¥ç¨å¤§ +- 鸡腿ã€é¸¡ç¿…等大å°ï¼šå…ˆåœ¨é£Ÿæä¸Šæ”¹å‡ é“花刀。鸡翅根ã€é¸¡è…¿å¯ä»¥ç”¨åˆ€æ‰Žå¯¹ç©¿å­”。然åŽå…ˆåœ¨ç¢—里混åˆå¥½è…Œæ–™ï¼Œåœ¨æŠŠé£Ÿææ”¾å…¥æ–™ç¢—中裹匀 +- 羊腿等大å°ï¼šä¸€èˆ¬å¯ä»¥åœ¨è‚‰è¾ƒåŽšçš„ä½ç½®æ‰Žå¯¹ç©¿å­”。然åŽè…Œæ–™æ··åˆå¥½åŽå‡åŒ€æ¶‚抹在食æè¡¨é¢ + +## è…Œæ¸å®¹å™¨åŠæ—¶é—´ + +- 选择能装下食æå’Œè…Œæ–™çš„容器å³å¯ã€‚包括碗ã€ç›˜å­ã€æ‰˜ç›˜ç­‰ã€‚此时是开å£è…Œæ¸ï¼Œä¸€èˆ¬æ—¶é—´è¾ƒçŸ­ï¼Œå¸¸è§ 0.5-2 å°æ—¶çš„è…Œæ¸æ—¶é—´ã€‚(烤)羊腿等也å¯ä»¥å¦‚此腌æ¸ï¼Œä½†æ—¶é—´è¾ƒé•¿ +- å¯ä»¥é€‰æ‹©è¶³å¤Ÿå¤§çš„食å“密å°è¢‹è…Œæ¸ã€‚此时是å°å£è…Œæ¸ï¼Œä¸€èˆ¬æ—¶é—´å¾ˆé•¿ï¼Œä¾‹å¦‚隔夜腌æ¸ï¼Œæˆ–è…Œæ¸ä¸æ˜“å…¥å‘³çš„æŽ’éª¨ç­‰ã€‚å¸¸è§ 4 å°æ—¶-隔夜。此时用料è¦ç¨å¾®å‡å°‘,防止æˆèœå£æ„Ÿå¤ªé‡å¤ªå’¸ + +## 常用的腌æ¸ç”¨æ–™ + +- 生抽:调酱香且带有咸味的底味。å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰è‚‰ç±» +- è€æŠ½ï¼šå’¸å‘³å¹¶ä¸å¼ºçƒˆï¼Œä½†æ˜¯æ˜“于染色。用于调底色和增香。一般ä¸åº”大é‡ä½¿ç”¨é˜²æ­¢äº§ç”Ÿè±†è…¥å‘³ã€‚å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰çº¢è‚‰ç±»ï¼ˆè¾ƒå°‘用),猪è‚ç­‰å¯ä»¥å¤šåŠ  +- 食ç›ï¼šå’¸å‘³ä½†ç‚’制åŽä¸å¸¦æœ‰é…±é¦™å‘³ã€‚å¯ç”¨äºŽæ‰€æœ‰è‚‰ç±» +- 白(砂)糖:调甜味(é‡å¤§ï¼‰ï¼Œä¹Ÿå¯ä»¥ä¸ºè‚‰å¢žåŠ é²œå«©çš„å£æ„Ÿï¼ˆé‡å°‘)。å¯ç”¨äºŽæ‰€æœ‰ç¦½ç•œç±»è‚‰ç±»ï¼Œä½†é±¼ç±»å’Œæµ·é²œå¹¶ä¸å¸¸ç”¨ã€‚ +- çº¢ç³–ï¼šè°ƒç”œå‘³å’Œçº¢ç³–ç‰¹æœ‰çš„å£æ„Ÿï¼Œå£å‘³æ¯”白糖略é‡ã€‚å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰è‚‰ç±»ï¼ˆä¸€èˆ¬è‚‰è‰²è¾ƒæ·±æˆ–者æˆèœé¢œè‰²è¾ƒæ·±ï¼‰ +- èšæ²¹ï¼šå¢žåŠ é²œã€å’¸ã€ç”œçš„å£å‘³ã€‚一般用于红肉 +- 白醋/米醋:增加酸的å£å‘³ã€‚较少使用 +- 陈醋/香醋:ä¸ä»…带有酸的å£å‘³ï¼Œè¿˜èƒ½ä¸ºèœå“å¢žé¦™å¢žè‰²ã€‚é¦™é†‹æ¯”è¾ƒé€‚åˆæ·±è‰²é±¼ç±»ï¼ˆå°¤å…¶æ˜¯çƒ¤é±¼ï¼‰ +- 料酒:去腥增香。å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰è‚‰ç±»ã€‚ä½†æ˜¯ä½¿ç”¨éœ€è¦æ³¨æ„: + - 料酒本身的味é“å¾ˆæµ“ï¼Œå¾ˆå®¹æ˜“æŽ©ç›–é£Ÿææœ¬èº«çš„é¦™å‘³ï¼Œå¯¹äºŽè…¥å‘³ä¸æµ“éƒçš„食æï¼Œå¯ä»¥è€ƒè™‘ä¸ç”¨æ–™é…’æ¥åŽ»è…¥ã€‚ä¾‹å¦‚ï¼šç‰›è‚‰ï¼Œé±¼è‚‰å’Œé¸¡è‚‰ã€‚ + - 对于鸡肉,å¯ä»¥ç”¨ç™½é…’去代替料酒。 + - 对于牛肉,å¯ä»¥ç”¨å§œè‘±æ°´ä»£æ›¿æ–™é…’。 + - 对于白色鱼肉,åªéœ€è¦æ¸…洗干净血ä¸å’Œç²˜è†œå°±ä¸ä¼šæœ‰ä»€ä¹ˆè…¥å‘³äº†ï¼Œå»ºè®®ä¸åŠ æ–™é…’ã€‚ +- é»„é…’ï¼šåŽ»è…¥å¢žé¦™ï¼Œæ•ˆæžœæ¯”æ–™é…’æ›´å¥½ï¼Œé¦™å‘³æ¯”æ–™é…’æ›´å¤æ‚。一般用于白肉类。红肉也å¯ç”¨ï¼Œä½†æ˜¯æ•ˆæžœä¸Žæ–™é…’相当。 +- 五香粉/å三香:为肉类增加香味,是最简å•çš„å¤åˆé¦™æ–™ã€‚五香粉仅仅增加香味,å三香的香味比较独特,有辨识度。用此类香料腌æ¸åº”该控制用é‡ã€‚å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰è‚‰ç±»ï¼Œä½†é±¼ç±»å’Œæµ·é²œä¸å¸¸ç”¨ +- 辣椒粉:辣椒粉分为很多ç§ã€‚ä¸è°ˆè¾£æ¤’çš„ç§ç±»ï¼Œä»Žç ”磨精细度划分有辣椒粉/辣椒é¢ï¼Œè¾£æ¤’碎等。除了为肉类增加辣味,还能为æˆèœé…色。用辣椒腌æ¸çš„èœå“应该é¿å…辣椒过é‡ã€‚å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰éœ€è¦è¾£å‘³åº•味的肉类,但烹调时间应该略加控制,防止辣椒味é“å˜è‹¦ï¼Œæˆ–è€…é¢œè‰²å˜æ·± +- 孜然粉ã€å°èŒ´é¦™ç²‰ï¼šä¸€èˆ¬ç”¨ç£¨ç²‰ä½œä¸ºè…Œæ–™ï¼Œä¸ç”¨é¢—粒,这样å¯ä»¥ä½¿è‚‰ç±»æ›´å®¹æ˜“入味。å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰çº¢è‚‰å’Œé¸¡è‚‰ +- X 椒粉:为肉类增加辛ã€è¾£ã€é¦™ã€å‘›çš„å£å‘³ã€‚使用应该适é‡ï¼Œé˜²æ­¢ç›–过其他å£å‘³ + - 黑胡椒粉:å£å‘³è¾›ã€è¾£ã€‚å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰çº¢è‚‰ + - 白胡椒粉:å£å‘³è¾›ã€é¦™ã€‚比黑胡椒略弱,çªå‡ºé¦™å‘³ã€‚å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰è‚‰ç±» + - 花椒粉:å£å‘³è¾›ã€å‘›ã€‚有花椒特殊的香味,比较有辨识度。å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰è‚‰ç±» +- 豆瓣酱:为肉类增加豆类的酱香和咸味ã€è¾£å‘³ã€‚å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰çº¢è‚‰ç±» +- è‘±å§œè’œï¼šè‘±å§œåŽ»è…¥å¢žé¦™ï¼ŒåŽ»é™¤å¼‚å‘³ï¼›è’œå¢žåŠ è¾›é¦™å‘³ã€‚è‘±å¯æ ¹æ®éœ€è¦åˆ‡æ®µæˆ–者切片;姜一般切片,有些场景需è¦åŽ»çš®ï¼›è’œå¯åˆ‡ç‰‡æˆ–åˆ‡ç¢Žã€‚è‘±å§œä¸æƒ³å‡ºçŽ°åœ¨æˆèœä¸­ï¼Œæˆ–者å£å‘³éœ€è¦è¾ƒè½»ï¼Œå¯ä»¥å°†è‘±å§œå—放于有æžå°‘釿¸…水的碗里挤压出æ±ï¼Œç”¨è‘±å§œæ°´è…Œæ¸è‚‰ç±»ã€‚蒜一般ä¸ç›´æŽ¥åŠ å…¥ã€‚å¯ç”¨äºŽæ‰€æœ‰è‚‰ç±» +- 海鲜酱ã€è™¾é…±ç­‰ï¼šä¸ºè‚‰ç±»å¢žåŠ é²œã€å’¸å‘³ã€‚海鲜酱å£å‘³å甜,虾酱å£å‘³åé‡ã€‚å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰è‚‰ç±»ï¼Œä½†ä½¿ç”¨åœºæ™¯ä¸å¤š +- 豆豉:为肉类增加å‘酵豆类的香味和咸味。å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰çº¢è‚‰ç±»ï¼Œä½†æ˜¯ä½¿ç”¨çš„å¹¶ä¸å¤š +- 生粉:å³ä¸ºæ·€ç²‰ã€‚生粉是上浆的é‡è¦è…Œæ–™ã€‚上浆越厚,或者需è¦å£æ„Ÿè¶Šæ»‘嫩,需è¦çš„生粉越多。å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰è‚‰ç±»ã€‚`生粉å¯ä½œä¸ºç®€æ˜“的油炸外衣使用(一般根æ®éœ€è¦è¿˜éœ€åŠ å…¥é¢ç²‰ç­‰ï¼‰ï¼Œæ­¤æ—¶ä¸€èˆ¬ä¸åœ¨è…Œæ¸æ—¶åŠ å…¥` + - 玉米淀粉ã€åœŸè±†æ·€ç²‰ï¼šç²˜æ€§ä¸€èˆ¬æœ€å¤§ + - 红薯淀粉:粘性略低 +- æ²¹ï¼šåœ¨è…Œæ¸æ—¶åР入适釿²¹è¿›è¡Œæ²¹å°ï¼Œå¯ä»¥é”使°´åˆ†å’Œé£Žå‘³ã€‚如使用开å£å®¹å™¨è…Œæ¸ï¼Œä¸”时间较长(例如在碗里),油å°èƒ½æžå¤§ç¨‹åº¦ä¿è¯è‚‰è´¨ä¸å˜å¹²æˆ–å˜æŸ´ã€‚å¯ç”¨äºŽå‡ ä¹Žæ‰€æœ‰è‚‰ç±»ã€‚æ²¹å°åŽç‚’制应略微å‡å°‘底油,油炸则没有区别 + +## 几ç§è¾ƒä¸ºé€šç”¨çš„è…Œæ¸å…¬å¼ + +- 牛肉:使用适é‡ç”ŸæŠ½ï¼Œå°‘釿–™é…’,少é‡ç™½ç ‚ç³–è…Œæ¸ã€‚æ ¹æ®å£å‘³é€‰ç”¨é£Ÿç›ï¼ˆè¡¥å……å’¸å‘³ï¼‰ï¼Œèšæ²¹å’Œæžå°‘釿µ·é²œé…±ï¼ˆèšæ²¹ç‰›è‚‰ï¼‰ï¼Œäº”é¦™ç²‰/å三香(洋葱炒牛肉)。慎用葱姜 +- 鸡肉(包括鸡胸肉和鸡翅):使用适é‡ç”ŸæŠ½ï¼Œè¾ƒå°‘é‡ç™½ç ‚ç³–ï¼Œå°‘é‡æ–™é…’è…Œæ¸ã€‚æ ¹æ®å£å‘³é€‰ç”¨é£Ÿç›ï¼ˆè¡¥å……咸味),五香粉/å三香(炸鸡米花),æžå°‘é‡è€æŠ½ï¼ˆé¦™ç…Žé¸¡ç¿…中) +- 白色鱼肉:使用适é‡é£Ÿç›ï¼Œå°‘釿–™é…’/黄酒腌æ¸ã€‚æ ¹æ®å£å‘³é€‰ç”¨æµ·é²œé…±/海鲜酱油/蒸鱼豉油(香煎带鱼),葱姜(水)(烤带皮鱼肉) +- 红色鱼肉:使用适é‡ç”ŸæŠ½ï¼Œå°‘釿–™é…’è…Œæ¸ã€‚æ ¹æ®å£å‘³é€‰ç”¨æµ·é²œé…±æ²¹/å°‘é‡è’¸é±¼è±‰æ²¹ï¼ˆé¦™ç…Žä¸‰æ–‡é±¼ï¼‰ï¼Œçº¢ç³–(北欧香烤三文鱼) +- 猪è‚:使用适é‡ç”ŸæŠ½ï¼Œé€‚釿–™é…’è…Œæ¸ã€‚æ ¹æ®å£å‘³é€‰ç”¨ç”Ÿç²‰å’Œé€‚é‡è€æŠ½ï¼ˆæ»‘炒猪è‚),少é‡ç³–ç­‰ + +## èœå“实战示例 + +- 洋葱炒牛肉:以一人份的 150g 牛肉为例。牛肉应切片,æˆèœå£æ„Ÿåº”嫩滑,需炒制 + - 生抽 10ml(约 2 汤匙) + - 料酒 5ml(约 1 汤匙) + - 白砂糖 2.5-10g(约 1-4 茶匙,根æ®å£å‘³ç”œåº¦é€‰æ‹©ï¼‰ + - 孜然粉 5g(约 2 茶匙) + - 生粉 10-15g(约 1 å°æŠŠï¼‰ + - æ²¹ 10ml(约 2 汤匙) + - (å¯é€‰ï¼‰å三香 1g(约 0.5 茶匙) + - (å¯é€‰ï¼‰é»‘胡椒粉 1g(约 0.5 茶匙) + +- èšæ²¹ç‰›è‚‰ï¼šä»¥ä¸€äººä»½çš„ 150g 牛肉为例。牛肉应切片,æˆèœå£æ„Ÿåº”嫩滑且上浆感足,此èœå£æ„Ÿå甜,需炒制 + - 生抽 5ml(约 1 汤匙) + - 料酒 5ml(约 1 汤匙) + - èšæ²¹ 10-20ml(约 2-4 汤匙,根æ®å£å‘³å’¸åº¦é€‰æ‹©ï¼Œèšæ²¹æ¯”è¾ƒå’¸ï¼‰ + - 白砂糖 5-15g(约 2-6 茶匙,根æ®å£å‘³ç”œåº¦é€‰æ‹©ï¼‰ + - 生粉 25-35g(约 1 大把) + - æ²¹ 10ml(约 2 汤匙) + +- 五香ç›é…¥é¸¡ï¼šä»¥ä¸€äººä»½çš„ 150g 鸡胸肉为例。鸡肉应切æˆéª°å­å½¢çŠ¶ï¼Œéœ€ç‚¸åˆ¶ + - 生抽 10ml(约 2 汤匙) + - 料酒 2.5ml(约 0.5 汤匙) + - 五香粉 5g(约 2 茶匙)或å三香 2.5-5g(约 1-2 茶匙) + - (å¯é€‰ï¼‰å­œç„¶ç²‰ 1g(约 0.5 茶匙) + - (å¯é€‰ï¼‰ç™½èƒ¡æ¤’粉 1g(约 0.5 茶匙) + +- 蜜æ±çƒ¤é¸¡ç¿…:以一人份的 250g 带骨鸡翅中为例。鸡翅上应切几é“花刀,æˆèœå’¸ç”œï¼Œä½†çªå‡ºç”œå£ï¼Œéœ€çƒ¤åˆ¶ + - 生抽 10ml(约 2 汤匙) + - 料酒 2.5ml(约 0.5 汤匙) + - 白砂糖 5-15g(约 2-6 茶匙,根æ®å£å‘³ç”œåº¦é€‰æ‹©ï¼‰ + - 蜂蜜/糖浆 10-20ml(约 2-4 汤匙,根æ®å£å‘³ç”œåº¦é€‰æ‹©ã€‚如白砂糖超过或等于 10g,建议åªåŠ å…¥ 10ml) + - (å¯é€‰ï¼‰äº”香粉 2.5g(约 1 茶匙。ä¸å¯ç”¨å三香) + +- 香烤三文鱼:以一人份的 200g 去骨三文鱼排为例。鱼肉ä¸åº”改刀,需烤箱烤制 + - 生抽 10ml(约 2 汤匙) + - 料酒 2.5ml(约 0.5 汤匙) + - 红糖 10-20g(约 4-8 茶匙,根æ®å£å‘³ç”œåº¦é€‰æ‹©ï¼‰ + - æ„大利黑醋/镇江香醋 2.5-5ml(约 0.5-1 汤匙,根æ®å£å‘³é…¸åº¦é€‰æ‹©ï¼‰ + - 肉豆蔻粉 2.5g(约 1 茶匙) + - 百里香粉 1g(约 0.5 茶匙) + - 姜粉 1g(约 0.5 茶匙) + - 迷迭香粉 1-2g(约 0.5-1 茶匙) + - (å¯é€‰ï¼‰ç™½èƒ¡æ¤’粉 1g(约 0.5 茶匙) + - (å¯é€‰ï¼‰å¹²è¾£æ¤’碎 2.5-10g(约 1-4 茶匙,根æ®å£å‘³è¾£åº¦é€‰æ‹©ï¼‰ diff --git a/assets/md/tips/learn/学习蒸.md b/assets/md/tips/learn/学习蒸.md new file mode 100644 index 0000000..cc888cf --- /dev/null +++ b/assets/md/tips/learn/学习蒸.md @@ -0,0 +1,22 @@ +# è’¸ + +## æ–¹å¼ + +### 蒸锅 + +蒸锅为多层结构,最底部用于盛水,利用水开åŽäº§ç”Ÿçš„æ°´è’¸æ°”的热é‡ï¼ŒåŠ çƒ­ä¸Šå±‚é£Ÿç‰©ã€‚ + +è’¸é”…æœ€åº•å±‚åŠ å…¥é€‚é‡æ°´â€”—将食物放于上层蒸屉中——蒸锅放于ç«ä¸ŠåŠ çƒ­ + +### é“é”… + +å¦‚æžœæ²¡æœ‰è’¸é”…ï¼Œåªæœ‰æ™®é€šçš„é“锅(éžå¹³åº•锅),å¯ä»¥åœ¨é”…底放置一个三脚架,并注入足够的水,以此达到类似于蒸锅的效果。 + +é“é”…åº•éƒ¨åŠ å…¥è¶³é‡æ°´â€”——放入三脚架———将食物置于三脚架上———开ç«å…³ç›– + +## 注æ„事项 + +* 由于热æºä¸ºæ°´è’¸æ°”,较低的蒸屉中的食物底部å¯èƒ½è¢«æ°´æµ¸æ¹¿ã€‚å¯å°†è’¸ç¬¼å¸ƒæ”¾åœ¨é£Ÿç‰©åº•下以é¿å…è¿™ç§æƒ…å†µã€‚ç”¨ç­·å­æ­ä¸ªæ”¾é£Ÿç‰©çš„简易支架也å¯ä»¥ã€‚ +* å¯ä»¥åˆ©ç”¨æ™ºèƒ½è®¾å¤‡è®¾ç½®è®¡æ—¶å™¨ï¼Œæé†’å…³ç«ï¼Œä»¥é˜²å¿˜è®°ä»¥è‡´çƒ§å¹²ã€‚ +* 在使用蒸笼制作食å“çš„è¿‡ç¨‹ä¸­ï¼Œéœ€è¦æ³¨æ„底部区域的剩余水é‡ï¼Œ**特别是é“é”…**,é¿å…干锅从而造æˆå®‰å…¨é—®é¢˜ã€‚ +* (å¯é€‰ï¼‰ä½¿ç”¨é“锅蒸食物时,å¯ä»¥åœ¨ä¸‰è„šæž¶ä¸Šé¢æ”¾ç½®ä¸€ä¸ªè’¸ç›˜ã€‚ diff --git a/assets/md/tips/learn/微波炉.md b/assets/md/tips/learn/微波炉.md new file mode 100644 index 0000000..0dc0dad --- /dev/null +++ b/assets/md/tips/learn/微波炉.md @@ -0,0 +1,47 @@ +# 使用微波炉 + +## 什么是微波炉 + +微波炉是 1945 å¹´ç”± [ç€è¥¿Â·å‹’巴朗·斯宾塞](https://en.wikipedia.org/wiki/Percy_Spencer) 呿˜Žçš„。 + +他在担任雷达系统工程师时,由于å‘现雷达一开å¯ä»–å£è¢‹é‡Œçš„巧克力棒就开始èžåŒ–ï¼Œä»Žè€Œäº§ç”Ÿæž„æƒ³å¹¶å‘æ˜Žçš„。 + +### å·¥ä½œæ–¹å¼ + +å¾®æ³¢æ—¶é€šè¿‡ç£æŽ§ç®¡åˆ¶é€ çš„é¢‘çŽ‡ 24.5 äº¿èµ«å…¹çš„ç”µç£æ³¢ï¼Œè¿™ä¸ªé¢‘çŽ‡ä¼šä½¿æ°´å’Œæ²¹çš„åˆ†å­æŒ¯åЍ并å‘热。 + +## æµç¨‹ + +微波炉在很多烹饪任务中效果相当出色。 + +强ç«é€‚用于: + +* [烹煮] çƒ¹ç…®è”¬èœ +* [烹煮] è½¯åŒ–å«æ°´çŽ‡é«˜çš„ç¡¬è´¨è”¬èœï¼ˆå¦‚é©¬é“ƒè–¯ã€æ´‹è‘±å’Œæœé²œè“Ÿï¼‰ +* [膨化] 爆点心,如泡芙ã€å°åº¦å¸•帕达姆薄脆饼ã€çˆ†ç±³èб + +中ç«é€‚用于: + +* [烹煮] 海鲜 (例如 [微波葱姜黑鳕鱼](../../dishes/aquatic/微波葱姜黑鳕鱼.md)) +* [烹煮] 软化肉类 +* [脱水] 干燥蔬果皮 +* [脱水] 制作肉干 +* [炸] 炸脆香料æ¤ç‰© +* [炸] 软化å¶ç±»è”¬èœ +* [加热] åŠ çƒ­å‰©èœ + +å¼±ç«ç”¨äºŽï¼š + +* [解冻] 解冻食物 +* [解冻] èžåŒ–黄油和巧克力 + +## 注æ„事项 + +* 微波炉ä¸åº”该被用于加热水,这å¯èƒ½ä¼šè‡´å…¶æš´æ²¸ï¼Œå¯¹æ‚¨çš„人身安全造æˆä¸€å®šå½±å“ +* ç»å¯¹ä¸è¦ä½¿ç”¨å¾®æ³¢ç‚‰åŠ çƒ­é¸¡è›‹ï¼é™¤éžå®ƒæ˜¯**去壳**çš„**生**é¸¡è›‹ï¼Œå¹¶ä¸”éœ€è¦æˆ³ç ´è›‹é»„。因为加热会使鸡蛋内部的气体膨胀,导致爆炸。 +* ä¸ç®¡æ‚¨æ˜¯å‡ºäºŽä»€ä¹ˆç›®çš„,å³ä½¿æ‚¨å¾ˆå¥½å¥‡ï¼Œä¹Ÿä¸åº”该使用微波炉加热完整的水果(如 è‘¡è„ã€è“莓ã€åœ£å¥³æžœï¼‰ï¼Œè¿™å¯èƒ½ä¼šä½¿å…¶çˆ†ç‚¸ +* 由于微波的波长为 12.2 cm,因此微波炉加热å°ç‰©ä½“çš„é€Ÿåº¦è¦æ¯”大物体慢。因此如果是很å°çš„食æï¼Œå»ºè®®èšé›†åœ¨ä¸€èµ·è¿›è¡ŒåŠ çƒ­ã€‚ +* 微波仅能深入食物几厘米,因此有时候外部很烫了,内部å¯èƒ½è¿˜æ˜¯å†°å‡‰çš„。解决办法是将食æåŠ ä¸Šå°‘é‡æ¶²ä½“放进密å°è¢‹ï¼Œæˆ–放入碗中åŽè’™ä¸Šä¿é²œè†œï¼Œè®©å®¹å™¨å†…产生足够的蒸汽æ¥å¼¥è¡¥å¾®æ³¢ç‚‰å®¹æ˜“烹饪ä¸å‡çš„缺点。 + * tips:打开密å°è¢‹æ—¶ï¼Œå½“心蒸汽喷出 +* 微波åªèƒ½åŠ çƒ­æ°´å’Œæ²¹ç­‰å«æœ‰æ¶²ä½“分å­çš„物体,因此ä¿é²œè†œå’Œå¯†å°è¢‹éƒ½ä¸ä¼šè¢«å¾®æ³¢åŠ çƒ­ã€‚ +* 金属能够å射微波而ä¸ä¼šè¢«åŠ çƒ­ï¼Œè¯·é¿å…ä½¿ç”¨å«æœ‰é‡‘è¾¹ã€é‡‘属花纹的容器,é¿å…å—热ä¸å‡å¯¼è‡´å®¹å™¨ç ´è£‚。请务必使用瓷ã€çŽ»ç’ƒå®¹å™¨æˆ–å¾®æ³¢ç‚‰ä¸“ç”¨çƒ¤ç›˜ã€‚ diff --git a/assets/md/tips/learn/空气炸锅.md b/assets/md/tips/learn/空气炸锅.md new file mode 100644 index 0000000..db0ee2b --- /dev/null +++ b/assets/md/tips/learn/空气炸锅.md @@ -0,0 +1,65 @@ +# 使用空气炸锅 + +## 什么是空气炸锅 + +空气炸锅为一ç§ç”µå­ç‚Šå…·ï¼Œç”¨ç©ºæ°”替代原本热油加热,让食物å˜ç†Ÿï¼Œä»¤é£Ÿææ— éœ€é‡æ²¹ä¹Ÿèƒ½è¾¾åˆ°è¿‘似油炸的效果。 + +### å·¥ä½œæ–¹å¼ + +空气炸锅借由上方的加热器产生高温热风,让热空气在食物周é­å¾ªçޝ坹æµï¼Œå¿«é€ŸåŠ çƒ­é£Ÿç‰©è‡ªèº«çš„æ²¹è„‚ï¼Œå¸¦èµ°é£Ÿç‰©çš„æ°´åˆ†ï¼Œäº§ç”Ÿæ²¹ç‚¸çš„æ•ˆæžœï¼Œå¹¶åˆ›é€ ç±»ä¼¼æ²¹ç‚¸é£Ÿç‰©çš„é…¥è„†æ„Ÿã€‚ + +### 优点 + +* 由于无需添加食用油,因此å¯ä»¥**大幅å‡å°‘**æ‘„å…¥å«æœ‰é«˜é‡è„‚肪和热é‡çš„食用油。 +* 高速循环的热空气使食物脱水,表é¢å˜å¾—金黄酥脆,让食物å˜å¾—外焦里嫩。 +* æ“作简å•,对新人å‹å¥½ã€‚ + +## æµç¨‹ + +* 将空气炸锅放在稳固ã€å¹³æ•´ä¸”水平的隔热表é¢ä¸Šã€‚ +* å–å‡ºç…Žé”…ï¼Œå°†é£Ÿææ”¾å…¥ç‚¸ç¯®ï¼Œå°†ç…Žé”…滑入产å“中。 +* 修改预设温度,旋转旋钮调整烹饪时间。 +* 调整好烹饪时间åŽï¼Œäº§å“将开始烹饪,等待定时器å“铃时烹饪完æˆã€‚ +* 将炸篮中的食物全部倒入碗或碟中。务必从所用煎锅中å–出装有原料的炸篮,因为煎锅底部**å¯èƒ½æ®‹ç•™æœ‰çƒ­æ²¹æˆ–油脂**。 + +## 注æ„事项 + +* 使用空气炸锅应注æ„设置温度ä¸å®œè¿‡é«˜ï¼ˆå°½é‡åœ¨ 120℃内,最好ä¸è¶…过 168℃),制作时间ä¸å®œå¤ªé•¿ï¼ˆçº¦ 10 分钟左å³ï¼‰ï¼Œé¿å…生æˆè¿‡å¤šæœ‰å®³æˆåˆ†[丙烯酰胺](https://zh.wikipedia.org/wiki/%E4%B8%99%E7%83%AF%E9%85%B0%E8%83%BA)。 +* å‡å°‘用空气炸锅烹饪淀粉类食物,如土豆ã€é¢åŒ…ã€æ²¹æ¡ç­‰ï¼Œå¯ç›¸åº”å‡å°‘[丙烯酰胺](https://zh.wikipedia.org/wiki/%E4%B8%99%E7%83%AF%E9%85%B0%E8%83%BA)摄入。相对而言,空气炸锅适åˆçƒ¹è°ƒè„‚肪或水分å«é‡æ›´é«˜çš„食物,如肉类ã€è”¬èœã€‚ +* 使用过程中,ä¸èƒ½é®æŒ¡é¡¶éƒ¨çš„进风å£å’ŒèƒŒé¢çš„出风å£ã€‚ç”¨æ‰‹é®æŒ¡çš„è¯ï¼Œå¯èƒ½ä¼šè¢«**热空气烫伤**。 +* ä¸åŒå“牌炸锅温差å¯è¾¾Â±10℃,首次å°è¯•建议å‡å°‘ 10%æ—¶é—´åŽé€æ­¥è°ƒæ•´ + +## 烹饪建议 + +### 常用食物 + +| 食物åç§° | 温度(℃) | 时间(分钟) | 方法步骤 | +|---------|---------|--------|--------------------------------------------------------------------| +| **è–¯æ¡** | 200 | 15-20 | 1. å†·å†»è–¯æ¡æ— éœ€è§£å†»ï¼Œè¡¨é¢å–·å°‘釿²¹ï¼›- 2. 平铺炸篮(ä¸é‡å ï¼‰ï¼Œæ¯5分钟摇晃一次;- 3. 最åŽ2分钟å¯è°ƒè‡³210℃上色。 | +| **鸡翅** | 180 | 18-22 | 1. é¸¡ç¿…åˆ’åˆ€ï¼Œç”¨ç”ŸæŠ½ã€æ–™é…’ã€èšæ²¹ã€è’œæœ«è…Œåˆ¶1å°æ—¶ï¼›- 2. 平铺炸篮,表é¢åˆ·èœ‚蜜水;- 3. 烤10分钟åŽç¿»é¢ç»§ç»­çƒ¤ã€‚ | +| **鱼类** | 180-190 | 12-15 | 1. 鱼身两é¢åˆ’刀,用姜片ã€è‘±æ®µã€ç›ã€æ–™é…’腌制20分钟;- 2. 鱼表é¢åˆ·æ²¹ï¼Œåž«é”¡çº¸é˜²ç²˜ï¼›- 3. 中途翻é¢ä¸€æ¬¡ã€‚ | +| **牛排** | 200 | 8-12 | 1. 牛排室温回温,åŒé¢æ’’ç›ã€é»‘胡椒和橄榄油;- 2. 空气炸锅预热5åˆ†é’Ÿï¼Œç‰›æŽ’æ”¾å…¥åŽæ ¹æ®åŽšåº¦çƒ¤åˆ¶ï¼ˆæ¯é¢4-6分钟)。 | +| **牛肉å—** | 180 | 15-18 | 1. 牛肉切2cmç«‹æ–¹å—ï¼Œç”¨ç”ŸæŠ½ã€æ·€ç²‰ã€é»‘胡椒腌制30分钟;- 2. 平铺炸篮,烤10分钟åŽç¿»åŠ¨ä¸€æ¬¡ï¼›- 3. å¯åŠ æ´‹è‘±ã€å½©æ¤’åŒçƒ¤ã€‚ | +| **猪肉排** | 175-185 | 16-20 | 1. çŒªæŽ’ç”¨åˆ€èƒŒæ‹æ¾ï¼Œç”ŸæŠ½ã€è’œç²‰ã€äº”香粉腌制40分钟;- 2. 表é¢å–·æ²¹ï¼Œåž«çƒ˜ç„™çº¸ï¼›- 3. 中途翻é¢å¹¶åˆ·è…Œæ–™æ±ã€‚ | +| **蛋挞** | 170-180 | 12-15 | 1. 蛋挞皮解冻åŽå€’入自制蛋液(牛奶+淡奶油+ç³–+蛋黄);- 2. 炸锅无需预热,烤至挞皮金黄ã€ä¸­å¿ƒå¾®ç„¦å³å¯ã€‚ | +| **蛋糕** | 160 | 25-30 | 1. 6寸模具垫油纸,倒入蛋糕糊(7分满);- 2. 低温慢烤,æ’入牙签无粘连å³ç†Ÿï¼›- 3. 倒扣冷å´é˜²å¡Œé™·ã€‚ | +| **披è¨** | 180-190 | 8-12 | 1. å†·å†»æŠ«è¨æ— éœ€è§£å†»ï¼Œå¯æ’’é¢å¤–èŠå£«ï¼›- 2. 垫锡纸防æ¼ï¼Œçƒ¤è‡³èŠå£«èµ·ç„¦æ–‘ï¼›- 3. 自制披è¨éœ€å…ˆçƒ¤é¥¼åº•5分钟å†åŠ æ–™ã€‚ | +| **花生米** | 160 | 10-12 | 1. 生花生米浸泡5åˆ†é’ŸåŽæ²¥å¹²ï¼›- 2. 喷少釿²¹+ç›æ‹ŒåŒ€ï¼›- 3. 平铺å•层,æ¯3分钟摇晃一次。 | + +### æ“作è¦ç‚¹ + +1. **预处ç†å…³é”®** + - è‚‰ç±»éœ€å……åˆ†è§£å†»å¹¶æ“¦å¹²è¡¨é¢æ°´åˆ†ï¼ˆç‰›æŽ’/猪排建议室温回温) + - 冷冻食å“(薯æ¡/披è¨ï¼‰å¯ç›´æŽ¥çƒ¹é¥ªï¼Œä½†éœ€åŠ å¤§æ‘‡æ™ƒ/ç¿»é¢é¢‘率 + +2. **防粘技巧** + - 鱼类/蛋糕等易粘食物建议垫烘焙纸或锡纸 + - 炸篮底部å¯é“ºæ´‹è‘±ç‰‡/柠檬片æå‡é£Žå‘³å¹¶éš”ç¦»æ±æ°´ + +3. **上色控制** + - æœ€åŽ 2-3 分钟调高 10-20℃å¯ä½¿è¡¨é¢æ›´é…¥è„†ï¼ˆé€‚用于薯æ¡/鸡翅) + - 蛋挞/蛋糕表é¢åŠ ç›–é”¡çº¸å¯é˜²æ­¢è¿‡åº¦ç„¦åŒ– + +4. **熟度检测** + - è‚‰ç±»ï¼šç”¨ç­·å­æŒ‰åŽ‹ï¼Œç¡¬æŒºä¸ºå…¨ç†Ÿï¼ŒæŸ”è½¯å¸¦å¼¹æ€§ä¸ºåŠç†Ÿ + - 蛋糕:牙签æ’入中心无é¢ç³Šç²˜è¿žå³ç†Ÿ diff --git a/assets/md/tips/learn/食å“安全.md b/assets/md/tips/learn/食å“安全.md new file mode 100644 index 0000000..d1dfe8a --- /dev/null +++ b/assets/md/tips/learn/食å“安全.md @@ -0,0 +1,115 @@ +# 食å“安全 + +## 中毒 + +以下食物有造æˆä¸­æ¯’的风险: + +* 未æˆç†Ÿçš„é’西红柿 +* 未熟é€çš„四季豆(芸豆)ã€è±‡è±†ï¼ˆè±†è§’)ã€[白刀豆](https://zh.wikipedia.org/wiki/%E7%99%BD%E5%88%80%E8%B1%86) +* å‘芽的土豆(山è¯ï¼‰ã€ç•ªè–¯ï¼ˆçº¢è–¯ï¼‰ã€èŠ±ç”Ÿ +* 未正确处ç†/未熟é€çš„[黄花èœ](https://zh.m.wikipedia.org/wiki/%E9%BB%84%E8%8A%B1%E8%8F%9C)å¯å¯¼è‡´[秋水仙碱](https://zh.m.wikipedia.org/wiki/%E7%A7%8B%E6%B0%B4%E4%BB%99%E7%B4%A0)中毒 +* 生豆浆 +* æ³¡å‘æ—¶é—´è¿‡é•¿çš„æœ¨è€³ï¼ˆä¸æ­¢æœ¨è€³ï¼Œæ‰€æœ‰èŒç±»æ³¡å‘æ—¶é—´è¿‡é•¿å‡æœ‰ä¸­æ¯’风险) +* æœªçƒ¹é¥ªç†Ÿçš„åŠ¨ç‰©å†…è„ +* ä¸è®¤è¯†çš„è˜‘è‡æˆ–者未煮熟的蘑è‡ï¼ˆæœ‰å¥è°šè¯­è¯´ï¼šçº¢ä¼žä¼žï¼Œç™½æ†æ†ï¼Œåƒå®Œä¸€èµ·èººæ¿æ¿ã€‚一般æ¥è¯´ï¼Œè¶Šæ˜¯æ¼‚亮的蘑è‡è¶Šå±é™©ã€‚) +* ……(欢迎补充) + +酸性食物在é“制容器中较长时间储存&çƒ¹é¥ªåŒæ ·ä¹Ÿä¼šæœ‰é€ æˆä¸­æ¯’的风险, 如 + +* é…¸èœ +* 笋干 +* 番茄酱 +* æŸ æª¬æ± +* å¤è‚‰ +* é…±æ²¹å’Œé…±èœ +* ……(欢迎补充) + +## è¿‡æ• + +以下为常è§è¿‡æ•食物(注æ„:过æ•å应一般情况下是终生的): + +æˆäººï¼š + +* 虾ã€èŸ¹ã€è´ç±»æµ·é²œï¼ˆé£Ÿç”¨ä¸æ–°é²œè€Œå¯¼è‡´ç»†èŒæ»‹ç”Ÿçš„æµ·é²œï¼‰ +* 花生 +* åšæžœ +* 鱼类 + +儿童: + +* 花生 +* åšæžœ +* 蛋类 +* ç‰›å¥¶ï¼ˆä¸»è¦æ˜¯å¯¹ç‰›å¥¶ä¸­ A1 蛋白ä¸è€å—) +* å°éº¦å’Œå¤§è±† + +## 沙门æ°èŒæ„ŸæŸ“ + +沙门æ°èŒè¾ƒå¸¸è§äºŽåŠ¨ç‰©æºæ€§é£Ÿç‰©ï¼ŒåŒ…括蔬èœä¹Ÿå¯èƒ½å› å—ç²ªä¾¿æ±¡æŸ“è€Œå«æœ‰æ²™é—¨æ°èŒã€‚ + +ä¸‹åˆ—é£Ÿå“æœ‰é€ æˆæ²™é—¨æ°èŒæ„ŸæŸ“的风险: + +* 未完全煮熟的蛋 +* 未完全煮熟的肉 +* 未ç»è¿‡æ€èŒçš„奶 + +## 黄曲霉素 + +黄曲霉素常由黄曲霉åŠå¯„生曲霉等å¦å¤–几ç§éœ‰èŒåœ¨éœ‰å˜çš„谷物中产生,如大米ã€è±†ç±»ã€èŠ±ç”Ÿç­‰ï¼Œæ˜¯ç›®å‰ä¸ºæ­¢æœ€å¼ºçš„致癌物质。加热至 280℃以上æ‰å¼€å§‹åˆ†è§£ï¼Œæ‰€ä»¥ä¸€èˆ¬çš„åŠ çƒ­ä¸æ˜“ç ´å其结构。 + +ä¸‹åˆ—é£Ÿå“æœ‰é€ æˆé»„曲霉素中毒的风险: + +* è…å的花生 +* è…å的大米 +* è…å的玉米 + +注æ„,以上食å“还包括其对应制å“,如米粉ã€çŽ‰ç±³é¢ï¼› ç»å®‰å…¨åŸ¹è‚²ç”Ÿäº§çš„花生苗(å‘芽花生)å¯ä»¥è®¤ä¸ºå®‰å…¨å¯é£Ÿç”¨ã€‚ + +## 3-ç¡åŸºä¸™é…¸ + +3-ç¡åŸºä¸™é…¸ç”±è”—生节è±å­¢èŒäº§ç”Ÿï¼Œè¯¥çœŸèŒå¸¸è§å¯„ç”ŸäºŽç”˜è”—ã€æ¤°å­ä¸­ã€‚中毒症的主è¦è¡¨çŽ°ä¸ºä¸­æž¢ç¥žç»ç³»ç»Ÿå—æŸã€‚急性期的症状有呕åã€çœ©æ™•ã€é˜µå‘性抽æã€çœ¼çƒåä¾§å‡è§†ã€æ˜è¿·ï¼Œç”šè‡³æ­»äº¡ï¼ŒåŽé—症主è¦ä¸ºé”¥ä½“外系的æŸå®³ï¼Œä¸»è¦ç—‡çŠ¶æœ‰å±ˆæ›²ã€æ‰­è½¬ã€ç—‰æŒ›ï¼Œè‚¢ä½“å¼ºç›´ï¼Œé™æ­¢æ—¶å¼ åŠ›å‡ä½Žç­‰ã€‚该毒素暂无特效解毒è¯ç‰©ã€‚ + +ä¸‹åˆ—é£Ÿå“æœ‰é€ æˆ 3-ç¡åŸºä¸™é…¸ä¸­æ¯’的风险: + +* 红心甘蔗 +* [è…å的椰å­](https://www.bilibili.com/video/BV1w84y147TU) + +除了视觉上的外观作为判断标准,气味也是é‡è¦çš„评价标准。è…åçš„æ¤ç‰©å¾€å¾€æ•£å‘特殊的气味(酒糟味ã€é…¸å‘³ç­‰ï¼‰ã€‚对闻起æ¥è…åçš„é£Ÿç‰©æœ€å¥½çš„å¤„ç†æ–¹å¼å°±æ˜¯ä¸¢å¼ƒã€‚ + +## 寄生虫 + +寄生虫å¯é€šè¿‡ç©ºæ°”,饮用水,食物和直接接触进入人体。若寄生虫进入人体循环系统,一方é¢å¯ä»¥æ”»å‡»ç™½ç»†èƒžï¼Œå¦ä¸€æ–¹é¢å¯è¾¾è‚ºã€è‚ç­‰è„器或是堵塞血管或淋巴管é“,会引起如è‚硬化ã€é—¨è„‰é«˜åŽ‹ã€è±¡çš®ç—…等疾病。而人如果是猪肉绦虫的中间宿主,寄生虫甚至会达眼çƒã€å¿ƒè„和大脑,å±åŠç”Ÿå‘½ã€‚ + +ä¸‹åˆ—é£Ÿå“æœ€å¥½ç¡®ä¿å®Œå…¨çƒ§ç†Ÿï¼Œå¦åˆ™å¯èƒ½åœ¨ä½“内留下相应的寄生虫: + +* 田螺:管圆线虫 +* 生鱼片:è‚å¸è™« +* 黄é³ï¼šé¢šå£çº¿è™« +* 牛蛙:曼æ°è£‚头蚴寄生虫 +* 猪肉:猪肉绦虫 +* 牛肉表é¢ï¼ˆåªè¦è¡¨é¢ç†Ÿäº†å°±å¯ä»¥åƒï¼‰ï¼šç‰›è‚‰ç»¦è™« + +## 食å“安全温度 + +é€šè¿‡è¶³å¤Ÿçš„æ¸©åº¦åŠ çƒ­é£Ÿç‰©å¹¶ä¿æŒä¸€å®šçš„æ—¶é—´ï¼Œå¯ä»¥åœ¨ä¸€å®šç¨‹åº¦ä¸Šå‡å°ç»†èŒã€å¯„生虫存活的风险。 +å„ç±»é£Ÿå“æœ‰ä¸åŒçš„æ¸©åº¦è¦æ±‚ï¼Œçƒ¹é¥ªè€…æµ‹é‡æ¸©åº¦åº”该使用厨房用温度计测é‡é£Ÿç‰©ä¸­å¿ƒæ¸©åº¦ã€‚ + +æµ‹é‡æ¸©åº¦åº”该使用:厨房用温度计 +测é‡é£Ÿç‰©ä¸­å¿ƒæ¸©åº¦ + +下列是业界标准的食物安全温度: + +| | æ•´å— | 碎肉 | å…¨åª | +|-----------|------------------------------------------------|-------|-------| +| 猪肉 | 71°C | 71°C | | +| 禽肉 | 74°C | 74°C | 85°C | +| 牛肉/羊肉 | 3 分熟:63°Cï¼›5 分熟:71°Cï¼›7 分熟:77°C | 71°C | | +| 剩èœå†åŠ çƒ­ | 74°C | | | + +## 食å“与è¯ç‰©è”用å应 + +部分食å“与è¯ç‰©è”åˆä½¿ç”¨ä¼šå¯¼è‡´ä¸¥é‡å®‰å…¨é£Žé™© +下列为一些有安全风险的行为: + +* 头孢与酒精 +* (欢迎补充...) diff --git a/assets/md/tips/learn/高压力锅.md b/assets/md/tips/learn/高压力锅.md new file mode 100644 index 0000000..93a8a31 --- /dev/null +++ b/assets/md/tips/learn/高压力锅.md @@ -0,0 +1,36 @@ +# 蒸(米)/炖(使用电饭煲/高压锅/电压力锅) + +## 什么是压力锅 + +压力锅其实是一般的锅加上å¯é”ç´§çš„åŠå¯†å°ç›–,盖上有阀门,å¯ç”¨äºŽæŽ§åˆ¶é”…内的压力。 + +### å·¥ä½œæ–¹å¼ + +åŽ‹åŠ›é”…çš„å·¥ä½œæ–¹å¼æ˜¯è®©è’¸æ±½ç§¯èšåœ¨é”…中,æé«˜é”…内的压力。锅内压力æé«˜æ—¶ï¼Œæ°´çš„æ²¸ç‚¹ä¹Ÿéšä¹‹æé«˜ï¼Œå¯ä½¿å«æ°´çš„食物烹煮温度超过 100 ℃。 + +### 优点 + +* 由于压力锅的实际烹饪温度较高,因此å¯ä»¥å¤§å¹…缩短烹饪时间。 +* 压力锅内部的高温å¯ä¿ƒè¿›è¤å˜å’Œç„¦ç³–化,能够产生独有的风味。 + +## æµç¨‹ + +* 食æå’Œæ°´æ”¾å…¥å†…胆åŽåˆç›–,**ç¡®ä¿é”…体密å°**,加热。 +* 对于韧性较大的食æï¼Œå¦‚蹄筋类食物,使用高压锅å¯ä»¥è¾ƒè½»æ¾å°†å…¶ç…®çƒ‚ï¼ŒèŽ·å¾—è¾ƒå¥½å£æ„Ÿã€‚ +* 压力锅通常有一个自é”阀(浮å­é˜€ï¼‰ã€‚在蒸煮时,éšç€é”…内压力增大,自é”阀会å¯åЍ并é”闭,隔ç»é”…内与锅外气体,为锅内增压创造æ¡ä»¶ã€‚自é”阀å¯åЍåŽè¿˜ä¼šé”ä½é”…盖,防止强行打开,起到安全ä¿éšœä½œç”¨ã€‚在蒸煮时需è¦ç¡®è®¤è‡ªé”阀ä¸è¢«å¼‚ç‰©é®æŒ¡ï¼Œè®©é«˜åŽ‹é”…æ­£å¸¸å·¥ä½œã€‚ +* 切æ¢è‡³ä¿æ¸©çжæ€åŽï¼Œ**通过排气阀将锅内蒸汽排空方å¯å¼€ç›–**。 + +### 注æ„事项 + +* **水蒸气很烫,ä¸è¦å‡‘到排气阀上。** +* 烹饪**æµè´¨é£Ÿç‰©**的过程中,**ä¸è¦æ‰‹åŠ¨æŽ’æ°”**,å°å¿ƒå–·æº…(å¯ä»¥å°†é£Ÿææ”¾å…¥å¯†å°ç½æˆ–者真空包装袋中å†ç”¨é«˜åŽ‹é”…çƒ¹é¥ªï¼‰ã€‚ +* 烹饪部分èœç³»ï¼ˆå¦‚汤类)手动放气**å¯èƒ½ä¼šå½±å“食物的味é“以åŠå£æ„Ÿ**。 +* 开盖å‰éœ€ç¡®è®¤è’¸æ°”已排空。开盖时请勿一次性全部打开,尤其是**ä¸è¦å¯¹ç€äººæ­£é¢å¼€ç›–**,以å…蒸气烫伤。 +* 蒸煮完æˆåŽï¼Œéšç€é«˜åŽ‹é”…å†…æ°”åŽ‹é™ä½Žè‡³ä¸Žå¤–界气压平衡,自é”阀会æ¾å¼€ã€‚这个å¯ä»¥ä½œä¸ºé”…盖是å¦èƒ½æ‰“开的判断标志。 +* 高压锅的密å°ä¾èµ–é”…ç›–é‡Œçš„å¯†å°æ©¡èƒ¶åœˆï¼Œå¯¹äºŽè€æ—§çš„é«˜åŽ‹é”…éœ€è¦æ£€æŸ¥å¯†å°æ©¡èƒ¶åœˆæ˜¯å¦ä»ç„¶æœ‰æ•ˆã€‚ +* 确认橡胶圈完全干净,任何微粒å¡åœ¨å…¶ä¸­éƒ½å¯èƒ½ç ´å密闭环境。 +* å¾ˆå¤šåŽ‹åŠ›é”…æœ‰ä¸€ä¸ªå®‰å…¨çº¿ï¼Œææ–™å’Œæ¶²ä½“ä¸åº”该超过这个线,太多的食æå’Œæ¶²ä½“å¯èƒ½ä¼šè®©æ°´è’¸æ°”喷涌堵塞排气阀,或喷溅出太多水蒸气ä¸å¥½æ¸…ç†ã€‚ +* 没有安全线的压力锅,最好也ä¸è¦è®©æ°´ä½çº¿è¶…过锅体的 2/3。 +* **ä¸è¦ä½¿ç”¨é«˜åŽ‹é”…çƒ¹é¥ªç‡•éº¦æˆ–è€…æŒ‚é¢ç­‰å®¹æ˜“产生泡沫的食物**。泡沫å¯èƒ½ä¼šé˜»å¡žè’¸æ±½é˜€å’Œæ³„压管。 +* 烹饪过程中,当压力阀å‡é«˜å¹¶å–·å‡ºè’¸æ±½æˆ–者烟雾时,说明高压锅内部过度加压,压力阀为了ä¿è¯å®‰å…¨ï¼Œé‡Šæ”¾å‡ºäº†å¤šä½™çš„压力。尽管喷出的蒸汽带有浓éƒçš„香味会带æ¥è¾ƒé«˜çš„æ„‰æ‚¦æ„Ÿï¼Œä½†ä¸€æ¥é£Ÿç‰©çš„风味有æŸå¤±ï¼ŒäºŒæ¥è¿‡åº¦åŠ åŽ‹å¯èƒ½ä¼šä½¿éƒ¨åˆ†ç±»åž‹é«˜åŽ‹é”…çš„å¡æ§½å¼¯æ›²ã€‚因此当看到喷出蒸汽时,å¯å‡å°ç«åŠ›ã€‚ +* tips:从侧é¢å¼€ç›–是一ç§ä¸é”™çš„选择。 diff --git a/assets/md/tips/厨房准备.md b/assets/md/tips/厨房准备.md new file mode 100644 index 0000000..4b6e458 --- /dev/null +++ b/assets/md/tips/厨房准备.md @@ -0,0 +1,142 @@ +# 厨房准备 + +在阅读和å‚考èœè°±ä¹‹å‰ï¼Œå‡æƒ³ä½ å·²ç»åœ¨åŽ¨æˆ¿ä¸­å‡†å¤‡å¥½äº†ä¸‹åˆ—ç‰©å“。这些物å“ä¸ä¼šåœ¨åŽŸææ–™å’Œå·¥å…·éƒ¨åˆ†æåŠã€‚ + +```text +燃气ç¶ï¼Œé¥®ç”¨æ°´ï¼Œç‚’锅,蒸锅,煮锅,电饭锅,食用油,洗èœç›†ï¼Œç¢Ÿå­ï¼Œç¢—,筷å­ï¼Œå‹ºå­ï¼Œæ±¤å‹ºï¼Œæ¼å‹ºï¼Œæ´—涤剂,抹布,钢ä¸çƒï¼Œèœåˆ€ï¼Œç”Ÿé£Ÿæ¡ˆæ¿ï¼Œç†Ÿé£Ÿæ¡ˆæ¿ï¼Œå‰Šçš®åˆ€ï¼Œçƒ­æ°´å£¶ +``` + +ä¸‹åˆ—ææ–™å¯èƒ½ä¼šè¢«é«˜é¢‘使用。建议æå‰ä¸ºåŽ¨æˆ¿é‡‡è´­å¥½ï¼Œå¹¶æ°¸è¿œä¿éšœæœ‰æ–°é²œçš„å¯ä»¥å–用。 + +```text +大葱,å°è‘±ï¼Œç”Ÿå§œï¼Œå¤§è’œï¼ŒèŠ±æ¤’ï¼Œå…«è§’ï¼Œæ¡‚çš®ï¼Œé¦™å¶ +干辣椒,å°ç±³æ¤’ï¼Œç”ŸæŠ½ï¼Œè€æŠ½ï¼Œèšæ²¹ï¼Œæ–™é…’(黄酒,å¯é€‰ï¼‰ +黑醋(香醋ã€é™ˆé†‹),白醋,豆瓣酱,冰糖,棉白糖,ç›ï¼Œå‘³ç²¾\鸡精 +黑胡椒,白胡椒,五香粉,玉米淀粉,番薯淀粉 +``` + +如果你需è¦åº”对çªå‘æƒ…å†µæˆ–é•¿æœŸå±…å®¶éœ€æ±‚ï¼Œå»ºè®®åŒæ ·é‡‡è´­å¥½ä¸‹åˆ—内容: + +```text +冰箱ã€å¾®æ³¢ç‚‰ã€ä¿é²œè†œã€ä¿é²œè¢‹ +鸡蛋ã€é’椒ã€èƒ¡èåœã€é»„瓜ã€è¥¿çº¢æŸ¿ã€æœ¨è€³ã€é‡Œè„Šè‚‰ã€èŒ„å­ã€ç±³ã€æŒ‚颿ˆ–æ–¹ä¾¿é¢ +``` + +如果你éžå¸¸æƒ³è¿½æ±‚å½¢å¼åŒ–ã€æ ‡å‡†åŒ–å’Œä»ªå¼æ„Ÿï¼Œå¹¶ä¸”想拥有一个与众ä¸åŒçš„æœ‰è¶£åŽ¨æˆ¿ï¼Œé‚£å°±åŒæ ·é‡‡è´­ä¸‹åˆ—内容: + +```text +电å­ç§¤ï¼ˆæˆ–å¤©å¹³ï¼‰ã€æ¸¸æ ‡å¡å°ºã€é‡ç­’ã€åœè¡¨ã€çƒ§æ¯ã€æµ‹æ¸©æžªã€ç§»æ¶²å™¨ +``` + +如果你想节约时间,å¯ä»¥è´­ä¹°åŠæˆå“并简å•处ç†åŽé£Ÿç”¨ï¼š + +```text +预炸过的炸鸡å—ã€å†·å†»æ‰‹æŠ“饼ã€åŒ…好的饺å­ã€è¢‹è£…å’–å–±ã€å„ç§ä¸¼ç±»ï¼ˆç›–饭)ã€è‡ªçƒ­é£Ÿå“ã€æ‹Œé¢æ–™åŒ…ã€å¤–å–åŒ…ã€æ–¹ä¾¿é£Ÿå“ +``` + +其它针对æ¯é“èœçš„åŽŸææ–™ï¼Œè¯·å…·ä½“å‚考èœå“本身的`æ‰€éœ€åŽŸææ–™`章节。 + +## 选购油 + +在选购油之å‰ï¼Œéœ€è¦äº†è§£ä¸€äº›è„‚肪酸的基础知识 + +### 脂肪酸的分类 + +脂肪酸分为: + +* 饱和脂肪酸 (尽é‡é¿å…) +* ä¸é¥±å’Œè„‚肪酸 + * 顺å¼è„‚肪酸 + * åå¼è„‚肪酸 (尤其注æ„é¿å…) + * 多ä¸é¥±å’Œè„‚肪酸 + * å•ä¸é¥±å’Œè„‚肪酸 + +饱和脂肪酸在室温下会呈固æ€ï¼Œè€Œä¸é¥±å’Œè„‚肪酸在室温下会呈液æ€ã€‚ + +### é¿å…的脂肪酸 + +其中,**饱和脂肪酸**å’Œ**åå¼è„‚肪酸**一般是被认为ä¸å¥åº·çš„。 + +饱和脂肪酸会增加肥胖ã€é«˜èƒ†å›ºé†‡ã€å¿ƒè„病的风险。 + +研究表明,长期过é‡é£Ÿç”¨æ°¢åŒ–加工产生的åå¼è„‚肪酸å¯å¼•起人体血脂代谢异常,从而增加心血管疾病å‘生的风险。也有研究显示å¯èƒ½ä¼šå¢žåŠ ç³–å°¿ç—…ã€è‚¥èƒ–等慢性疾病的患病风险。 + +世界å«ç”Ÿç»„织建议:为增进心血管å¥åº·ï¼Œåº”å°½é‡æŽ§åˆ¶è†³é£Ÿä¸­çš„åå¼è„‚肪酸,最大摄å–é‡ä¸è¶…过总能é‡çš„ 1%。也就是说,如果按一个æˆå¹´äººå¹³å‡æ¯å¤©æ‘„å…¥èƒ½é‡ 2000 åƒå¡æ¥ç®—,则æ¯å¤©æ‘„å…¥åå¼è„‚肪酸ä¸åº”超过 2.2 克。 + +GB 28050-2011 规定,食å“é…æ–™å«æœ‰æˆ–生产过程中使用了氢化和(或)部分氢化油脂时,在è¥å…»æˆåˆ†è¡¨ä¸­è¿˜åº”标示出åå¼è„‚肪(酸)的å«é‡ã€‚ + +### 食å“中的的åå¼è„‚肪酸 + +æ ¹æ®ç›¸å…³è°ƒæŸ¥ï¼Œç„™çƒ¤é£Ÿå“(糕点ã€é¥¼å¹²ã€é¢åŒ…等)ã€è°ƒå‘³å“ã€æ²¹ç‚¸é£Ÿå“çš„åå¼è„‚肪酸平å‡å«é‡åœ¨ 0.30~0.50 g/100g 之间。 + +å› æ­¤ä¸å¿…太过担心——日常食å“中的åå¼è„‚肪酸并ä¸è¶³ä»¥å±å®³å¥åº·ã€‚但以防万一,在选购零食时,ä¸å¦¨å…³æ³¨è¥å…»æˆåˆ†è¡¨ä¸­æ ‡æ³¨çš„ `åå¼è„‚肪(酸)` å«é‡ã€‚ + +### 烹饪中的åå¼è„‚肪酸 + +æ® 2021 年调查显示,我国æ¤ç‰©æ²¹çš„åå¼è„‚肪酸平å‡å«é‡ä¸º 0.86 g/100g,无需太过担心。 + +è¦é¢å¤–注æ„çš„åå¼è„‚è‚ªé…¸æ¥æºæ˜¯çƒ¹é¥ªè¿‡ç¨‹ï¼š + +æ¤ç‰©æ²¹ä¸­å¾€å¾€å«æœ‰è¾ƒé«˜æ¯”例的多ä¸é¥±å’Œè„‚肪酸,热稳定性比较差,容易在高温下转化æˆåå¼è„‚肪。 + +因此,在ä¸åŒåœºæ™¯ä¸‹ï¼Œæˆ‘们需è¦åˆç†é€‰æ‹©æ²¹å“,并尽å¯èƒ½å‡å°‘æ²¹å“的加热时间。 + +### æ¤ç‰©æ²¹çš„选择 + +| æ²¹å“åç§° | 饱和脂肪酸 (%) | Omega 3 (%) | Omega 6 (%) | Omega 9 (%) | +| :----: | :----: | :----: | :----: | :----: | +| 芥花油 | 7% | 11% | 21% | 61% | +| 亚麻籽油 | 9% | 57% | 16% | 18% | +| 葵花油 | 12% | 1% | 71% | 16% | +| 玉米油 | 13% | 1% | 57% | 29% | +| 橄榄油 | 15% | 1% | 9% | 75% | +| 大豆油 | 15% | 8% | 54% | 23% | +| 花生油 | 19% | 0% | 33% | 48% | +| 棉籽油 | 27% | 0% | 54% | 19% | +| 猪油 | 43% | 1% | 9% | 47% | +| 棕榈油 | 51% | 0% | 10% | 39% | +| 牛油 | 68% | 1% | 3% | 28% | +| æ¤°å­æ²¹ | 91% | 0% | 2% | 7% | + +* `花生油`富å«`å•ä¸é¥±å’Œè„‚肪`。但åªå»ºè®®é€‰æ‹©é«˜å“è´¨çš„ã€‚åŠ å·¥æ—¶ä¹Ÿè¦æ³¨æ„ä¸è¦åŠ çƒ­è¿‡ä¹…ä»¥å…产生`åå¼è„‚肪酸`。 +* `橄榄油`富å«`å•ä¸é¥±å’Œè„‚肪`ï¼Œå…¶åªæœ‰ä¸€ä¸ªä¸é¥±å’Œé”®ã€‚橄榄油`饱和脂肪酸`å«é‡å°‘。但åªå»ºè®®é€‰æ‹©é«˜å“è´¨çš„ã€‚åŠ å·¥æ—¶ä¹Ÿè¦æ³¨æ„ä¸è¦åŠ çƒ­è¿‡ä¹…ä»¥å…产生åå¼è„‚肪酸。 +* `大豆油`ä¸å«`饱和脂肪酸`ï¼Œä¸”å«æœ‰äºšæ²¹é…¸ã€ç»´ç”Ÿç´ ã€‚但大豆油ä¸ç¨³å®šï¼Œå®¹æ˜“在加工时产生`åå¼è„‚肪酸`,因此ä¸å»ºè®®é•¿æœŸé£Ÿç”¨ï¼Œå¯ä»¥ç”¨äºŽå‡‰æ‹Œã€‚ +* `èœç±½æ²¹`热稳定性好,富å«`多ä¸é¥±å’Œè„‚肪酸`,但å¯èƒ½å«æœ‰èŠ¥é…¸ï¼Œå¯èƒ½ä¼šå¼•èµ·è„‚è‚ªæ²‰ç§¯å’Œå¿ƒè„æŸä¼¤ã€‚èœç±½æ²¹ç¼ºå°‘亚油酸,è¥å…»ä»·å€¼è¾ƒä½Žï¼Œå®¹æ˜“è…败。 +* `æ¤°å­æ²¹`çš„`饱和脂肪酸`éžå¸¸é«˜ï¼Œçƒ­ç¨³å®šæ€§å¥½ï¼Œä½†æ³¨æ„有些食å“ä¼šä½¿ç”¨æ°¢åŒ–æ¤°å­æ²¹ã€‚适åˆåœ¨åŽ¨æˆ¿ç”¨äºŽç…Žç‚¸ï¼Œç»å¸¸é£Ÿç”¨ä¼šå¢žåŠ è‚¥èƒ–é£Žé™©ã€‚ +* `棕榈油`çš„`饱和脂肪酸`éžå¸¸é«˜ï¼Œçƒ­ç¨³å®šæ€§å¥½ï¼Œç»å¸¸é£Ÿç”¨ä¼šå¢žåŠ é«˜èƒ†å›ºé†‡é£Žé™©ã€‚ +* `猪油`,`牛油`等动物油脂,富å«`饱和脂肪酸`,ç»å¸¸é£Ÿç”¨ä¼šå¢žåŠ é«˜èƒ†å›ºé†‡é£Žé™©ã€‚ä¸æŽ¨è长期食用。 + +因此,根æ®ä¸Šè¿°è¡¨æ ¼ï¼Œæˆ‘们å¯ä»¥å¾—出一些结论: + +* æ²¡æœ‰ä»»ä½•ä¸€ç§æ²¹å“是完美的,æ¯ç§æ²¹å“都有其优缺点。因此,我们应该根æ®ä¸åŒçš„烹饪场景选择ä¸åŒçš„æ²¹å“。 +* ä¸åº”该始终使用åŒä¸€ç±»æ²¹å“,应该根æ®ä¸åŒçš„烹饪场景选择ä¸åŒçš„æ²¹å“,以确ä¿è¥å…»å‡è¡¡ã€‚ +* ä¸ºäº†ä¸æ‘„入太多 `åå¼è„‚肪酸`。在加热时,ä¸è¦é€‰æ‹©çƒ­ä¸ç¨³å®šçš„æ²¹å“,ä¸è¦åŠ çƒ­è¿‡ä¹…ã€‚ +* ä¸è¦å¤§é‡é£Ÿç”¨ç…Žç‚¸é£Ÿå“。热稳定性好的油往往åˆå«æœ‰å¤§é‡çš„`饱和脂肪酸`,ä¸é€‚åˆé•¿æœŸé£Ÿç”¨ã€‚ +* ä¸è¦é‡å¤ä½¿ç”¨æ²¹å“。油å“在加热过程中会产生大é‡çš„`åå¼è„‚肪酸`。 +* ä¸è¦é•¿æ—¶é—´é£Ÿç”¨å¤–å–食å“,因为很难确定他们使用了什么油å“。 + +#### ç‚’èœæ²¹ + +* 花生油 (选择高油å“质) +* 橄榄油 (选择高油å“质) +* èœç±½æ²¹ (选择低芥酸) + +èŠ±ç”Ÿæ²¹ã€æ©„榄油ã€èœç±½æ²¹å«æœ‰è¾ƒå¤šä¸é¥±å’Œè„‚è‚ªé…¸ï¼Œå«æœ‰è¾ƒå°‘的饱和脂肪酸。但是其热稳定性较差,容易在加热过程中产生åå¼è„‚è‚ªé…¸ã€‚å› æ­¤ï¼Œè¦æ³¨æ„控制加热时间,ä¸è¦åŠ çƒ­è¿‡ä¹…ã€‚ + +#### 煎炸油 + +* æ¤°å­æ²¹ +* 棕榈油 +* 牛油 +* 猪油 + +çˆ†ç‚’ã€æ²¹ç‚¸æ—¶éœ€è¦ä½¿ç”¨çƒ­ç¨³å®šæ€§æ›´å¥½çš„æ²¹ï¼Œå¦‚ï¼šæ¤°å­æ²¹ã€æ£•榈油ã€ç‰›æ²¹ã€‚它们产生的åå¼è„‚肪酸会更少。但是,它们的饱和脂肪酸å«é‡è¾ƒé«˜ï¼Œä¸é€‚åˆé•¿æœŸé£Ÿç”¨ã€‚ + +#### 凉拌ã€ç‚–煮油 + +* 亚麻籽油 +* èŠéº»æ²¹ +* 核桃油 +* ç´«è‹æ²¹ + +这类场景ä¸éœ€è¦åŠ çƒ­ï¼Œå› æ­¤ä¸ä¼šäº§ç”Ÿåå¼è„‚肪酸。ä¸è¦é€‰æ‹©æœ‰å¤ªå¤šé¥±å’Œè„‚肪酸的油å“。 diff --git a/assets/md/tips/如何选择现在åƒä»€ä¹ˆ.md b/assets/md/tips/如何选择现在åƒä»€ä¹ˆ.md new file mode 100644 index 0000000..4383201 --- /dev/null +++ b/assets/md/tips/如何选择现在åƒä»€ä¹ˆ.md @@ -0,0 +1,40 @@ +# 如何决策åƒä»€ä¹ˆ + +如何决策åƒä»€ä¹ˆä¹Ÿæ˜¯æˆ‘åšèœä¹‹å‰ä¸€å¤§éš¾é¢˜ã€‚所以åªèƒ½ç”¨æ•°å­¦æè¿°ä¸€ä¸‹äº†ã€‚ + +## 计算方法 + +### 计算è¤èœå’Œç´ èœæ•°é‡ + +* èœçš„æ•°é‡ = 人数 + 1。 +* è¤èœæ¯”ç´ èœå¤šä¸€ä¸ªï¼Œæˆ–一样多å³å¯ã€‚ + +由此得到è¤èœæ•°é‡å’Œç´ èœæ•°é‡ï¼Œå†åœ¨ä¸Šä¸€æ­¥çš„èœè°±ä¸­é€‰æ‹©å³å¯ã€‚ + +#### å½¢å¼è¯­è¨€æè¿° + +当 有人数 `N` 时, +设 `ç´ èœæ•°` 为 `a`, `è¤èœæ•°`为 `b`。 +`N`, `a`, `b`å‡ä¸ºæ•´æ•°ã€‚ + +此时有下列ä¸ç­‰å¼ç»„: + +* a + b = N + 1 +* a ≤ b ≤ a+1 + +解得 + +```javascript +const a = Math.floor((N+1)/2); +const b = Math.ceil((N+1)/2); +``` + +### èœçš„选择 + +* 如果人数超过 8 人,考虑在è¤èœä¸­å¢žåŠ é±¼ç±»è¤èœã€‚ +* 如果有å°å­©ï¼Œè€ƒè™‘增加有甜味的èœã€‚ +* 考虑增加特色èœã€æ‹¿æ‰‹èœã€‚ +* 注æ„决策è¤èœæ—¶ä¸è¦å…¨éƒ¨ä½¿ç”¨åŒä¸€ç§åŠ¨ç‰©çš„è‚‰ã€‚è€ƒè™‘é¡ºåºä¸ºï¼š`猪肉`ã€`鸡肉`ã€`牛肉`ã€`羊肉`ã€`鸭肉`ã€`鱼肉`。 +* ä¸è¦é€‰æ‹©å¥‡å¥‡æ€ªæ€ªçš„动物åšè¤èœã€‚ + +如果ä»ç„¶æ‹¿ä¸å‡†ï¼Œè¯·ä½¿ç”¨ [今天åƒä»€ä¹ˆ?](https://github.com/ryanuo/whatToEat) 工具æ¥é€‰æ‹©ä»Šå¤©åƒä»€ä¹ˆã€‚ diff --git a/assets/md/tips/食æç›¸å…‹ä¸Žç¦å¿Œ.md b/assets/md/tips/食æç›¸å…‹ä¸Žç¦å¿Œ.md new file mode 100644 index 0000000..c9d5411 --- /dev/null +++ b/assets/md/tips/食æç›¸å…‹ä¸Žç¦å¿Œ.md @@ -0,0 +1,68 @@ +# æ­ç§˜é£Ÿææ­é…的智慧:这些食物ä¸å®œåŒé£Ÿ + +在日常烹饪中,我们都希望åšå‡ºç¾Žå‘³åˆå¥åº·çš„家常èœã€‚然而,有些食æçœ‹ä¼¼æ™®é€šï¼Œæ­é…在一起å´å¯èƒ½æš—è—“玄机â€ï¼Œä¸ä»…å½±å“食物的色香味,更å¯èƒ½é˜»ç¢è¥å…»å¸æ”¶ï¼Œç”šè‡³å¯¹èº«ä½“å¥åº·äº§ç”Ÿå¾®å¦™çš„å½±å“。了解这些“食æç›¸å…‹â€ä¸Žâ€œé£Ÿç”¨ç¦å¿Œâ€ï¼Œæ˜¯æå‡é¥®é£Ÿæ™ºæ…§ã€å®ˆæŠ¤å®¶äººå¥åº·çš„é‡è¦ä¸€æ­¥ã€‚ + +## 常è§é£Ÿææ­é…误区与科学解读 + +ä»¥ä¸‹æ˜¯ä¸€äº›åœ¨æˆ‘ä»¬çš„é¤æ¡Œä¸Šï¼Œéœ€è¦ç‰¹åˆ«ç•™æ„的食æç»„åˆï¼š + +1. **è èœ + 豆è…:è‰é…¸ä¸Žé’™è´¨çš„“交锋â€** + * **相克原ç†**:è èœå¯Œå«è‰é…¸ï¼Œè€Œè±†è…æ˜¯é’™è´¨çš„ä¼˜è´¨æ¥æºã€‚当两者åŒé£Ÿæ—¶ï¼Œè‰é…¸ä¼šä¸Žé’™ç¦»å­ç»“åˆå½¢æˆä¸æº¶äºŽæ°´çš„è‰é…¸é’™ã€‚ + * **å¯èƒ½å½±å“**:è‰é…¸é’™ä¸ä»…éš¾ä»¥è¢«äººä½“å¸æ”¶åˆ©ç”¨ï¼Œé•¿æœŸå¤§é‡æ‘„入还å¯èƒ½å¢žåŠ ç»“çŸ³çš„é£Žé™©ã€‚ + * **å¥åº·å»ºè®®**:在烹饪è èœå‰ï¼Œå»ºè®®å…ˆç”¨æ²¸æ°´ç„¯çƒ«ä¸€ä¸‹ï¼Œå¯ä»¥æœ‰æ•ˆåŽ»é™¤å¤§éƒ¨åˆ†è‰é…¸ï¼Œä»Žè€Œå‡å°‘其与钙的结åˆã€‚ + +2. **胡èåœ + 白èåœï¼šç»´ç”Ÿç´ C的“æŸè€—者â€** + * **相克原ç†**:胡èåœä¸­å«æœ‰ä¸€ç§ç‰¹æ®Šçš„“抗å血酸氧化酶â€ï¼ˆå³ç»´ç”Ÿç´  C 分解酶),它会破å其他食物中的维生素 C。 + * **å¯èƒ½å½±å“**:导致白èåœï¼ˆä»¥åŠå…¶ä»–富å«ç»´ç”Ÿç´  C 的食物,如柑橘类)中的维生素 C 大釿µå¤±ï¼Œé™ä½Žå…¶è¥å…»ä»·å€¼ã€‚ + * **å¥åº·å»ºè®®**:两者最好分开食用,或将胡èåœçƒ¹ç†ŸåŽå†ä¸Žå¯Œå«ç»´ç”Ÿç´  C 的食物åŒé£Ÿï¼Œå› ä¸ºé«˜æ¸©ä¼šä½¿é…¶å¤±åŽ»æ´»æ€§ã€‚ + +3. **虾类 + 大é‡ç»´ç”Ÿç´ Cï¼šæ½œåœ¨çš„é£Žé™©ï¼Œä½†æ— éœ€è¿‡åº¦ææ…Œ** + * **相克原ç†**:虾等甲壳类水产å“体内嫿œ‰ä¸€ç§â€œäº”ä»·ç ·â€åŒ–åˆç‰©ã€‚在æžé«˜å‰‚é‡ç»´ç”Ÿç´  C 的还原作用下,五价砷ç†è®ºä¸Šå¯èƒ½è¢«è¿˜åŽŸä¸ºå‰§æ¯’çš„â€œä¸‰ä»·ç ·â€ï¼ˆä¿—称砒霜)。 + * **å¯èƒ½å½±å“**:ç†è®ºä¸Šä¸­æ¯’,但**请注æ„**:日常饮食中虾类和维生素 C 的摄入é‡ï¼Œè¿œä¸è¶³ä»¥è¾¾åˆ°å¼•å‘中毒的剂é‡ã€‚这是一个被夸大的“相克â€ï¼Œä¸å¿…è¿‡åº¦ææ…Œã€‚ + * **å¥åº·å»ºè®®**:正常饮食å³å¯ï¼Œæ— éœ€åˆ»æ„回é¿ã€‚é¿å…ä¸€æ¬¡æ€§å¤§é‡æ‘„入。 + +4. **æŸ¿å­ + 螃蟹:消化é“的“åŒé‡è€ƒéªŒâ€** + * **相克原ç†**:柿å­å¯Œå«éž£é…¸ï¼ˆåˆç§°å•å®é…¸ï¼‰ï¼ŒèžƒèŸ¹åˆ™è›‹ç™½è´¨å«é‡é«˜ã€‚鞣酸é‡åˆ°è›‹ç™½è´¨å®¹æ˜“å‡å›ºæˆä¸æ˜“消化的å—状物——鞣酸蛋白。 + * **å¯èƒ½å½±å“**:å¯èƒ½å¯¼è‡´è‚ èƒƒä¸é€‚,如腹胀ã€è…¹ç—›ã€æ¶å¿ƒã€å‘•å,甚至加é‡ä¾¿ç§˜ã€‚ + * **å¥åº·å»ºè®®**:尽é‡é¿å…åŒé£Ÿï¼Œæˆ–è‡³å°‘é—´éš”æ•°å°æ—¶ã€‚è„¾èƒƒè™šå¯’è€…å°¤å…¶è¦æ³¨æ„。 + +5. **牛奶 + å·§å…‹åŠ›ï¼šé’™è´¨å¸æ”¶çš„“éšå½¢éšœç¢â€** + * **相克原ç†**ï¼šå·§å…‹åŠ›ä¸­å«æœ‰è‰é…¸ï¼Œä¸Žç‰›å¥¶ä¸­çš„钙结åˆï¼Œå½¢æˆè‰é…¸é’™ã€‚ + * **å¯èƒ½å½±å“**:影å“é’™çš„å¸æ”¶ï¼Œé™ä½Žç‰›å¥¶çš„补钙效果。 + * **å¥åº·å»ºè®®**:建议分开食用,或间隔一段时间。 + +6. **豆浆 + 鸡蛋:蛋白质的“消化挑战â€** + * **相克原ç†**ï¼šæœªç…®ç†Ÿçš„è±†æµ†ä¸­å«æœ‰ä¸€ç§èƒ°è›‹ç™½é…¶æŠ‘制剂,会影å“äººä½“å¯¹è›‹ç™½è´¨çš„æ¶ˆåŒ–å’Œå¸æ”¶ã€‚ + * **å¯èƒ½å½±å“**:é™ä½Žé¸¡è›‹è›‹ç™½è´¨çš„利用率,å¯èƒ½å¼•起消化ä¸è‰¯ã€‚ + * **å¥åº·å»ºè®®**:确ä¿è±†æµ†å½»åº•煮沸ã€ç…®é€åŽï¼ˆå‡æ²¸ä¸ç®—ï¼‰ï¼Œå†æ­é…鸡蛋食用,这样胰蛋白酶抑制剂会被破å,ä¸ä¼šäº§ç”Ÿä¸è‰¯å½±å“。 + +7. **黄瓜 + 西红柿:维生素C的“默默æµå¤±â€** + * **相克原ç†**:与胡èåœç±»ä¼¼ï¼Œé»„ç“œä¸­ä¹Ÿå«æœ‰ä¸€ç§ç»´ç”Ÿç´  C 分解酶。 + * **å¯èƒ½å½±å“**:破å西红柿等食物中的维生素 C,é™ä½Žå…¶æŠ—氧化和å…疫增强作用。 + * **å¥åº·å»ºè®®**:最好分开食用,如果è¦åšæ²™æ‹‰ï¼Œå¯ä»¥è€ƒè™‘å…ˆåƒè¥¿çº¢æŸ¿ï¼Œå†åƒé»„瓜,或将两者分别处ç†ã€‚ + +8. **羊肉 + 西瓜:寒热的“碰撞â€** + * **相克原ç†**:羊肉性温热,具有补虚祛寒的功效;西瓜性寒凉,有清热解暑作用。 + * **å¯èƒ½å½±å“**:两者åŒé£Ÿï¼Œå¯’热性质相悖,å¯èƒ½å¯¼è‡´è„¾èƒƒä¸é€‚,引起腹泻ã€è…¹èƒ€ç­‰æ¶ˆåŒ–问题,尤其对于脾胃虚弱者。 + * **å¥åº·å»ºè®®**:é¿å…在åŒä¸€é¤ä¸­å¤§é‡é£Ÿç”¨ã€‚ + +9. **猪肉 + èŒ¶ï¼šè›‹ç™½è´¨å¸æ”¶çš„“阻ç¢â€** + * **相克原ç†**:茶å¶ä¸­å«æœ‰éž£é…¸ï¼Œä¸ŽçŒªè‚‰ä¸­çš„蛋白质结åˆï¼Œä¼šå½¢æˆä¸æ˜“消化的沉淀物。 + * **å¯èƒ½å½±å“**:影å“è›‹ç™½è´¨çš„æ¶ˆåŒ–å¸æ”¶ï¼Œå¯èƒ½å¼•起便秘或消化ä¸è‰¯ã€‚ + * **å¥åº·å»ºè®®**:饭åŽä¸€å°æ—¶å†é¥®èŒ¶ï¼Œæˆ–é¿å…在åƒè‚‰ç±»æ—¶å¤§é‡é¥®ç”¨æµ“茶。 + +10. **蜂蜜 + 豆è…:消化“ä¸åè°ƒâ€** + * **相克原ç†**:蜂蜜中的有机酸与豆è…中的蛋白质结åˆï¼Œå¯èƒ½å½¢æˆä¸æ˜“消化的物质。 + * **å¯èƒ½å½±å“**:å¯èƒ½å¼•起肠胃ä¸é€‚,如腹泻。 + * **å¥åº·å»ºè®®**:尽é‡é¿å…åŒé£Ÿã€‚ + +## 科学看待“相克â€ï¼Œæ™ºæ…§æ­é…日常饮食 + +* **“相克â€å¹¶éžç»å¯¹ç¦å¿Œ**:大多数所谓的“食物相克â€ï¼Œåœ¨ç§‘学研究中并未å‘现能引起严é‡ä¸­æ¯’æˆ–è‡´å‘½åŽæžœã€‚很多是基于传统ç»éªŒã€å°‘数案例或体外实验的推测。日常少é‡é£Ÿç”¨æˆ–å¶å°”æ­é…,通常ä¸ä¼šå¯¹å¥åº·é€ æˆæ˜Žæ˜¾å½±å“。 +* **é‡åœ¨å‡è¡¡å¤šæ ·**:å¥åº·çš„饮食原则是å‡è¡¡å’Œå¤šæ ·åŒ–。与其过分担心“相克â€ï¼Œä¸å¦‚关注整体膳食结构的åˆç†æ€§ï¼Œé¿å…åé£Ÿã€æŒ‘食。 +* **çƒ¹é¥ªæ–¹å¼æœ‰å½±å“**:æŸäº›â€œç›¸å…‹â€é—®é¢˜å¯ä»¥é€šè¿‡æ°å½“的烹饪方å¼ï¼ˆå¦‚焯水ã€é«˜æ¸©åŠ çƒ­ï¼‰æ¥é¿å…或å‡è½»ã€‚ +* **个体差异大**:æ¯ä¸ªäººçš„ä½“è´¨ã€æ¶ˆåŒ–èƒ½åŠ›å’Œå¯¹é£Ÿç‰©çš„æ•æ„Ÿåº¦éƒ½ä¸åŒã€‚对æŸäº›äººæ¥è¯´å¯èƒ½å¼•èµ·ä¸é€‚的组åˆï¼Œå¯¹å¦ä¸€äº›äººå¯èƒ½æ¯«æ— å½±å“。 +* **关注自身感å—**:如果在食用æŸç§æ­é…åŽæ„Ÿåˆ°ä¸é€‚,应予以留æ„并在下次é¿å…。 +* **特殊人群请咨询专业人士**:如果您有特殊的å¥åº·çŠ¶å†µã€æ…¢æ€§ç–¾ç—…(如糖尿病ã€è‚¾ç—…等)或对æŸäº›é£Ÿç‰©è¿‡æ•å²ï¼ŒåŠ¡å¿…å’¨è¯¢åŒ»ç”Ÿæˆ–æ³¨å†Œè¥å…»å¸ˆçš„专业æ„è§ï¼Œä»–们能æä¾›æ›´å…·é’ˆå¯¹æ€§å’Œä¸ªæ€§åŒ–的饮食建议。 + +å¸Œæœ›è¿™ä»½è¯¦å°½çš„é£Ÿææ­é…æŒ‡å—,能帮助您在享å—烹饪ä¹è¶£çš„åŒæ—¶ï¼Œæ›´å¥½åœ°ä¸ºè‡ªå·±å’Œå®¶äººæž„筑一é“å¥åº·é˜²çº¿ï¼è®©æˆ‘们一起åƒå¾—美味,åƒå¾—安心,åƒå¾—å¥åº·ï¼ diff --git a/docs/api/api_discover.php b/docs/api/api_discover.php index 9cf5a25..38eca9c 100644 --- a/docs/api/api_discover.php +++ b/docs/api/api_discover.php @@ -5,9 +5,10 @@ * @file api_discover.php * @author AI Assistant * @date 2026-04-12 - * @version 1.0.0 + * @version 1.1.0 * @desc æä¾›éšæœºæ•°æ®ç”¨äºŽé¦–页/å‘现页瀑布æµï¼Œæ”¯æŒå“应å¼å¸ƒå±€ - * @lastUpdate 2026-04-12 åˆå§‹ç‰ˆæœ¬ + * åŒä¸€å®¢æˆ·ç«¯å¤šæ¬¡è¯·æ±‚返回ä¸é‡å¤æ•°æ®ï¼Œ30分钟åŽé‡ç½® + * @lastUpdate 2026-04-12 添加已返回ID排除机制,é¿å…é‡å¤æ•°æ® */ $startTime = microtime(true); @@ -47,47 +48,52 @@ $format = ApiResponse::getFormat(); $forceRefresh = isset($params['_refresh']) && $params['_refresh'] === '1'; $clientIP = getClientIP(); -$requestInfo = ApiCache::getDiscoverRequestCount($clientIP); - -$shouldUseCache = !$forceRefresh && $requestInfo['count'] >= 2 && $requestInfo['data'] !== null; - -if ($shouldUseCache) { - header('X-Cache: HIT'); - header('X-Request-Count: ' . $requestInfo['count']); - $result = $requestInfo['data']; - $result['meta']['cache_status'] = 'cached'; - $result['meta']['request_count'] = $requestInfo['count']; - $result['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms'; - ApiResponse::output($result, $format); - exit; -} header('X-Cache: MISS'); $counts = calculateCounts($params); $data = array( - 'recipes' => getRandomRecipes($counts['recipe']), - 'ingredients' => getRandomIngredients($counts['ingredient']), - 'categories' => getRandomCategories($counts['category']), - 'tags' => getRandomTags($counts['tag']), - 'nutrition_types' => getNutritionTypes($counts['nutrition']), + 'recipes' => getRandomRecipes($counts['recipe'], $clientIP), + 'ingredients' => getRandomIngredients($counts['ingredient'], $clientIP), + 'categories' => getRandomCategories($counts['category'], $clientIP), + 'tags' => getRandomTags($counts['tag'], $clientIP), + 'nutrition_types' => getNutritionTypes($counts['nutrition'], $clientIP), 'meal_times' => getMealTimes($counts['meal_time']) ); +$recipeIds = array_column($data['recipes'], 'id'); +$ingredientIds = array_column($data['ingredients'], 'id'); +$categoryIds = array_column($data['categories'], 'id'); +$tagIds = array_column($data['tags'], 'id'); +$nutritionNames = array_column($data['nutrition_types'], 'name'); + +if (!empty($recipeIds)) { + ApiCache::addDiscoverReturnedIds($clientIP, 'recipe', $recipeIds); +} +if (!empty($ingredientIds)) { + ApiCache::addDiscoverReturnedIds($clientIP, 'ingredient', $ingredientIds); +} +if (!empty($categoryIds)) { + ApiCache::addDiscoverReturnedIds($clientIP, 'category', $categoryIds); +} +if (!empty($tagIds)) { + ApiCache::addDiscoverReturnedIds($clientIP, 'tag', $tagIds); +} +if (!empty($nutritionNames)) { + ApiCache::addDiscoverReturnedIds($clientIP, 'nutrition', $nutritionNames); +} + $result = array( 'code' => 200, 'message' => 'success', 'data' => $data, 'meta' => array( - 'request_count' => $requestInfo['count'] + 1, 'cache_status' => 'fresh', 'counts' => $counts ) ); -ApiCache::incrementDiscoverRequestCount($clientIP, $result); - $result['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms'; ApiResponse::output($result, $format); @@ -112,15 +118,15 @@ function getClientIP() { * 计算å„ç±»åž‹æ•°é‡ */ function calculateCounts($params) { - $total = isset($params['total']) ? min((int) $params['total'], 100) : 30; + $total = isset($params['total']) ? min((int) $params['total'], 100) : 50; $ratios = array( - 'recipe' => 0.25, - 'ingredient' => 0.15, - 'category' => 0.15, - 'tag' => 0.20, - 'nutrition' => 0.15, - 'meal_time' => 0.10 + 'recipe' => 0.75, + 'ingredient' => 0.03, + 'category' => 0.10, + 'tag' => 0.03, + 'nutrition' => 0.02, + 'meal_time' => 0.02 ); $counts = array(); @@ -130,7 +136,7 @@ function calculateCounts($params) { foreach ($ratios as $type => $ratio) { $paramName = $type === 'nutrition' ? 'nutrition' : $type; if (isset($params[$paramName]) && (int) $params[$paramName] > 0) { - $counts[$type] = min((int) $params[$paramName], 20); + $counts[$type] = min((int) $params[$paramName], 80); $specifiedTotal += $counts[$type]; $specifiedTypes[] = $type; } @@ -159,7 +165,7 @@ function calculateCounts($params) { /** * 获å–éšæœºèœå“ */ -function getRandomRecipes($limit) { +function getRandomRecipes($limit, $clientIP = '') { global $zbp; if ($limit <= 0) return array(); @@ -169,6 +175,17 @@ function getRandomRecipes($limit) { $tablePostStat = $zbp->db->dbpre . 'post_stat'; $tableIdMap = $zbp->db->dbpre . 'recipe_id_map'; + $excludeIds = array(); + if (!empty($clientIP)) { + $excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'recipe'); + } + + $whereClause = "p.log_Type = 0 AND p.log_Status = 0"; + if (!empty($excludeIds)) { + $excludeIdsStr = implode(',', array_map('intval', $excludeIds)); + $whereClause .= " AND p.log_ID NOT IN ($excludeIdsStr)"; + } + $sql = "SELECT p.log_ID, p.log_Title, p.log_CateID, p.log_ViewNums, c.cate_Name, s.rate_nums, s.rate_score, @@ -177,12 +194,17 @@ function getRandomRecipes($limit) { LEFT JOIN $tableCategory c ON p.log_CateID = c.cate_ID LEFT JOIN $tablePostStat s ON p.log_ID = s.log_id LEFT JOIN $tableIdMap m ON p.log_ID = m.new_log_id - WHERE p.log_Type = 0 AND p.log_Status = 0 + WHERE $whereClause ORDER BY RAND() LIMIT $limit"; $results = $zbp->db->Query($sql); + if (empty($results) && !empty($excludeIds)) { + ApiCache::clearDiscoverReturnedIds($clientIP, 'recipe'); + return getRandomRecipes($limit, $clientIP); + } + $list = array(); foreach ($results as $row) { $rateNums = (int) ($row['rate_nums'] ?? 0); @@ -213,7 +235,7 @@ function getRandomRecipes($limit) { /** * 获å–éšæœºé£Ÿæ */ -function getRandomIngredients($limit) { +function getRandomIngredients($limit, $clientIP = '') { global $zbp; if ($limit <= 0) return array(); @@ -221,15 +243,32 @@ function getRandomIngredients($limit) { $tableIngredient = $zbp->db->dbpre . 'ingredient_detail'; $tableCategory = $zbp->db->dbpre . 'category'; + $excludeIds = array(); + if (!empty($clientIP)) { + $excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'ingredient'); + } + + $whereClause = "1=1"; + if (!empty($excludeIds)) { + $excludeIdsStr = implode(',', array_map('intval', $excludeIds)); + $whereClause .= " AND i.ingredient_id NOT IN ($excludeIdsStr)"; + } + $sql = "SELECT i.ingredient_id, i.name, i.cate_ID, i.allergen, i.introduction, c.cate_Name FROM $tableIngredient i LEFT JOIN $tableCategory c ON i.cate_ID = c.cate_ID + WHERE $whereClause ORDER BY RAND() LIMIT $limit"; $results = $zbp->db->Query($sql); + if (empty($results) && !empty($excludeIds)) { + ApiCache::clearDiscoverReturnedIds($clientIP, 'ingredient'); + return getRandomIngredients($limit, $clientIP); + } + $list = array(); foreach ($results as $row) { $allergen = array(); @@ -261,20 +300,72 @@ function getRandomIngredients($limit) { /** * 获å–éšæœºåˆ†ç±» */ -function getRandomCategories($limit) { +function getRandomCategories($limit, $clientIP = '') { global $zbp; if ($limit <= 0) return array(); $tableCategory = $zbp->db->dbpre . 'category'; + $tableIngredient = $zbp->db->dbpre . 'ingredient_detail'; - $sql = "SELECT cate_ID, cate_Name, cate_Count, cate_ParentID - FROM $tableCategory - WHERE cate_ID > 10 - ORDER BY RAND() - LIMIT $limit"; + $excludeIds = array(); + if (!empty($clientIP)) { + $excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'category'); + } - $results = $zbp->db->Query($sql); + $excludeClause = ''; + if (!empty($excludeIds)) { + $excludeIdsStr = implode(',', array_map('intval', $excludeIds)); + $excludeClause = " AND cate_ID NOT IN ($excludeIdsStr)"; + } + + $recipeLimit = max(1, ceil($limit * 0.6)); + $ingredientLimit = $limit - $recipeLimit; + + $results = array(); + + $sqlRecipe = "SELECT cate_ID, cate_Name, cate_Count, cate_ParentID + FROM $tableCategory + WHERE cate_ID > 10 AND cate_ID < 1000 AND cate_ParentID != 11 $excludeClause + ORDER BY RAND() + LIMIT $recipeLimit"; + $recipeResults = $zbp->db->Query($sqlRecipe); + $results = array_merge($results, $recipeResults); + + $sqlIngredient = "SELECT cate_ID, cate_Name, cate_Count, cate_ParentID + FROM $tableCategory + WHERE cate_ID >= 1000 $excludeClause + ORDER BY RAND() + LIMIT $ingredientLimit"; + $ingredientResults = $zbp->db->Query($sqlIngredient); + $results = array_merge($results, $ingredientResults); + + if (empty($results) && !empty($excludeIds)) { + ApiCache::clearDiscoverReturnedIds($clientIP, 'category'); + return getRandomCategories($limit, $clientIP); + } + + $ingredientCounts = array(); + $cateIds = array_column($results, 'cate_ID'); + if (!empty($cateIds)) { + $cateIdsStr = implode(',', array_map('intval', $cateIds)); + $sqlIngredientCount = "SELECT cate_ID, COUNT(*) as cnt FROM $tableIngredient WHERE cate_ID IN ($cateIdsStr) GROUP BY cate_ID"; + $ingredientCountResults = $zbp->db->Query($sqlIngredientCount); + foreach ($ingredientCountResults as $row) { + $ingredientCounts[$row['cate_ID']] = (int) $row['cnt']; + } + } + + $parentIds = array_unique(array_filter(array_column($results, 'cate_ParentID'))); + $parentNames = array(); + if (!empty($parentIds)) { + $parentIdsStr = implode(',', array_map('intval', $parentIds)); + $sqlParent = "SELECT cate_ID, cate_Name FROM $tableCategory WHERE cate_ID IN ($parentIdsStr)"; + $parentResults = $zbp->db->Query($sqlParent); + foreach ($parentResults as $row) { + $parentNames[$row['cate_ID']] = $row['cate_Name']; + } + } $list = array(); foreach ($results as $row) { @@ -285,36 +376,62 @@ function getRandomCategories($limit) { $type = 'recipe_main'; } + $recipeCount = (int) ($row['cate_Count'] ?? 0); + $ingredientCount = isset($ingredientCounts[$row['cate_ID']]) ? $ingredientCounts[$row['cate_ID']] : 0; + $parentId = (int) $row['cate_ParentID']; + $parentName = isset($parentNames[$parentId]) ? $parentNames[$parentId] : ''; + $list[] = array( 'id' => (int) $row['cate_ID'], 'name' => $row['cate_Name'], 'type' => $type, - 'count' => (int) ($row['cate_Count'] ?? 0), - 'parent_id' => (int) $row['cate_ParentID'] + 'recipe_count' => $recipeCount, + 'ingredient_count' => $ingredientCount, + 'count' => $type === 'ingredient' ? $ingredientCount : $recipeCount, + 'parent_id' => $parentId, + 'parent_name' => $parentName ); } + shuffle($list); + return $list; } /** * 获å–éšæœºæ ‡ç­¾ */ -function getRandomTags($limit) { +function getRandomTags($limit, $clientIP = '') { global $zbp; if ($limit <= 0) return array(); $tableTag = $zbp->db->dbpre . 'tag'; + $excludeIds = array(); + if (!empty($clientIP)) { + $excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'tag'); + } + + $whereClause = "tag_Alias IN ('å£å‘³', 'åšæ³•')"; + if (!empty($excludeIds)) { + $excludeIdsStr = implode(',', array_map('intval', $excludeIds)); + $whereClause .= " AND tag_ID NOT IN ($excludeIdsStr)"; + } + $sql = "SELECT tag_ID, tag_Name, tag_Alias, tag_Count FROM $tableTag - WHERE tag_Alias IN ('å£å‘³', 'åšæ³•') + WHERE $whereClause ORDER BY RAND() LIMIT $limit"; $results = $zbp->db->Query($sql); + if (empty($results) && !empty($excludeIds)) { + ApiCache::clearDiscoverReturnedIds($clientIP, 'tag'); + return getRandomTags($limit, $clientIP); + } + $list = array(); foreach ($results as $row) { $type = 'taste'; @@ -336,21 +453,40 @@ function getRandomTags($limit) { /** * 获å–è¥å…»æˆåˆ†ç±»åž‹ */ -function getNutritionTypes($limit) { +function getNutritionTypes($limit, $clientIP = '') { global $zbp; if ($limit <= 0) return array(); $tableNutrition = $zbp->db->dbpre . 'recipe_nutrition'; + $excludeNames = array(); + if (!empty($clientIP)) { + $excludeNames = ApiCache::getDiscoverReturnedIds($clientIP, 'nutrition'); + } + + $whereClause = "name IS NOT NULL AND name != ''"; + if (!empty($excludeNames)) { + $escapedNames = array_map(function($name) use ($zbp) { + return $zbp->db->EscapeString($name); + }, $excludeNames); + $excludeNamesStr = "'" . implode("','", $escapedNames) . "'"; + $whereClause .= " AND name NOT IN ($excludeNamesStr)"; + } + $sql = "SELECT DISTINCT name, unit FROM $tableNutrition - WHERE name IS NOT NULL AND name != '' + WHERE $whereClause ORDER BY RAND() LIMIT $limit"; $results = $zbp->db->Query($sql); + if (empty($results) && !empty($excludeNames)) { + ApiCache::clearDiscoverReturnedIds($clientIP, 'nutrition'); + return getNutritionTypes($limit, $clientIP); + } + $list = array(); foreach ($results as $row) { $list[] = array( diff --git a/docs/api/doc/API_DOC.md b/docs/api/doc/API_DOC.md index f7d8a25..57c7b9f 100644 --- a/docs/api/doc/API_DOC.md +++ b/docs/api/doc/API_DOC.md @@ -1,11 +1,27 @@ # èœè°± API æŽ¥å£æ–‡æ¡£ -> **版本**: v3.2.0 -> **更新日期**: 2026-04-12 +> **版本**: v3.2.1 +> **更新日期**: 2026-04-13 > **基础地å€**: `http://eat.wktyl.com/api/` --- +## 📠更新日志 + +### v3.2.1 (2026-04-13) +- **æ–‡æ¡£åŒæ­¥æ›´æ–°**:完善å‘现页接å£å­—段说明 +- **分类字段补充**:添加 `id` 字段到分类返回结构 + +### v3.2.0 (2026-04-12) +- **å‘现页接å£ä¼˜åŒ–**:`api_discover.php` 多项改进 + - åŽ»é‡æœºåˆ¶ï¼šåŒä¸€å®¢æˆ·ç«¯å¤šæ¬¡è¯·æ±‚返回ä¸é‡å¤æ•°æ® + - 自动é‡ç½®ï¼š30分钟åŽè‡ªåЍé‡ç½® + - æ··åˆåˆ†ç±»ï¼šèœè°±åˆ†ç±»60% + 食æåˆ†ç±»40% + - 新增字段:`recipe_count`ã€`ingredient_count`ã€`parent_name` + - 标签 `count` å­—æ®µä¿®å¤ + +--- + ## ðŸ“ æŽ¥å£æ–‡ä»¶è¯´æ˜Ž @@ -1588,10 +1604,17 @@ Content-Type: application/json ``` GET api_discover.php?total=30 GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_time=3 +GET api_discover.php?total=30&_refresh=1 ``` **功能**: 获å–éšæœºæ•°æ®ç”¨äºŽé¦–页/å‘现页瀑布æµå±•示 +**核心特性**: +- ✅ **åŽ»é‡æœºåˆ¶**:åŒä¸€å®¢æˆ·ç«¯å¤šæ¬¡è¯·æ±‚返回ä¸é‡å¤æ•°æ®ï¼ˆåŸºäºŽIP记录已返回ID) +- ✅ **自动é‡ç½®**:30分钟åŽè‡ªåЍé‡ç½®ï¼Œé‡æ–°å¼€å§‹å±•示 +- ✅ **æ··åˆåˆ†ç±»**:分类返回混åˆç±»åž‹ï¼ˆèœè°±åˆ†ç±»60% + 食æåˆ†ç±»40%) +- ✅ **完整信æ¯**:分类包å«çˆ¶åˆ†ç±»å称,标签包å«å®žé™…使用次数 + **è¯·æ±‚å‚æ•°**: | 傿•° | 类型 | 默认值 | 说明 | @@ -1603,7 +1626,7 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim | tag | int | - | æ ‡ç­¾æ•°é‡ | | nutrition | int | - | è¥å…»æˆåˆ†æ•°é‡ | | meal_time | int | - | æ—¶æ®µæ•°é‡ | -| _refresh | int | 0 | 强制刷新缓存 | +| _refresh | int | 0 | 强制刷新(清除已返回ID记录) | **æ•°é‡åˆ†é…比例**(使用 total 傿•°æ—¶ï¼‰: @@ -1622,12 +1645,12 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim |------|------| | `recipes` | éšæœºèœå“列表 | | `ingredients` | éšæœºé£Ÿæåˆ—表 | -| `categories` | éšæœºåˆ†ç±»åˆ—表 | +| `categories` | éšæœºåˆ†ç±»åˆ—表(混åˆç±»åž‹ï¼‰ | | `tags` | éšæœºæ ‡ç­¾åˆ—表 | | `nutrition_types` | è¥å…»æˆåˆ†ç±»åž‹åˆ—表 | | `meal_times` | ç”¨é¤æ—¶æ®µåˆ—表 | -| `meta.request_count` | 当å‰IP在5秒内的请求次数 | -| `meta.cache_status` | 缓存状æ€ï¼šfresh/cached | +| `meta.cache_status` | 缓存状æ€ï¼šfresh | +| `meta.counts` | å„ç±»åž‹å®žé™…è¿”å›žæ•°é‡ | **èœå“字段**: @@ -1652,12 +1675,27 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim | `allergen` | 过æ•原数组 | | `intro` | 简介(截å–100字) | -**缓存策略**: +**分类字段**(新增): -| 请求次数 | è¿”å›žæ•°æ® | 说明 | -|----------|----------|------| -| 第1-2次 | æ–°é²œæ•°æ® | 查询数æ®åº“ | -| 第3次åŠä»¥åŽ | ç¼“å­˜æ•°æ® | 返回缓存(5ç§’TTL) | +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | int | 分类ID | +| `name` | string | 分类åç§° | +| `type` | string | 类型:recipe(èœè°±åˆ†ç±») / ingredient(食æåˆ†ç±») / recipe_main(食谱大类) | +| `recipe_count` | int | 该分类下的èœå“æ•°é‡ | +| `ingredient_count` | int | è¯¥åˆ†ç±»ä¸‹çš„é£Ÿææ•°é‡ | +| `count` | int | æ ¹æ®ç±»åž‹è‡ªåŠ¨é€‰æ‹©ï¼šingredientæ˜¾ç¤ºé£Ÿææ•°ï¼Œrecipe显示èœå“æ•° | +| `parent_id` | int | 父分类ID | +| `parent_name` | string | 父分类åç§° | + +**标签字段**: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | int | 标签ID | +| `name` | string | 标签åç§° | +| `type` | string | 类型:taste(å£å‘³) / cooking(工艺) | +| `count` | int | 使用次数(已修å¤ï¼‰ | **返回示例**: ```json @@ -1669,10 +1707,10 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim { "id": 26940, "title": "é…¸èœè›‡æ®µæ±¤", - "cover": "http://eat.wktyl.com/zb_users/upload/1605a.jpg", + "cover": "http://eat.wktyl.com/api/assets/pic/1030a.jpg", "category": {"id": 175, "name": "汤类"}, - "rating": {"score": 4.5, "nums": 128, "display": "4.5分 (128人评分)", "status": "sufficient", "level": "推è", "star": 5}, - "views": 1024 + "rating": {"score": 0, "nums": 0, "display": "暂无评分", "star": 0}, + "views": 0 } ], "ingredients": [ @@ -1685,20 +1723,39 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim } ], "categories": [ - {"id": 12, "name": "中国èœ", "type": "recipe_main", "count": 1234, "parent_id": 11} + { + "id": 42, + "name": "家常èœ", + "type": "recipe", + "recipe_count": 4581, + "ingredient_count": 0, + "count": 4581, + "parent_id": 12, + "parent_name": "中国èœ" + }, + { + "id": 1150, + "name": "æ­æ¤’", + "type": "ingredient", + "recipe_count": 0, + "ingredient_count": 1, + "count": 1, + "parent_id": 1001, + "parent_name": "蔬èœç±»" + } ], "tags": [ - {"id": 1, "name": "麻辣", "type": "taste", "count": 567} + {"id": 1, "name": "咸鲜味", "type": "taste", "count": 12973}, + {"id": 50, "name": "ç‚’", "type": "cooking", "count": 2053} ], "nutrition_types": [ {"name": "蛋白质", "unit": "å…‹"} ], "meal_times": [ - {"id": 1, "name": "æ—©é¤", "count": 1234} + {"id": 1, "name": "中é¤", "count": 481} ] }, "meta": { - "request_count": 1, "cache_status": "fresh", "counts": {"recipe": 8, "ingredient": 5, "category": 4, "tag": 6, "nutrition": 4, "meal_time": 3} }, @@ -1711,6 +1768,28 @@ GET api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_tim - 📱 å“应å¼å¸ƒå±€å±•示 - 🎨 动æ€å¡ç‰‡ç”Ÿæˆ - 🔄 下拉刷新加载 +- 📂 分类导航(显示父分类å称) + +**客户端实现**: +```dart +// Flutter 示例 +final response = await http.get( + Uri.parse('$baseUrl/api_discover.php?total=30') +); +final data = json.decode(response.body)['data']; + +// 显示分类å¡ç‰‡ +Widget buildCategoryCard(Map category) { + return Card( + child: Column( + children: [ + Text('${category['parent_name']} > ${category['name']}'), + Text('${category['count']} 个${category['type'] == 'ingredient' ? '食æ' : 'èœè°±'}'), + ], + ), + ); +} +``` --- diff --git a/docs/api/doc/APP_GUIDE.md b/docs/api/doc/APP_GUIDE.md index c1225f5..5be7aa5 100644 --- a/docs/api/doc/APP_GUIDE.md +++ b/docs/api/doc/APP_GUIDE.md @@ -1,81 +1,27 @@ # App æŽ¥å…¥æŒ‡å— -> **版本**: v2.9.0 -> **更新日期**: 2026-04-12 +> **版本**: v2.10.1 +> **更新日期**: 2026-04-13 > **基础地å€**: `http://eat.wktyl.com/api/` --- ## 📠更新日志 -### v2.9.0 (2026-04-12) -- **新增å‘现页接å£**:`api_discover.php` éšæœºæ•°æ®æŽ¥å£ - - 支æŒè¿”回多ç§ç±»åž‹éšæœºæ•°æ®ï¼šèœå“ã€é£Ÿæã€åˆ†ç±»ã€æ ‡ç­¾ã€è¥å…»æˆåˆ†ã€æ—¶æ®µ - - 支æŒåˆ†ç±»æ•°é‡å‚数和总数é‡è‡ªåŠ¨åˆ†é… - - 基于IP的请求频率控制:5秒内å‰2次返回新鲜数æ®ï¼Œç¬¬3次起返回缓存 - - 适用于首页/å‘现页瀑布æµã€å“应å¼å¸ƒå±€ +### v2.10.1 (2026-04-13) +- **æ–‡æ¡£åŒæ­¥æ›´æ–°**:与 API_DOC.md ä¿æŒä¸€è‡´ +- **分类字段完善**:补充 `id` 字段说明 -### v2.8.0 (2026-04-12) -- **评分显示优化**:所有èœè°±æŽ¥å£è¿”回数æ®ä¸­æ–°å¢ž `rating` 字段 - - 自动处ç†è¾¹ç¼˜æƒ…况:暂无评分ã€è¯„分数é‡å°‘ã€è¯„分异常 - - æä¾›æ ¼å¼åŒ–显示文本,å¯ç›´æŽ¥ç”¨äºŽUI展示 - - æä¾›è¯„分状æ€å’Œç­‰çº§ï¼Œä¾¿äºŽå®¢æˆ·ç«¯å·®å¼‚化展示 -- **çƒ­é—¨æŽ’è¡ŒæŽ¥å£æ›´æ–°**:`sort` 傿•°ä»Ž `recommend` 改为 `rate` +### v2.10.0 (2026-04-12) +- **å‘现页接å£ä¼˜åŒ–**:`api_discover.php` 多项改进 + - åŒä¸€å®¢æˆ·ç«¯å¤šæ¬¡è¯·æ±‚返回ä¸é‡å¤æ•°æ®ï¼ˆåŸºäºŽIP记录已返回ID) + - 30分钟åŽè‡ªåЍé‡ç½®ï¼Œé‡æ–°å¼€å§‹å±•示 + - 分类返回混åˆç±»åž‹ï¼šèœè°±åˆ†ç±»60% + 食æåˆ†ç±»40% + - 分类新增字段:`recipe_count`(èœå“数)ã€`ingredient_count`ï¼ˆé£Ÿææ•°ï¼‰ã€`parent_name`(父分类å称) + - 标签 `count` 字段已修å¤ï¼Œæ˜¾ç¤ºå®žé™…使用次数 + - æ•°æ®åº“统计字段已更新:`cate_Count`ã€`tag_Count` -### v2.7.0 (2026-04-12) -- **接å£å˜æ›´**:`recommend` æŽ¥å£æ”¹ä¸º `rate` è¯„åˆ†æŽ¥å£ - - 评分范围:1-5分 - - æ¯æ—¥é™åˆ¶ï¼šæ¯ä¸ªIPæ¯å¤©æœ€å¤š30次 - - ä¸å¯å–æ¶ˆï¼šè¯„åˆ†åŽæ— æ³•撤销 - - 自动计算平å‡åˆ† -- æ•°æ®åº“字段更新:`recommend_nums`/`recommend_score` 改为 `rate_nums`/`rate_score` -- 新增本地评分日志:记录IPã€æ—¶é—´ã€åˆ†æ•°åˆ°æœ¬åœ°æ–‡ä»¶ -### v2.6.0 (2026-04-12) -- 新增接å£ï¼š`api_hot.php` çƒ­é—¨æŽ’è¡ŒæŽ¥å£ - -### v2.5.0 (2026-04-12) -- 文档更新:简化客户端代ç ç¤ºä¾‹ä¸º1-2行 -- æ–°å¢žæŽ¥å£æè¿°ï¼š`api_action.php?act=ip_status` IPçŠ¶æ€æŸ¥è¯¢ -- æ–°å¢žæŽ¥å£æè¿°ï¼š`api_feed.php?act=feed` ä¿¡æ¯æµæŽ¥å£ -- æ–°å¢žæŽ¥å£æè¿°ï¼š`cache_manage.php` ç¼“å­˜ç®¡ç†æŽ¥å£ -- ä¿®æ­£æŽ¥å£æè¿°ï¼šéšæœºæŽ¨è使用 `filter_apply&count=1` è€Œéž `random` -- ä¿®æ­£æŽ¥å£æè¿°ï¼šçƒ­é—¨æŽ’è¡Œä½¿ç”¨ `api_hot.php` 替代 `stats_full.php?act=hot` -- 删除ä¸å­˜åœ¨çš„æŽ¥å£æè¿° - -### v2.4.0 (2026-04-12) -- ç­›é€‰æŽ¥å£æ–°å¢žæŽ’é™¤ç­›é€‰å‚æ•°ï¼š - - `exclude_category`/`exclude_category_name` 排除分类 - - `exclude_taste`/`exclude_taste_name` 排除å£å‘³ - - `exclude_cooking`/`exclude_cooking_name` 排除工艺 - - `exclude_ingredient` 排除食æ - - `exclude_allergen` 排除过æ•原 - - `exclude_author` 排除作者 - - `exclude_meal_time` æŽ’é™¤ç”¨é¤æ—¶æ®µ - -### v2.3.0 (2026-04-12) -- æ–°å¢žè¿·ä½ ç‰ˆæŽ¥å£ (api.php?act=mini),适用于列表页快速加载 -- ç­›é€‰æŽ¥å£æ–°å¢žé«˜çº§ç­›é€‰å‚数: - - `nutrition_name`/`nutrition_min`/`nutrition_max` 按è¥å…»æˆåˆ†ç­›é€‰ - - `allergen` 按过æ•原筛选(排除å«è¯¥è¿‡æ•原的èœå“) - - `ingredient` 按食æå称筛选 - - `author_id` 按作者筛选 - - `category_name` 按分类å称筛选 - - `taste_name`/`cooking_name` 按å£å‘³/工艺å称筛选 - -### v2.2.0 (2026-04-12) -- 新增全局æœç´¢æŽ¥å£ (api_filter.php?act=global_search) -- æ”¯æŒæœç´¢é£Ÿè°±ã€é£Ÿæã€å£å‘³æ ‡ç­¾ã€å·¥è‰ºæ ‡ç­¾ -- æœç´¢ç»“æžœåŒ…å«æ‰€å±žåˆ†ç±»ã€å…³è”èœå“æ•°é‡ç­‰ä¿¡æ¯ -- æ”¯æŒæ¨¡ç³Šæœç´¢å’Œå…³é”®è¯é«˜äº® - -### v2.1.0 (2026-04-12) -- 删除个性化推èç›¸å…³æŽ¥å£ (api_preference.php) -- 删除 api_feed.php 中的 personal æŽ¥å£ -- 删除 api.php 中的 use_preference 傿•° -- 更新推è算法说明 - ---- ## ä¸€ã€æŽ¥å£æ–‡ä»¶è¯´æ˜Ž @@ -223,6 +169,11 @@ Widget buildRating(Map rating) { 用于首页/å‘现页瀑布æµå±•示,返回多ç§ç±»åž‹çš„éšæœºæ•°æ®ã€‚ +**特性**: +- åŒä¸€å®¢æˆ·ç«¯å¤šæ¬¡è¯·æ±‚返回ä¸é‡å¤æ•°æ®ï¼ˆåŸºäºŽIP记录已返回ID) +- 30分钟åŽè‡ªåЍé‡ç½®ï¼Œé‡æ–°å¼€å§‹å±•示 +- 分类返回混åˆç±»åž‹ï¼šèœè°±åˆ†ç±»60% + 食æåˆ†ç±»40% + **请求示例**: ```dart // 获å–30æ¡éšæœºæ•°æ®ï¼ˆè‡ªåŠ¨åˆ†é…) @@ -234,8 +185,74 @@ final response = await http.get( final response = await http.get( Uri.parse('$baseUrl/api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_time=3') ); + +// 强制刷新(清除已返回ID记录) +final response = await http.get( + Uri.parse('$baseUrl/api_discover.php?total=30&_refresh=1') +); ``` +**返回数æ®ç»“æž„**: + +```json +{ + "code": 200, + "message": "success", + "data": { + "recipes": [...], + "ingredients": [...], + "categories": [ + { + "id": 42, + "name": "家常èœ", + "type": "recipe", + "recipe_count": 4581, + "ingredient_count": 0, + "count": 4581, + "parent_id": 12, + "parent_name": "中国èœ" + }, + { + "id": 1150, + "name": "æ­æ¤’", + "type": "ingredient", + "recipe_count": 0, + "ingredient_count": 1, + "count": 1, + "parent_id": 1001, + "parent_name": "蔬èœç±»" + } + ], + "tags": [ + { + "id": 1, + "name": "咸鲜味", + "type": "taste", + "count": 12973 + }, + { + "id": 50, + "name": "ç‚’", + "type": "cooking", + "count": 2053 + } + ], + "nutrition_types": [...], + "meal_times": [...] + } +} +``` + +**分类字段说明**: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `type` | string | 分类类型:recipe(èœè°±åˆ†ç±») / ingredient(食æåˆ†ç±») / recipe_main(食谱大类) | +| `recipe_count` | int | 该分类下的èœå“æ•°é‡ | +| `ingredient_count` | int | è¯¥åˆ†ç±»ä¸‹çš„é£Ÿææ•°é‡ | +| `count` | int | æ ¹æ®ç±»åž‹è‡ªåŠ¨é€‰æ‹©ï¼šingredientæ˜¾ç¤ºé£Ÿææ•°ï¼Œrecipe显示èœå“æ•° | +| `parent_name` | string | 父分类å称,å¯ç›´æŽ¥æ˜¾ç¤ºä¸º"父分类 > å­åˆ†ç±»" | + **Flutter 瀑布æµç¤ºä¾‹**: ```dart Widget buildDiscoverPage(Map data) { @@ -251,9 +268,13 @@ Widget buildDiscoverPage(Map data) { items.add(IngredientCard(ingredient: ingredient)); } - // 分类å¡ç‰‡ + // 分类å¡ç‰‡ï¼ˆæ˜¾ç¤ºçˆ¶åˆ†ç±»å称) for (final category in data['categories']) { - items.add(CategoryCard(category: category)); + items.add(CategoryCard( + category: category, + subtitle: '${category['parent_name']} > ${category['name']}', + count: category['count'], + )); } // 标签å¡ç‰‡ diff --git a/docs/api/doc/api_discover b/docs/api/doc/api_discover new file mode 100644 index 0000000..0e22cc0 --- /dev/null +++ b/docs/api/doc/api_discover @@ -0,0 +1,322 @@ +https://eat.wktyl.com/api/api_discover.php?recipe=8&ingredient=5&category=4&tag=6&nutrition=4&meal_time=3 + +{ + "code": 200, + "message": "success", + "data": { + "recipes": [ + { + "id": 40420, + "title": "旱蒸酸èœé¸­", + "cover": "http://eat.wktyl.com/api/assets/pic/15303a.jpg", + "category": { + "id": 43, + "name": "ç§å®¶èœ" + }, + "rating": { + "score": 0, + "nums": 0, + "display": "暂无评分", + "star": 0 + }, + "views": 0 + }, + { + "id": 56308, + "title": "炸山鸡çƒ", + "cover": "", + "category": { + "id": 21, + "name": "京èœ" + }, + "rating": { + "score": 0, + "nums": 0, + "display": "暂无评分", + "star": 0 + }, + "views": 0 + }, + { + "id": 44289, + "title": "å…«å®é¸¡æ±¤", + "cover": "http://eat.wktyl.com/api/assets/pic/19248a.jpg", + "category": { + "id": 78, + "name": "气血åŒè¡¥é£Ÿè°±" + }, + "rating": { + "score": 0, + "nums": 0, + "display": "暂无评分", + "star": 0 + }, + "views": 0 + }, + { + "id": 56918, + "title": "金枪鱼芸豆色拉", + "cover": "http://eat.wktyl.com/api/assets/pic/26164a.jpg", + "category": { + "id": 242, + "name": "西é¤å…¶ä»–" + }, + "rating": { + "score": 0, + "nums": 0, + "display": "暂无评分", + "star": 0 + }, + "views": 0 + }, + { + "id": 50247, + "title": "è·å…°è±†é¥º", + "cover": "http://eat.wktyl.com/api/assets/pic/25317a.jpg", + "category": { + "id": 241, + "name": "å¿«é¤/主食" + }, + "rating": { + "score": 0, + "nums": 0, + "display": "暂无评分", + "star": 0 + }, + "views": 0 + }, + { + "id": 37723, + "title": "æ¤’ç›æ‹ŒèŠ±ç”Ÿç±³", + "cover": "http://eat.wktyl.com/api/assets/pic/12514a.jpg", + "category": { + "id": 43, + "name": "ç§å®¶èœ" + }, + "rating": { + "score": 0, + "nums": 0, + "display": "暂无评分", + "star": 0 + }, + "views": 0 + }, + { + "id": 49117, + "title": "糟香æ±å¤è‚¥é¸¡", + "cover": "http://eat.wktyl.com/api/assets/pic/24016a.jpg", + "category": { + "id": 238, + "name": "å¤é…±èœ" + }, + "rating": { + "score": 0, + "nums": 0, + "display": "暂无评分", + "star": 0 + }, + "views": 0 + }, + { + "id": 48252, + "title": "ç‰ç’ƒå±±è¯", + "cover": "http://eat.wktyl.com/api/assets/pic/23228a.jpg", + "category": { + "id": 239, + "name": "甜å“/点心" + }, + "rating": { + "score": 0, + "nums": 0, + "display": "暂无评分", + "star": 0 + }, + "views": 0 + } + ], + "ingredients": [ + { + "id": 56, + "name": "黄豆芽", + "category": { + "id": 1057, + "name": "黄豆芽" + }, + "allergen": [ + "黄豆", + "豆" + ], + "intro": "豆芽是大豆ç»åР工处ç†å‘出的嫩芽,是一ç§è¥å…»ä¸°å¯Œçš„蔬èœã€‚当今世界“天然â€ã€â€œå¥åº·â€é£Ÿç‰©å¼•领风骚者之一,乃是æºè‡ªæˆ‘国的豆芽èœï¼Œç‰¹åˆ«æ˜¯é‡‘ç¿ç¿çš„“如æ„èœâ€â€”黄豆芽。明人陈嶷曾有过赞美黄豆芽的诗å¥ï¼šâ€œæœ‰å½¼ç‰©å…®ï¼Œ..." + }, + { + "id": 490, + "name": "鱼丸", + "category": { + "id": 1498, + "name": "鱼丸" + }, + "allergen": [ + "é±¼" + ], + "intro": "鱼丸是闽å—ã€ç¦å·žã€å¹¿å·žä¸€å¸¦ç»å¸¸çƒ¹åˆ¶çš„传统食å“。因为它味é“鲜美,多åƒä¸è…»ï¼Œå¯ä½œç‚¹å¿ƒé…料,åˆå¯ä½œæ±¤ï¼Œæ˜¯æ²¿æµ·äººä»¬ä¸å¯å°‘的海味佳肴。" + }, + { + "id": 1262, + "name": "酸黄瓜", + "category": { + "id": 2282, + "name": "酸黄瓜" + }, + "allergen": [], + "intro": "" + }, + { + "id": 214, + "name": "黑枣(无核)", + "category": { + "id": 1216, + "name": "黑枣(无核)" + }, + "allergen": [], + "intro": "黑枣产å„地山区,野生于山å¡ã€è°·åœ°æˆ–栽培;分布于辽å®ã€æ²³åŒ—ã€å±±ä¸œã€é™•西ã€ä¸­å—åŠè¥¿å—å„地。 æè´¨ä¼˜è‰¯ï¼Œå¯ä½œä¸€èˆ¬ç”¨æï¼›æžœå®žåŽ»æ¶©ç”Ÿé£Ÿæˆ–é…¿é…’ã€åˆ¶é†‹ï¼Œå«ç»´ç”Ÿç´ ä¸™ï¼Œå¯æå–供医用;ç§å­å…¥è¯ï¼Œèƒ½æ¶ˆæ¸´åŽ»çƒ­ã€‚" + }, + { + "id": 172, + "name": "枣(干)", + "category": { + "id": 1174, + "name": "枣(干)" + }, + "allergen": [], + "intro": "枣为鼠æŽç§‘è½å¶çŒæœ¨æˆ–å°ä¹”木æ¤ç‰©æž£æ ‘çš„æˆç†Ÿæžœå®žã€‚我国栽培枣树范围æžå¹¿ï¼ŒåŒ—边达到辽å®çš„锦州ã€åŒ—é•‡ä¸€å¸¦ï¼Œä»¥å±±ä¸œã€æ²³åŒ—ã€å±±è¥¿ã€é™•西ã€ç”˜è‚ƒã€å®‰å¾½ã€æµ™æ±Ÿäº§é‡æœ€å¤šã€‚è‘—åå“ç§æœ‰é‡‘ä¸å°æž£ï¼Œæžœå®žå°ï¼Œå«ç³–é‡å¤šï¼Œç”Ÿäº§å±±ä¸œä¹é™µ..." + } + ], + "categories": [ + { + "id": 1219, + "name": "椰蓉", + "type": "ingredient", + "recipe_count": 0, + "ingredient_count": 1, + "count": 1, + "parent_id": 1173, + "parent_name": "水果类åŠåˆ¶å“" + }, + { + "id": 101, + "name": "婴儿食谱", + "type": "recipe", + "recipe_count": 64, + "ingredient_count": 0, + "count": 64, + "parent_id": 98, + "parent_name": "人群è¥å…»è†³é£Ÿ" + }, + { + "id": 212, + "name": "咽炎食谱", + "type": "recipe", + "recipe_count": 2, + "ingredient_count": 0, + "count": 2, + "parent_id": 172, + "parent_name": "疾病调ç†" + }, + { + "id": 38, + "name": "滇黔èœ", + "type": "recipe", + "recipe_count": 158, + "ingredient_count": 0, + "count": 158, + "parent_id": 12, + "parent_name": "中国èœ" + } + ], + "tags": [ + { + "id": 57, + "name": "炸", + "type": "cooking", + "count": 624 + }, + { + "id": 110, + "name": "ç„—", + "type": "cooking", + "count": 70 + }, + { + "id": 18, + "name": "酸咸味", + "type": "taste", + "count": 212 + }, + { + "id": 35, + "name": "怪味", + "type": "taste", + "count": 67 + }, + { + "id": 133, + "name": "煎烹", + "type": "cooking", + "count": 17 + }, + { + "id": 144, + "name": "红外线烤", + "type": "cooking", + "count": 1 + } + ], + "nutrition_types": [ + { + "name": "维生素B", + "unit": "微克" + }, + { + "name": "锌", + "unit": "毫克" + }, + { + "name": "泛酸", + "unit": "毫克" + }, + { + "name": "蛋白质", + "unit": "å…‹" + } + ], + "meal_times": [ + { + "id": 1, + "name": "中é¤", + "count": 481 + }, + { + "id": 2, + "name": "晚é¤", + "count": 475 + }, + { + "id": 3, + "name": "æ—©é¤", + "count": 325 + } + ] + }, + "meta": { + "cache_status": "fresh", + "counts": { + "recipe": 8, + "ingredient": 5, + "category": 4, + "tag": 6, + "nutrition": 4, + "meal_time": 3 + } + }, + "_query_time": "2129.4ms" +} \ No newline at end of file diff --git a/docs/dev/PAGE_STRUCTURE_ANALYSIS.md b/docs/dev/PAGE_STRUCTURE_ANALYSIS.md index 34993c0..ff97a19 100644 --- a/docs/dev/PAGE_STRUCTURE_ANALYSIS.md +++ b/docs/dev/PAGE_STRUCTURE_ANALYSIS.md @@ -375,24 +375,23 @@ │ └───────────────────────┘ │ │ │ │ ── 🳠烹饪助手 ── │ -│ ┌──┠┌──┠┌──┠│ -│ │â±ï¸â”‚ │ðŸ“│ │🧮│ │ -│ │计时│ │笔记│ │æ¢ç®—│ │ -│ │ ↓│ │ ↓│ │ ↓│ │ -│ └──┘ └──┘ └──┘ │ +│ ┌──┠┌──┠│ ↠GridView 2列布局 +│ │â±ï¸â”‚ │ðŸ“│ │ +│ │计时│ │笔记│ │ +│ │ ↓│ │ ↓│ │ +│ │使用│ │使用│ ↠新增"使用工具"按钮 +│ └──┘ └──┘ │ │ │ │ ── 📋 è§„åˆ’ç®¡ç† â”€â”€ │ -│ ┌──┠┌──┠┌──┠│ -│ │📅│ │🛒│ │⚖ï¸â”‚ │ -│ │èœå•│ │购物│ │缩放│ │ -│ │ ↓│ │ ↓│ │ ↓│ │ -│ └──┘ └──┘ └──┘ │ +│ ┌──┠┌──┠│ +│ │📅│ │🛒│ │ +│ │èœå•│ │购物│ │ +│ └──┘ └──┘ │ │ │ │ ── 🥠å¥åº·å·¥å…· ── │ │ ┌──┠┌──┠│ │ │BMI│ │🌙│ │ │ │计算│ │就å¯â”‚ │ -│ │ ↓│ │ ↓│ │ │ └──┘ └──┘ │ │ │ │ ── ðŸ·ï¸ 热门标签 ── │ @@ -419,6 +418,7 @@ | BMI计算 | `Get.toNamed` | BMI计算器 | `/bmi-calculator` | - | | 🌙 就坿醒 | `Get.toNamed` | 就坿醒页 | `/profile/bedtime-reminder` | - | | 热门标签 | `Get.toNamed` | 标签èœè°±åˆ—表 | `/tag-recipe-list` | tagName, tagId, tagType | +| 🎮 使用工具按钮 | `Get.toNamed` | 工具功能页 | 工具对应路由 | - | ### 🎨 美观问题 | # | 问题 | 严é‡åº¦ | 建议 | @@ -515,6 +515,112 @@ --- +## â¤ï¸ æ”¶è—页 (FavoritesPage) + +``` +┌─────────────────────────────┠+│ ↠[↑返回] æˆ‘çš„æ”¶è— â”‚ +├─────────────────────────────┤ +│ │ +│ ┌──┠┌──┠│ ↠GridView 2列布局 +│ │🖼ï¸â”‚ │🖼ï¸â”‚ │ +│ │èœâ”‚ │èœâ”‚ │ +│ │å“│ │å“│ │ +│ │1│ │2│ │ +│ └──┘ └──┘ │ +│ ┌──┠┌──┠│ +│ │🖼ï¸â”‚ │🖼ï¸â”‚ │ +│ │èœâ”‚ │èœâ”‚ │ +│ │å“│ │å“│ │ +│ │3│ │4│ │ +│ └──┘ └──┘ │ +│ │ +└─────────────────────────────┘ +``` + +### 📄 页é¢è§†å›¾æ–‡ä»¶ +- `lib/src/pages/profile/social/favorites_page.dart`(主页é¢ï¼‰ + +### 🔗 跳转关系 +| 触å‘元素 | è·³è½¬æ–¹å¼ | ç›®æ ‡é¡µé¢ | 路由 | ä¼ å‚ | +|---------|---------|---------|------|------| +| ↠返回 | `Get.back` | ä¸Šä¸€ä¸ªé¡µé¢ | - | - | +| èœå“å¡ç‰‡ | `Get.toNamed` | èœå“详情页 | `/recipe-detail` | id | + +### 🎨 美观问题 +| # | 问题 | 严é‡åº¦ | 建议 | +|---|------|--------|------| +| 1 | 空收è—状æ€ç¼ºå°‘引导 | 🟡中 | 添加"去å‘现美食"按钮引导用户 | + +### âš¡ 功能缺失 +| # | 功能 | 优先级 | 说明 | +|---|------|--------|------| +| 1 | æ”¶è—分组 | P3 | 按分类/æ ‡ç­¾åˆ†ç»„ç®¡ç†æ”¶è— | +| 2 | æ”¶è—æŽ’åº | P3 | æŒ‰æ”¶è—æ—¶é—´/è¯„åˆ†æŽ’åº | + +--- + +## 📋 å…³äºŽé¡µé¢ (AboutPage) + +``` +┌─────────────────────────────┠+│ ↠[↑返回] 关于 │ +├─────────────────────────────┤ +│ │ +│ ┌───────────────────────┠│ +│ │ 🳠妈妈厨房 │ │ +│ │ Version 0.92.4 │ │ +│ └───────────────────────┘ │ +│ │ +│ ── åº”ç”¨ä¿¡æ¯ â”€â”€ │ +│ ┌───────────────────────┠│ +│ │ 📱 应用版本 0.92.4 │ │ +│ │ 📅 更新日期 2026-04 │ │ +│ │ ðŸ·ï¸ 构建版本 92 │ │ +│ └───────────────────────┘ │ +│ │ +│ ── è”系我们 ── │ +│ ┌───────────────────────┠│ +│ │ 💬 用户å馈 [→] │ │ ↠[→æ„è§å馈页] +│ │ ⭠评价应用 [→] │ │ +│ │ 📧 è”系邮箱 [→] │ │ +│ └───────────────────────┘ │ +│ │ +│ ── æ³•å¾‹ä¿¡æ¯ â”€â”€ │ +│ ┌───────────────────────┠│ +│ │ 📜 用户åè®® [→] │ │ +│ │ 🔒 éšç§æ”¿ç­– [→] │ │ +│ └───────────────────────┘ │ +│ │ +└─────────────────────────────┘ +``` + +### 📄 页é¢è§†å›¾æ–‡ä»¶ +- `lib/src/pages/profile/about_page.dart`(主页é¢ï¼‰ + +### 🔗 跳转关系 +| 触å‘元素 | è·³è½¬æ–¹å¼ | ç›®æ ‡é¡µé¢ | 路由 | ä¼ å‚ | +|---------|---------|---------|------|------| +| ↠返回 | `Get.back` | ä¸Šä¸€ä¸ªé¡µé¢ | - | - | +| 💬 用户å馈 | `Get.toNamed` | æ„è§å馈页 | `/chat` | - | +| ⭠评价应用 | 打开应用商店 | App Store | - | - | +| 📧 è”系邮箱 | 打开邮件客户端 | Mail App | - | - | +| 📜 用户åè®® | `Get.toNamed` | 用户å议页 | `/user-agreement` | - | +| 🔒 éšç§æ”¿ç­– | `Get.toNamed` | éšç§æ”¿ç­–页 | `/privacy-policy` | - | + +### 🎨 美观问题 +| # | 问题 | 严é‡åº¦ | 建议 | +|---|------|--------|------| +| 1 | 应用图标区域å¯å¢žåŠ åŠ¨ç”»æ•ˆæžœ | 🟢低 | 添加呼å¸åŠ¨ç”»æˆ–æ¸å˜æ•ˆæžœ | + +### âš¡ 功能缺失 +| # | 功能 | 优先级 | 说明 | +|---|------|--------|------| +| 1 | 检查更新 | P2 | 检测新版本并æç¤ºæ›´æ–° | +| 2 | 更新日志 | P3 | 展示版本更新内容 | + +--- + ## 🥬 食æè¯¦æƒ…页 (IngredientDetailPage) ``` @@ -737,10 +843,10 @@ | P1 | 下拉刷新 | 首页/å‘现 | 缺少下拉手势刷新 | ðŸ”´å¾…å¼€å‘ | | P2 | æœç´¢åŽ†å² | æœç´¢é¡µ | 无本地æœç´¢è®°å½• | ðŸ”´å¾…å¼€å‘ | | P2 | 分页加载 | 多个列表页 | 分类æµè§ˆ/标签列表无分页 | ðŸ”´å¾…å¼€å‘ | -| P2 | 相关推è | 详情页 | 缺少相关èœè°±æŽ¨è | ðŸ”´å¾…å¼€å‘ | +| P2 | 相关推è | 详情页 | 缺少相关èœè°±æŽ¨è | ✅v0.92.0 | | P2 | çƒ¹é¥ªæ¨¡å¼ | 详情页 | 免屿­¥éª¤+计时器 | ðŸ”´å¾…å¼€å‘ | -| P2 | 过æ•原警示 | 详情页 | 食æå«è¿‡æ•原时警告 | ðŸ”´å¾…å¼€å‘ | -| P2 | è¥å…»å¯è§†åŒ– | 详情页 | 环形图/进度æ¡å±•示è¥å…»å æ¯” | ðŸ”´å¾…å¼€å‘ | +| P2 | 过æ•原警示 | 详情页 | 食æå«è¿‡æ•原时警告 | ✅v0.92.0 | +| P2 | è¥å…»å¯è§†åŒ– | 详情页 | 环形图/进度æ¡å±•示è¥å…»å æ¯” | ✅v0.92.0 | | P3 | 排åºç­›é€‰ | 列表页 | 按评分/æµè§ˆé‡/æœ€æ–°æŽ’åº | ðŸ”´å¾…å¼€å‘ | | P3 | 评论系统 | 详情页 | 需åŽç«¯æ”¯æŒ | 🔴需åŽç«¯ | | P3 | 用户等级 | 个人中心 | ç»éªŒå€¼+等级+徽章 | 🔴需åŽç«¯ | @@ -755,72 +861,62 @@ | æŽ¥å£æ–‡ä»¶ | Repository | 已用act | çŠ¶æ€ | |---------|-----------|---------|------| -| `api.php` | RecipeRepository | list/detail/full/ingredients/ingredient_detail/search/categories/tags/stats/unified_list/unified_detail/unified_search/unified_hot/query | ✅ | +| `api.php` | RecipeRepository | list/detail/full/ingredients/ingredient_detail/search/categories/tags/stats/unified_list/unified_detail/unified_search/unified_hot/query/mini | ✅ | | `api_action.php` | ActionRepository | like/rate/view/ip_status | ✅ | | `api_feed.php` | FeedRepository | recommend/latest/hot/prefetch | ✅ | -| `api_filter.php` | RecipeRepository+SearchController | recipe_main_categories/taste_tags/cooking_tags/filter_recipes/global_search | ✅ | -| `api_hot.php` | HotRepository(→stats_full.php?act=hot) | hot | ✅ | -| `api_what_to_eat.php` | WhatToEatRepository | filter_apply/detail | ✅ | +| `api_filter.php` | RecipeRepository+SearchController | recipe_main_categories/taste_tags/cooking_tags/filter_recipes/global_search/meal_times/recipe_sub_categories/ingredient_main_categories/ingredient_sub_categories/category_tags/filter_ingredients/ingredient_recipes | ✅ | +| `api_hot.php` | HotRepository(→stats_full.php?act=hot) | hot(sort=view/like/rate) | ✅ | +| `api_what_to_eat.php` | WhatToEatRepository | filter_apply/detail/filter_steps | ✅ | | `api_discover.php` | DiscoverRepository | éšæœºæ•°æ® | ✅ | -| `stats_full.php` | StatsRepository | online/request/hot | ✅ | -| 陿€æ•°æ® | 未使用 | eating_times.json/nutrition_types.json/gmy.json | âŒæœªä½¿ç”¨ | +| `api_check_duplicate.php` | RecipeRepository | check_title/check_ingredient/check_step/check_content/check_all | ✅v0.92.0 | +| `stats_full.php` | StatsRepository | online/request/hot/stats | ✅ | +| 陿€æ•°æ® | 部分使用 | eating_times.json(✅)/nutrition_types.json(✅)/gmy.json(✅) | ✅v0.92.0 | ### 📊 未使用API接å£ä¸€è§ˆ +> 注:以下接å£åœ¨v0.92.0版本已大部分实现,仅ä¿ç•™å°‘é‡å¾…å¼€å‘æŽ¥å£ + | æŽ¥å£ | act/傿•° | 文档æè¿° | 当å‰çŠ¶æ€ | |------|---------|---------|---------| -| `api.php?act=mini` | mini | 迷你版èœè°±ä¿¡æ¯(~1KB),适用于列表页快速加载 | âŒæœªè°ƒç”¨ | -| `api_filter.php?act=meal_times` | meal_times | ç”¨é¤æ—¶æ®µåˆ—表(æ—©é¤/中é¤/晚é¤ç­‰) | âŒæœªè°ƒç”¨ | -| `api_filter.php?act=recipe_sub_categories` | recipe_sub_categories&parent_id= | 食谱å­åˆ†ç±»åˆ—表 | âŒæœªè°ƒç”¨ | -| `api_filter.php?act=ingredient_main_categories` | ingredient_main_categories | 食æå¤§ç±»åˆ—表 | âŒæœªè°ƒç”¨ | -| `api_filter.php?act=ingredient_sub_categories` | ingredient_sub_categories&parent_id= | 食æå­åˆ†ç±»åˆ—表 | âŒæœªè°ƒç”¨ | -| `api_filter.php?act=category_tags` | category_tags&category_id= | 指定分类下的å£å‘³+工艺标签 | âŒæœªè°ƒç”¨ | -| `api_filter.php` exclude_*傿•° | exclude_category/taste/cooking/allergenç­‰ | 排除筛选(排除分类/å£å‘³/工艺/过æ•原) | âŒæœªè°ƒç”¨ | -| `api_filter.php` 高级筛选 | nutrition_name/min/max, allergen, ingredient, author_id | è¥å…»èŒƒå›´/过æ•原/食æ/作者筛选 | âŒæœªè°ƒç”¨ | -| `api_what_to_eat.php?act=filter_steps` | filter_steps&category= | 获å–筛选步骤和èœè°±æ•°é‡ | âŒæœªè°ƒç”¨ | -| `api_what_to_eat.php?act=detail&code=` | detail&code=CP032892 | ç¼–ç æŸ¥è¯¢èœè°±è¯¦æƒ… | âŒæœªè°ƒç”¨ | -| `api_what_to_eat.php?act=detail&title=&fuzzy=1` | detail&title=&fuzzy=1 | 模糊标题æœç´¢ | âŒæœªè°ƒç”¨ | -| `api_hot.php?sort=rate` | sort=rate | 按评分排åºçš„热门排行 | âŒæœªè°ƒç”¨ | -| `api.php?act=unified_list&type=ingredient` | unified_list&type=ingredient | 统一格å¼é£Ÿæåˆ—表 | âŒæœªè°ƒç”¨ | -| `api.php?act=unified_detail&type=ingredient` | unified_detail&type=ingredient | 统一格å¼é£Ÿæè¯¦æƒ… | âŒæœªè°ƒç”¨ | -| `api.php?act=unified_hot&type=ingredient` | unified_hot&type=ingredient | 统一格å¼é£Ÿæçƒ­é—¨ | âŒæœªè°ƒç”¨ | -| `eating_times.json` | 陿€èµ„æº | 34ç§ç”¨é¤æ—¶æ®µæ•°æ®(标准/组åˆ/频率/方法) | âŒæœªä½¿ç”¨ | -| `nutrition_types.json` | 陿€èµ„æº | 31ç§è¥å…»æˆåˆ†æ•°æ®(å«å•ä½) | âŒæœªä½¿ç”¨ | -| `gmy.json` | 陿€èµ„æº | 585ç§è¿‡æ•原数æ®(21大类) | âŒæœªä½¿ç”¨ | +| `api.php?act=unified_list&type=ingredient` | unified_list&type=ingredient | 统一格å¼é£Ÿæåˆ—表 | ðŸ”´å¾…å¼€å‘ | +| `api.php?act=unified_detail&type=ingredient` | unified_detail&type=ingredient | 统一格å¼é£Ÿæè¯¦æƒ… | ðŸ”´å¾…å¼€å‘ | +| `api.php?act=unified_hot&type=ingredient` | unified_hot&type=ingredient | 统一格å¼é£Ÿæçƒ­é—¨ | ðŸ”´å¾…å¼€å‘ | --- ### 🟢 已有APIå¯ç›´æŽ¥å¼€å‘(P2优先级) -| # | 功能 | APIæŽ¥å£ | æ•°æ®æº | 页é¢ä½ç½® | å¼€å‘夿‚度 | 说明 | +> 注:以下功能在v0.92.0版本已全部实现 + +| # | 功能 | APIæŽ¥å£ | æ•°æ®æº | 页é¢ä½ç½® | å¼€å‘夿‚度 | çŠ¶æ€ | |---|------|---------|--------|---------|-----------|------| -| 1 | ðŸ• ç”¨é¤æ—¶æ®µæŽ¨è | `api_filter.php?act=meal_times` | eating_times.json(34ç§) | 首页/工具中心 | â­â­ | æ ¹æ®å½“剿—¶é—´æ™ºèƒ½æŽ¨èæ—©/åˆ/晚é¤ï¼Œé¦–页瀑布æµå·²æœ‰æ—¶æ®µå¡ç‰‡ä½†è·³è½¬æœç´¢é¡µï¼Œåº”改为时段专属推è页 | -| 2 | âš ï¸ è¿‡æ•原警示 | `api.php?act=full` allergens字段 | gmy.json(585ç§) | èœå“详情页 | â­â­ | 详情页显示过æ•原警告,结åˆç”¨æˆ·è¿‡æ•原设置自动过滤,已有allergen字段但未展示 | -| 3 | 📊 è¥å…»å¯è§†åŒ– | `api.php?act=full` nutrition字段 | nutrition_types.json(31ç§) | èœå“详情页/è¥å…»ä¸­å¿ƒ | â­â­â­ | 环形图/进度æ¡å±•示è¥å…»å æ¯”,nutrition字段已有数æ®ï¼Œéœ€å‰ç«¯å¯è§†åŒ–组件 | -| 4 | 🆠评分排行榜 | `api_hot.php?type=recipe&sort=rate` | api_hot.php | 热门排行页 | â­ | 热门页已有Tab,增加"评分榜"排åºé€‰é¡¹ï¼ŒAPI已支æŒsort=rate | -| 5 | 📱 è¿·ä½ ä¿¡æ¯åŠ è½½ | `api.php?act=mini&id=` | api.php | 列表页/å¡ç‰‡ | â­â­ | 列表页使用mini接å£(~1KB)替代detail(~10KB),10倿€§èƒ½æå‡ï¼Œéœ€æ”¹é€ åˆ—表加载逻辑 | -| 6 | 🔠排除筛选 | `api_filter.php?act=filter_recipes` exclude_*傿•° | api_filter.php | 高级æœç´¢é¡µ | â­ | 高级æœç´¢é¡µå¢žåŠ "排除"选项,如排除辣味/油炸等,API已支æŒ7个exclude傿•° | -| 7 | 🌠IPçŠ¶æ€æ˜¾ç¤º | `api_action.php?act=ip_status` | api_action.php | èœå“详情页 | â­ | è¯„åˆ†å‰æ˜¾ç¤ºä»Šæ—¥å‰©ä½™è¯„分次数,ActionRepositoryå·²å°è£…fetchIpStatus()但UI未使用 | -| 8 | ðŸ·ï¸ 分类标签è”动 | `api_filter.php?act=category_tags&category_id=` | api_filter.php | 分类æµè§ˆé¡µ/高级æœç´¢ | â­â­ | 选择分类åŽè‡ªåŠ¨åŠ è½½è¯¥åˆ†ç±»ä¸‹çš„å£å‘³+工艺标签,æå‡ç­›é€‰ä½“验 | -| 9 | 🥗 食æåˆ†ç±»æµè§ˆ | `api_filter.php?act=ingredient_main_categories/sub_categories` | api_filter.php | å‘现页/工具中心 | â­â­ | 食æå¤§ç±»â†’å­ç±»â†’食æåˆ—表三级æµè§ˆï¼Œå½“å‰å‘现页食æåˆ†ç±»è·³è½¬åˆ†ç±»æµè§ˆé¡µä½†æ— é£Ÿæä¸“用分类 | -| 10 | 📋 食谱å­åˆ†ç±» | `api_filter.php?act=recipe_sub_categories&parent_id=` | api_filter.php | 分类æµè§ˆé¡µ | â­ | 当å‰åˆ†ç±»æµè§ˆä½¿ç”¨api.php?act=categories,改用filter接å£å¯èŽ·å–æ›´ä¸°å¯Œçš„å­åˆ†ç±»æ•°æ® | -| 11 | 🎲 筛选步骤引导 | `api_what_to_eat.php?act=filter_steps` | api_what_to_eat.php | 今天åƒä»€ä¹ˆ | â­â­ | 逿­¥ç­›é€‰ï¼šå…ˆé€‰åˆ†ç±»â†’å†é€‰æ ‡ç­¾â†’æ˜¾ç¤ºåŒ¹é…æ•°é‡ï¼Œæ¯”当å‰ç›´æŽ¥filter_apply体验更好 | -| 12 | 🔢 ç¼–ç /模糊查询 | `api_what_to_eat.php?act=detail&code=/title=` | api_what_to_eat.php | æœç´¢é¡µ | â­ | 支æŒèœè°±ç¼–ç (CP032892)查询和标题模糊æœç´¢ï¼Œæ‰©å±•æœç´¢èƒ½åŠ› | +| 1 | ðŸ• ç”¨é¤æ—¶æ®µæŽ¨è | `api_filter.php?act=meal_times` | eating_times.json(34ç§) | 首页/工具中心 | â­â­ | ✅v0.92.0 | +| 2 | âš ï¸ è¿‡æ•原警示 | `api.php?act=full` allergens字段 | gmy.json(585ç§) | èœå“详情页 | â­â­ | ✅v0.92.0 | +| 3 | 📊 è¥å…»å¯è§†åŒ– | `api.php?act=full` nutrition字段 | nutrition_types.json(31ç§) | èœå“详情页/è¥å…»ä¸­å¿ƒ | â­â­â­ | ✅v0.92.0 | +| 4 | 🆠评分排行榜 | `api_hot.php?type=recipe&sort=rate` | api_hot.php | 热门排行页 | â­ | ✅v0.92.0 | +| 5 | 📱 è¿·ä½ ä¿¡æ¯åŠ è½½ | `api.php?act=mini&id=` | api.php | 列表页/å¡ç‰‡ | â­â­ | ✅v0.92.0 | +| 6 | 🔠排除筛选 | `api_filter.php?act=filter_recipes` exclude_*傿•° | api_filter.php | 高级æœç´¢é¡µ | â­ | ✅v0.92.0 | +| 7 | 🌠IPçŠ¶æ€æ˜¾ç¤º | `api_action.php?act=ip_status` | api_action.php | èœå“详情页 | â­ | ✅v0.92.0 | +| 8 | ðŸ·ï¸ 分类标签è”动 | `api_filter.php?act=category_tags&category_id=` | api_filter.php | 分类æµè§ˆé¡µ/高级æœç´¢ | â­â­ | ✅v0.92.0 | +| 9 | 🥗 食æåˆ†ç±»æµè§ˆ | `api_filter.php?act=ingredient_main_categories/sub_categories` | api_filter.php | å‘现页/工具中心 | â­â­ | ✅v0.92.0 | +| 10 | 📋 食谱å­åˆ†ç±» | `api_filter.php?act=recipe_sub_categories&parent_id=` | api_filter.php | 分类æµè§ˆé¡µ | â­ | ✅v0.92.0 | +| 11 | 🎲 筛选步骤引导 | `api_what_to_eat.php?act=filter_steps` | api_what_to_eat.php | 今天åƒä»€ä¹ˆ | â­â­ | ✅v0.92.0 | +| 12 | 🔢 ç¼–ç /模糊查询 | `api_what_to_eat.php?act=detail&code=/title=` | api_what_to_eat.php | æœç´¢é¡µ | â­ | ✅v0.92.0 | ### 🟡 需组åˆAPIå¼€å‘(P3优先级) -| # | 功能 | 所需API | 页é¢ä½ç½® | å¼€å‘夿‚度 | 说明 | +| # | 功能 | 所需API | 页é¢ä½ç½® | å¼€å‘夿‚度 | çŠ¶æ€ | |---|------|---------|---------|-----------|------| -| 1 | 🧠 智能推è | `api_feed.php?act=recommend` + `api_filter.php?act=filter_recipes` + `gmy.json` + 用户å好设置 | 首页 | â­â­â­â­ | ç»“åˆæ—¶æ®µ+è¥å…»+过æ•原+用户å好的智能推è,需设计推è算法æƒé‡ | -| 2 | 📅 æ¯æ—¥èœå•规划 | `api_what_to_eat.php?act=filter_apply` × 3次 + `eating_times.json` | 工具中心 | â­â­â­ | ä¸€æ¬¡æ€§ç”Ÿæˆæ—©ä¸­æ™šé¤å®Œæ•´èœå•,需新建èœå•è§„åˆ’é¡µé¢ | -| 3 | 📱 äºŒç»´ç æµ·æŠ¥ | `api.php?act=detail` code字段 + qr_flutter库 | èœå“详情页 | â­â­ | 生æˆèœè°±äºŒç»´ç åˆ†äº«å›¾ï¼Œcode字段已有(如CP032892) | -| 4 | 🔗 社交分享增强 | `api.php?act=detail` code字段 + `api_hot.php` statistics | èœå“详情页 | â­â­ | 分享链接å«èœè°±ç¼–ç +热度标签,当å‰åˆ†äº«åŠŸèƒ½å·²æœ‰ä½†å†…å®¹ç®€å• | -| 5 | ðŸ‹ï¸ å¥èº«é¤æŽ¨è | `api_filter.php?act=filter_recipes` nutrition_min/max + nutrition_types.json | 工具中心/å‘现页 | â­â­â­ | 高蛋白(>30g)/低脂(<10g)/低碳水èœè°±ç­›é€‰ï¼Œéœ€è¥å…»ç›®æ ‡è®¾ç½®UI | -| 6 | 📋 过æ•原报告 | `api.php?act=full` allergens + `gmy.json` | èœå“详情页 | â­â­â­ | èœè°±è¿‡æ•åŽŸå®Œæ•´åˆ†æžæŠ¥å‘Šï¼Œäº¤å‰æ¯”对585ç§è¿‡æ•åŽŸæ•°æ® | -| 7 | 🥗 食æè¥å…»è¯¦æƒ… | `api.php?act=ingredient_detail` + `nutrition_types.json` | 食æè¯¦æƒ…页 | â­â­ | 食æè¯¦æƒ…页增加è¥å…»æˆåˆ†è¡¨æ ¼+å•ä½åŒ¹é…,nutrition_types.jsonæä¾›31ç§è¥å…»å•ä½ | -| 8 | 🔄 é£Ÿææ›¿ä»£å»ºè®® | `api_filter.php?act=filter_recipes` ingredient傿•° + `gmy.json` | 食æè¯¦æƒ…页 | â­â­â­ | 缺少æŸé£Ÿææ—¶æŽ¨è替代å“ï¼Œéœ€å»ºç«‹é£Ÿææ›¿ä»£å…³ç³»æ˜ å°„ | -| 9 | 📈 è¥å…»ç›®æ ‡è¿½è¸ª | `api.php?act=full` nutrition × 多èœè°± + `nutrition_types.json` | è¥å…»ä¸­å¿ƒ | â­â­â­â­ | æ¯æ—¥è¥å…»æ‘„入统计+ç›®æ ‡è¿½è¸ªï¼Œéœ€è®°å½•ç”¨æˆ·æ¯æ—¥é¥®é£Ÿ | -| 10 | ðŸ·ï¸ 统一格å¼è¾“出 | `api.php?act=unified_list/detail/search/hot` type=ingredient | 食æç›¸å…³é¡µé¢ | â­â­ | 统一格å¼ç®€åŒ–é£Ÿææ•°æ®å¤„ç†ï¼Œå½“å‰é£Ÿæé¡µé¢ä½¿ç”¨ä¸åŒæŽ¥å£æ ¼å¼ | +| 1 | 🧠 智能推è | `api_feed.php?act=recommend` + `api_filter.php?act=filter_recipes` + `gmy.json` + 用户å好设置 | 首页 | â­â­â­â­ | ðŸ”´å¾…å¼€å‘ | +| 2 | 📅 æ¯æ—¥èœå•规划 | `api_what_to_eat.php?act=filter_apply` × 3次 + `eating_times.json` | 工具中心 | â­â­â­ | ðŸ”´å¾…å¼€å‘ | +| 3 | 📱 äºŒç»´ç æµ·æŠ¥ | `api.php?act=detail` code字段 + qr_flutter库 | èœå“详情页 | â­â­ | ðŸ”´å¾…å¼€å‘ | +| 4 | 🔗 社交分享增强 | `api.php?act=detail` code字段 + `api_hot.php` statistics | èœå“详情页 | â­â­ | ðŸ”´å¾…å¼€å‘ | +| 5 | ðŸ‹ï¸ å¥èº«é¤æŽ¨è | `api_filter.php?act=filter_recipes` nutrition_min/max + nutrition_types.json | 工具中心/å‘现页 | â­â­â­ | ✅v0.92.0 | +| 6 | 📋 过æ•原报告 | `api.php?act=full` allergens + `gmy.json` | èœå“详情页 | â­â­â­ | ✅v0.92.0 | +| 7 | 🥗 食æè¥å…»è¯¦æƒ… | `api.php?act=ingredient_detail` + `nutrition_types.json` | 食æè¯¦æƒ…页 | â­â­ | ✅v0.92.0 | +| 8 | 🔄 é£Ÿææ›¿ä»£å»ºè®® | `api_filter.php?act=filter_recipes` ingredient傿•° + `gmy.json` | 食æè¯¦æƒ…页 | â­â­â­ | ✅v0.92.0 | +| 9 | 📈 è¥å…»ç›®æ ‡è¿½è¸ª | `api.php?act=full` nutrition × 多èœè°± + `nutrition_types.json` | è¥å…»ä¸­å¿ƒ | â­â­â­â­ | ✅v0.92.0 | +| 10 | ðŸ·ï¸ 统一格å¼è¾“出 | `api.php?act=unified_list/detail/search/hot` type=ingredient | 食æç›¸å…³é¡µé¢ | â­â­ | ðŸ”´å¾…å¼€å‘ | ### 🔴 需åŽç«¯æ–°å¼€å‘API diff --git a/docs/dev/UNFINISHED_FEATURES.md b/docs/dev/UNFINISHED_FEATURES.md index c26c2c7..5a3aaa9 100644 --- a/docs/dev/UNFINISHED_FEATURES.md +++ b/docs/dev/UNFINISHED_FEATURES.md @@ -1,6 +1,6 @@ # 📋 未完æˆåŠŸèƒ½æ¸…å• -> 创建: 2026-04-09 | æ›´æ–°: 2026-04-13 v0.92.0 | 优先级: P1=核心 P2=é‡è¦ P3=增强 | 优先级值1-5(5=最高) +> 创建: 2026-04-09 | æ›´æ–°: 2026-04-13 v0.92.4 | 优先级: P1=核心 P2=é‡è¦ P3=增强 | 优先级值1-5(5=最高) --- @@ -33,7 +33,9 @@ | 三åå›› | 食æè¯¦æƒ…本地缓存+ç¼“å­˜ç®¡ç† | 2 | 2 | 100% | ✅ | | 三å五 | 食æè¯¦æƒ…é¡µé—ªé€€ä¿®å¤ | 1 | 1 | 100% | ✅ | | 三åå…­ | 21项功能批é‡å®žçް | 21 | 21 | 100% | ✅ | -| **åˆè®¡** | **220** | **210** | **95%** | | +| 三å七 | 目录结构整ç†+å¯¼å…¥è·¯å¾„ä¿®å¤ | 8 | 8 | 100% | ✅ | +| 三åå…« | UI布局优化+ç¼“å­˜ä¿®å¤ | 4 | 4 | 100% | ✅ | +| **åˆè®¡** | **232** | **222** | **96%** | | --- @@ -217,6 +219,22 @@ - 📈 æ•°æ®ç®¡ç†ä¸­å¿ƒå¢žå¼ºï¼šDataCenterPage新增è¿è¥å¤§å±å…¥å£ - 🔧 代ç è´¨é‡ï¼šflutter analyze零错误 +### 阶段三å七:目录结构整ç†+å¯¼å…¥è·¯å¾„ä¿®å¤ âœ… +- 📠lib/src目录é‡ç»„:æ¯ä¸ªæ–‡ä»¶å¤¹â‰¤8文件,按功能分å­ç›®å½• +- 🔧 å¯¼å…¥è·¯å¾„æ›´æ–°ï¼šæ‰¹é‡æ›´æ–°importè·¯å¾„é€‚é…æ–°ç›®å½•结构 +- 🧹 BOM字符清ç†ï¼š72个Dart文件移除UTF-8 BOM (U+FEFF) +- 🛠类型错误修å¤ï¼šShoppingItemModel/MealRecordModelç±»åž‹åŒ¹é… +- 🧹 æœªä½¿ç”¨ä»£ç æ¸…ç†ï¼šç§»é™¤æœªä½¿ç”¨çš„导入和字段 +- 📊 flutter analyze:解决所有critical错误 +- 🔄 è·¯ç”±å‚æ•°ä¿®å¤ï¼šCategoryModel类型转æ¢é—®é¢˜ä¿®å¤ +- ðŸ“ æ–‡æ¡£æ›´æ–°ï¼šåŒæ­¥ç›®å½•ç»“æž„å˜æ›´ + +### 阶段三å八:UI布局优化+ç¼“å­˜ä¿®å¤ âœ… +- 📱 æ”¶è—页é¢ç½‘格布局:GridView 2列å¡ç‰‡å±•示 +- ðŸ› ï¸ å·¥å…·ä¸­å¿ƒå¢žå¼ºï¼šæ–°å¢ž"使用工具"按钮直接打开工具功能 +- 💾 食æç¼“存修å¤ï¼šCacheServiceé”®å匹é…é—®é¢˜ä¿®å¤ +- 🔗 缓存管ç†é¡µé¢è·³è½¬ä¿®å¤ï¼šæ­£ç¡®è·³è½¬åˆ°é£Ÿæè¯¦æƒ…页 + ### 阶段三å:å‘现页å£å‘³/工艺筛选 ✅ - ✅ å£å‘³æ ‡ç­¾ç­›é€‰ / ✅ 工艺标签筛选 - ✅ 相关èœè°±æŽ¨è(详情页底部)— v0.92.0实现 @@ -245,46 +263,25 @@ ## 📊 API接å£ä½¿ç”¨çжæ€ä¸€è§ˆ -> 基于 API_DOC.md v3.2.0 + APP_GUIDE.md v2.9.0 +> 基于 API_DOC.md v3.2.0 + APP_GUIDE.md v2.9.0,更新于 v0.92.0 ### ✅ å·²ä½¿ç”¨æŽ¥å£ | æŽ¥å£æ–‡ä»¶ | Repository | 已用act | |---------|-----------|---------| -| `api.php` | RecipeRepository | list/detail/full/ingredients/ingredient_detail/search/categories/tags/stats/unified_list/unified_detail/unified_search/unified_hot/query | +| `api.php` | RecipeRepository | list/detail/full/ingredients/ingredient_detail/search/categories/tags/stats/unified_list/unified_detail/unified_search/unified_hot/query/mini | | `api_action.php` | ActionRepository | like/rate/view/ip_status | | `api_feed.php` | FeedRepository | recommend/latest/hot/prefetch | -| `api_filter.php` | RecipeRepository+SearchController | recipe_main_categories/taste_tags/cooking_tags/filter_recipes/global_search | -| `api_hot.php` | HotRepository | hot(today/month/total) | -| `api_what_to_eat.php` | WhatToEatRepository | filter_apply/detail | +| `api_filter.php` | RecipeRepository+SearchController | recipe_main_categories/taste_tags/cooking_tags/filter_recipes/global_search/meal_times/recipe_sub_categories/ingredient_main_categories/ingredient_sub_categories/category_tags/filter_ingredients/ingredient_recipes | +| `api_hot.php` | HotRepository | hot(today/month/total) sort=view/like/rate | +| `api_what_to_eat.php` | WhatToEatRepository | filter_apply/detail/filter_steps | | `api_discover.php` | DiscoverRepository | éšæœºæ•°æ® | -| `stats_full.php` | StatsRepository+OnlineRepository | online/request/hot/heartbeat | +| `api_check_duplicate.php` | RecipeRepository | check_title/check_ingredient/check_step/check_content/check_all | +| `stats_full.php` | StatsRepository+OnlineRepository | online/request/hot/stats/heartbeat | +| 陿€æ•°æ® | å„é¡µé¢ | eating_times.json(✅)/nutrition_types.json(✅)/gmy.json(✅) | -### ⌠未使用接å£ï¼ˆå¯ç›´æŽ¥è°ƒç”¨ï¼‰ +### 🔴 未使用接å£ï¼ˆå¾…å¼€å‘) | æŽ¥å£ | act | 功能 | å¯å¼€å‘功能 | |------|-----|------|-----------| -| `api.php` | mini | 迷你版èœè°±(~1KB) | 列表页性能优化 | -| `api_filter.php` | meal_times | ç”¨é¤æ—¶æ®µåˆ—表 | 时段推è | -| `api_filter.php` | recipe_sub_categories | 食谱å­åˆ†ç±» | 分类æµè§ˆå¢žå¼º | -| `api_filter.php` | ingredient_main_categories | 食æå¤§ç±» | 食æåˆ†ç±»æµè§ˆ | -| `api_filter.php` | ingredient_sub_categories | 食æå­åˆ†ç±» | 食æä¸‰çº§æµè§ˆ | -| `api_filter.php` | category_tags | 分类下标签 | 标签è”动筛选 | -| `api_filter.php` | filter_ingredients | 食æç­›é€‰ | 高级æœç´¢é£Ÿæ | -| `api_filter.php` | ingredient_recipes | 食æå¯¹åº”èœå“ | 食æè¯¦æƒ…页èœå“列表 | -| `api_filter.php` | index | 接å£ç´¢å¼• | 调试/æŽ¥å£æ–‡æ¡£ | -| `api_what_to_eat.php` | filter_steps | 筛选步骤引导 | "åƒä»€ä¹ˆ"步骤UI | -| `api_hot.php` | sort=rate | 评分排行 | 评分排行榜 | -| `api_filter.php` | exclude_*傿•°(7个) | 排除筛选 | 高级æœç´¢æŽ’除选项 | -| `api_filter.php` | nutrition_min/max | è¥å…»èŒƒå›´ç­›é€‰ | å¥èº«é¤æŽ¨è | -| `api_check_duplicate.php` | 5ç§act | æŸ¥é‡æ£€æµ‹ | èœè°±ä¸Šä¼ æŸ¥é‡ | | `api.php` | unified_* type=ingredient | 统一格å¼é£Ÿæ | é£Ÿææ•°æ®æ ‡å‡†åŒ– | -| `stats_full.php` | stats layer=detail/full | 详细/完整统计 | è¿è¥æ•°æ®å¤§å± | - -### âŒ æœªä½¿ç”¨é™æ€èµ„æº - -| 文件 | æ•°æ®é‡ | 当å‰ä½¿ç”¨æƒ…况 | å¯å¼€å‘功能 | -|------|--------|-------------|-----------| -| `eating_times.json` | 34ç§æ—¶æ®µ | ä»…meal_time_recommend_page直接HTTP请求 | ç”¨é¤æ—¶æ®µæŽ¨è | -| `nutrition_types.json` | 31ç§è¥å…»(å«å•ä½) | âŒå®Œå…¨æœªä½¿ç”¨ | è¥å…»å¯è§†åŒ–+目标追踪 | -| `gmy.json` | 585ç§è¿‡æ•原(21大类) | ä»…allergen_checker_page直接HTTP请求 | 过æ•原警示+报告 | diff --git a/lib/src/config/app_routes.dart b/lib/src/config/app_routes.dart index 362a52d..e21a49b 100644 --- a/lib/src/config/app_routes.dart +++ b/lib/src/config/app_routes.dart @@ -1,4 +1,4 @@ -import 'package:get/get.dart'; +import 'package:get/get.dart'; import 'package:mom_kitchen/src/models/recipe/category_model.dart'; import 'package:mom_kitchen/src/pages/home/home_page.dart'; import 'package:mom_kitchen/src/pages/profile/settings/theme_demo_page.dart'; @@ -8,6 +8,7 @@ import 'package:mom_kitchen/src/pages/profile/profile_page.dart'; import 'package:mom_kitchen/src/pages/profile/settings/personalization_page.dart'; import 'package:mom_kitchen/src/pages/profile/social/chat_page.dart' show FeedbackPage; +import 'package:mom_kitchen/src/pages/profile/about_page.dart'; import 'package:mom_kitchen/src/widgets/navigation_widgets.dart'; import 'package:mom_kitchen/src/standards/page_validator.dart'; import 'package:mom_kitchen/src/standards/route_middleware.dart'; @@ -43,6 +44,8 @@ import 'package:mom_kitchen/src/pages/tools/planning/eating_times_page.dart'; import 'package:mom_kitchen/src/pages/tools/planning/weekly_menu_planner_page.dart'; import 'package:mom_kitchen/src/pages/tools/planning/daily_menu_page.dart'; import 'package:mom_kitchen/src/pages/profile/bedtime_reminder_page.dart'; +import 'package:mom_kitchen/src/pages/tools/cooking_tips_list_page.dart'; +import 'package:mom_kitchen/src/pages/profile/references_page.dart'; import 'package:mom_kitchen/src/app_binding.dart'; class AppRoutes { @@ -91,6 +94,9 @@ class AppRoutes { static const String allergenReport = '/allergen-report'; static const String duplicateCheck = '/duplicate-check'; static const String statsDashboard = '/stats-dashboard'; + static const String cookingTips = '/cooking-tips'; + static const String about = '/about'; + static const String references = '/references'; static final List pages = [ GetPage( @@ -128,6 +134,11 @@ class AppRoutes { page: () => const FeedbackPage(), middlewares: [PageStandardsMiddleware()], ), + GetPage( + name: about, + page: () => const AboutPage(), + middlewares: [PageStandardsMiddleware()], + ), GetPage( name: main, page: () => const MainTabView(), @@ -319,7 +330,7 @@ class AppRoutes { : null); return CategoryBrowsePage( category: category, - title: args?['title'] ?? '·ÖÀàä¯ÀÀ', + title: args?['title'] ?? '�������', loadRecipesDirectly: args?['loadRecipesDirectly'] ?? false, isIngredient: args?['isIngredient'] ?? false, ); @@ -384,6 +395,16 @@ class AppRoutes { page: () => const StatsDashboardPage(), middlewares: [PageStandardsMiddleware()], ), + GetPage( + name: cookingTips, + page: () => const CookingTipsListPage(), + middlewares: [PageStandardsMiddleware()], + ), + GetPage( + name: references, + page: () => const ReferencesPage(), + middlewares: [PageStandardsMiddleware()], + ), ]; static void registerAllPages() { @@ -391,7 +412,7 @@ class AppRoutes { PageInfo( route: home, name: 'Home Page', - description: 'Ö÷Ò³Ãæ', + description: '��ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -404,7 +425,7 @@ class AppRoutes { PageInfo( route: theme, name: 'Theme Demo Page', - description: 'Ö÷ÌâÑÝÊ¾Ò³Ãæ', + description: '������ʾҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -421,7 +442,7 @@ class AppRoutes { PageInfo( route: favorites, name: 'Favorites Page', - description: 'ÊÕ²ØÒ³Ãæ', + description: '�ղ�ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -433,7 +454,7 @@ class AppRoutes { PageInfo( route: discover, name: 'Discover Page', - description: '·¢ÏÖÒ³Ãæ', + description: '����ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -445,7 +466,7 @@ class AppRoutes { PageInfo( route: profile, name: 'Profile Page', - description: '¸öÈË×ÊÁÏÒ³Ãæ', + description: '��������ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -457,7 +478,7 @@ class AppRoutes { PageInfo( route: personalization, name: 'Personalization Page', - description: '¸öÐÔ»¯ÉèÖÃÒ³Ãæ', + description: '���Ի�����ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -472,7 +493,7 @@ class AppRoutes { PageInfo( route: chat, name: 'Feedback Page', - description: 'Òâ¼û·´À¡Ò³Ãæ', + description: '�������ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -484,7 +505,7 @@ class AppRoutes { PageInfo( route: main, name: 'Main Tab View', - description: 'Ö÷±êǩҳ', + description: '����ǩҳ', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -496,7 +517,7 @@ class AppRoutes { PageInfo( route: whatToEat, name: 'What To Eat Page', - description: '½ñÌì³ÔÊ²Ã´Ò³Ãæ', + description: '�����ʲôҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -508,7 +529,7 @@ class AppRoutes { PageInfo( route: '/hot', name: 'Hot Page', - description: 'ÈÈÃÅÅÅÐÐÒ³Ãæ', + description: '��������ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -520,7 +541,7 @@ class AppRoutes { PageInfo( route: nutrition, name: 'Nutrition Center Page', - description: 'ÓªÑøÖÐÐÄÒ³Ãæ', + description: 'Ӫ������ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -532,7 +553,7 @@ class AppRoutes { PageInfo( route: goalSetting, name: 'Goal Setting Page', - description: 'Ä¿±êÉèÖÃÒ³Ãæ', + description: 'Ŀ������ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -544,7 +565,7 @@ class AppRoutes { PageInfo( route: shoppingList, name: 'Shopping List Page', - description: '¹ºÎïÇåµ¥Ò³Ãæ', + description: '�����嵥ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -556,7 +577,7 @@ class AppRoutes { PageInfo( route: search, name: 'Search Page', - description: 'ËÑË÷Ò³Ãæ', + description: '����ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -568,7 +589,7 @@ class AppRoutes { PageInfo( route: recipeDetail, name: 'Recipe Detail Page', - description: '²ËÆ×ÏêÇéÒ³Ãæ', + description: '��������ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -580,7 +601,7 @@ class AppRoutes { PageInfo( route: nutritionReport, name: 'Nutrition Report Page', - description: 'ÓªÑø±¨¸æÒ³Ãæ', + description: 'Ӫ������ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -592,7 +613,7 @@ class AppRoutes { PageInfo( route: cookingTimer, name: 'Cooking Timer Page', - description: 'Åë⿼ÆÊ±Æ÷Ò³Ãæ', + description: '��⿼�ʱ��ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -604,7 +625,7 @@ class AppRoutes { PageInfo( route: unitConverter, name: 'Unit Converter Page', - description: 'ÓÃÁ¿»»Ë㹤¾ßÒ³Ãæ', + description: '�������㹤��ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -616,7 +637,7 @@ class AppRoutes { PageInfo( route: bmiCalculator, name: 'BMI Calculator Page', - description: 'BMI¼ÆËãÆ÷Ò³Ãæ', + description: 'BMI������ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -628,7 +649,7 @@ class AppRoutes { PageInfo( route: servingScaler, name: 'Serving Scaler Page', - description: '·ÝÁ¿Ëõ·Å¹¤¾ßÒ³Ãæ', + description: '�������Ź���ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -640,7 +661,7 @@ class AppRoutes { PageInfo( route: toolsIngredient, name: 'Ingredient Detail Page', - description: 'ʳ²ÄÏêÇéÒ³Ãæ', + description: 'ʳ������ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -652,7 +673,7 @@ class AppRoutes { PageInfo( route: toolsIngredientRecommend, name: 'Ingredient Recommend Page', - description: 'ʳ²ÄÍÆ¼öÒ³Ãæ', + description: 'ʳ���Ƽ�ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -664,7 +685,7 @@ class AppRoutes { PageInfo( route: toolsIngredientRecipes, name: 'Ingredient Recipe List Page', - description: 'ʳ²Ä²ËÆ·ÁбíÒ³Ãæ', + description: 'ʳ�IJ�Ʒ�б�ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -677,7 +698,7 @@ class AppRoutes { PageInfo( route: toolsCenter, name: 'Tools Center Page', - description: '¹¤¾ßÖÐÐÄÒ³Ãæ', + description: '��������ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -689,7 +710,7 @@ class AppRoutes { PageInfo( route: cookingNote, name: 'Cooking Note Page', - description: 'Åë⿱ʼÇÒ³Ãæ', + description: '��⿱ʼ�ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, @@ -701,7 +722,7 @@ class AppRoutes { PageInfo( route: dataCenter, name: 'Data Center Page', - description: 'Êý¾Ý¹ÜÀíÖÐÐÄÒ³Ãæ', + description: '���ݹ�������ҳ��', requiredStandards: const [ StandardCheck.themeColors, StandardCheck.textColors, diff --git a/lib/src/models/data/cooking_tip_model.dart b/lib/src/models/data/cooking_tip_model.dart new file mode 100644 index 0000000..7713e38 --- /dev/null +++ b/lib/src/models/data/cooking_tip_model.dart @@ -0,0 +1,65 @@ +/* + * 文件: cooking_tip_model.dart + * åç§°: 烹饪技巧模型 + * 作用: 存储烹饪技巧的元数æ®å’Œå†…容 + * 创建: 2026-04-13 + * æ›´æ–°: 2026-04-13 åˆå§‹åˆ›å»ºï¼Œä½¿ç”¨æœ¬åœ° assets 路径 + */ + +class CookingTipModel { + final String id; + final String name; + final String category; + final String assetPath; + final String icon; + String? content; + + CookingTipModel({ + required this.id, + required this.name, + required this.category, + required this.assetPath, + required this.icon, + this.content, + }); + + factory CookingTipModel.fromJson(Map json) { + return CookingTipModel( + id: json['id'] as String, + name: json['name'] as String, + category: json['category'] as String, + assetPath: json['assetPath'] as String, + icon: json['icon'] as String, + content: json['content'] as String?, + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'category': category, + 'assetPath': assetPath, + 'icon': icon, + 'content': content, + }; + } + + CookingTipModel copyWith({ + String? id, + String? name, + String? category, + String? assetPath, + String? icon, + String? content, + }) { + return CookingTipModel( + id: id ?? this.id, + name: name ?? this.name, + category: category ?? this.category, + assetPath: assetPath ?? this.assetPath, + icon: icon ?? this.icon, + content: content ?? this.content, + ); + } +} diff --git a/lib/src/models/discover_model.dart b/lib/src/models/discover_model.dart index 53c64fc..624194d 100644 --- a/lib/src/models/discover_model.dart +++ b/lib/src/models/discover_model.dart @@ -126,6 +126,9 @@ class DiscoverData { for (final t in tags) { items.add(DiscoverItem.tag(t)); } + for (final n in nutritionTypes) { + items.add(DiscoverItem.nutrition(n)); + } for (final m in mealTimes) { items.add(DiscoverItem.mealTime(m)); } @@ -150,7 +153,7 @@ class DiscoverData { mealTimes.length; } -enum DiscoverItemType { recipe, ingredient, category, tag, mealTime } +enum DiscoverItemType { recipe, ingredient, category, tag, mealTime, nutrition } class DiscoverItem { final DiscoverItemType type; @@ -159,6 +162,7 @@ class DiscoverItem { final DiscoverCategory? category; final DiscoverTag? tag; final DiscoverMealTime? mealTime; + final DiscoverNutrition? nutrition; DiscoverItem._({ required this.type, @@ -167,6 +171,7 @@ class DiscoverItem { this.category, this.tag, this.mealTime, + this.nutrition, }); factory DiscoverItem.recipe(DiscoverRecipe r) => @@ -179,6 +184,8 @@ class DiscoverItem { DiscoverItem._(type: DiscoverItemType.tag, tag: t); factory DiscoverItem.mealTime(DiscoverMealTime m) => DiscoverItem._(type: DiscoverItemType.mealTime, mealTime: m); + factory DiscoverItem.nutrition(DiscoverNutrition n) => + DiscoverItem._(type: DiscoverItemType.nutrition, nutrition: n); } class DiscoverRecipe { @@ -244,12 +251,7 @@ class DiscoverRating { } Map toJson() { - return { - 'score': score, - 'nums': nums, - 'display': display, - 'star': star, - }; + return {'score': score, 'nums': nums, 'display': display, 'star': star}; } bool get hasRating => score > 0; @@ -269,10 +271,7 @@ class DiscoverCategoryRef { } Map toJson() { - return { - 'id': id, - 'name': name, - }; + return {'id': id, 'name': name}; } } @@ -318,6 +317,9 @@ class DiscoverCategory { final String type; final int count; final int parentId; + final int recipeCount; + final int ingredientCount; + final String parentName; DiscoverCategory({ required this.id, @@ -325,6 +327,9 @@ class DiscoverCategory { required this.type, required this.count, required this.parentId, + this.recipeCount = 0, + this.ingredientCount = 0, + this.parentName = '', }); factory DiscoverCategory.fromJson(Map json) { @@ -334,6 +339,9 @@ class DiscoverCategory { type: json['type'] ?? 'recipe', count: json['count'] ?? 0, parentId: json['parent_id'] ?? 0, + recipeCount: json['recipe_count'] ?? 0, + ingredientCount: json['ingredient_count'] ?? 0, + parentName: json['parent_name'] ?? '', ); } @@ -344,32 +352,54 @@ class DiscoverCategory { 'type': type, 'count': count, 'parent_id': parentId, + 'recipe_count': recipeCount, + 'ingredient_count': ingredientCount, + 'parent_name': parentName, }; } + + String get displayCount { + if (type == 'ingredient') { + return '$ingredientCountç§é£Ÿæ'; + } + return '$recipeCounté“èœè°±'; + } + + String get hierarchyName { + if (parentName.isNotEmpty) { + return '$parentName > $name'; + } + return name; + } } class DiscoverTag { final int id; final String name; final String type; + final int count; - DiscoverTag({required this.id, required this.name, required this.type}); + DiscoverTag({ + required this.id, + required this.name, + required this.type, + this.count = 0, + }); factory DiscoverTag.fromJson(Map json) { return DiscoverTag( id: json['id'] ?? 0, name: json['name'] ?? '', type: json['type'] ?? 'taste', + count: json['count'] ?? 0, ); } Map toJson() { - return { - 'id': id, - 'name': name, - 'type': type, - }; + return {'id': id, 'name': name, 'type': type, 'count': count}; } + + String get typeLabel => type == 'taste' ? 'å£å‘³' : '工艺'; } class DiscoverNutrition { @@ -386,10 +416,7 @@ class DiscoverNutrition { } Map toJson() { - return { - 'name': name, - 'unit': unit, - }; + return {'name': name, 'unit': unit}; } } @@ -409,10 +436,6 @@ class DiscoverMealTime { } Map toJson() { - return { - 'id': id, - 'name': name, - 'count': count, - }; + return {'id': id, 'name': name, 'count': count}; } } diff --git a/lib/src/pages/profile/about_page.dart b/lib/src/pages/profile/about_page.dart new file mode 100644 index 0000000..ec89c21 --- /dev/null +++ b/lib/src/pages/profile/about_page.dart @@ -0,0 +1,432 @@ +/* + * 文件: about_page.dart + * åç§°: å…³äºŽé¡µé¢ + * 作用: 展示应用信æ¯ã€ç‰ˆæœ¬å·ã€ç”¨æˆ·å馈入å£ã€å¼€å‘者文档等 + * 创建: 2026-04-13 + * æ›´æ–°: 2026-04-13 新增关于页é¢ï¼ŒåŒ…å«ç”¨æˆ·åé¦ˆå…¥å£ + * æ›´æ–°: 2026-04-13 新增开å‘者文档入å£ï¼ˆAPI文档ã€App接入指å—) + */ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart' show Divider; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/config/design_tokens.dart'; +import 'package:mom_kitchen/src/pages/profile/references_page.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class AboutPage extends StatelessWidget { + const AboutPage({super.key}); + + @override + Widget build(BuildContext context) { + final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; + + return CupertinoPageScaffold( + backgroundColor: isDark + ? DarkDesignTokens.background + : DesignTokens.background, + navigationBar: CupertinoNavigationBar( + middle: Text( + '关于', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + backgroundColor: isDark + ? DarkDesignTokens.glass.withValues(alpha: 0.8) + : DesignTokens.glass.withValues(alpha: 0.8), + border: null, + ), + child: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.all(DesignTokens.space4), + child: Column( + children: [ + _buildAppHeader(isDark), + const SizedBox(height: DesignTokens.space5), + _buildAppInfoSection(isDark), + const SizedBox(height: DesignTokens.space3), + _buildDeveloperDocsSection(isDark), + const SizedBox(height: DesignTokens.space3), + _buildContactSection(isDark), + const SizedBox(height: DesignTokens.space3), + _buildLegalSection(isDark), + const SizedBox(height: DesignTokens.space3), + _buildReferencesSection(isDark), + const SizedBox(height: DesignTokens.space5), + _buildFooter(isDark), + ], + ), + ), + ), + ); + } + + Widget _buildAppHeader(bool isDark) { + return Container( + padding: const EdgeInsets.all(DesignTokens.space5), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusLg, + boxShadow: DesignTokens.shadowsSm, + ), + child: Column( + children: [ + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + DesignTokens.dynamicPrimary, + DesignTokens.dynamicPrimary.withValues(alpha: 0.7), + ], + ), + borderRadius: DesignTokens.borderRadiusLg, + ), + child: const Center( + child: Text('ðŸ³', style: TextStyle(fontSize: 40)), + ), + ), + const SizedBox(height: DesignTokens.space3), + Text( + '妈妈厨房', + style: TextStyle( + fontSize: DesignTokens.fontXl, + fontWeight: FontWeight.bold, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const SizedBox(height: DesignTokens.space1), + Text( + 'Version 0.92.4', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + ); + } + + Widget _buildAppInfoSection(bool isDark) { + return _buildSection( + title: '📱 应用信æ¯', + isDark: isDark, + children: [ + _buildInfoTile('应用版本', '0.92.4', isDark), + _buildDivider(isDark), + _buildInfoTile('更新日期', '2026-04', isDark), + _buildDivider(isDark), + _buildInfoTile('构建版本', '92', isDark), + ], + ); + } + + Widget _buildDeveloperDocsSection(bool isDark) { + return _buildSection( + title: '📚 å¼€å‘者文档', + isDark: isDark, + children: [ + _buildActionTile( + icon: CupertinoIcons.doc_text, + title: 'API æŽ¥å£æ–‡æ¡£', + subtitle: '查看完整的 API 接å£è¯´æ˜Ž', + isDark: isDark, + onTap: () => _openUrl('https://eat.wktyl.com/api/doc/API_DOC.md'), + ), + _buildDivider(isDark), + _buildActionTile( + icon: CupertinoIcons.device_phone_portrait, + title: 'App 接入指å—', + subtitle: '快速接入 API çš„å¼€å‘æŒ‡å—', + isDark: isDark, + onTap: () => _openUrl('https://eat.wktyl.com/api/doc/APP_GUIDE.md'), + ), + _buildDivider(isDark), + _buildActionTile( + icon: CupertinoIcons.globe, + title: 'API 基础地å€', + subtitle: 'https://eat.wktyl.com/api/', + isDark: isDark, + onTap: () => _openUrl('https://eat.wktyl.com/api/'), + ), + ], + ); + } + + Future _openUrl(String url) async { + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + Get.snackbar('æç¤º', '无法打开链接: $url', snackPosition: SnackPosition.BOTTOM); + } + } + + Widget _buildContactSection(bool isDark) { + return _buildSection( + title: '📞 è”系我们', + isDark: isDark, + children: [ + _buildActionTile( + icon: CupertinoIcons.chat_bubble_text, + title: '用户å馈', + subtitle: 'æäº¤æ„è§æˆ–建议', + isDark: isDark, + onTap: () => Get.toNamed('/chat'), + ), + _buildDivider(isDark), + _buildActionTile( + icon: CupertinoIcons.star_fill, + title: '评价应用', + subtitle: '在应用商店给我们评分', + isDark: isDark, + onTap: () { + Get.snackbar( + 'æç¤º', + 'å³å°†è·³è½¬åˆ°åº”用商店', + snackPosition: SnackPosition.BOTTOM, + ); + }, + ), + _buildDivider(isDark), + _buildActionTile( + icon: CupertinoIcons.mail, + title: 'è”系邮箱', + subtitle: 'support@momkitchen.app', + isDark: isDark, + onTap: () { + Get.snackbar( + 'æç¤º', + 'å³å°†æ‰“开邮件客户端', + snackPosition: SnackPosition.BOTTOM, + ); + }, + ), + ], + ); + } + + Widget _buildLegalSection(bool isDark) { + return _buildSection( + title: '📜 法律信æ¯', + isDark: isDark, + children: [ + _buildActionTile( + icon: CupertinoIcons.doc_text, + title: '用户åè®®', + subtitle: '查看用户æœåŠ¡åè®®', + isDark: isDark, + onTap: () { + Get.snackbar( + 'æç¤º', + '用户å议页é¢å¼€å‘中', + snackPosition: SnackPosition.BOTTOM, + ); + }, + ), + _buildDivider(isDark), + _buildActionTile( + icon: CupertinoIcons.lock_shield, + title: 'éšç§æ”¿ç­–', + subtitle: '查看éšç§ä¿æŠ¤æ”¿ç­–', + isDark: isDark, + onTap: () { + Get.snackbar( + 'æç¤º', + 'éšç§æ”¿ç­–页é¢å¼€å‘中', + snackPosition: SnackPosition.BOTTOM, + ); + }, + ), + ], + ); + } + + Widget _buildReferencesSection(bool isDark) { + return _buildSection( + title: '📖 å‚考文献', + isDark: isDark, + children: [ + _buildActionTile( + icon: CupertinoIcons.book_fill, + title: 'å‚考文献', + subtitle: 'å¥åº·é¥®é£Ÿæƒå¨èµ„æ–™æ¥æº', + isDark: isDark, + onTap: () => Get.to(() => const ReferencesPage()), + ), + ], + ); + } + + Widget _buildSection({ + required String title, + required bool isDark, + required List children, + }) { + return Container( + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusLg, + boxShadow: DesignTokens.shadowsSm, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB( + DesignTokens.space4, + DesignTokens.space3, + DesignTokens.space4, + DesignTokens.space1, + ), + child: Text( + title, + style: TextStyle( + fontSize: DesignTokens.fontXs, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ), + ), + ...children, + ], + ), + ); + } + + Widget _buildInfoTile(String title, String value, bool isDark) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + vertical: DesignTokens.space3, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: TextStyle( + fontSize: DesignTokens.fontMd, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + Text( + value, + style: TextStyle( + fontSize: DesignTokens.fontMd, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + ); + } + + Widget _buildActionTile({ + required IconData icon, + required String title, + required String subtitle, + required bool isDark, + required VoidCallback onTap, + }) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + vertical: DesignTokens.space3, + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.12), + borderRadius: DesignTokens.borderRadiusSm, + ), + child: Icon(icon, size: 20, color: DesignTokens.dynamicPrimary), + ), + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w500, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + ), + const SizedBox(height: 2), + Text( + subtitle, + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + ], + ), + ), + Icon( + CupertinoIcons.chevron_right, + size: 16, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ], + ), + ), + ); + } + + Widget _buildDivider(bool isDark) { + return Padding( + padding: const EdgeInsets.only( + left: DesignTokens.space4 + 36 + DesignTokens.space3, + ), + child: Divider( + height: 1, + color: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.1) + : DesignTokens.text3.withValues(alpha: 0.1), + ), + ); + } + + Widget _buildFooter(bool isDark) { + return Column( + children: [ + Text( + '© 2026 妈妈厨房', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ), + const SizedBox(height: DesignTokens.space1), + Text( + '用心烹饪,用爱生活', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ), + ], + ); + } +} diff --git a/lib/src/pages/profile/data/cache_manage_page.dart b/lib/src/pages/profile/data/cache_manage_page.dart index 725093a..4c80762 100644 --- a/lib/src/pages/profile/data/cache_manage_page.dart +++ b/lib/src/pages/profile/data/cache_manage_page.dart @@ -13,6 +13,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:get/get.dart'; +import 'package:mom_kitchen/src/config/app_routes.dart'; import 'package:mom_kitchen/src/config/design_tokens.dart'; import 'package:mom_kitchen/src/models/discover_model.dart'; import 'package:mom_kitchen/src/models/recipe/ingredient_model.dart'; @@ -695,7 +696,7 @@ class _CacheManagePageState extends State { ), onPressed: () { Get.toNamed( - '/ingredient-detail', + AppRoutes.toolsIngredient, arguments: {'id': item.id, 'name': item.name}, ); }, diff --git a/lib/src/pages/profile/profile_home.dart b/lib/src/pages/profile/profile_home.dart index 3d0d9bb..742f053 100644 --- a/lib/src/pages/profile/profile_home.dart +++ b/lib/src/pages/profile/profile_home.dart @@ -1,4 +1,4 @@ -/* +/* * 文件: profile_home.dart * åç§°: 个人中心首页标签 * 作用: iOS 26 风格的用户信æ¯å±•示ã€åŠŸèƒ½å…¥å£å’Œæ¶ˆæ¯é¢„览 @@ -266,7 +266,12 @@ class ProfileHomeTab extends StatelessWidget { Widget _buildFeatureGrid(bool isDark) { final items = [ - _FeatureItem(CupertinoIcons.star, '技巧', DesignTokens.orange, null), + _FeatureItem( + CupertinoIcons.star, + '技巧', + DesignTokens.orange, + AppRoutes.cookingTips, + ), _FeatureItem(CupertinoIcons.heart, '关注', DesignTokens.red, null), _FeatureItem( CupertinoIcons.cart, diff --git a/lib/src/pages/profile/profile_settings.dart b/lib/src/pages/profile/profile_settings.dart index 47718d2..f23e56e 100644 --- a/lib/src/pages/profile/profile_settings.dart +++ b/lib/src/pages/profile/profile_settings.dart @@ -1,4 +1,4 @@ -/* +/* * 文件: profile_settings.dart * åç§°: 个人中心设置标签 * 作用: iOS 26 风格的设置选项,使用 DesignTokens å’Œ GlassSettingsTile @@ -107,7 +107,7 @@ class ProfileSettingsTab extends StatelessWidget { icon: CupertinoIcons.chat_bubble_text, title: '关于', isDark: isDark, - onTap: () => Get.toNamed('/chat'), + onTap: () => Get.toNamed('/about'), ), ], ), diff --git a/lib/src/pages/profile/references_page.dart b/lib/src/pages/profile/references_page.dart new file mode 100644 index 0000000..0306dc6 --- /dev/null +++ b/lib/src/pages/profile/references_page.dart @@ -0,0 +1,288 @@ +/* + * 文件: references_page.dart + * åç§°: å‚è€ƒæ–‡çŒ®é¡µé¢ + * 作用: 展示å¥åº·é¥®é£Ÿç›¸å…³çš„æƒå¨å‚考文献链接 + * 创建时间: 2026-04-13 + * æ›´æ–°æ—¶é—´: 2026-04-13 + */ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/config/design_tokens.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class ReferenceItem { + final String title; + final String description; + final String url; + final String source; + final String category; + + const ReferenceItem({ + required this.title, + required this.description, + required this.url, + required this.source, + required this.category, + }); +} + +class ReferencesPage extends StatelessWidget { + const ReferencesPage({super.key}); + + static const List _references = [ + ReferenceItem( + title: 'å‡ç›', + description: '世界å«ç”Ÿç»„织关于å‡å°‘钠摄入的事实说明,介ç»é’ æ‘„入过é‡çš„å¥åº·é£Žé™©åŠå‡ç›ç­–略。', + url: 'https://www.who.int/zh/news-room/fact-sheets/detail/sodium-reduction', + source: '世界å«ç”Ÿç»„织 WHO', + category: 'è¥å…»å¥åº·', + ), + ReferenceItem( + title: 'å¥åº·é¥®é£Ÿ', + description: '世界å«ç”Ÿç»„织å¥åº·é¥®é£ŸæŒ‡å—,涵盖å‡è¡¡è†³é£Ÿã€è¥å…»ç´ æ‘„入建议åŠé¥®é£ŸåŽŸåˆ™ã€‚', + url: 'https://www.who.int/zh/news-room/fact-sheets/detail/healthy-diet', + source: '世界å«ç”Ÿç»„织 WHO', + category: 'è¥å…»å¥åº·', + ), + ReferenceItem( + title: 'æˆäººå’Œå„¿ç«¥ç³–æ‘„å…¥é‡æŒ‡å—', + description: 'WHO关于æˆäººå’Œå„¿ç«¥ç³–æ‘„å…¥é‡çš„官方指å—ï¼Œå»ºè®®æ¸¸ç¦»ç³–æ‘„å…¥é‡æŽ§åˆ¶åœ¨æ€»èƒ½é‡çš„10%以下。', + url: 'https://www.who.int/publications/i/item/9789241549028', + source: '世界å«ç”Ÿç»„织 WHO', + category: 'è¥å…»å¥åº·', + ), + ReferenceItem( + title: '身体活动和久å行为指å—', + description: 'WHO关于身体活动的建议,介ç»ä¸åŒå¹´é¾„段人群的è¿åЍ釿ލèåŠä¹…åçš„å¥åº·é£Žé™©ã€‚', + url: 'https://www.who.int/publications/i/item/9789240015128', + source: '世界å«ç”Ÿç»„织 WHO', + category: '生活方å¼', + ), + ReferenceItem( + title: '中国居民膳食指å—(2022)', + description: '中国è¥å…»å­¦ä¼šå‘布的官方膳食指å—,针对中国居民的饮食特点和è¥å…»éœ€æ±‚æä¾›å»ºè®®ã€‚', + url: 'https://www.cnsoc.org/', + source: '中国è¥å…»å­¦ä¼š', + category: '膳食指å—', + ), + ReferenceItem( + title: '食å“安全五大è¦ç‚¹', + description: 'WHOå‘布的食å“å®‰å…¨åŸºæœ¬å‡†åˆ™ï¼ŒåŒ…æ‹¬ä¿æŒæ¸…æ´ã€ç”Ÿç†Ÿåˆ†å¼€ã€çƒ§ç†Ÿç…®é€ç­‰å…³é”®è¦ç‚¹ã€‚', + url: 'https://www.who.int/zh/news-room/fact-sheets/detail/food-safety', + source: '世界å«ç”Ÿç»„织 WHO', + category: '食å“安全', + ), + ReferenceItem( + title: 'å¾®é‡è¥å…»ç´ ç¼ºä¹', + description: 'WHO关于维生素和矿物质缺ä¹çš„ä¿¡æ¯ï¼Œä»‹ç»ç¢˜ã€ç»´ç”Ÿç´ Aã€é“等微é‡è¥å…»ç´ ç¼ºä¹çš„å½±å“。', + url: 'https://www.who.int/zh/news-room/fact-sheets/detail/micronutrient-deficiencies', + source: '世界å«ç”Ÿç»„织 WHO', + category: 'è¥å…»å¥åº·', + ), + ReferenceItem( + title: '中国食物æˆåˆ†è¡¨', + description: '中国疾病预防控制中心è¥å…»ä¸Žå¥åº·æ‰€å‘布的食物è¥å…»æˆåˆ†æ•°æ®åº“,æä¾›æƒå¨çš„食物è¥å…»æ•°æ®ã€‚', + url: 'https://cdc.chinacdc.cn/', + source: '中国疾控中心', + category: 'æ•°æ®æ¥æº', + ), + ]; + + @override + Widget build(BuildContext context) { + final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; + + return CupertinoPageScaffold( + backgroundColor: isDark ? DarkDesignTokens.background : DesignTokens.background, + navigationBar: CupertinoNavigationBar( + middle: Text( + 'å‚考文献', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + backgroundColor: isDark + ? DarkDesignTokens.glass.withValues(alpha: 0.8) + : DesignTokens.glass.withValues(alpha: 0.8), + border: null, + ), + child: SafeArea( + child: ListView.builder( + padding: const EdgeInsets.all(DesignTokens.space4), + itemCount: _references.length, + itemBuilder: (context, index) { + return _buildReferenceCard(_references[index], isDark); + }, + ), + ), + ); + } + + Widget _buildReferenceCard(ReferenceItem ref, bool isDark) { + return GestureDetector( + onTap: () => _showLinkDialog(ref, isDark), + child: Container( + margin: const EdgeInsets.only(bottom: DesignTokens.space3), + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusLg, + boxShadow: DesignTokens.shadowsSm, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space2, + vertical: DesignTokens.space1, + ), + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.1), + borderRadius: DesignTokens.borderRadiusSm, + ), + child: Text( + ref.category, + style: TextStyle( + fontSize: DesignTokens.fontXs, + fontWeight: FontWeight.w500, + color: DesignTokens.dynamicPrimary, + ), + ), + ), + const Spacer(), + Icon( + CupertinoIcons.link, + size: 16, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ], + ), + const SizedBox(height: DesignTokens.space2), + Text( + ref.title, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const SizedBox(height: DesignTokens.space1), + Text( + ref.description, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + height: 1.4, + ), + ), + const SizedBox(height: DesignTokens.space2), + Row( + children: [ + Icon( + CupertinoIcons.book_fill, + size: 12, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + const SizedBox(width: DesignTokens.space1), + Text( + ref.source, + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ), + ], + ), + ], + ), + ), + ); + } + + void _showLinkDialog(ReferenceItem ref, bool isDark) { + showCupertinoDialog( + context: Get.context!, + builder: (context) => CupertinoAlertDialog( + title: Text(ref.title), + content: Padding( + padding: const EdgeInsets.only(top: DesignTokens.space2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'å³å°†è®¿é—®å¤–部链接', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + const SizedBox(height: DesignTokens.space2), + Container( + padding: const EdgeInsets.all(DesignTokens.space2), + decoration: BoxDecoration( + color: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.1) + : DesignTokens.text3.withValues(alpha: 0.1), + borderRadius: DesignTokens.borderRadiusSm, + ), + child: Text( + ref.url, + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + actions: [ + CupertinoDialogAction( + isDefaultAction: false, + onPressed: () { + Clipboard.setData(ClipboardData(text: ref.url)); + Get.back(); + Get.snackbar( + 'å·²å¤åˆ¶', + '链接已å¤åˆ¶åˆ°å‰ªè´´æ¿', + snackPosition: SnackPosition.BOTTOM, + duration: const Duration(seconds: 2), + ); + }, + child: const Text('å¤åˆ¶é“¾æŽ¥'), + ), + CupertinoDialogAction( + isDefaultAction: false, + onPressed: () => Get.back(), + child: const Text('å–æ¶ˆ'), + ), + CupertinoDialogAction( + isDefaultAction: true, + onPressed: () async { + Get.back(); + final uri = Uri.parse(ref.url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + Get.snackbar( + 'æç¤º', + '无法打开链接', + snackPosition: SnackPosition.BOTTOM, + ); + } + }, + child: const Text('å‰å¾€'), + ), + ], + ), + ); + } +} diff --git a/lib/src/pages/profile/social/favorites_page.dart b/lib/src/pages/profile/social/favorites_page.dart index 720292c..3e4c300 100644 --- a/lib/src/pages/profile/social/favorites_page.dart +++ b/lib/src/pages/profile/social/favorites_page.dart @@ -232,16 +232,13 @@ class _FavoritesPageState extends State { width: 44, height: 44, decoration: BoxDecoration( - color: - (DesignTokens.dynamicPrimary) - .withValues(alpha: 0.1), + color: (DesignTokens.dynamicPrimary).withValues( + alpha: 0.1, + ), borderRadius: DesignTokens.borderRadiusMd, ), child: Center( - child: Text( - tool.icon, - style: TextStyle(fontSize: 24), - ), + child: Text(tool.icon, style: TextStyle(fontSize: 24)), ), ), Positioned( @@ -294,9 +291,7 @@ class _FavoritesPageState extends State { : DesignTokens.primaryLight.withValues(alpha: 0.7), borderRadius: DesignTokens.borderRadiusMd, border: Border.all( - color: - (DesignTokens.dynamicPrimary) - .withValues(alpha: 0.25), + color: (DesignTokens.dynamicPrimary).withValues(alpha: 0.25), ), ), child: Column( @@ -306,16 +301,16 @@ class _FavoritesPageState extends State { width: 44, height: 44, decoration: BoxDecoration( - color: - (DesignTokens.dynamicPrimary) - .withValues(alpha: 0.15), + color: (DesignTokens.dynamicPrimary).withValues( + alpha: 0.15, + ), borderRadius: DesignTokens.borderRadiusMd, ), child: Center( child: Text('🛠ï¸', style: TextStyle(fontSize: 24)), ), ), - SizedBox(height: 6), + SizedBox(height: 6), Text( '更多', style: TextStyle( @@ -442,7 +437,9 @@ class _FavoritesPageState extends State { ), decoration: BoxDecoration( color: isSelected - ? DesignTokens.dynamicPrimary.withValues(alpha: 0.85) + ? DesignTokens.dynamicPrimary.withValues( + alpha: 0.85, + ) : (isDark ? DarkDesignTokens.glass : DesignTokens.card.withValues( @@ -663,14 +660,18 @@ class _FavoritesPageState extends State { } Widget _buildFavoritesList(List favorites, bool isDark) { - return ListView.separated( + return GridView.builder( padding: const EdgeInsets.symmetric( horizontal: DesignTokens.space4, vertical: DesignTokens.space2, ), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: DesignTokens.space3, + mainAxisSpacing: DesignTokens.space3, + childAspectRatio: 0.85, + ), itemCount: favorites.length, - separatorBuilder: (context, index) => - const SizedBox(height: DesignTokens.space2 + 2), itemBuilder: (context, index) { final item = favorites[index]; return _buildFavoriteItem(item, isDark); @@ -709,102 +710,109 @@ class _FavoritesPageState extends State { width: isEditMode && isSelected ? 1.5 : 0.5, ), ), - child: Row( + child: Stack( children: [ - if (isEditMode) ...[ - Container( - width: 24, - height: 24, - margin: EdgeInsets.only(right: DesignTokens.space3), - decoration: BoxDecoration( - color: isSelected - ? DesignTokens.dynamicPrimary - : const Color(0x00000000), - borderRadius: DesignTokens.borderRadiusSm, - border: Border.all( - color: isSelected - ? DesignTokens.dynamicPrimary - : (isDark - ? DarkDesignTokens.text3 - : DesignTokens.text3), - width: 2, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isEditMode) + Align( + alignment: Alignment.topRight, + child: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: isSelected + ? DesignTokens.dynamicPrimary + : const Color(0x00000000), + borderRadius: DesignTokens.borderRadiusSm, + border: Border.all( + color: isSelected + ? DesignTokens.dynamicPrimary + : (isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3), + width: 2, + ), + ), + child: isSelected + ? const Icon( + CupertinoIcons.checkmark_alt, + size: 14, + color: CupertinoColors.white, + ) + : null, + ), + ), + Center( + child: Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: DesignTokens.primaryLight.withValues( + alpha: 0.5, + ), + borderRadius: DesignTokens.borderRadiusMd, + ), + child: Icon( + CupertinoIcons.book, + size: 28, + color: DesignTokens.dynamicPrimary, + ), ), ), - child: isSelected - ? const Icon( - CupertinoIcons.checkmark_alt, - size: 14, - color: CupertinoColors.white, - ) - : null, - ), - ], - Container( - width: 56, - height: 56, - decoration: BoxDecoration( - color: DesignTokens.primaryLight.withValues(alpha: 0.5), - borderRadius: DesignTokens.borderRadiusMd, - ), - child: Icon( - CupertinoIcons.book, - size: 24, - color: DesignTokens.dynamicPrimary, - ), + const SizedBox(height: DesignTokens.space2), + Text( + item.title ?? '', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + const SizedBox(height: DesignTokens.space1), + Text( + item.intro ?? '', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + ], ), - const SizedBox(width: DesignTokens.space3), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - item.title ?? '', - style: TextStyle( - fontSize: DesignTokens.fontMd, - fontWeight: FontWeight.w600, - color: isDark - ? DarkDesignTokens.text1 - : DesignTokens.text1, + if (!isEditMode) + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: () => + _favoritesController!.removeFavorite(item.id), + behavior: HitTestBehavior.opaque, + child: Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: DesignTokens.red.withValues(alpha: 0.1), + borderRadius: DesignTokens.borderRadiusSm, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: DesignTokens.space1), - Text( - item.intro ?? '', - style: TextStyle( - fontSize: DesignTokens.fontSm, - color: isDark - ? DarkDesignTokens.text2 - : DesignTokens.text2, + child: Icon( + CupertinoIcons.heart_slash, + size: 16, + color: DesignTokens.red, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - if (!isEditMode) ...[ - const SizedBox(width: DesignTokens.space2), - GestureDetector( - onTap: () => - _favoritesController!.removeFavorite(item.id), - behavior: HitTestBehavior.opaque, - child: Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: DesignTokens.red.withValues(alpha: 0.1), - borderRadius: DesignTokens.borderRadiusSm, - ), - child: const Icon( - CupertinoIcons.heart_fill, - size: 18, - color: DesignTokens.red, ), ), ), - ], ], ), ), diff --git a/lib/src/pages/tools/cooking_tip_detail_page.dart b/lib/src/pages/tools/cooking_tip_detail_page.dart new file mode 100644 index 0000000..504cd31 --- /dev/null +++ b/lib/src/pages/tools/cooking_tip_detail_page.dart @@ -0,0 +1,332 @@ +/* + * 文件: cooking_tip_detail_page.dart + * åç§°: çƒ¹é¥ªæŠ€å·§è¯¦æƒ…é¡µé¢ + * 作用: 显示 Markdown æ ¼å¼çš„烹饪技巧内容 + * 创建: 2026-04-13 + * æ›´æ–°: 2026-04-13 从本地 assets 加载内容 + */ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; +import 'package:mom_kitchen/src/config/design_tokens.dart'; +import 'package:mom_kitchen/src/models/data/cooking_tip_model.dart'; + +class CookingTipDetailPage extends StatefulWidget { + final CookingTipModel tip; + + const CookingTipDetailPage({super.key, required this.tip}); + + @override + State createState() => _CookingTipDetailPageState(); +} + +class _CookingTipDetailPageState extends State { + String? _content; + bool _isLoading = true; + String? _error; + + @override + void initState() { + super.initState(); + _loadContent(); + } + + Future _loadContent() async { + try { + final content = await rootBundle.loadString(widget.tip.assetPath); + if (mounted) { + setState(() { + _content = content; + _isLoading = false; + }); + } + } catch (e) { + if (mounted) { + setState(() { + _error = '加载失败: $e'; + _isLoading = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; + + return CupertinoPageScaffold( + backgroundColor: isDark + ? DarkDesignTokens.background + : DesignTokens.background, + navigationBar: CupertinoNavigationBar( + middle: Text( + widget.tip.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + backgroundColor: isDark + ? DarkDesignTokens.glass.withValues(alpha: 0.8) + : DesignTokens.glass.withValues(alpha: 0.8), + border: null, + ), + child: SafeArea(child: _buildBody(isDark)), + ); + } + + Widget _buildBody(bool isDark) { + if (_isLoading) { + return const Center(child: CupertinoActivityIndicator()); + } + + if (_error != null) { + return _buildErrorState(isDark); + } + + if (_content == null || _content!.isEmpty) { + return _buildEmptyState(isDark); + } + + return CustomScrollView( + slivers: [ + SliverToBoxAdapter(child: _buildHeader(isDark)), + SliverToBoxAdapter(child: _buildMarkdownContent(isDark)), + SliverToBoxAdapter(child: _buildFooter(isDark)), + ], + ); + } + + Widget _buildHeader(bool isDark) { + return Container( + margin: const EdgeInsets.all(DesignTokens.space3), + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + ), + child: Row( + children: [ + Text(widget.tip.icon, style: const TextStyle(fontSize: 32)), + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.tip.name, + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const SizedBox(height: DesignTokens.space1), + Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space2, + vertical: 2, + ), + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(DesignTokens.radiusSm), + ), + child: Text( + widget.tip.category, + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: DesignTokens.dynamicPrimary, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildMarkdownContent(bool isDark) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: DesignTokens.space3), + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + ), + child: MarkdownBody( + data: _content!, + selectable: true, + styleSheet: MarkdownStyleSheet( + h1: TextStyle( + fontSize: DesignTokens.fontXl, + fontWeight: FontWeight.bold, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + h2: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + h3: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + p: TextStyle( + fontSize: DesignTokens.fontMd, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + height: 1.6, + ), + listBullet: TextStyle(color: DesignTokens.dynamicPrimary), + code: TextStyle( + fontSize: DesignTokens.fontSm, + backgroundColor: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.2) + : DesignTokens.text3.withValues(alpha: 0.1), + color: DesignTokens.dynamicPrimary, + ), + codeblockDecoration: BoxDecoration( + color: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.1) + : DesignTokens.text3.withValues(alpha: 0.05), + borderRadius: BorderRadius.circular(DesignTokens.radiusSm), + ), + blockquote: TextStyle( + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + fontStyle: FontStyle.italic, + ), + blockquoteDecoration: BoxDecoration( + border: Border( + left: BorderSide(color: DesignTokens.dynamicPrimary, width: 4), + ), + ), + tableHead: TextStyle( + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + tableBody: TextStyle( + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + tableBorder: TableBorder.all( + color: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.3) + : DesignTokens.text3.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(DesignTokens.radiusSm), + ), + horizontalRuleDecoration: BoxDecoration( + border: Border( + top: BorderSide( + color: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.3) + : DesignTokens.text3.withValues(alpha: 0.2), + width: 1, + ), + ), + ), + ), + onTapLink: (text, href, title) { + if (href != null) { + debugPrint('Link tapped: $href'); + } + }, + ), + ); + } + + Widget _buildFooter(bool isDark) { + return Container( + margin: const EdgeInsets.all(DesignTokens.space3), + padding: const EdgeInsets.all(DesignTokens.space3), + child: Column( + children: [ + Divider( + color: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.2) + : DesignTokens.text3.withValues(alpha: 0.1), + ), + const SizedBox(height: DesignTokens.space2), + Text( + '📖 æ•°æ®æ¥æºäºŽ HowToCook å¼€æºé¡¹ç›®', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: DesignTokens.space1), + Text( + 'github.com/Anduin2017/HowToCook', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: DesignTokens.dynamicPrimary, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + } + + Widget _buildErrorState(bool isDark) { + return Center( + child: Container( + margin: const EdgeInsets.all(DesignTokens.space4), + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: BorderRadius.circular(DesignTokens.radiusLg), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('âŒ', style: TextStyle(fontSize: 48)), + const SizedBox(height: DesignTokens.space3), + Text( + '加载失败', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const SizedBox(height: DesignTokens.space2), + Text( + _error ?? '未知错误', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: DesignTokens.space3), + CupertinoButton.filled( + onPressed: _loadContent, + child: const Text('é‡è¯•'), + ), + ], + ), + ), + ); + } + + Widget _buildEmptyState(bool isDark) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('📄', style: TextStyle(fontSize: 48)), + const SizedBox(height: DesignTokens.space3), + Text( + '暂无内容', + style: TextStyle( + fontSize: DesignTokens.fontLg, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/pages/tools/cooking_tips_list_page.dart b/lib/src/pages/tools/cooking_tips_list_page.dart new file mode 100644 index 0000000..b1ccca2 --- /dev/null +++ b/lib/src/pages/tools/cooking_tips_list_page.dart @@ -0,0 +1,402 @@ +/* + * 文件: cooking_tips_list_page.dart + * åç§°: çƒ¹é¥ªæŠ€å·§åˆ—è¡¨é¡µé¢ + * 作用: 显示烹饪技巧分类列表,支æŒç‚¹å‡»æŸ¥çœ‹è¯¦æƒ… + * 创建: 2026-04-13 + * æ›´æ–°: 2026-04-13 åˆå§‹åˆ›å»ºï¼Œæ•°æ®æ¥æºäºŽ HowToCook 项目 + */ + +import 'dart:convert'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/config/design_tokens.dart'; +import 'package:mom_kitchen/src/models/data/cooking_tip_model.dart'; +import 'package:mom_kitchen/src/pages/tools/cooking_tip_detail_page.dart'; +import 'package:mom_kitchen/src/widgets/glass/glass_container.dart'; + +class CookingTipsListPage extends StatefulWidget { + const CookingTipsListPage({super.key}); + + @override + State createState() => _CookingTipsListPageState(); +} + +class _CookingTipsListPageState extends State { + List _allTips = []; + Map> _groupedTips = {}; + List _categories = []; + bool _isLoading = true; + String _searchQuery = ''; + final TextEditingController _searchController = TextEditingController(); + + @override + void initState() { + super.initState(); + _loadTips(); + } + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + + Future _loadTips() async { + try { + final String jsonString = await rootBundle.loadString( + 'assets/json/cooking_tips.json', + ); + final List jsonList = json.decode(jsonString); + final tips = jsonList + .map((json) => CookingTipModel.fromJson(json as Map)) + .toList(); + + final grouped = >{}; + for (final tip in tips) { + grouped.putIfAbsent(tip.category, () => []).add(tip); + } + + setState(() { + _allTips = tips; + _groupedTips = grouped; + _categories = grouped.keys.toList()..sort(); + _isLoading = false; + }); + } catch (e) { + debugPrint('加载烹饪技巧失败: $e'); + setState(() => _isLoading = false); + } + } + + List _getFilteredTips() { + if (_searchQuery.isEmpty) return _allTips; + final query = _searchQuery.toLowerCase(); + return _allTips.where((tip) { + return tip.name.toLowerCase().contains(query) || + tip.category.toLowerCase().contains(query); + }).toList(); + } + + @override + Widget build(BuildContext context) { + final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; + + return CupertinoPageScaffold( + backgroundColor: isDark + ? DarkDesignTokens.background + : DesignTokens.background, + navigationBar: CupertinoNavigationBar( + middle: const Text('烹饪技巧'), + backgroundColor: isDark + ? DarkDesignTokens.glass.withValues(alpha: 0.8) + : DesignTokens.glass.withValues(alpha: 0.8), + border: null, + ), + child: SafeArea( + child: _isLoading + ? const Center(child: CupertinoActivityIndicator()) + : CustomScrollView( + slivers: [ + SliverToBoxAdapter(child: _buildSearchBar(isDark)), + SliverToBoxAdapter(child: _buildHeader(isDark)), + if (_searchQuery.isEmpty) + ..._buildGroupedList(isDark) + else + _buildSearchResults(isDark), + ], + ), + ), + ); + } + + Widget _buildSearchBar(bool isDark) { + return Padding( + padding: const EdgeInsets.all(DesignTokens.space3), + child: GlassContainer( + borderRadius: DesignTokens.radiusMd, + padding: const EdgeInsets.symmetric(horizontal: DesignTokens.space3), + child: Row( + children: [ + Icon( + CupertinoIcons.search, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + size: 20, + ), + const SizedBox(width: DesignTokens.space2), + Expanded( + child: CupertinoTextField( + controller: _searchController, + placeholder: 'æœç´¢çƒ¹é¥ªæŠ€å·§...', + placeholderStyle: TextStyle( + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + style: TextStyle( + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + decoration: null, + onChanged: (value) { + setState(() => _searchQuery = value); + }, + ), + ), + if (_searchQuery.isNotEmpty) + GestureDetector( + onTap: () { + _searchController.clear(); + setState(() => _searchQuery = ''); + }, + child: Icon( + CupertinoIcons.clear_circled_solid, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + size: 20, + ), + ), + ], + ), + ), + ); + } + + Widget _buildHeader(bool isDark) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: DesignTokens.space3), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text('📚 ', style: TextStyle(fontSize: DesignTokens.fontLg)), + Text( + '烹饪技巧大全', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space1), + Text( + 'æ•°æ®æ¥æºäºŽ HowToCook å¼€æºé¡¹ç›®', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ), + const SizedBox(height: DesignTokens.space3), + ], + ), + ); + } + + List _buildGroupedList(bool isDark) { + final widgets = []; + + for (final category in _categories) { + final tips = _groupedTips[category]!; + widgets.add( + SliverToBoxAdapter( + child: _buildCategoryHeader(category, tips.length, isDark), + ), + ); + widgets.add( + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: DesignTokens.space3), + sliver: SliverGrid( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: DesignTokens.space2, + crossAxisSpacing: DesignTokens.space2, + childAspectRatio: 1.5, + ), + delegate: SliverChildBuilderDelegate( + (context, index) => _buildTipCard(tips[index], isDark), + childCount: tips.length, + ), + ), + ), + ); + widgets.add( + const SliverToBoxAdapter(child: SizedBox(height: DesignTokens.space3)), + ); + } + + return widgets; + } + + Widget _buildCategoryHeader(String category, int count, bool isDark) { + return Padding( + padding: const EdgeInsets.only( + left: DesignTokens.space3, + bottom: DesignTokens.space2, + ), + child: Row( + children: [ + Container( + width: 4, + height: 16, + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimary, + borderRadius: BorderRadius.circular(DesignTokens.radiusSm), + ), + ), + const SizedBox(width: DesignTokens.space2), + Text( + category, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const SizedBox(width: DesignTokens.space2), + Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space2, + vertical: 2, + ), + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(DesignTokens.radiusSm), + ), + child: Text( + '$count篇', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: DesignTokens.dynamicPrimary, + ), + ), + ), + ], + ), + ); + } + + Widget _buildTipCard(CookingTipModel tip, bool isDark) { + return GestureDetector( + onTap: () => Get.to(() => CookingTipDetailPage(tip: tip)), + child: GlassContainer( + borderRadius: DesignTokens.radiusMd, + child: Container( + padding: const EdgeInsets.all(DesignTokens.space3), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text(tip.icon, style: const TextStyle(fontSize: 24)), + const Spacer(), + Icon( + CupertinoIcons.chevron_right, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + size: 16, + ), + ], + ), + const Spacer(), + Text( + tip.name, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ), + ); + } + + Widget _buildSearchResults(bool isDark) { + final results = _getFilteredTips(); + + if (results.isEmpty) { + return SliverFillRemaining( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('ðŸ”', style: TextStyle(fontSize: 48)), + const SizedBox(height: DesignTokens.space3), + Text( + '未找到相关技巧', + style: TextStyle( + fontSize: DesignTokens.fontMd, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + ), + ); + } + + return SliverPadding( + padding: const EdgeInsets.all(DesignTokens.space3), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => _buildSearchResultItem(results[index], isDark), + childCount: results.length, + ), + ), + ); + } + + Widget _buildSearchResultItem(CookingTipModel tip, bool isDark) { + return GestureDetector( + onTap: () => Get.to(() => CookingTipDetailPage(tip: tip)), + child: GlassContainer( + borderRadius: DesignTokens.radiusMd, + margin: const EdgeInsets.only(bottom: DesignTokens.space2), + child: Padding( + padding: const EdgeInsets.all(DesignTokens.space3), + child: Row( + children: [ + Text(tip.icon, style: const TextStyle(fontSize: 28)), + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + tip.name, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w500, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + ), + const SizedBox(height: DesignTokens.space1), + Text( + tip.category, + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + ], + ), + ), + Icon( + CupertinoIcons.chevron_right, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + size: 16, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/pages/tools/tools_center_page.dart b/lib/src/pages/tools/tools_center_page.dart index 37fb81a..d132335 100644 --- a/lib/src/pages/tools/tools_center_page.dart +++ b/lib/src/pages/tools/tools_center_page.dart @@ -259,7 +259,9 @@ class _ToolsCenterPageState extends State color: DesignTokens.dynamicPrimary.withValues(alpha: 0.1), borderRadius: DesignTokens.borderRadiusLg, border: Border.all( - color: DesignTokens.dynamicPrimary.withValues(alpha: 0.2), + color: DesignTokens.dynamicPrimary.withValues( + alpha: 0.2, + ), ), ), child: Text( @@ -716,6 +718,42 @@ class _ToolsCenterPageState extends State ), ), ), + const SizedBox(height: DesignTokens.space2), + GestureDetector( + onTap: () => _useTool(tool), + child: Container( + width: double.infinity, + height: 32, + decoration: BoxDecoration( + color: isDark + ? DarkDesignTokens.glass + : DesignTokens.card.withValues(alpha: 0.9), + borderRadius: DesignTokens.borderRadiusMd, + border: Border.all( + color: gradientColors[0].withValues(alpha: 0.3), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + CupertinoIcons.play_circle, + size: 16, + color: gradientColors[0], + ), + const SizedBox(width: 6), + Text( + '使用工具', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w600, + color: gradientColors[0], + ), + ), + ], + ), + ), + ), ], ), ); @@ -765,6 +803,17 @@ class _ToolsCenterPageState extends State CupertinoPageRoute(builder: (context) => _ToolDetailPage(tool: tool)), ); } + + void _useTool(ToolItem tool) { + _controller?.recordUsage(tool.id); + if (tool.route.isNotEmpty) { + Get.toNamed(tool.route); + } else { + Navigator.of(context).push( + CupertinoPageRoute(builder: (context) => _ToolDetailPage(tool: tool)), + ); + } + } } class _ToolDetailPage extends StatelessWidget { @@ -858,7 +907,9 @@ class _ToolDetailPage extends StatelessWidget { ], ), borderRadius: DesignTokens.borderRadiusXl, - border: Border.all(color: DesignTokens.dynamicPrimary.withValues(alpha: 0.1)), + border: Border.all( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.1), + ), ), child: Column( children: [ diff --git a/lib/src/repositories/discover_repository.dart b/lib/src/repositories/discover_repository.dart index 17f32fc..9371947 100644 --- a/lib/src/repositories/discover_repository.dart +++ b/lib/src/repositories/discover_repository.dart @@ -18,14 +18,14 @@ class DiscoverRepository { // total傿•°æŽ§åˆ¶è¿”å›žæ•°æ®æ€»é‡ï¼Œé¦–页用20,点击"刷新加载更多"时递增15 Future fetchDiscover({ - int total = 30, - int recipe = 8, - int ingredient = 4, - int category = 4, - int tag = 6, - int nutrition = 4, - int mealTime = 3, - bool refresh = false, + int total = 80, // 总数æ®é‡ï¼ˆæœ€å¤§100) + int recipe = 52, // èœè°±æ•°é‡ï¼ˆå æ¯”约27%) + int ingredient = 9, // é£Ÿææ•°é‡ï¼ˆå æ¯”约13%) + int category = 6, // 分类数é‡ï¼ˆå æ¯”约13%) + int tag = 6, // 标签数é‡ï¼ˆå æ¯”约20%) + int nutrition = 4, // è¥å…»æˆåˆ†æ•°é‡ï¼ˆå æ¯”约13%) + int mealTime = 3, // ç”¨é¤æ—¶æ®µæ•°é‡ï¼ˆå æ¯”约10%) + bool refresh = false, // 是å¦å¼ºåˆ¶åˆ·æ–°ç¼“å­˜ }) async { final params = { 'total': total, diff --git a/lib/src/widgets/discover/category_discover_card.dart b/lib/src/widgets/discover/category_discover_card.dart index 84e8983..e7c35ec 100644 --- a/lib/src/widgets/discover/category_discover_card.dart +++ b/lib/src/widgets/discover/category_discover_card.dart @@ -3,8 +3,9 @@ * åç§°: 分类å‘现å¡ç‰‡ * 作用: 瀑布æµä¸­çš„分类展示å¡ç‰‡ï¼ŒLiquid Glass风格 * 创建时间: 2026-04-12 - * æ›´æ–°æ—¶é—´: 2026-04-12 + * æ›´æ–°æ—¶é—´: 2026-04-13 * æ›´æ–°: 2026-04-13 添加最å°é«˜åº¦çº¦æŸï¼Œä¿®å¤ç€‘布æµå¸ƒå±€é—®é¢˜ + * æ›´æ–°: 2026-04-13 适é…API新字段:recipe_countã€ingredient_countã€parent_name */ import 'package:flutter/cupertino.dart'; @@ -57,48 +58,57 @@ class CategoryDiscoverCard extends StatelessWidget { borderWidth: 0.5, child: Padding( padding: const EdgeInsets.all(DesignTokens.space3), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, + child: Row( children: [ Container( - width: 48, - height: 48, + width: 44, + height: 44, decoration: BoxDecoration( color: color.withValues(alpha: 0.12), shape: BoxShape.circle, ), - child: Icon(icon, size: 24, color: color), + child: Icon(icon, size: 22, color: color), ), - const SizedBox(height: DesignTokens.space2), - Text( - category.name, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: DesignTokens.fontSm, - fontWeight: FontWeight.w600, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: DesignTokens.space1), - Container( - padding: const EdgeInsets.symmetric( - horizontal: DesignTokens.space2, - vertical: 2, - ), - decoration: BoxDecoration( - color: color.withValues(alpha: 0.1), - borderRadius: DesignTokens.borderRadiusMd, - ), - child: Text( - '${category.count}é“èœè°±', - style: TextStyle( - fontSize: 10, - color: color, - fontWeight: FontWeight.w500, - ), + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + if (category.parentName.isNotEmpty) + Text( + category.parentName, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w500, + color: color.withValues(alpha: 0.8), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Text( + category.name, + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w600, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + category.displayCount, + style: TextStyle( + fontSize: 10, + color: color, + fontWeight: FontWeight.w600, + ), + ), + ], ), ), ], diff --git a/lib/src/widgets/discover/discover_waterfall.dart b/lib/src/widgets/discover/discover_waterfall.dart index 5f27bbc..263be2f 100644 --- a/lib/src/widgets/discover/discover_waterfall.dart +++ b/lib/src/widgets/discover/discover_waterfall.dart @@ -16,6 +16,9 @@ import 'package:mom_kitchen/src/widgets/base/skeleton_loader.dart'; import 'package:mom_kitchen/src/widgets/discover/recipe_discover_card.dart'; import 'package:mom_kitchen/src/widgets/discover/ingredient_discover_card.dart'; import 'package:mom_kitchen/src/widgets/discover/category_discover_card.dart'; +import 'package:mom_kitchen/src/widgets/discover/tag_discover_card.dart'; +import 'package:mom_kitchen/src/widgets/discover/nutrition_discover_card.dart'; +import 'package:mom_kitchen/src/widgets/discover/meal_time_discover_card.dart'; import 'package:mom_kitchen/src/models/discover_model.dart'; class DiscoverWaterfall extends StatelessWidget { @@ -101,10 +104,13 @@ class DiscoverWaterfall extends StatelessWidget { card = CategoryDiscoverCard(key: key, category: item.category!); break; case DiscoverItemType.tag: - card = _DiscoverTagChip(key: key, tag: item.tag!); + card = TagDiscoverCard(key: key, tag: item.tag!); + break; + case DiscoverItemType.nutrition: + card = NutritionDiscoverCard(key: key, nutrition: item.nutrition!); break; case DiscoverItemType.mealTime: - card = _DiscoverMealTimeCard(key: key, mealTime: item.mealTime!); + card = MealTimeDiscoverCard(key: key, mealTime: item.mealTime!); break; } @@ -124,6 +130,8 @@ class DiscoverWaterfall extends StatelessWidget { return 'category_${item.category?.id ?? index}'; case DiscoverItemType.tag: return 'tag_${item.tag?.name ?? index}'; + case DiscoverItemType.nutrition: + return 'nutrition_${item.nutrition?.name ?? index}'; case DiscoverItemType.mealTime: return 'mealtime_${item.mealTime?.name ?? index}'; } @@ -258,138 +266,6 @@ class DiscoverWaterfall extends StatelessWidget { } } -class _DiscoverTagChip extends StatelessWidget { - final DiscoverTag tag; - - const _DiscoverTagChip({super.key, required this.tag}); - - @override - Widget build(BuildContext context) { - final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; - final isTaste = tag.type == 'taste'; - final baseColor = isTaste ? DesignTokens.dynamicPrimary : DesignTokens.orange; - - return GlassContainer( - padding: const EdgeInsets.all(DesignTokens.space3), - child: Row( - children: [ - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: baseColor.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(DesignTokens.radiusSm), - ), - child: Center( - child: Icon( - isTaste ? CupertinoIcons.tag_fill : CupertinoIcons.flame_fill, - size: 18, - color: baseColor, - ), - ), - ), - const SizedBox(width: DesignTokens.space2), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - tag.name, - style: TextStyle( - fontSize: DesignTokens.fontSm, - fontWeight: FontWeight.w600, - color: baseColor, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - Text( - isTaste ? 'å£å‘³' : '工艺', - style: TextStyle( - fontSize: 10, - color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, - ), - ), - ], - ), - ), - ], - ), - ); - } -} - -class _DiscoverMealTimeCard extends StatelessWidget { - final DiscoverMealTime mealTime; - - const _DiscoverMealTimeCard({super.key, required this.mealTime}); - - @override - Widget build(BuildContext context) { - final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; - - return GlassContainer( - padding: const EdgeInsets.all(DesignTokens.space3), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 36, - height: 36, - decoration: const BoxDecoration( - gradient: LinearGradient( - colors: [DesignTokens.purple, Color(0xFF673AB7)], - ), - borderRadius: BorderRadius.all( - Radius.circular(DesignTokens.radiusSm), - ), - ), - child: const Center( - child: Text('ðŸ•', style: TextStyle(fontSize: 18)), - ), - ), - const SizedBox(width: DesignTokens.space2), - Expanded( - child: Text( - mealTime.name, - style: TextStyle( - fontSize: DesignTokens.fontMd, - fontWeight: FontWeight.w600, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - const SizedBox(height: DesignTokens.space2), - Container( - padding: const EdgeInsets.symmetric( - horizontal: DesignTokens.space2, - vertical: DesignTokens.space1, - ), - decoration: BoxDecoration( - color: DesignTokens.purple.withValues(alpha: 0.1), - borderRadius: DesignTokens.borderRadiusMd, - ), - child: Text( - '${mealTime.count} 铿ލèèœè°±', - style: const TextStyle( - fontSize: 11, - color: DesignTokens.purple, - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ), - ); - } -} - class _SkeletonWaterfall extends StatelessWidget { const _SkeletonWaterfall(); diff --git a/lib/src/widgets/discover/meal_time_discover_card.dart b/lib/src/widgets/discover/meal_time_discover_card.dart new file mode 100644 index 0000000..8cf27d9 --- /dev/null +++ b/lib/src/widgets/discover/meal_time_discover_card.dart @@ -0,0 +1,131 @@ +/* + * 文件: meal_time_discover_card.dart + * åç§°: ç”¨é¤æ—¶é—´å‘现å¡ç‰‡ + * 作用: 瀑布æµä¸­çš„ç”¨é¤æ—¶æ®µå±•示å¡ç‰‡ï¼ŒLiquid Glass风格 + * 创建时间: 2026-04-13 + * æ›´æ–°æ—¶é—´: 2026-04-13 + */ + +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/config/app_routes.dart'; +import 'package:mom_kitchen/src/config/design_tokens.dart'; +import 'package:mom_kitchen/src/widgets/glass/glass_container.dart'; +import 'package:mom_kitchen/src/models/discover_model.dart'; + +class MealTimeDiscoverCard extends StatelessWidget { + final DiscoverMealTime mealTime; + + const MealTimeDiscoverCard({super.key, required this.mealTime}); + + static final Map _mealTimeIcons = { + 'æ—©é¤': CupertinoIcons.sunrise_fill, + 'æ—©': CupertinoIcons.sunrise_fill, + '中é¤': CupertinoIcons.sun_max_fill, + 'åˆ': CupertinoIcons.sun_max_fill, + 'åˆé¤': CupertinoIcons.sun_max_fill, + '晚é¤': CupertinoIcons.sunset_fill, + '晚': CupertinoIcons.sunset_fill, + '夜宵': CupertinoIcons.moon_stars_fill, + '夜': CupertinoIcons.moon_stars_fill, + '零食': CupertinoIcons.gift_fill, + '下åˆèŒ¶': CupertinoIcons.heart_fill, + '加é¤': CupertinoIcons.plus_circle_fill, + }; + + static final Map _mealTimeColors = { + 'æ—©é¤': Color(0xFFFFB74D), + 'æ—©': Color(0xFFFFB74D), + '中é¤': Color(0xFFFF9800), + 'åˆ': Color(0xFFFF9800), + 'åˆé¤': Color(0xFFFF9800), + '晚é¤': Color(0xFF7E57C2), + '晚': Color(0xFF7E57C2), + '夜宵': Color(0xFF5C6BC0), + '夜': Color(0xFF5C6BC0), + '零食': Color(0xFFF06292), + '下åˆèŒ¶': Color(0xFF4DB6AC), + '加é¤': Color(0xFFAED581), + }; + + @override + Widget build(BuildContext context) { + final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; + final icon = _getIcon(); + final color = _getColor(); + + return GestureDetector( + onTap: () => + Get.toNamed(AppRoutes.search, arguments: {'keyword': mealTime.name}), + child: GlassContainer( + backgroundColor: color.withValues(alpha: 0.08), + borderColor: color.withValues(alpha: 0.2), + borderWidth: 0.5, + child: Padding( + padding: const EdgeInsets.all(DesignTokens.space2), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + color.withValues(alpha: 0.3), + color.withValues(alpha: 0.15), + ], + ), + shape: BoxShape.circle, + ), + child: Icon(icon, size: 18, color: color), + ), + const SizedBox(height: DesignTokens.space1), + Text( + mealTime.name, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: DesignTokens.fontXs, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + '${mealTime.count}é“', + style: TextStyle( + fontSize: 9, + color: color, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ); + } + + IconData _getIcon() { + for (final entry in _mealTimeIcons.entries) { + if (mealTime.name.contains(entry.key)) { + return entry.value; + } + } + return CupertinoIcons.clock_fill; + } + + Color _getColor() { + for (final entry in _mealTimeColors.entries) { + if (mealTime.name.contains(entry.key)) { + return entry.value; + } + } + return DesignTokens.purple; + } +} diff --git a/lib/src/widgets/discover/nutrition_discover_card.dart b/lib/src/widgets/discover/nutrition_discover_card.dart new file mode 100644 index 0000000..c9b0c87 --- /dev/null +++ b/lib/src/widgets/discover/nutrition_discover_card.dart @@ -0,0 +1,138 @@ +/* + * 文件: nutrition_discover_card.dart + * åç§°: è¥å…»æˆåˆ†å‘现å¡ç‰‡ + * 作用: 瀑布æµä¸­çš„è¥å…»æˆåˆ†å±•示å¡ç‰‡ï¼ŒLiquid Glass风格 + * 创建时间: 2026-04-13 + * æ›´æ–°æ—¶é—´: 2026-04-13 + */ + +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/config/app_routes.dart'; +import 'package:mom_kitchen/src/config/design_tokens.dart'; +import 'package:mom_kitchen/src/widgets/glass/glass_container.dart'; +import 'package:mom_kitchen/src/models/discover_model.dart'; + +class NutritionDiscoverCard extends StatelessWidget { + final DiscoverNutrition nutrition; + + const NutritionDiscoverCard({super.key, required this.nutrition}); + + static final Map _nutritionIcons = { + '蛋白质': CupertinoIcons.heart_fill, + '脂肪': CupertinoIcons.flame_fill, + '碳水': CupertinoIcons.bolt_fill, + '纤维': CupertinoIcons.arrow_right_circle_fill, + '维生素': CupertinoIcons.star_fill, + '矿物质': CupertinoIcons.hexagon_fill, + 'é’™': CupertinoIcons.star_fill, + 'é“': CupertinoIcons.drop_fill, + '锌': CupertinoIcons.shield_lefthalf_fill, + 'é•': CupertinoIcons.moon_fill, + 'å¶é…¸': CupertinoIcons.plus_circle_fill, + '核黄素': CupertinoIcons.sun_min_fill, + '泛酸': CupertinoIcons.circle_fill, + }; + + @override + Widget build(BuildContext context) { + final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; + final icon = _getIcon(); + final color = _getColor(); + + return GestureDetector( + onTap: () => + Get.toNamed(AppRoutes.search, arguments: {'keyword': nutrition.name}), + child: GlassContainer( + backgroundColor: color.withValues(alpha: 0.06), + borderColor: color.withValues(alpha: 0.15), + borderWidth: 0.5, + child: Padding( + padding: const EdgeInsets.all(DesignTokens.space2), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + color.withValues(alpha: 0.2), + color.withValues(alpha: 0.1), + ], + ), + shape: BoxShape.circle, + ), + child: Icon(icon, size: 18, color: color), + ), + const SizedBox(height: DesignTokens.space1), + Text( + nutrition.name, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: DesignTokens.fontXs, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (nutrition.unit.isNotEmpty) ...[ + const SizedBox(height: 2), + Text( + nutrition.unit, + style: TextStyle( + fontSize: 9, + color: color, + fontWeight: FontWeight.w500, + ), + ), + ], + ], + ), + ), + ), + ); + } + + IconData _getIcon() { + for (final entry in _nutritionIcons.entries) { + if (nutrition.name.contains(entry.key)) { + return entry.value; + } + } + return CupertinoIcons.chart_bar_fill; + } + + Color _getColor() { + if (nutrition.name.contains('维生素') || nutrition.name.contains('å¶é…¸')) { + return DesignTokens.orange; + } else if (nutrition.name.contains('蛋白') || + nutrition.name.contains('氨基酸')) { + return DesignTokens.red; + } else if (nutrition.name.contains('脂肪') || nutrition.name.contains('è„‚')) { + return Color(0xFFFF9800); + } else if (nutrition.name.contains('碳水') || + nutrition.name.contains('ç³–') || + nutrition.name.contains('能é‡')) { + return DesignTokens.dynamicPrimary; + } else if (nutrition.name.contains('纤维')) { + return DesignTokens.green; + } else if (nutrition.name.contains('é’™') || nutrition.name.contains('磷')) { + return Color(0xFF9C27B0); + } else if (nutrition.name.contains('é“') || nutrition.name.contains('血红')) { + return Color(0xFFF44336); + } else if (nutrition.name.contains('锌') || nutrition.name.contains('ç¡’')) { + return Color(0xFF607D8B); + } else if (nutrition.name.contains('é•') || + nutrition.name.contains('é’¾') || + nutrition.name.contains('é’ ')) { + return Color(0xFF00BCD4); + } + return DesignTokens.purple; + } +} diff --git a/lib/src/widgets/discover/recipe_discover_card.dart b/lib/src/widgets/discover/recipe_discover_card.dart index ffed40d..2087862 100644 --- a/lib/src/widgets/discover/recipe_discover_card.dart +++ b/lib/src/widgets/discover/recipe_discover_card.dart @@ -74,7 +74,7 @@ class _RecipeDiscoverCardState extends State /// æ˜¯å¦æœ‰å¯ç”¨çš„图片æºï¼ˆcoveréžç©º 或 能æå–出picId) bool get _hasImageSource { final cover = widget.recipe.cover; - if (cover != null && cover.isNotEmpty) return true; + if (cover.isNotEmpty) return true; return _extractPicId() != null; } diff --git a/lib/src/widgets/discover/tag_discover_card.dart b/lib/src/widgets/discover/tag_discover_card.dart new file mode 100644 index 0000000..dc6d981 --- /dev/null +++ b/lib/src/widgets/discover/tag_discover_card.dart @@ -0,0 +1,132 @@ +/* + * 文件: tag_discover_card.dart + * åç§°: 标签å‘现å¡ç‰‡ï¼ˆå£å‘³/工艺) + * 作用: 瀑布æµä¸­çš„æ ‡ç­¾å±•示å¡ç‰‡ï¼ŒLiquid Glassé£Žæ ¼ï¼Œæ”¯æŒæ•°é‡æ˜¾ç¤º + * 创建时间: 2026-04-13 + * æ›´æ–°æ—¶é—´: 2026-04-13 æ–°å¢žæ•°é‡æ˜¾ç¤ºåŠŸèƒ½ + */ + +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/config/app_routes.dart'; +import 'package:mom_kitchen/src/config/design_tokens.dart'; +import 'package:mom_kitchen/src/widgets/glass/glass_container.dart'; +import 'package:mom_kitchen/src/models/discover_model.dart'; + +class TagDiscoverCard extends StatelessWidget { + final DiscoverTag tag; + + const TagDiscoverCard({super.key, required this.tag}); + + @override + Widget build(BuildContext context) { + final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; + final isTaste = tag.type == 'taste'; + final baseColor = isTaste ? DesignTokens.dynamicPrimary : DesignTokens.orange; + + return GestureDetector( + onTap: () => Get.toNamed(AppRoutes.search, arguments: {'keyword': tag.name}), + child: GlassContainer( + backgroundColor: baseColor.withValues(alpha: 0.05), + borderColor: baseColor.withValues(alpha: 0.12), + borderWidth: 0.5, + child: Padding( + padding: const EdgeInsets.all(DesignTokens.space3), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + baseColor.withValues(alpha: 0.2), + baseColor.withValues(alpha: 0.1), + ], + ), + borderRadius: BorderRadius.circular(DesignTokens.radiusSm), + ), + child: Center( + child: Icon( + isTaste ? CupertinoIcons.tag_fill : CupertinoIcons.flame_fill, + size: 20, + color: baseColor, + ), + ), + ), + const SizedBox(width: DesignTokens.space2), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + tag.name, + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Row( + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: DesignTokens.space1, + vertical: 1, + ), + decoration: BoxDecoration( + color: baseColor.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + tag.typeLabel, + style: TextStyle( + fontSize: 10, + color: baseColor, + fontWeight: FontWeight.w700, + ), + ), + ), + if (tag.count > 0) ...[ + SizedBox(width: DesignTokens.space1), + Icon( + CupertinoIcons.number, + size: 10, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + SizedBox(width: 2), + Text( + _formatCount(tag.count), + style: TextStyle( + fontSize: 10, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ), + ], + ], + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + String _formatCount(int count) { + if (count >= 10000) { + return '${(count / 10000).toStringAsFixed(1)}万'; + } else if (count >= 1000) { + return '${(count / 1000).toStringAsFixed(1)}k'; + } + return count.toString(); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index fefbd72..90180c1 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,6 +12,7 @@ import path_provider_foundation import share_plus import shared_preferences_foundation import sqflite_darwin +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) @@ -21,4 +22,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/packages/temp_fluttertoast b/packages/temp_fluttertoast deleted file mode 160000 index 54a9cca..0000000 --- a/packages/temp_fluttertoast +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 54a9ccaf32fbf0e53e0ce16ced890ebc49e04599 diff --git a/pubspec.lock b/pubspec.lock index 5b4cd96..c887bc6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -292,6 +292,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_markdown_plus: + dependency: "direct main" + description: + name: flutter_markdown_plus + sha256: "039177906850278e8fb1cd364115ee0a46281135932fa8ecea8455522166d2de" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.7" flutter_staggered_grid_view: dependency: "direct main" description: @@ -452,6 +460,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: ee85086ad7698b42522c6ad42fe195f1b9898e4d974a1af4576c1a3a176cada9 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.3.1" matcher: dependency: transitive description: @@ -915,6 +931,31 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + path: "packages/url_launcher/url_launcher" + ref: HEAD + resolved-ref: "99056a7a25cd0154267fb12122743d34668d0a3d" + url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" + source: git + version: "6.1.12" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.29" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.6" url_launcher_linux: dependency: transitive description: @@ -923,6 +964,23 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.5" + url_launcher_ohos: + dependency: transitive + description: + path: "packages/url_launcher/url_launcher_ohos" + ref: HEAD + resolved-ref: "99056a7a25cd0154267fb12122743d34668d0a3d" + url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" + source: git + version: "6.0.38" url_launcher_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4f9320a..35ee188 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -125,6 +125,11 @@ dependencies: # flutter_screenutil: # path: packages/flutter_screenutil + url_launcher: + git: + url: https://gitcode.com/openharmony-tpc/flutter_packages.git + path: "packages/url_launcher/url_launcher" + path_provider: git: url: "https://gitcode.com/openharmony-sig/flutter_packages.git" @@ -135,6 +140,8 @@ dependencies: qr: ^3.0.2 + flutter_markdown_plus: ^1.0.7 + dev_dependencies: flutter_test: sdk: flutter @@ -164,6 +171,10 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/photos/ + - assets/json/ + - assets/md/tips/ + - assets/md/tips/advanced/ + - assets/md/tips/learn/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images diff --git a/scripts/test_discover_api.dart b/scripts/test_discover_api.dart new file mode 100644 index 0000000..b7b9e0c --- /dev/null +++ b/scripts/test_discover_api.dart @@ -0,0 +1,206 @@ +/* + * 文件: test_discover_api.dart + * åç§°: å‘现页API测试脚本 + * 作用: éªŒè¯ api_discover.php 接å£è¿”回的新字段 + * 创建时间: 2026-04-13 + * æ›´æ–°æ—¶é—´: 2026-04-13 + * + * è¿è¡Œæ–¹å¼: dart run scripts/test_discover_api.dart + */ + +import 'dart:convert'; +import 'dart:io'; + +const String baseUrl = 'https://eat.wktyl.com/api'; +const String discoverEndpoint = '/api_discover.php'; + +void main(List args) async { + print('========================================'); + print(' å‘现页 API æŽ¥å£æµ‹è¯•'); + print(' 测试时间: ${DateTime.now().toString().split('.')[0]}'); + print('========================================\n'); + + try { + await testBasicRequest(); + await testCustomParams(); + await testRefreshParam(); + await testNewFields(); + await testMultipleRequests(); + + print('\n✅ 所有测试完æˆï¼'); + } catch (e, stackTrace) { + print('\n⌠测试失败: $e'); + print('Stack trace: $stackTrace'); + } +} + +Future testBasicRequest() async { + print('📋 测试1: åŸºç¡€è¯·æ±‚ï¼ˆé»˜è®¤å‚æ•°ï¼‰'); + print('----------------------------------------'); + + final url = Uri.parse('$baseUrl$discoverEndpoint?total=20'); + final response = await _makeRequest(url); + + if (response['code'] == 200) { + final data = response['data'] as Map; + print(' ✅ 请求æˆåŠŸ'); + print(' - èœè°±æ•°é‡: ${(data['recipes'] as List).length}'); + print(' - é£Ÿææ•°é‡: ${(data['ingredients'] as List).length}'); + print(' - 分类数é‡: ${(data['categories'] as List).length}'); + print(' - 标签数é‡: ${(data['tags'] as List).length}'); + print(' - è¥å…»æˆåˆ†æ•°é‡: ${(data['nutrition_types'] as List).length}'); + print(' - ç”¨é¤æ—¶æ®µæ•°é‡: ${(data['meal_times'] as List).length}'); + print(' - 查询时间: ${response['_query_time']}'); + print(' - 缓存状æ€: ${response['meta']?['cache_status'] ?? 'unknown'}'); + } else { + print(' ⌠请求失败: ${response['message']}'); + } + print(''); +} + +Future testCustomParams() async { + print('📋 测试2: è‡ªå®šä¹‰å‚æ•°è¯·æ±‚'); + print('----------------------------------------'); + + final url = Uri.parse( + '$baseUrl$discoverEndpoint?recipe=5&ingredient=3&category=4&tag=5&nutrition=2&meal_time=2', + ); + final response = await _makeRequest(url); + + if (response['code'] == 200) { + final data = response['data'] as Map; + print(' ✅ 请求æˆåŠŸ'); + print(' - èœè°±æ•°é‡: ${(data['recipes'] as List).length} (预期: 5)'); + print(' - é£Ÿææ•°é‡: ${(data['ingredients'] as List).length} (预期: 3)'); + print(' - 分类数é‡: ${(data['categories'] as List).length} (预期: 4)'); + print(' - 标签数é‡: ${(data['tags'] as List).length} (预期: 5)'); + print(' - è¥å…»æˆåˆ†æ•°é‡: ${(data['nutrition_types'] as List).length} (预期: 2)'); + print(' - ç”¨é¤æ—¶æ®µæ•°é‡: ${(data['meal_times'] as List).length} (预期: 2)'); + } else { + print(' ⌠请求失败: ${response['message']}'); + } + print(''); +} + +Future testRefreshParam() async { + print('📋 测试3: å¼ºåˆ¶åˆ·æ–°å‚æ•°'); + print('----------------------------------------'); + + final url = Uri.parse('$baseUrl$discoverEndpoint?total=10&_refresh=1'); + final response = await _makeRequest(url); + + if (response['code'] == 200) { + print(' ✅ 请求æˆåŠŸ'); + print(' - 缓存状æ€: ${response['meta']?['cache_status'] ?? 'unknown'}'); + print(' - 请求次数: ${response['meta']?['request_count'] ?? 0}'); + final cacheHeader = response['_cache_header'] ?? ''; + print(' - X-Cache Header: $cacheHeader'); + } else { + print(' ⌠请求失败: ${response['message']}'); + } + print(''); +} + +Future testNewFields() async { + print('📋 测试4: éªŒè¯æ–°å­—段'); + print('----------------------------------------'); + + final url = Uri.parse('$baseUrl$discoverEndpoint?category=10'); + final response = await _makeRequest(url); + + if (response['code'] == 200) { + final data = response['data'] as Map; + final categories = data['categories'] as List; + + print(' ✅ 分类数æ®éªŒè¯:'); + for (var i = 0; i < categories.length && i < 3; i++) { + final cat = categories[i] as Map; + print(' ---'); + print(' 分类 ${i + 1}:'); + print(' - id: ${cat['id']}'); + print(' - name: ${cat['name']}'); + print(' - type: ${cat['type']}'); + print(' - count: ${cat['count']}'); + print(' - recipe_count: ${cat['recipe_count']} (新字段)'); + print(' - ingredient_count: ${cat['ingredient_count']} (新字段)'); + print(' - parent_id: ${cat['parent_id']}'); + print(' - parent_name: ${cat['parent_name']} (新字段)'); + } + + final tags = data['tags'] as List; + print(' ✅ 标签数æ®éªŒè¯:'); + for (var i = 0; i < tags.length && i < 3; i++) { + final tag = tags[i] as Map; + print(' ---'); + print(' 标签 ${i + 1}:'); + print(' - id: ${tag['id']}'); + print(' - name: ${tag['name']}'); + print(' - type: ${tag['type']}'); + print(' - count: ${tag['count']} (已修å¤)'); + } + } else { + print(' ⌠请求失败: ${response['message']}'); + } + print(''); +} + +Future testMultipleRequests() async { + print('📋 测试5: 多次请求去é‡éªŒè¯'); + print('----------------------------------------'); + + final recipeIds = {}; + final categoryIds = {}; + + for (var i = 0; i < 3; i++) { + final url = Uri.parse('$baseUrl$discoverEndpoint?total=15'); + final response = await _makeRequest(url); + + if (response['code'] == 200) { + final data = response['data'] as Map; + + final recipes = data['recipes'] as List; + final categories = data['categories'] as List; + + final newRecipeIds = + recipes.map((r) => (r as Map)['id'] as int).toSet(); + final newCategoryIds = + categories.map((c) => (c as Map)['id'] as int).toSet(); + + final duplicateRecipes = recipeIds.intersection(newRecipeIds); + final duplicateCategories = categoryIds.intersection(newCategoryIds); + + print(' 请求 ${i + 1}:'); + print(' - æ–°èœè°±: ${newRecipeIds.length}'); + print(' - é‡å¤èœè°±: ${duplicateRecipes.length}'); + print(' - 新分类: ${newCategoryIds.length}'); + print(' - é‡å¤åˆ†ç±»: ${duplicateCategories.length}'); + + recipeIds.addAll(newRecipeIds); + categoryIds.addAll(newCategoryIds); + + await Future.delayed(const Duration(milliseconds: 500)); + } else { + print(' ⌠请求 ${i + 1} 失败: ${response['message']}'); + } + } + + print(' ✅ 去é‡éªŒè¯å®Œæˆ'); + print(' - 总计èœè°±ID: ${recipeIds.length}'); + print(' - 总计分类ID: ${categoryIds.length}'); + print(''); +} + +Future> _makeRequest(Uri url) async { + final client = HttpClient(); + try { + final request = await client.getUrl(url); + final response = await request.close(); + final body = await response.transform(utf8.decoder).join(); + + final result = json.decode(body) as Map; + result['_cache_header'] = response.headers.value('X-Cache') ?? ''; + return result; + } finally { + client.close(); + } +}