红点系统第一阶段 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_count、unread_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_count、unread_change_requests_count 等字段。
- Service Request 列表项继续使用已有
artist_is_read、user_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 单独查询红点。
- 必须使用
whereExists、whereHas、join 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 后端
- 定义第一阶段 path 注册表。
- 新增或完善
snapshot 接口。
- 为
user.center.total、user.work_task.total、user.work_task.status.* 增加 dot-only provider。
- 为
artist.center.total、artist.work_task.total、artist.service.total 增加 dot-only provider。
- 为
admin.center.total 增加 dot-only provider,内部可复用现有 job_count 逻辑。
- 评估并决定是否第一阶段实现 Work Task
attention=1。
- 保留旧接口作为 fallback。
11.2 前端
- 新增
useRedDots store 或等价 composable。
- 登录后或进入相关 layout 时请求 snapshot。
- 改造 topnav 账号入口静态红点。
- 在账号下拉的 User Center / Artist Center / Admin Center 菜单项上展示红点。
- 改造用户中心约稿菜单和状态 tab 红点。
- 改造画师中心 Work Task / Service 菜单红点。
- 保留列表项现有红点逻辑。
12. 风险与处理
| 风险 | 处理 |
|---|
| 第一阶段 scope 扩大成完整红点系统 | 明确只做 dot,不做 count,不做全量 counter |
| 父级红点查询变慢 | 使用 exists,必要时减少第一阶段 path |
attention=1 查询拖慢列表 | 第一阶段允许延期,第二阶段用实体摘要索引解决 |
| 新旧接口并存导致状态不一致 | 新红点只用于父级入口,列表项继续旧字段;迁移完成前保留 fallback |
| topnav 静态红点误导用户 | 第一阶段必须先移除或接入真实数据 |
13. 第一阶段结论
第一阶段采用“长期红点模型的最小可用版本”:
只做 dot
不做 count
父级入口接入真实红点
列表项保持现状
待处理筛选视性能决定是否落地
不引入重基础设施
这能先解决用户看不到上级入口红点的问题,同时避免做成新的临时 job_count 系统。