2026-06-28-customer-detail-simulation-gallery-design.md 5.2 KB

顾客详情页 AI 模拟结果卡片流设计

目标

在顾客详情页的“过往照片和AI发型模拟”区块中,替换当前未开发占位卡片,展示该顾客历史 AI 发型模拟生成结果。每一张生成图对应一张卡片,按创建时间倒序展示,点击卡片进入对应任务详情页。

背景

  • 当前顾客详情页中,“过往照片和AI发型模拟”区块仍是静态占位,不承载真实业务结果。
  • AI 发型模拟主流程已经具备创建任务、查询任务详情、展示子任务结果图的能力。
  • 用户要求在顾客详情页直接看到“刚刚任务的结果”,并明确要求:
    • 一个图片就是一个任务卡片
    • 按时间顺序全部展示
    • 点击图片卡片进入对应任务详情页
    • 排序采用最新在前

范围

本次包含

  • 新增顾客维度的 AI 模拟结果卡片列表接口
  • 顾客详情页接入真实结果卡片流
  • 每张生成图展开为一张独立卡片
  • 点击卡片跳转到对应模拟任务详情页
  • 空状态展示

本次不包含

  • 分页
  • 筛选
  • 失败任务和处理中任务的卡片展示
  • 在顾客详情页直接轮询任务状态
  • 任务详情页结构变更

交互设计

区块位置

继续使用顾客详情页现有“过往照片和AI发型模拟”区块,不改变整体页面骨架、标题和右上角“AI发型模拟”入口按钮位置。

卡片规则

  • 一张生成图展开为一张卡片
  • 一个任务下如果有多张生成图,则展开成多张卡片
  • 同一个 simulationId 下出现多张卡片属于预期行为
  • 卡片至少展示:
    • 生成图
    • 模板名称
    • 生成时间

点击行为

  • 点击任意卡片,跳转至 /:locale/simulation/result/:simulationId
  • 不跳到子任务维度页面,继续复用现有任务详情页

排序

  • 按模拟任务创建时间倒序排列
  • 最新的结果卡片排在最前面

空状态

  • 当顾客还没有任何成功生成的 AI 模拟结果时,不显示假卡片
  • 使用清晰的空状态文案提示当前暂无 AI 模拟结果

后端设计

新接口

  • 路由:GET /api/v1/simulations/customer/:customerId/gallery
  • 权限:仅允许当前登录发型师查看自己名下顾客的数据

返回结构

返回扁平化卡片列表,而不是主任务嵌套结构。每条记录代表一张生成图卡片。

建议 DTO:

export type CustomerSimulationGalleryItemDto = {
  simulationId: string;
  taskId: string;
  imageUrl: string;
  templateName: string;
  createdAt: string;
  taskStatus: 'SUCCESSES';
};

响应示例:

{
  "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 中补齐对应类型。

建议签名:

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 骨架不被破坏