Files
shanxia2.0/openspec/changes/zkxw-engineering-standards/specs/permission-conventions/spec.md
T
2026-06-01 08:21:21 +08:00

128 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## ADDED Requirements
### Requirement: 角色与菜单授权
系统 SHALL 通过 `sys_role`、`sys_role_menu` 等模型维护角色与菜单的关联;角色管理界面 SHALL 支持为角色配置可访问菜单树,且勾选子菜单时 SHALL 自动包含必要父菜单(与《shanxia2.0需求 V0.3》§2.1.5 描述一致)。
#### Scenario: 勾选子菜单
- **WHEN** 管理员在角色菜单配置弹窗中仅勾选叶子菜单
- **THEN** 保存后角色 SHALL 同时获得该叶子到根路径上的父菜单权限,避免出现「有子无父」的不可达状态
### Requirement: 数据权限范围
角色 SHALL 具有 `data_scope` 字段,取值语义为:全部、本部门、本部门及以下、仅本人、自定义等(与需求 §2.1.5 及表注释一致);当为自定义时,系统 SHALL 使用 `sys_role_data_scope_detail` 维护 `scope_type`(部门/用户)与 `scope_value`。
#### Scenario: 配置自定义数据权限
- **WHEN** 管理员将角色数据权限设为自定义并在弹窗中选择部门与用户
- **THEN** 持久化 SHALL 写入 `sys_role_data_scope_detail`,且运行时查询 SHALL 按明细过滤
### Requirement: 多角色权限合并
单个用户允许绑定多个角色;其**菜单权限**与**数据权限** SHALL 分别取所有的角色的权限**并集**(与需求 §2.1.5「特别注意」一致)。
#### Scenario: 两角色菜单合并
- **WHEN** 用户同时具有角色 R1(菜单集合 M1)与角色 R2(菜单集合 M2)
- **THEN** 用户可见菜单 SHALL 为 M1 ∪ M2
#### Scenario: 两角色数据范围合并
- **WHEN** 用户同时具有角色 R1 与 R2,各自数据权限范围不同
- **THEN** 可访问数据 SHALL 为两角色数据权限的并集(在实现上与产品确认:取更宽或按需求文档并集语义)
### Requirement: Web 与小程序菜单树
菜单管理 SHALL 同时支撑 Web 端与小程序端菜单;系统 SHALL 预置两棵顶层虚拟根(需求中的 parent_id=0 的「web 端菜单」「移动端菜单」),后续菜单 SHALL 挂载其下(与需求 §2.1.6)。
#### Scenario: 新增移动端菜单
- **WHEN** 管理员新增小程序端一级菜单
- **THEN** 该节点 SHALL 挂在移动端根节点之下,且 `visible`/`status` 字段符合规范枚举
### Requirement: Web 管理端菜单注册与侧栏展示(三件套同步)
`web-admin` 新增或变更**可在侧栏点击访问的页面菜单**时,SHALL 同时维护下列三处,并在同一迭代/提交中交付。仅执行 `docs/sql/` 中的 `sys_menu` INSERT **不足以**在主页侧栏显示菜单。
| 序号 | 位置 | 作用 |
|------|------|------|
| 1 | **数据库 `sys_menu`**(脚本放 `docs/sql/`) | 菜单主数据;`sys_role_menu` 授权;`perms` 供非超管过滤;系统管理「菜单管理」展示 |
| 2 | **前端静态路由** `web-admin/src/router/modules/sqxp.ts`(经 `index.ts` → `asyncRoutes`) | **当前默认侧栏与动态路由注册的数据源**(见下节访问模式) |
| 3 | **后端静态菜单树** `backend/app/api/menu.py` 的 `STATIC_MENU_TREE` | 与 `sqxp.ts` **字段对齐**,供后端菜单模式、`/web/menu/list` 及文档对照;SHALL 与 `sqxp.ts` 同步维护 |
**访问模式(`VITE_ACCESS_MODE`,见 `web-admin/.env` / `.env.development`)**
- **`frontend`(本仓库默认)**:登录后 `MenuProcessor`(`src/router/core/MenuProcessor.ts`)从 **`asyncRoutes` / `sqxp.ts`** 构建侧栏并 `register` 路由;**不**读取 `sys_menu` 树渲染侧栏。
- **`backend`**:侧栏来自 `GET /api/web/menu/list`,响应体为 `menu.py` 的 `STATIC_MENU_TREE`。
**运行时可见性(`frontend` 模式)**
- **`super_admin`**:展示 `sqxp.ts` 中全部静态菜单(不按 `meta.roles` 裁剪)。
- **`admin` 及其他非超管**:在静态菜单基础上,按 `GET /api/web/auth/menu-codes` 返回的 `menuCodes`(来源于用户角色关联的 `sys_menu.perms`)过滤;`menuCodes` 与路由 `meta.menuCode` **SHALL 一致**(`MenuProcessor` 会做 snake_case 规范化)。
- 非超管角色若未在「角色管理」中勾选新菜单(或未写入 `sys_role_menu`),即使 `sqxp.ts` 已注册路由,侧栏也 **SHALL NOT** 显示该项。
**字段对齐(页面菜单 `menu_type = C`)**
| `sys_menu` | `sqxp.ts` / `STATIC_MENU_TREE` | 说明 |
|------------|-------------------------------|------|
| `id` | `id` | 建议一致,便于角色授权与排错 |
| `parent_id` | 父节点 `id` / `children` 嵌套 | 目录 `M` 仅 SQL + 静态树父级;`sqxp.ts` 用 `children` |
| `path` | `path` | 子路由为**相对路径**(如 `customer`),非 `/crm/customer` |
| `component` | `component` | 如 `/crm/customer/index`,对应 `src/views` 下页面 |
| `perms` | `meta.menuCode` | 列表权限标识,如 `crm:customer:list` |
| `menu_name` | `meta.title` | 侧栏展示文案 |
| `visible` / `status` | — | `'1'` 启用;库内维护,静态路由默认可见 |
**按钮权限(`menu_type = F`)**
- 仅写入 **`sys_menu`**(`parent_id` 指向页面菜单 `C`),`perms` 如 `crm:customer:add`。
- **SHALL NOT** 写入 `sqxp.ts` / `STATIC_MENU_TREE` 的 `children`(按钮不出现在侧栏)。
**一级目录菜单图标**
- `sqxp.ts` / `STATIC_MENU_TREE` 中一级菜单 `meta.icon` SHALL 在 `web-admin/src/utils/ui/ep-icon-map.ts` 注册(见 `ui-conventions` 反例 **EC-UI-002**)。
**参考实现**
- CRM 客户域:`docs/sql/crm-customer-menu.sql` + `sqxp.ts` 中 `CRM 管理` 下 `customer` / `customer-pool` 子路由 + `menu.py` 同结构节点。
#### Scenario: 新增业务页面菜单(frontend 模式)
- **WHEN** 新增可在侧栏访问的业务页(如 CRM 管理 → 客户管理)
- **THEN** 开发者 SHALL:
1. 在 `docs/sql/` 编写 `sys_menu` INSERT(页面 `C` + 所需按钮 `F`;`tenant_no`、父菜单 `id` 与现网一致)
2. 在 `web-admin/src/router/modules/sqxp.ts` 对应父级 `children` 追加路由(`path`、`component`、`meta.menuCode` 与 SQL 一致)
3. 在 `backend/app/api/menu.py` 的 `STATIC_MENU_TREE` 追加同构节点
4. 为非 `super_admin` 角色在「角色管理」勾选新菜单(或脚本维护 `sys_role_menu`)
5. 部署后要求用户**重新登录**或触发路由初始化,以注册新动态路由
#### Scenario: 仅执行 sys_menu SQL 未改 sqxp.ts
- **WHEN** 开发者只执行了 `docs/sql/*-menu.sql`,未修改 `sqxp.ts`
- **THEN** 在 `VITE_ACCESS_MODE=frontend` 下侧栏 **SHALL NOT** 出现新菜单;系统管理「菜单管理」中可能可见该记录——此为**缺陷状态**,SHALL 补全 `sqxp.ts` 与 `menu.py`
#### Scenario: 废弃/删除菜单
- **WHEN** 废弃或删除一个菜单项
- **THEN** 开发者 SHALL:
1. 在 `docs/sql/` 中将对应 `sys_menu` **软删除**(`del_flag=1`,SHALL NOT 物理删除)
2. 从 `sqxp.ts` 移除对应路由节点
3. 从 `menu.py` 的 `STATIC_MENU_TREE` 移除对应节点
4. 若父目录下无剩余子菜单,父目录 SHALL 一并软删除
5. 清理或调整相关 `sys_role_menu` 关联(按需)
#### Scenario: 代码评审检查项
- **WHEN** 评审涉及菜单变更的 PR
- **THEN** 评审者 SHALL 确认:`docs/sql/` 脚本、`sqxp.ts`、`menu.py` 三者 **id / path / component / menuCode↔perms** 一致;`sqxp.ts` 已修改(frontend 模式);按钮 `F` 未误入静态路由树;一级 `meta.icon` 已在 `ep-icon-map.ts` 注册
### Requirement: 超级管理员与租户
租户管理 SHALL 仅对超级系统管理员开放(与需求 §2.1.1);其他系统管理 SHALL 在租户上下文中执行,与 `api-conventions` 中租户隔离一致。
#### Scenario: 非 superadmin 访问租户管理
- **WHEN** 非超级管理员请求租户管理接口或页面
- **THEN** 系统 SHALL 拒绝访问