# 顾客详情页 AI 模拟结果卡片流设计 ## 目标 在顾客详情页的“过往照片和AI发型模拟”区块中,替换当前未开发占位卡片,展示该顾客历史 AI 发型模拟生成结果。每一张生成图对应一张卡片,按创建时间倒序展示,点击卡片进入对应任务详情页。 ## 背景 - 当前顾客详情页中,“过往照片和AI发型模拟”区块仍是静态占位,不承载真实业务结果。 - AI 发型模拟主流程已经具备创建任务、查询任务详情、展示子任务结果图的能力。 - 用户要求在顾客详情页直接看到“刚刚任务的结果”,并明确要求: - 一个图片就是一个任务卡片 - 按时间顺序全部展示 - 点击图片卡片进入对应任务详情页 - 排序采用最新在前 ## 范围 ### 本次包含 - 新增顾客维度的 AI 模拟结果卡片列表接口 - 顾客详情页接入真实结果卡片流 - 每张生成图展开为一张独立卡片 - 点击卡片跳转到对应模拟任务详情页 - 空状态展示 ### 本次不包含 - 分页 - 筛选 - 失败任务和处理中任务的卡片展示 - 在顾客详情页直接轮询任务状态 - 任务详情页结构变更 ## 交互设计 ### 区块位置 继续使用顾客详情页现有“过往照片和AI发型模拟”区块,不改变整体页面骨架、标题和右上角“AI发型模拟”入口按钮位置。 ### 卡片规则 - 一张生成图展开为一张卡片 - 一个任务下如果有多张生成图,则展开成多张卡片 - 同一个 `simulationId` 下出现多张卡片属于预期行为 - 卡片至少展示: - 生成图 - 模板名称 - 生成时间 ### 点击行为 - 点击任意卡片,跳转至 `/:locale/simulation/result/:simulationId` - 不跳到子任务维度页面,继续复用现有任务详情页 ### 排序 - 按模拟任务创建时间倒序排列 - 最新的结果卡片排在最前面 ### 空状态 - 当顾客还没有任何成功生成的 AI 模拟结果时,不显示假卡片 - 使用清晰的空状态文案提示当前暂无 AI 模拟结果 ## 后端设计 ### 新接口 - 路由:`GET /api/v1/simulations/customer/:customerId/gallery` - 权限:仅允许当前登录发型师查看自己名下顾客的数据 ### 返回结构 返回扁平化卡片列表,而不是主任务嵌套结构。每条记录代表一张生成图卡片。 建议 DTO: ```ts export type CustomerSimulationGalleryItemDto = { simulationId: string; taskId: string; imageUrl: string; templateName: string; createdAt: string; taskStatus: 'SUCCESSES'; }; ``` 响应示例: ```json { "items": [ { "simulationId": "91", "taskId": "task-11", "imageUrl": "https://cdn.example.com/result-1.png", "templateName": "Layer Bob", "createdAt": "2026-06-28T09:00:00.000Z", "taskStatus": "SUCCESSES" } ] } ``` ### 查询规则 - 只查询当前发型师名下的顾客 - 只查询该顾客关联的 `ai_hair_simulation` - 只展开 `ai_hair_simulation_task` 中 `task_status = 'SUCCESSES'` 的任务 - 从 `result_images` 中展开真实图片列表 - 每张图片映射为一条扁平记录 - 按所属模拟任务 `create_time` 倒序输出 ### 数据来源 - 主表:`ai_hair_simulation` - 子表:`ai_hair_simulation_task` - 模板名来自 `hair_template.template_name` ### 安全约束 - 必须校验 `customerId` 属于当前发型师 - 不允许跨发型师读取顾客结果 ## 前端设计 ### API 层 在 `frontend/src/services/api/simulations.ts` 中新增顾客详情页所需的 gallery 查询方法,并在 `contracts.ts` 中补齐对应类型。 建议签名: ```ts export async function listCustomerSimulationGallery( customerId: string, ): Promise<{ items: CustomerSimulationGalleryItem[] }> ``` ### 顾客详情页 在 `CustomerDetailPage.tsx` 中替换原有 `photoPlaceholders` 占位卡片渲染逻辑: - 页面加载后并行请求: - 顾客详情 - 顾客 AI 模拟结果卡片列表 - 卡片区改为真实图片网格 - 每张卡片使用 `Link` - 跳转目标为 `path(\`/simulation/result/${simulationId}\`)` ### 展示字段 每张卡片展示: - 主图:`imageUrl` - 标题:`templateName` - 辅助时间:`createdAt` 的格式化日期时间 ## 边界与异常 - 如果接口失败: - 顾客详情主体仍按现有策略处理 - AI 模拟图片区块显示错误或空状态,而不是整页崩溃 - 如果某个成功任务的 `result_images` 为空数组: - 该任务不生成卡片 - 如果模板被删除: - 模板名称允许回退为默认文案,但不阻塞图片展示 ## 测试策略 ### 后端 - service 单测: - 顾客归属校验 - 仅展开成功任务 - 一个任务多图展开为多条记录 - 倒序输出 - e2e: - 返回当前顾客 gallery - 非本人顾客不可访问 ### 前端 - API 测试: - 请求路径正确 - 页面测试: - 成功渲染真实卡片 - 空状态渲染 - 点击卡片跳转到结果详情页 ## 验收标准 - 顾客详情页“过往照片和AI发型模拟”区块展示真实 AI 模拟结果卡片 - 一张生成图对应一张卡片 - 最新结果排在最前 - 点击卡片进入对应任务详情页 - 页面整体 UI 骨架不被破坏