# 模板使用量与顶部统计设计 ## 1. 背景 当前“我的发型模板”页面已经具备模板列表卡片和顶部 4 个统计卡片的 UI 骨架,但这部分数据还不是真实业务数据: - 模板列表接口 `GET /api/v1/templates` 当前返回的 `usedCount` 仍是固定值 `0`。 - 前端 `TemplateListPage` 中顶部统计使用的是本地默认值,且请求的 `/api/v1/stylist/dashboard/template-summary` 在后端并没有真实实现。 - 用户希望在当前模板表中新增“使用量”字段,用于支撑: - 列表卡片中的“使用 x 次” - 顶部统计中的“本周使用” - 其余 4 个统计卡片的真实展示 本轮目标是在尽量不改动现有模板列表 UI 结构的前提下,把模板使用量和顶部统计接成真实、可持续维护的数据链路。 ## 2. 目标 本轮交付后,需要满足以下目标: 1. `hair_template` 表新增累计使用量字段。 2. 模板列表中的 `usedCount` 返回真实值,卡片展示“使用 x 次”。 3. “我的发型模板”顶部 4 个统计卡片接入真实后端数据。 4. “本周使用”按本周增量统计,而不是累计值。 5. AI 发型模拟成功后,相关模板的累计使用量自动递增。 ## 3. 非目标 以下内容不在本次范围内: 1. 不补历史数据回填。当前项目没有历史数据,本轮无需做初始化聚合脚本。 2. 不新增第五个或更多顶部统计卡片。 3. 不调整模板列表已确认过的 UI 结构、卡片布局和交互。 4. 不改变“使用量”的业务含义,本轮统一定义为“成功用于 AI 发型模拟的次数”。 5. 不引入新的使用日志表。 ## 4. 方案选择 ### 方案 A:仅存累计使用量,顶部“本周使用”也直接显示累计值 做法: - 只在 `hair_template` 增加累计字段。 - 列表卡片和顶部“本周使用”都读累计值。 优点: - 实现最简单。 缺点: - “本周使用”语义错误,不符合用户确认的口径。 ### 方案 B:查询时临时聚合使用次数 做法: - 不在 `hair_template` 落累计字段。 - 列表和顶部统计都在查询时实时聚合 `ai_hair_simulation_task`。 优点: - 数据完全从事实表推导。 缺点: - 列表接口每次查询都要聚合,复杂度和查询成本更高。 - 不符合用户已明确提出的“在模板表新增字段”要求。 ### 方案 C:模板表存累计使用量,顶部“本周使用”从任务表做周增量统计 做法: - 在 `hair_template` 增加 `used_count`,保存累计使用量。 - AI 模拟任务成功时,对对应模板 `used_count + 1`。 - 顶部“本周使用”单独从 `ai_hair_simulation_task` 统计本周成功任务数。 优点: - 列表卡片直接读累计值,查询简单。 - 顶部“本周使用”保留正确的周增量语义。 - 改动范围可控,符合当前系统结构。 缺点: - 需要同时维护累计字段和周统计逻辑。 ### 结论 采用 **方案 C:模板表存累计使用量,顶部“本周使用”从任务表做周增量统计**。 ## 5. 业务口径 ### 5.1 模板累计使用量 - 落库字段:`hair_template.used_count` - 含义:该模板累计被成功用于 AI 发型模拟的次数 - 消费位置: - 模板列表卡片中的“使用 x 次” - 未来如有模板详情统计,也可直接复用 ### 5.2 本周使用 - 含义:当前自然周内,属于当前美发师模板的 AI 发型模拟成功任务总数 - 统计来源:`ai_hair_simulation_task` - 判断条件: - `task_status = 'SUCCESSES'` - `task_finish_time` 落在当前自然周范围内 - `is_deleted = 0` - `template_id` 能关联到当前美发师自己的 `hair_template` 说明: - 统计单位是“成功任务数”,不是“去重后的模板数”。 - 同一个模板在本周内成功使用多次,应累计多次。 ### 5.3 顶部 4 个统计卡片 继续沿用当前页面已有的 4 个统计项,不改 UI 结构: 1. `total` - 含义:当前美发师未删除模板总数 2. `published` - 含义:`status = 'PUBLIC'` 的未删除模板数 3. `weeklyUsed` - 含义:本周成功使用次数 4. `draft` - 含义:`status = 'DRAFT'` 的未删除模板数 ## 6. 数据库设计 ### 6.1 表结构 在 `hair_template` 新增字段: ```sql used_count int unsigned not null default 0 comment '模板累计使用次数' ``` 要求: - 默认为 `0` - 非空 - 保留字段注释 ### 6.2 Prisma 模型 在 `HairTemplate` 模型新增: ```prisma usedCount Int @default(0) @map("used_count") @db.UnsignedInt ``` ### 6.3 数据迁移策略 - 本轮仅新增字段,不做历史数据回填。 - 新建模板默认 `used_count = 0`。 - 上线后由真实的 AI 模拟成功事件持续累积。 ## 7. 后端设计 ### 7.1 模板列表接口 继续使用现有接口: ```http GET /api/v1/templates ``` 返回结构保持不变,但 `usedCount` 改为真实映射: ```ts { items: Array<{ id: string name: string category: 'LONG' | 'MEDIUM' | 'SHORT' | 'DYE' status: 'DRAFT' | 'PUBLIC' description: string tags: string[] image: string makeTaskId?: string makeTaskStatus?: string usedCount: number }> total: number page: number pageSize: number } ``` 实现要求: - `usedCount` 直接来自 `hair_template.used_count` - 不再返回固定值 `0` ### 7.2 顶部统计接口 新增模板模块内的真实统计接口: ```http GET /api/v1/templates/summary ``` 返回结构: ```json { "total": 24, "published": 18, "weeklyUsed": 6, "draft": 4 } ``` 说明: - 不再依赖不存在的 `/api/v1/stylist/dashboard/template-summary` - 接口归属放在模板模块内,方便和模板列表、模板编辑共用权限与领域边界 ### 7.3 统计实现 `TemplatesService` 新增 `getSummary(stylistId)` 方法,返回: - `total` - `published` - `weeklyUsed` - `draft` 建议实现方式: 1. `total` - `hair_template.count` - 条件:`hairstylist_id = 当前美发师`,`is_deleted = 0` 2. `published` - `hair_template.count` - 条件加上 `status = 'PUBLIC'` 3. `draft` - `hair_template.count` - 条件加上 `status = 'DRAFT'` 4. `weeklyUsed` - 统计 `ai_hair_simulation_task` 中本周成功任务数 - 为避免把其他人的模板统计进来,需要按模板归属过滤 `weeklyUsed` 的推荐查询思路: 1. 先计算当前自然周的起止时间 2. 在 `ai_hair_simulation_task` 中筛选: - `taskStatus = 'SUCCESSES'` - `taskFinishTime` 在本周范围内 - `isDeleted = 0` 3. 通过 `templateId` 关联 `hair_template` 4. 仅统计: - `hair_template.hairstylist_id = 当前美发师` - `hair_template.is_deleted = 0` 如果 Prisma 直接跨表过滤不够顺手,可以采用两段式最小实现: 1. 先取当前美发师所有未删除模板 ID 2. 再用这些模板 ID 统计本周成功任务数 ### 7.4 累计使用量更新 累计值的更新触发点放在“AI 发型模拟任务成功”的链路里。 当前系统已经存在 `ai_hair_simulation_task` 的状态刷新和结果落库逻辑,因此本轮在任务状态首次转为 `SUCCESSES` 时,补充: ```ts hair_template.used_count += 1 ``` 要求: - 只在任务真正从未成功状态转为 `SUCCESSES` 时递增一次 - 避免重复轮询、重复刷新导致同一个任务重复累加 推荐做法: 1. 在更新任务状态前读取旧状态 2. 只有当: - 旧状态不是 `SUCCESSES` - 新状态是 `SUCCESSES` 时,才对对应模板执行一次递增 说明: - 触发位置应靠近 `SimulationsService` 中任务刷新成功的逻辑,而不是放在列表查询时临时聚合。 - 这样可以保证模板表里的累计值始终可直接复用。 ## 8. 前端设计 ### 8.1 模板列表页 `TemplateListPage` 保持现有布局与交互,数据层做以下调整: - 页面初始化时请求真实的模板 summary - 顶部 4 个统计卡片展示真实值 - 列表卡片继续读 `template.usedCount` ### 8.2 summary 接口调整 前端 `getTemplateDashboardSummary()` 当前请求: ```ts /stylist/dashboard/template-summary ``` 本轮改为请求: ```ts /templates/summary ``` ### 8.3 初始值与失败态 当前前端存在默认假数据: ```ts { total: 24, published: 18, weeklyUsed: 6, draft: 4, } ``` 本轮改为: - 初始值使用全 `0` - 请求失败时回退到全 `0` 原因: - 避免页面出现和真实业务无关的假统计 - 保持失败时的 UI 稳定,不额外扩大本轮范围 ## 9. 测试设计 ### 9.1 后端单测 更新 `templates.service.spec.ts`,至少覆盖: 1. 模板列表返回真实 `usedCount` 2. `getSummary()` 返回: - `total` - `published` - `weeklyUsed` - `draft` 3. AI 模拟任务首次成功时会触发对应模板 `used_count + 1` 4. 同一个任务重复刷新为成功时不会重复累加 ### 9.2 后端 E2E 更新 `templates.e2e-spec.ts`,至少覆盖: 1. `GET /api/v1/templates` 返回真实 `usedCount` 2. `GET /api/v1/templates/summary` 返回 4 个真实统计字段 如现有 E2E 框架不便直接覆盖“模拟任务成功触发模板使用量递增”,可优先用单测锁住该业务规则。 ### 9.3 前端测试 更新 `TemplateListPage.test.tsx`,至少覆盖: 1. 顶部 4 个统计卡片展示真实 summary 2. 列表卡片中的“使用 x 次”展示接口返回值 3. 不再依赖假默认 summary 4. summary 请求失败时展示 `0` ## 10. 风险与约束 1. `weeklyUsed` 依赖 `task_finish_time`,因此只有成功完成并记录完成时间的任务才会被统计。 2. `template_id` 在 `ai_hair_simulation_task` 中是字符串,需要在统计时注意与 `hair_template.id` 的类型转换。 3. 若未来存在“删除模板但保留历史任务”的场景,本轮设计会按当前规则忽略已删除模板对应的周统计,这与当前“只看未删除模板业务数据”的页面语义一致。 ## 11. 交付结果 本轮完成后,系统应具备以下结果: 1. 模板表具备 `used_count` 字段 2. 模板列表返回真实使用量 3. 顶部 4 个统计卡片展示真实数据 4. “本周使用”按本周增量统计 5. AI 发型模拟成功后自动累计模板使用量