128 lines
7.7 KiB
Markdown
128 lines
7.7 KiB
Markdown
## 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 拒绝访问
|