Files
msh-system/.cursor/plans/计算器结果保存食谱_f149b3dc.plan.md

167 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: 计算器结果保存食谱
overview: 在用户采纳营养计划时,将食谱计算器的配餐方案提取并保存到 v2_recipes 食谱表,并在首页"精选食谱"中混合展示用户自己的食谱(排在前面)。
todos:
- id: db-migration
content: 新建 SQL 变更文件v2_recipes 表新增 source 和 source_id 字段
status: completed
- id: entity-update
content: V2Recipe.java 实体类新增 source 和 sourceId 属性
status: completed
- id: adopt-save-recipe
content: ToolCalculatorServiceImpl.adopt() 中新增保存食谱到 v2_recipes 的逻辑(含幂等检查)
status: completed
- id: home-mix-recipes
content: ToolHomeServiceImpl.getRecommendedRecipes() 混入当前用户的计算器食谱
status: completed
- id: home-return-fields
content: 推荐食谱接口返回字段补充 description、source、totalProtein
status: completed
- id: frontend-recipe-card
content: 首页 index.vue 食谱卡片展示适配tag、desc 等字段映射)
status: completed
isProject: false
---
# 食谱计算器结果保存到食谱表
## 整体流程
```mermaid
sequenceDiagram
participant User as 用户
participant FE as 前端
participant Ctrl as ToolController
participant CalcSvc as ToolCalculatorServiceImpl
participant RecipeDao as V2RecipeDao
participant HomeSvc as ToolHomeServiceImpl
User->>FE: 点击"采纳计划"
FE->>Ctrl: POST /calculator/adopt
Ctrl->>CalcSvc: adopt(resultId)
CalcSvc->>CalcSvc: 创建营养计划 (已有逻辑)
CalcSvc->>RecipeDao: 保存一条食谱到 v2_recipes
CalcSvc-->>FE: 返回 adoptResponse
Note over FE: 首页刷新时
FE->>Ctrl: GET /home/recipes
Ctrl->>HomeSvc: getRecommendedRecipes()
HomeSvc->>RecipeDao: 查询推荐食谱 + 用户自己的食谱
HomeSvc-->>FE: 返回混合列表
```
## 1. 数据库变更 - v2_recipes 新增字段
需要新增两个字段以追踪食谱来源:
```sql
ALTER TABLE v2_recipes
ADD COLUMN source VARCHAR(20) DEFAULT 'manual' COMMENT '来源manual(手动)/calculator(计算器)/ai(AI生成)' AFTER sort_order,
ADD COLUMN source_id BIGINT DEFAULT NULL COMMENT '来源ID如计算器结果ID' AFTER source;
```
同时新增一个变更 SQL 文件记录此次 DDL。
## 2. 后端 - 实体类更新
**文件**: [V2Recipe.java](msh_crmeb_22/crmeb-common/src/main/java/com/zbkj/common/model/tool/V2Recipe.java)
新增两个字段:
```java
@ApiModelProperty(value = "来源manual/calculator/ai")
private String source;
@ApiModelProperty(value = "来源ID计算器结果ID等")
private Long sourceId;
```
## 3. 后端 - 采纳时保存食谱
**文件**: [ToolCalculatorServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolCalculatorServiceImpl.java)
`adopt()` 方法中,步骤 5创建营养计划之后新增步骤"保存食谱到 v2_recipes"
- 注入 `V2RecipeDao`
-`V2CalculatorResult` 中解析 `mealPlanJson` 得到 MealPlan
- 构建 `V2Recipe` 对象:
- `userId` = 当前用户ID
- `name` = "每日营养配餐 - {ckdStage}",如 "每日营养配餐 - CKD 5期"
- `description` = "蛋白质 {proteinIntake}g/天 | 能量 {energyIntake}kcal/天"
- `coverImage` = 取午餐第一道菜的图片 URL最具代表性
- `mealType` = null整日配餐非单餐
- `category` = "营养配餐"
- `tagsJson` = `["AI配餐", "{ckdStage}"]`
- `ingredientsJson` = 聚合三餐所有食材
- `stepsJson` = 存放完整 mealPlanJson早/午/晚餐详情),复用此字段存储配餐详情
- `totalProtein` = result.getProteinIntake()
- `totalEnergy` = result.getEnergyIntake()
- `suitableStagesJson` = `["{ckdStage}"]`
- `suitableDialysis` = result.getHasDialysis()
- `status` = "published"
- `isRecommend` = 0不进入官方推荐通过 source + userId 查询)
- `isOfficial` = 0
- `source` = "calculator"
- `sourceId` = resultId
- 幂等处理:先检查是否已存在 `source='calculator' AND source_id=resultId` 的记录,避免重复
## 4. 后端 - 首页推荐食谱混入用户食谱
**文件**: [ToolHomeServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolHomeServiceImpl.java)
修改 `getRecommendedRecipes()` 方法:
1. 如果用户已登录,先查询该用户的食谱(`user_id = currentUserId AND status = 'published' AND source = 'calculator'`,按 `created_at DESC`,取最新 1 条)
2. 再查询官方推荐食谱(现有逻辑,`is_recommend = 1`
3. 将用户食谱排在前面,官方推荐排在后面,合并后返回
4. 总数仍控制在 `limit` 范围内(如用户有 1 条自己的食谱,则官方推荐取 limit-1 条)
5. 缓存 key 需要区分用户(登录用户的缓存 key 加上 userId
## 5. 后端 - 推荐食谱接口返回字段补充
**文件**: [ToolHomeServiceImpl.java](msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolHomeServiceImpl.java)
在返回数据 map 中增加字段,使前端能够区分并展示更多信息:
```java
map.put("id", recipe.getRecipeId());
map.put("name", recipe.getName());
map.put("coverImage", recipe.getCoverImage());
map.put("totalEnergy", recipe.getTotalEnergy());
map.put("description", recipe.getDescription()); // 新增
map.put("source", recipe.getSource()); // 新增:标记来源
map.put("totalProtein", recipe.getTotalProtein()); // 新增
```
## 6. 前端 - 首页食谱展示适配
**文件**: [index.vue (首页)](msh_single_uniapp/pages/tool_main/index.vue)
调整食谱卡片展示逻辑:
- `item.tag`:如果 `source === 'calculator'` 显示 "我的配餐",否则显示 "推荐"
- `item.tagClass`:根据来源使用不同样式(如用户食谱用不同颜色)
- `item.desc`:使用 `description` 字段(如 "蛋白质 75.6g/天 | 能量 2205kcal/天"
- `item.time`:可不显示或显示 "每日配餐"
- `item.views`:使用 `viewCount` 或隐藏
修改 `loadData` 中对 `recipeList` 的处理逻辑,将后端返回的数据映射为前端需要的格式。
## 7. 前端 - 食谱详情页适配
点击首页食谱卡片跳转到 `recipe-detail` 页面,需要确认该页面能正确显示来自计算器的食谱内容(特别是 `stepsJson` 中存储的 mealPlan 数据)。如果需要特殊处理,在详情页根据 `source` 字段做渲染分支。
## 涉及文件清单
| 层级 | 文件 | 改动 |
| -------- | --------------------------------------------- | ------------------------------ |
| SQL | `docs/sql/v2_recipes_add_source.sql`(新建) | 新增 source、source_id 字段 |
| Entity | `V2Recipe.java` | 新增 source、sourceId 字段 |
| Service | `ToolCalculatorServiceImpl.java` | adopt() 中新增保存食谱逻辑 |
| Service | `ToolHomeServiceImpl.java` | getRecommendedRecipes() 混入用户食谱 |
| Frontend | `msh_single_uniapp/pages/tool_main/index.vue` | 食谱卡片展示适配 |