|
|
@@ -0,0 +1,89 @@
|
|
|
+# 需求变更 · 推荐食材页面改版(页面 polish + 分页 + 共餐人选择)
|
|
|
+
|
|
|
+> **来源**:用户 2026-11-08 拍板 7 条修改意见(`@skill:tdd-orchestrator 修改推荐食材页面`)。
|
|
|
+> **页面**:`cfc-frontend/pages/diet/recommendation.vue`(1176 行,入口 `pages/diet/index.vue` L93-98 跳转)。
|
|
|
+> **涉及组件**:`cfc-frontend/components/MemberSelector.vue`(minimal 变体 L196-237)。
|
|
|
+> **涉及后端**:`DietRecommendationController`(`/api/diet/recommendation`)+ `DietRecommendationService.generateRecommendation`。
|
|
|
+> **任务拆解**:2 个能力键入池(见 §4),`diet.recommendation.uiRework` 先派、`diet.recommendation.participantSelect` 后派(共用 recommendation.vue,须串行,避免同文件冲突)。
|
|
|
+> **状态**:已入池待派发(RED,非契约补建)。
|
|
|
+
|
|
|
+## 1. 现状(7 条需求对应的代码定位,2026-11-08 只读核查实证)
|
|
|
+
|
|
|
+| # | 需求 | 现状落点 |
|
|
|
+|---|---|---|
|
|
|
+| 1 | 去掉页面顶部「推荐食材」 | `recommendation.vue` L6-9 `.section-title-bar`(标题「推荐食材」+ 副标题「查看家庭成员适合与戒备的食材」)|
|
|
|
+| 2 | 选家庭总览后,个人/成员不能再是选中态 | `switchView('family')`(L658-660)只置 `currentView='family'`,**不清 `currentMemberId`** → `MemberSelector` minimal 的 `selected-member-id` 仍指向成员 → 个人 chip(`selfChipActive` L331)/成员 chip(`m._selected` L318)仍高亮 |
|
|
|
+| 3 | 今日食谱单独显示,与「适合/戒备/全部」放一行做 Tab 切换 | 现筛选栏(L65-75 适合/戒备/全部)在食材区;「今日食谱」section(L135-235)垂直排在页面**最底部**,被食材列表顶出可视区 |
|
|
|
+| 4 | 推荐食材列表支持分页,上滑加载更多 | 食材列表(L103-123)v-for `filteredFoods`(computed L285-334)一次性全量渲染;外层已有 `<scroll-view scroll-y>`(L4)但未监听触底事件。后端 `/api/health/foods`(`HealthReportController.listFoods` L1965)返回 `List<Food>` **不分页**,量级为成员推荐食材集合(有限)→ **前端分页足矣,不动后端** |
|
|
|
+| 5 | 「个人」改成「我」 | `recommendation.vue` 未传 `self-label` → `MemberSelector.vue` 默认「👤 个人」(prop `selfLabel` L270) |
|
|
|
+| 6 | 点「生成食谱」要有「正在生成」提示 | `generateRecipe()`(L407-425)置 `self.loading = true` 但模板无任何 loading UI,也无 `uni.showLoading` |
|
|
|
+| 7 | 家庭总览进要先选共餐人;个人/成员进只显示当前选中人 | 后端 `DietRecommendationService.generateRecommendation`(L67-140)共餐人走 `queryParticipantIds(familyId, date)`(L214-255:MealConfig 临时配置 → 工作日/周末默认 → 全部家庭成员),**不接受前端传参**;前端 `generateRecipe()` 只传 `{date, meal_type}` |
|
|
|
+
|
|
|
+## 2. 需求明细与实现方案
|
|
|
+
|
|
|
+### 需求 1:去掉页面顶部「推荐食材」
|
|
|
+- 删除 `recommendation.vue` L6-9 `.section-title-bar` 块。
|
|
|
+- 成员选择条(L13-21 `MemberSelector`)保留,上移为页面首个元素。
|
|
|
+
|
|
|
+### 需求 2:家庭总览 ↔ 个人/成员互斥选中
|
|
|
+- `switchView('family')` 时置 `this.currentMemberId = null`(`selected-member-id` 绑 null → 个人/成员 chip 均不高亮;家庭总览 chip 由 `overview-active="currentView === 'family'"` 高亮)。
|
|
|
+- 反向:点个人/成员 chip(`switchMember` L669-674)已置 `currentView='personal'` + `currentMemberId`,互斥成立。
|
|
|
+- ⚠ 注意 `loadMembers`(L483-486):`currentMemberId` 为空且成员列表非空时会自动默认选「个人」——仅限 onLoad 首次,`switchView('family')` 置空后**不得**再触发默认选中(改交互时确认该分支只在成员加载完成且从未选中时生效)。
|
|
|
+
|
|
|
+### 需求 3:今日食谱独立 Tab(与适合/戒备/全部同行)
|
|
|
+- 将筛选栏改造为 **4 Tab 行**:`今日食谱 | 适合 | 戒备 | 全部`(复用 `filterType` 语义:suitable/unsuitable/all;新增 `currentTab: 'recipe'`)。
|
|
|
+- 数据状态:新增 `currentTab`(初值 `'recipe'`);`filteredFoods` 逻辑不变;模板按 `currentTab` 切换内容区:
|
|
|
+ - `recipe` → 原「今日食谱」区(L141-235:无推荐空态+生成按钮 / 有推荐卡片+菜品+营养+操作)
|
|
|
+ - `suitable`/`unsuitable`/`all` → 原食材区(成员选择条 + 搜索 + 分类 + 家庭总览/食材列表)
|
|
|
+- 交互:点 Tab 切换对应内容;进入页面默认 `recipe`(今日食谱优先可见,解决「被顶到底看不到」)。家庭总览/个人切换仍有效。
|
|
|
+- 视觉:Tab 行放在原筛选栏位置(L65-75 区域),原「今日食谱」section-title(L136-138)删除。
|
|
|
+
|
|
|
+### 需求 4:推荐食材列表分页(上滑加载更多)
|
|
|
+- 纯前端分页(数据源全量返回,量级有限,不动后端):
|
|
|
+ - 新增 `visibleCount`(初值 20)与 `PAGE_SIZE = 20`;
|
|
|
+ - 新增 computed `pagedFoods = filteredFoods.slice(0, visibleCount)`,模板 L104 v-for 改用 `pagedFoods`;
|
|
|
+ - 外层 `scroll-view`(L4)监听 `@scrolltolower="loadMore"`;`loadMore()`:`visibleCount < filteredFoods.length` 时 `visibleCount += PAGE_SIZE`(触底前判断,末尾自动停);
|
|
|
+ - 切换成员/分类/筛选/搜索/Tab 时重置 `visibleCount = PAGE_SIZE`;
|
|
|
+ - 序号 badge(L107 `idx + 1`)基于 `filteredFoods` 的全量序号或分页内序号均可,以现有渲染不跳动为准(建议保留 `pagedFoods` 内 idx,即每页从 1 起;或全量 idx——按实现取向二选一并在提交时说明)。
|
|
|
+
|
|
|
+### 需求 5:「个人」→「我」
|
|
|
+- `recommendation.vue` 的 `<MemberSelector>` 增加 `:self-label="'我'"`。
|
|
|
+- 不动 `MemberSelector.vue` 默认值(公共组件,避免影响其他页面)。
|
|
|
+
|
|
|
+### 需求 6:生成食谱「正在生成」提示
|
|
|
+- `generateRecipe()`(L407-425):发起时 `uni.showLoading({ title: '正在生成食谱…' })`,回调(成功/失败/catch)里 `uni.hideLoading()`;`meals` 无推荐态下生成成功调 `loadRecommendation()`(已存在)。
|
|
|
+- 保持 `self.loading` 布尔(供模板后续扩展「生成中」卡片态,可选)。
|
|
|
+
|
|
|
+### 需求 7:共餐人选择(前后端联动)
|
|
|
+**前端**(`recommendation.vue`):
|
|
|
+- 生成入口分两种视角:
|
|
|
+ - 家庭总览(`currentView === 'family'`)点「生成食谱」→ 先弹共餐人选择(多选,候选 = `memberList`(`/api/food/recommendation/members` 返回的 FamilyMemberVO,**含 `familyMemberId` + `nickname` + `isSelf`**),默认勾选全部家庭成员),确认后调 generate 并传 `participant_member_ids`(`[familyMemberId,...]` JSON 字符串);
|
|
|
+ - 个人/成员(`currentView === 'personal'`)点「生成食谱」→ 不弹选择,传 `participant_member_ids: [currentMemberId]`(只当前选中人)。
|
|
|
+- `generateDietRecommendation`(`utils/api.js` L3112)为通用透传 `request('.../generate','POST',data)`,无需改动。
|
|
|
+
|
|
|
+**后端**(`DietRecommendationService.generateRecommendation` L67-140):
|
|
|
+- 第 2 步查询共餐参与者改为:先读 `request.get("participant_member_ids")`,非空(JSON 数组字符串)→ 解析为 `participantIds`(**用户显式选择优先**);为空/null → 维持 `queryParticipantIds(familyId, date)` 兜底(MealConfig → 默认配置 → 全部成员)。
|
|
|
+- 解析复用 `parseMemberIds`(L257-267)。
|
|
|
+- 参与人数至少 1(沿用现逻辑:`participantIds` 空数组时 `participantsJson="[]"`,`normalizeMenu` 中 participantCount 兜底 1)。
|
|
|
+
|
|
|
+**读取侧不改**:`getTodayRecommendation`(L37-65)仍按 familyId+date 取当天第一条(未按共餐人区分多份);本次需求不引入「多份并存」,生成即覆盖更新(generate 幂等更新 L101-119 已有)。
|
|
|
+
|
|
|
+## 3. 验收判据(V/C)
|
|
|
+
|
|
|
+1. 页面顶部无「推荐食材」标题;首屏默认显示「今日食谱」Tab 内容。
|
|
|
+2. 切「家庭总览」后个人/成员 chip 均不高亮;切个人/成员后家庭总览 chip 不高亮。
|
|
|
+3. 4 Tab(今日食谱/适合/戒备/全部)一行切换,内容区正确互切;食材列表不再顶掉今日食谱。
|
|
|
+4. 食材列表触底上滑增量渲染(每批 20),末尾不再加载;切筛选/搜索/分类/成员后列表重置回首批。
|
|
|
+5. 成员选择条「个人」显示为「我」。
|
|
|
+6. 点「生成食谱」出现「正在生成」loading,成功/失败后消失并有 toast。
|
|
|
+7. 家庭总览进点生成 → 弹共餐人多选,确认后生成的菜单 `participants` = 所选人数;个人/成员进点生成 → 不弹选择,`participants` = 1;后端 generate 收到 `participant_member_ids` 时覆盖 MealConfig 默认共餐人。
|
|
|
+8. `--clean-check` 干净 + 实现/文档提交已推 origin/cfclub。
|
|
|
+
|
|
|
+## 4. 任务拆解(TDD 池)
|
|
|
+
|
|
|
+| 键 | 覆盖需求 | 阶段 | 溯源 |
|
|
|
+|---|---|---|---|
|
|
|
+| `diet.recommendation.uiRework` | 1-6(纯前端 recommendation.vue 重构 + MemberSelector 传参) | RED | requirement:本文档 §2 需求 1-6;design:本文档 §2 各条落点(行号)+ `cfc-frontend/pages/diet/recommendation.vue` + `components/MemberSelector.vue` |
|
|
|
+| `diet.recommendation.participantSelect` | 7(前端共餐人选择 + 后端 generate 支持 `participant_member_ids`) | RED | requirement:本文档 §2 需求 7;design:本文档 §2 需求 7(`DietRecommendationService.generateRecommendation` L67-140 + `parseMemberIds` L257-267;前端 `recommendation.vue generateRecipe` L407-425) |
|
|
|
+
|
|
|
+> 派发顺序约束:两键同属 `recommendation.vue` 不同区域(uiRework 动模板结构 L1-235 与筛选/Tab 逻辑;participantSelect 动 generateRecipe + 数据字段),**必须串行派发**(uiRework 先、participantSelect 后),编排侧派发第二个前确认第一个已合入 origin/cfclub。
|