From 35028a69d2ee1783511fc0c07b0f8995c1dc3342 Mon Sep 17 00:00:00 2001 From: liuyun Date: Sun, 11 Oct 2026 22:26:15 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20=E5=BD=92=E6=A1=A3=20in-app-notif?= =?UTF-8?q?ication=20+=20=E5=90=8C=E6=AD=A5=E9=80=9A=E7=9F=A5=E4=B8=BB=20s?= =?UTF-8?q?pec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- .../.openspec.yaml | 0 .../2026-10-11-in-app-notification}/design.md | 0 .../proposal.md | 0 .../specs/backend/notice/spec.md | 0 .../specs/frontend/AD/notice/spec.md | 0 .../specs/shared/api-contract/spec.md | 0 .../specs/shared/data-model/spec.md | 0 .../2026-10-11-in-app-notification}/tasks.md | 0 openspec/specs/backend/notice/spec.md | 29 +++++++++++++++++++ openspec/specs/frontend/AD/notice/spec.md | 25 ++++++++++++++++ openspec/specs/shared/api-contract/spec.md | 11 +++++++ openspec/specs/shared/data-model/spec.md | 13 ++++++++- 12 files changed, 77 insertions(+), 1 deletion(-) rename openspec/changes/{in-app-notification => archive/2026-10-11-in-app-notification}/.openspec.yaml (100%) rename openspec/changes/{in-app-notification => archive/2026-10-11-in-app-notification}/design.md (100%) rename openspec/changes/{in-app-notification => archive/2026-10-11-in-app-notification}/proposal.md (100%) rename openspec/changes/{in-app-notification => archive/2026-10-11-in-app-notification}/specs/backend/notice/spec.md (100%) rename openspec/changes/{in-app-notification => archive/2026-10-11-in-app-notification}/specs/frontend/AD/notice/spec.md (100%) rename openspec/changes/{in-app-notification => archive/2026-10-11-in-app-notification}/specs/shared/api-contract/spec.md (100%) rename openspec/changes/{in-app-notification => archive/2026-10-11-in-app-notification}/specs/shared/data-model/spec.md (100%) rename openspec/changes/{in-app-notification => archive/2026-10-11-in-app-notification}/tasks.md (100%) create mode 100644 openspec/specs/backend/notice/spec.md create mode 100644 openspec/specs/frontend/AD/notice/spec.md diff --git a/openspec/changes/in-app-notification/.openspec.yaml b/openspec/changes/archive/2026-10-11-in-app-notification/.openspec.yaml similarity index 100% rename from openspec/changes/in-app-notification/.openspec.yaml rename to openspec/changes/archive/2026-10-11-in-app-notification/.openspec.yaml diff --git a/openspec/changes/in-app-notification/design.md b/openspec/changes/archive/2026-10-11-in-app-notification/design.md similarity index 100% rename from openspec/changes/in-app-notification/design.md rename to openspec/changes/archive/2026-10-11-in-app-notification/design.md diff --git a/openspec/changes/in-app-notification/proposal.md b/openspec/changes/archive/2026-10-11-in-app-notification/proposal.md similarity index 100% rename from openspec/changes/in-app-notification/proposal.md rename to openspec/changes/archive/2026-10-11-in-app-notification/proposal.md diff --git a/openspec/changes/in-app-notification/specs/backend/notice/spec.md b/openspec/changes/archive/2026-10-11-in-app-notification/specs/backend/notice/spec.md similarity index 100% rename from openspec/changes/in-app-notification/specs/backend/notice/spec.md rename to openspec/changes/archive/2026-10-11-in-app-notification/specs/backend/notice/spec.md diff --git a/openspec/changes/in-app-notification/specs/frontend/AD/notice/spec.md b/openspec/changes/archive/2026-10-11-in-app-notification/specs/frontend/AD/notice/spec.md similarity index 100% rename from openspec/changes/in-app-notification/specs/frontend/AD/notice/spec.md rename to openspec/changes/archive/2026-10-11-in-app-notification/specs/frontend/AD/notice/spec.md diff --git a/openspec/changes/in-app-notification/specs/shared/api-contract/spec.md b/openspec/changes/archive/2026-10-11-in-app-notification/specs/shared/api-contract/spec.md similarity index 100% rename from openspec/changes/in-app-notification/specs/shared/api-contract/spec.md rename to openspec/changes/archive/2026-10-11-in-app-notification/specs/shared/api-contract/spec.md diff --git a/openspec/changes/in-app-notification/specs/shared/data-model/spec.md b/openspec/changes/archive/2026-10-11-in-app-notification/specs/shared/data-model/spec.md similarity index 100% rename from openspec/changes/in-app-notification/specs/shared/data-model/spec.md rename to openspec/changes/archive/2026-10-11-in-app-notification/specs/shared/data-model/spec.md diff --git a/openspec/changes/in-app-notification/tasks.md b/openspec/changes/archive/2026-10-11-in-app-notification/tasks.md similarity index 100% rename from openspec/changes/in-app-notification/tasks.md rename to openspec/changes/archive/2026-10-11-in-app-notification/tasks.md diff --git a/openspec/specs/backend/notice/spec.md b/openspec/specs/backend/notice/spec.md new file mode 100644 index 0000000..fddc7f8 --- /dev/null +++ b/openspec/specs/backend/notice/spec.md @@ -0,0 +1,29 @@ +# Notice Specification + +## Purpose + +定义站内通知/公告域的行为:管理员发布公告,用户查看并追踪已读/未读。 + +## Requirements + +### Requirement: 公告管理 +系统 SHALL 支持管理员对通知做分页查询、新增、编辑、删除(软删回收唯一键)。 + +#### Scenario: 发布公告 +- **WHEN** 管理员新增一条通知 +- **THEN** 通知写入,status=enabled 时对本租户所有用户可见 + +### Requirement: 用户通知与已读 +系统 SHALL 支持当前用户分页查看可见通知(含已读状态)、查询未读数、标记单条/全部已读。 + +#### Scenario: 查看通知列表 +- **WHEN** 用户查看通知 +- **THEN** 返回其可见通知(status=enabled),未读的 readAt 为空 + +#### Scenario: 未读数 +- **WHEN** 用户请求未读数 +- **THEN** 返回 status=enabled 且当前用户未读的通知数 + +#### Scenario: 标记已读 +- **WHEN** 用户标记某条或全部通知已读 +- **THEN** 对应 notice_read 记录其 read_at diff --git a/openspec/specs/frontend/AD/notice/spec.md b/openspec/specs/frontend/AD/notice/spec.md new file mode 100644 index 0000000..50b2190 --- /dev/null +++ b/openspec/specs/frontend/AD/notice/spec.md @@ -0,0 +1,25 @@ +# Notice Specification + +## Purpose + +定义站内通知的前端行为:顶栏铃铛展示未读数与通知列表、标记已读,管理页做通知 CRUD。 + +## Requirements + +### Requirement: 顶栏通知铃铛 +前端 SHALL 在顶栏展示通知铃铛与未读角标,点击展开通知列表,支持标记单条/全部已读;未读数轮询刷新。 + +#### Scenario: 展示未读数 +- **WHEN** 用户登录后 +- **THEN** 铃铛展示其未读通知数,并定时刷新 + +#### Scenario: 标记已读 +- **WHEN** 用户点开某条通知或点「全部已读」 +- **THEN** 该通知标记已读,未读数更新 + +### Requirement: 通知管理页 +前端 SHALL 提供通知管理页,支持管理员新增/编辑/删除通知。 + +#### Scenario: 管理通知 +- **WHEN** 管理员进入通知管理页 +- **THEN** 可分页查看并增删改通知 diff --git a/openspec/specs/shared/api-contract/spec.md b/openspec/specs/shared/api-contract/spec.md index af456c5..98bd928 100644 --- a/openspec/specs/shared/api-contract/spec.md +++ b/openspec/specs/shared/api-contract/spec.md @@ -85,3 +85,14 @@ #### Scenario: 锁定与解锁 - **WHEN** 客户端请求 POST /users/:id/lock 或 /users/:id/unlock - **THEN** 用户被锁定或解锁 + +### Requirement: 通知接口 +系统 SHALL 提供:`GET /notices`(分页,管理)、`POST /notices`、`PATCH /notices/:id`、`DELETE /notices/:id`(管理,需 `notice:*` 权限);`GET /notices/mine`(当前用户可见通知 + 已读状态)、`GET /notices/unread-count`(未读数)、`POST /notices/:id/read`(标记已读)、`POST /notices/read-all`(全部已读),后四者仅需登录态。 + +#### Scenario: 拉取未读数 +- **WHEN** 用户请求 GET /notices/unread-count +- **THEN** 返回其未读通知数 + +#### Scenario: 标记已读 +- **WHEN** 用户请求 POST /notices/:id/read +- **THEN** 该通知对当前用户标记为已读 diff --git a/openspec/specs/shared/data-model/spec.md b/openspec/specs/shared/data-model/spec.md index e4483ed..41105bb 100644 --- a/openspec/specs/shared/data-model/spec.md +++ b/openspec/specs/shared/data-model/spec.md @@ -18,12 +18,23 @@ - **THEN** 系统置 deleted_at 而非物理删除 ### Requirement: 实体目录 -系统 SHALL 包含实体 tenant、user、role、resource、org、dict_type、dict_item、config、audit_log、login_log、refresh_token,以及关联表 user_role、role_resource、role_org。 +系统 SHALL 包含实体 tenant、user、role、resource、org、dict_type、dict_item、config、audit_log、login_log、refresh_token、notice,以及关联表 user_role、role_resource、role_org、notice_read。 #### Scenario: 字典拆分 - **WHEN** 维护数据字典 - **THEN** 字典分为 dict_type(类型)与 dict_item(项)两类,经 dict_type_code 关联 +### Requirement: 通知实体 +系统 SHALL 包含 `notice` 实体(公告/通知:id、tenant_code、title、content、status、created_by、created_at、deleted_at)与 `notice_read` 关联表(notice_id + user_id + read_at,唯一 tenant_code+notice_id+user_id),用于站内通知与已读追踪。 + +#### Scenario: 发布通知 +- **WHEN** 管理员发布一条通知 +- **THEN** 写入 notice,status=enabled 时对本租户所有用户可见 + +#### Scenario: 记录已读 +- **WHEN** 用户阅读某通知 +- **THEN** 写入 notice_read 记录其 read_at + ### Requirement: 资源类型与代码 resource 实体 SHALL 通过 type 字段区分菜单(menu)、按钮(button)、接口(api)三种类型;「目录」由菜单节点是否拥有菜单子节点推导,不设独立类型。每个 resource SHALL 拥有租户内唯一的 code,作为关联键与权限标识。