diff --git a/docs/demos/oa-cc-unread-badge-demo.html b/docs/demos/oa-cc-unread-badge-demo.html new file mode 100644 index 00000000..241c124a --- /dev/null +++ b/docs/demos/oa-cc-unread-badge-demo.html @@ -0,0 +1,583 @@ + + + + + + OA 抄送我的 · 未读角标 Demo + + + + +
+

OA「抄送我的」未读角标 · Demo

+

侧栏数字对齐「质检工单」红色文字角标;列表支持「已读」,点「查看」默认标记已读。

+
+ +
+
+ + +
+
+
+

抄送我的

+
未读 3 条 · 可点操作栏按钮体验
+
+
+ + + + + + + + + + + + + + +
序号流程名称申请人申请时间当前节点流程状态阅读状态操作
+
+
+
+
+
+ +
+
+

参照:质检工单菜单角标

+
+ 质检工单 + + 12 +
+

红色小块数字 · 0 时灰底(本 Demo 未读为 0 时隐藏角标,也可改成灰「0」)

+
+
+

本需求:抄送我的

+
+ 抄送我的 + + 3 +
+

统计「抄送给我且尚未阅读」的记录数;列表操作可消减

+
+
+ +
+ 交互约定(Demo 可点): + +
+ +
+
+
+

审批详情

+ +
+
+
+
+
流程名称
+
-
+
+
+
申请人
+
-
+
+
+
申请时间
+
-
+
+
+
说明
+
打开详情即视为已读(与需求一致)。
+
+
+
+
+ +
+
+
+ +
+ + + + diff --git a/docs/superpowers/specs/2026-08-06-oa-cc-unread-badge-design.md b/docs/superpowers/specs/2026-08-06-oa-cc-unread-badge-design.md new file mode 100644 index 00000000..cc5fec3b --- /dev/null +++ b/docs/superpowers/specs/2026-08-06-oa-cc-unread-badge-design.md @@ -0,0 +1,127 @@ +# 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` |