Files
zkxw/docs/superpowers/specs/2026-08-06-oa-cc-unread-badge-design.md
T
zlwuandCursor 48de4be893 docs(oa): add CC unread badge design and demo
Capture the approved design for sidebar unread counts and read/batch-read flows on 抄送我的, with an interactive HTML demo for UI reference.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 11:57:33 +08:00

5.9 KiB
Raw Blame History

OA「抄送我的」未读角标 Design

项 内容
日期 2026-08-06
状态 已定稿(UI 参考 docs/demos/oa-cc-unread-badge-demo.html)
范围 侧栏「抄送我的」未读数角标 + 列表阅读状态筛选 / 已读 / 批量已读 / 查看即已读

背景

「抄送我的」目前仅列出抄送任务,无法区分是否已读,侧栏也无未读提示。产品要求对齐「质检工单」菜单上的红色数字角标,展示抄送给我且尚未阅读的数量;列表支持标记已读,点击「查看」默认已读。

现有 oa_task 抄送任务创建时 status=done(不阻断流程),没有阅读态字段,需扩展。

目标与非目标

目标

  • 侧栏「抄送我的」(menuCode: oa:cc)展示未读抄送数;样式对齐质检 .art-text-badge;未读为 0 时隐藏角标。
  • 列表:筛选下拉「阅读状态」(未读 / 已读 / 全部),默认仅未读;无 Tab。
  • 操作列「已读」+ 工具栏「批量已读」;「查看」进入详情前自动标记已读。
  • 历史抄送记录上线后全部视为未读。

非目标

  • 不改造小程序端。
  • 「打印」不自动已读。
  • 不引入独立已读表或前端 localStorage 持久化。
  • 不把阅读态与审批 status(pending/done)混用。

方案对比与结论

方案 做法 优点 缺点
1(选定) oa_task 增加 is_read / read_time 改动小;列表与角标查询简单 审批任务行也会多两列(可忽略)
2 独立 oa_cc_read 表 不改 oa_task 多 join;实现更绕
3 localStorage 无后端 换设备丢失;多端不一致

选定方案 1。

数据模型

表:oa_task 增量字段:

字段 类型 说明
is_read TINYINT NOT NULL DEFAULT 0 0 未读 / 1 已读;仅 task_type=cc 有业务意义
read_time DATETIME NULL 首次标记已读时间;重复标记不覆盖

行为:

  • 新建抄送任务:显式 is_read=0,read_time=NULL。
  • 历史数据迁移:全部 is_read=0(产品确认)。
  • 标记已读:本人 + task_type=cc + 未删除;已读则幂等成功。
  • 审批任务(task_type=approval)不读写这两字段的业务逻辑。

SQL:docs/sql/oa_task_cc_is_read.sql(ALTER + 注释;迁移默认未读即靠 DEFAULT / 显式 UPDATE)。

API(/api/admin/oa-approval)

方法 路径 说明
GET /tasks/cc/unread-count 当前用户未读抄送数 { count: number }
GET /tasks/cc 扩展:query readStatus=unread|read|all,缺省 unread;列表项含 isRead、readTime(及现有字段)
POST /tasks/{task_id}/read 单条已读;仅本人抄送任务;幂等
POST /tasks/cc/read-batch body { ids: string[] };仅处理本人、抄送、未删任务;返回 { updated: number }

约束(全部 SHALL):

  • 租户隔离:tenant_no = 当前用户租户。
  • 归属:assignee_user_id = 当前用户。
  • 类型:task_type=cc,del_flag=0。
  • 非本人 / 非抄送 / 不存在:单条 400;批量跳过非法 id,只更新合法未读行。

说明:若现网 GET /tasks/{kind} 且 kind=cc 已存在,则在该路径上增加 readStatus,不要为列表另起冲突路径;unread-count 与 read-batch 使用更具体路径,避免与 {kind} 路由抢配(实现时注意 FastAPI 路由注册顺序:更具体路径先于 {kind})。

前端

「抄送我的」页(web-admin/src/views/oa-approval/cc/index.vue)

  • 筛选 filter-block:el-select「阅读状态」选项未读 / 已读 / 全部;默认 unread;重置回到未读。
  • 工具栏:已选 N 项 →「批量已读」(中性描边,非主色);无选中或所选均已读时禁用。
  • 表格:selection;「阅读状态」列用 el-tag(未读 danger,已读 info);未读行流程名可选红点。
  • 操作列:查看 | 已读 | 打印;已读行禁用「已读」;查看:先 POST .../read 再跳转实例详情;打印不改已读。
  • 成功标记后:刷新列表 + refreshOaMenuBadges();onActivated 同样刷新。

对齐列表模板:/#/demo/unit(筛选 + 工具栏 + 勾选 + 批量)。

侧栏角标

  • 新增 store(如 oa-menu-badge)+ refreshOaMenuBadges(),模式对齐 qc-menu-badge。
  • 解析 menuCode === 'oa:cc'(及路由名 OaApprovalCc 兜底)。
  • SidebarSubmenu / 横向菜单接入;数字格式与质检一致(>99 → 99+);count===0 不渲染角标。
  • 登录后路由守卫中与质检角标一并刷新。

UI Demo:docs/demos/oa-cc-unread-badge-demo.html(交互示意;实现时补筛选与批量勾选,以本 spec 为准)。

错误与边界

场景 行为
重复已读 成功,不改 read_time
批量含已读 / 非法 id 跳过;合法未读照常更新;updated 为实际更新数
默认筛选未读且全部已读 列表空;角标隐藏;用户可改筛选看已读/全部
角标接口失败 角标视为未加载或 0(不阻塞菜单)

测试要点

  1. 新抄送产生后角标 +1;列表默认能看到该未读行。
  2. 「已读」/「批量已读」后角标与列表同步。
  3. 「查看」后该条变为已读,角标减少。
  4. 「打印」后仍为未读。
  5. 筛选已读 / 全部行为正确;重置回到未读。
  6. 历史数据上线后均为未读。
  7. 非抄送任务调 /read 应失败。

决策记录

决策 结论
未读为 0 隐藏角标
已读入口 操作列 + 工具栏批量已读
历史数据 全部未读
列表筛选 下拉「阅读状态」,无 Tab,默认未读
查看 默认已读
打印 不自动已读
存储 oa_task.is_read / read_time