红点系统第一阶段 PRD

1. 文档信息

  • 文档名称:红点系统第一阶段 PRD
  • 所属项目:Pipipen
  • 编写日期:2026-06-17
  • 关联完整方案:docs/pipipen/feature/red-dot-system-prd.md
  • 相关仓库:
    • 前端:D:\codes\pipipen-front
    • 后端:D:\codes\pipipen-api
    • 文档:D:\codes\pipipen-docs

2. 第一阶段目标

第一阶段只解决“用户在更外层能看到有待关注事项”的问题,不追求完整红点体系一次性落地。

核心目标:

  • 只展示红点,不展示数量。
  • 当前列表页已有红点继续保留原有前端模式。
  • 当前已有列表项红点对应的上级菜单,需要展示红点。
  • 右上角账号下拉菜单需要展示真实红点。
  • Work Task 状态筛选页支持待处理筛选;如果性能风险较高,第一阶段允许延期。
  • 后端用新的红点 path/provider 统一口径,避免继续扩散新的 job_count 临时接口。

第一阶段不是为了替换所有旧实现,而是建立长期红点系统的最小闭环。

3. 第一阶段范围

3.1 必须做

右上角账号下拉菜单

当前 components/topnav/topnav.vue 的账号入口存在静态 unread-dot。第一阶段需要改成真实业务红点。

需要覆盖:

  • 账号入口本身:有任一可见中心存在待关注事项时展示红点。
  • 下拉菜单里的 Admin Center:当前用户有后台权限且后台有待处理事项时展示红点。
  • 下拉菜单里的 Artist Center:当前用户有画师权限且画师侧有待处理事项时展示红点。
  • 下拉菜单里的 User Center:用户侧有待处理事项时展示红点。

不覆盖:

  • 语言切换下拉里的静态 unread-dot。它不是业务红点,第一阶段应移除或保持不接入红点系统。

用户中心 Work Task 上级菜单

需要覆盖:

用户中心 -> 约稿
用户中心 -> 约稿 -> 工作中
用户中心 -> 约稿 -> 已完成
用户中心 -> 约稿 -> 已取消

展示规则:

  • 只展示红点,不展示数量。
  • 有任一待处理 Work Task 时,约稿 展示红点。
  • 某个状态下存在待处理 Work Task 时,对应状态 tab 展示红点。
  • 列表项仍然沿用现有 hint-new-text 和列表接口字段。

画师中心上级菜单

需要覆盖当前已有红点事实来源对应的上级入口:

  • 画师 Work Task:例如修改意见未读、改价待处理。
  • 画师 Service:例如新的 service request。

展示规则:

  • 只展示红点,不展示数量。
  • Artist Center 下拉入口有任一画师侧待处理事项时展示红点。
  • 画师中心内对应菜单展示红点。
  • service 列表项和 service request 列表项继续保留当前字段直返模式。

后端红点 snapshot

新增或完善统一读取接口:

POST /api/red_dots/snapshot

第一阶段只要求返回 dot 语义:

{
  "code": 0,
  "data": {
    "nodes": {
      "user.center.total": {
        "path": "user.center.total",
        "has_dot": true,
        "display": "dot",
        "count": null,
        "count_metric": "dot_only"
      }
    }
  }
}

约束:

  • count 固定为 null 或不返回。
  • 前端不得用子节点自行求和生成数字。
  • provider 第一阶段以 exists 为主,不做昂贵 count

3.2 尽量做,但允许按性能评估延期

Work Task 列表待处理筛选

目标:支持用户从父级红点直接进入待处理列表,而不是在分页里找。

建议接口:

GET /api/user/work_tasks?status=working&attention=1&page=1
GET /api/user/work_tasks?attention=1&page=1
GET /api/artist/work_tasks?status=working&attention=1&page=1

规则:

  • attention=1 表示只返回有待处理事项的 Work Task。
  • 可以和现有 status 筛选组合。
  • 返回列表项仍使用现有红点字段,例如 unread_files_countunread_change_requests_count 或已有的子事件字段。

延期条件:

  • 如果实现需要跨多张大表复杂 join,且缺少索引,导致明显拖慢现有列表接口,可以第一阶段延期。
  • 如果无法在当前阶段清晰定义“待处理”的业务口径,可以第一阶段只做父级红点,不做 attention=1
  • 延期时必须在接口或代码 TODO 中说明原因和后续需要的索引/摘要表。

4. 第一阶段非目标

第一阶段明确不做:

  • 不展示 count。
  • 不做 99+、数字上限、数字聚合。
  • 不把所有列表项红点改成独立 red dot batch 接口。
  • 不在分页页码上展示红点。
  • 不强制引入 Redis。
  • 不强制新增 red_dot_counters
  • 不强制新增 red_dot_entity_summaries
  • 不把 WebSocket 作为交付依赖。
  • 不删除旧 job_count、旧 red dot 路由或旧列表字段。
  • 不重构聊天、通知、Work Task 的核心业务流程。

5. 第一阶段红点 Path

5.1 命名原则

  • 使用业务语义 path,不使用 UI 位置命名。
  • chat 表示当前系统里的私聊。
  • group 表示当前系统里的群聊。
  • 最外层消息入口使用 message_center,避免和私聊 chat 冲突。
  • 第一阶段所有节点 display = dot

5.2 第一阶段建议节点

account.center.total
user.center.total
user.work_task.total
user.work_task.status.working
user.work_task.status.completed
user.work_task.status.canceled
artist.center.total
artist.work_task.total
artist.service.total
admin.center.total

说明:

  • account.center.total 用于右上角账号入口,只表示当前用户可见的账号下拉中是否有任一待关注事项。
  • user.center.total 用于右上角下拉中的 User Center,以及用户中心外层入口。
  • artist.center.total 用于右上角下拉中的 Artist Center,以及画师中心外层入口。
  • admin.center.total 用于右上角下拉中的 Admin Center。
  • user.work_task.status.* 只用于状态 tab 是否展示红点,不返回数量。

6. 前端设计

6.1 数据流

6.2 前端接入规则

新增或使用统一 store:

type RedDotItem = {
  path: string;
  has_dot: boolean;
  display: 'dot';
  count?: null;
  count_metric: 'dot_only';
};

组件规则:

  • topnav.vue:账号入口读取 account.center.total
  • 账号下拉 Admin Center:读取 admin.center.total
  • 账号下拉 Artist Center:读取 artist.center.total
  • 账号下拉 User Center:读取 user.center.total
  • 用户中心约稿菜单:读取 user.work_task.total
  • 用户中心 Work Task 状态 tab:读取 user.work_task.status.*
  • 画师中心 Work Task 菜单:读取 artist.work_task.total
  • 画师中心 Service 菜单:读取 artist.service.total

列表项规则:

  • Work Task 列表项继续使用已有 unread_files_countunread_change_requests_count 等字段。
  • Service Request 列表项继续使用已有 artist_is_readuser_is_read 字段。
  • 已有 hint-new-text 组件不强制替换。

6.3 点击行为

  • 点击普通菜单文字:进入原列表。
  • 点击带红点的状态 tab:如果已支持 attention=1,进入待处理筛选列表。
  • 如果第一阶段没有实现 attention=1,点击行为保持原逻辑,只展示当前页列表项红点。

7. 后端设计

7.1 Provider 结果

第一阶段 provider 不需要返回数量,只返回是否有红点。

final class RedDotResult
{
    public function __construct(
        public string $path,
        public bool $hasDot,
        public string $display = 'dot',
        public ?int $count = null,
        public string $countMetric = 'dot_only',
    ) {}
}

Provider 查询原则:

  • 优先使用 exists()
  • 禁止为了 dot 使用昂贵 count(*)
  • 禁止为父级红点递归加载所有子节点。
  • 父级 dot 可以由 provider 显式 OR 多个低成本事实来源。

7.2 第一阶段事实来源

用户 Work Task:

work_task_files.user_is_read = false
WorktaskPageEventList 中面向用户且未关闭的 action item,例如 price_change

画师 Work Task:

work_task_file_change_requests.artist_is_read = false
WorktaskPageEventList 中面向画师且未关闭的 action item,例如 price_change

画师 Service:

service_requests.artist_is_read = false

用户侧 Service Request / Application:

service_requests.user_is_read = false
applications.user_is_read = false

通知:

notifications.read_at is null

后台:

沿用 /api/admin_center/profile/job_count 中已有待处理条件
第一阶段可以封装为 provider,但不要求删除旧接口

7.3 父级节点口径

父级红点只判断“当前业务范围内是否存在待关注事项”。

示例:

user.work_task.total = exists(用户侧任一待处理 Work Task)
user.work_task.status.working = exists(用户侧 working 状态任一待处理 Work Task)
user.center.total = exists(user.work_task.total or user service/application 等用户侧事项)
account.center.total = exists(当前用户可见的 user/artist/admin 任一中心事项)

注意:

  • 这里的 exists(a or b) 是业务口径,不要求真的递归调用 provider。
  • 可以在 provider 内部用低成本查询分别判断。
  • 第一阶段不做 count,所以不存在父级数字聚合问题。

8. 待处理筛选设计

8.1 查询流程

8.2 性能要求

第一阶段实现 attention=1 前必须确认 SQL 成本。

最低要求:

  • 不得为了筛选待处理而加载所有 Work Task 到内存。
  • 不得循环每个 Work Task 单独查询红点。
  • 必须使用 whereExistswhereHasjoin exists 或已有索引字段。
  • P95 不应明显慢于原列表接口。

建议索引:

work_task_files(work_task_id, user_is_read)
work_task_files(user_id, user_is_read)
work_task_file_change_requests(work_task_id, artist_is_read)
service_requests(artist_id, artist_is_read, service_id)
service_requests(user_id, user_is_read)

如果缺少索引且无法安全补充,attention=1 可以延期到第二阶段,与实体摘要索引一起实现。

9. 交互示例

9.1 用户侧 Work Task

9.2 画师侧 Service Request

10. 验收标准

10.1 功能验收

  • 右上角账号入口不再显示静态红点,必须由真实红点数据控制。
  • 未登录用户不展示业务红点。
  • 当前用户没有 admin 权限时,不请求或不展示 admin.center.total
  • 用户侧 Work Task 有待处理事项时,右上角 User Center 和用户中心约稿菜单展示红点。
  • 画师侧 Work Task 有待处理事项时,右上角 Artist Center 和画师 Work Task 菜单展示红点。
  • 画师有新的 service request 时,右上角 Artist Center 和画师 Service 菜单展示红点。
  • Work Task 状态 tab 有待处理事项时展示红点。
  • 列表项已有红点展示不发生回归。
  • 第一阶段所有新增红点都不展示数字。

10.2 性能验收

  • snapshot 接口不加载完整列表。
  • dot 查询优先使用 exists
  • snapshot P95 目标小于 200ms。
  • attention=1 如果实现,不得出现 N+1 查询。
  • 如果 attention=1 未实现,需要明确记录延期原因。

10.3 兼容验收

  • /api/profile/job_count 保留。
  • /api/admin_center/profile/job_count 保留。
  • 现有列表接口字段保留。
  • hint-new-text 展示不回归。
  • 旧 red dot 路由不在第一阶段删除。

11. 实施步骤

11.1 后端

  1. 定义第一阶段 path 注册表。
  2. 新增或完善 snapshot 接口。
  3. user.center.totaluser.work_task.totaluser.work_task.status.* 增加 dot-only provider。
  4. artist.center.totalartist.work_task.totalartist.service.total 增加 dot-only provider。
  5. admin.center.total 增加 dot-only provider,内部可复用现有 job_count 逻辑。
  6. 评估并决定是否第一阶段实现 Work Task attention=1
  7. 保留旧接口作为 fallback。

11.2 前端

  1. 新增 useRedDots store 或等价 composable。
  2. 登录后或进入相关 layout 时请求 snapshot。
  3. 改造 topnav 账号入口静态红点。
  4. 在账号下拉的 User Center / Artist Center / Admin Center 菜单项上展示红点。
  5. 改造用户中心约稿菜单和状态 tab 红点。
  6. 改造画师中心 Work Task / Service 菜单红点。
  7. 保留列表项现有红点逻辑。

12. 风险与处理

风险处理
第一阶段 scope 扩大成完整红点系统明确只做 dot,不做 count,不做全量 counter
父级红点查询变慢使用 exists,必要时减少第一阶段 path
attention=1 查询拖慢列表第一阶段允许延期,第二阶段用实体摘要索引解决
新旧接口并存导致状态不一致新红点只用于父级入口,列表项继续旧字段;迁移完成前保留 fallback
topnav 静态红点误导用户第一阶段必须先移除或接入真实数据

13. 第一阶段结论

第一阶段采用“长期红点模型的最小可用版本”:

只做 dot
不做 count
父级入口接入真实红点
列表项保持现状
待处理筛选视性能决定是否落地
不引入重基础设施

这能先解决用户看不到上级入口红点的问题,同时避免做成新的临时 job_count 系统。