library-picturebook-activity/docs/design/menu-config.md
zhonghua 7384a0423c feat: 工作台仪表盘补全、快捷操作与租户菜单优化
- TenantDashboard: 快捷操作去掉用户管理;最近活动依赖后端 recentContests

- ContestServiceImpl: getDashboard 返回最近活动、进行中、待审、今日报名及租户信息

- 机构管理: 子菜单全未选时剔除父菜单 ID(pruneOrphanParentMenuIds)

- 菜单管理: AntdIconPicker 与表单调整;设计文档同步

Made-with: Cursor
2026-04-09 18:25:13 +08:00

315 lines
14 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.

# 各端菜单配置规范
> 最后更新2026-04-08
> 维护人:开发团队
本文档记录各端的正确菜单配置,是菜单分配的**唯一权威来源**。修改菜单时必须对照此文档。
---
## 快速参考:各端登录信息
| 端 | 租户编码 | 登录URL | 用户名 | 密码 | 角色 |
|----|---------|---------|--------|------|------|
| 超管端 | super | `/super/login` | admin | admin123 | super_admin |
| 广东省图端(租户端) | gdlib | `/gdlib/login` | admin | admin123 | tenant_admin |
| 评委端 | judge | `/judge/login` | admin | admin123 | judge |
| 公众端 | — | `/p/login` | 自主注册 | — | public_user |
**注意**在同一浏览器中切换不同端时需要先登出或清除缓存localStorage否则旧 token 会导致跳转错误。建议用无痕窗口测试不同端。
---
## 一、超管端tenant_id=1, code='super')— 21条
**定位**:只读监管,不含操作类功能。
**一级菜单**5 个(活动监管、内容管理、机构管理、用户中心、系统设置)
**不包含**:数据统计(租户端专属)、活动管理(租户端专属)、我的评审(评委端专属)
```
活动监管 (id=37) ← 只读监管视角
├── 全部活动 (38)
├── 报名数据 (39)
├── 作品数据 (40)
├── 评审进度 (41)
└── 活动成果 (44)
内容管理 (id=46)
├── 作品审核 (47)
├── 作品管理 (48)
└── 标签管理 (49)
机构管理 (id=28)
└── 机构管理 (30)
用户中心 (id=29)
├── 用户管理 (32)
└── 角色管理 (33)
系统设置 (id=14)
├── 菜单管理 (17)
├── 数据字典 (18)
├── 系统配置 (19)
├── 日志记录 (20)
└── 权限管理 (21)
```
**tenant_menus ID 列表**21条
```
14, 17, 18, 19, 20, 21, 28, 29, 30, 32, 33, 37, 38, 39, 40, 41, 44, 46, 47, 48, 49
```
### 超管端不包含的菜单及原因
| 菜单 | ID | 原因 |
|------|-----|------|
| 评委管理 | 42 | 操作类功能,属于租户端活动管理 |
| 评审规则 | 43 | 操作类功能,属于租户端活动管理 |
| 通知管理 | 45 | 操作类功能,属于租户端活动管理 |
| 数据统计 | 52,53,54 | 设计文档明确标注"所属端:租户端"(见 docs/design/org-admin/data-analytics-dashboard.md |
| 活动管理 | 9 及子菜单 | 租户端操作菜单,超管用"活动监管"代替 |
| 我的评审 | 34,35,36 | 评委端专属 |
| 工作台 | 50 | 租户端工作台(TenantDashboard),超管端不需要 |
| 学校管理 | 2 及子菜单 | 已剥离(代码移至 competition-management-system-stripped-modules/ |
---
## 二、租户端(以 gdlib id=9 为例)— 18条
**定位**:机构管理员操作端,含全部业务操作功能。
**一级菜单**4 个(工作台、数据统计、活动管理、系统设置)
```
工作台 (id=50) ← TenantDashboard
页面内容:欢迎信息 + 6个统计卡片 + 快捷操作 + 待办提醒 + 最近活动
快捷操作:活动列表、报名管理、作品管理、评委管理(按权限显示;不含用户管理,见系统设置)
数据统计 (id=52) ← 租户端专属
├── 运营概览 (53) — 指标卡片 + 漏斗图 + 月度趋势 + 活动对比
└── 评审分析 (54) — 效率指标 + 评委工作量 + 奖项分布
活动管理 (id=9) ← 含全部操作功能(创建/编辑/审核/评委/评分/发布)
├── 活动列表 (10) — 创建、发布、编辑活动
├── 评委管理 (23) — 添加/移除评委、设置权重
├── 报名管理 (11) — 审核报名、添加指导老师
├── 作品管理 (12) — 查看作品、版本管理
├── 评审进度 (25) — 分配作品、查看进度
├── 评审规则 (26) — 创建/编辑评审规则和维度
├── 成果发布 (24) — 计算得分/排名、设置奖项、发布成果
└── 活动公告 (27) — 创建/编辑活动公告
系统设置 (id=14) ← 精简版
├── 机构信息 (51) — 查看/编辑机构名称和描述、复制登录地址
├── 用户管理 (15) — 本租户用户 CRUD
├── 角色管理 (16) — 本租户角色和权限分配
└── 日志记录 (20) — 操作日志查看
```
**tenant_menus ID 列表**18条
```
9, 10, 11, 12, 14, 15, 16, 20, 23, 24, 25, 26, 27, 50, 51, 52, 53, 54
```
### 租户评委与平台评委(菜单一致)
- **平台评委**:在评委租户(`code=judge`)登录,见第三节「评委端」,`t_sys_tenant_menu` 仅含 **34、35、36**
- **租户评委**:在机构租户(如 `tenantCode=test2`)登录,角色为 `judge`,与平台评委使用**同一套**「我的评审」菜单(仍为 **34、35、36** 对应的 `component``activities/Review`、`activities/PresetComments` 及父级)。
- **实现**`GET /api/menus/user-menus` 在 `SysMenuServiceImpl.getUserMenus` 中,若当前用户角色含 `judge`,会在 `t_sys_tenant_menu` 基础上**合并**评委端菜单(按组件路径识别,不依赖固定 ID评委角色权限由 `JudgeRolePermissionConfigurer``JudgesManagementServiceImpl` 保证与上表「评委端权限码」一致,以便 `/api/auth/user-info``permissions` 与菜单 `permission` 字段匹配。纯评委(仅有 `judge`、无 `tenant_admin`/`super_admin`**不展示**机构端「评委管理」菜单(`component=contests/judges/Index`),避免与 `judge:read` 权限码重叠导致误显。
- **可选**:若希望机构租户在「菜单管理」中显式看到评委菜单,也可在 `t_sys_tenant_menu` 中手工追加 **34、35、36**(与第三节一致),与合并逻辑效果相同。
### 租户端系统设置不包含的子菜单
| 菜单 | ID | 原因 |
|------|-----|------|
| 菜单管理 | 17 | 平台级功能,超管专属 |
| 数据字典 | 18 | 平台级功能,超管专属 |
| 系统配置 | 19 | 平台级功能,超管专属 |
| 权限管理 | 21 | 平台级功能,超管专属 |
| 租户管理 | 22 | 平台级功能,超管专属 |
### 新建租户时的菜单分配
创建新租户时,应分配与 gdlib 相同的菜单列表18条。在 `SysTenantServiceImpl.createTenant` 中实现。
---
## 三、评委端tenant_id=7, code='judge')— 3条
**定位**:评委评审工作台,只能看到自己被分配的活动和作品。租户评委(机构租户下的 `judge` 角色)与平台评委共用本节菜单与权限码。
**详细接口与字段说明**[评委端评审任务](./judge-portal/review-tasks.md)。
**一级菜单**1 个(我的评审)
```
我的评审 (id=34)
├── 评审任务 (35) — 查看分配的活动、作品列表、打分
└── 预设评语 (36) — 管理评审常用评语模板,支持跨活动同步
```
**tenant_menus ID 列表**3条
```
34, 35, 36
```
### 评委端权限码
| 权限码 | 用途 |
|--------|------|
| review:score | 提交/更新评分 |
| review:read | 查看评审数据 |
| review:create | 创建评审记录 |
| review:update | 更新评审记录 |
| activity:read | 查看活动列表 |
| judge:read | 查看评委信息 |
| judge:assign | 分配评审 |
| work:read | 查看作品 |
| notice:read | 查看公告 |
| workbench:read | 查看工作台 |
---
## 四、公众端tenant_id=8, code='public'
公众端不使用动态菜单系统,路由固定在前端 `router/index.ts` 中。
### 导航结构
```
顶部导航栏Web 端)/ 底部 TabH5 移动端):
├── 发现 (/p/gallery) — 作品广场,推荐+标签+搜索
├── 活动 (/p/activities) — 活动大厅,浏览+报名
├── 创作 (/p/create) — 绘本创作4步流程
├── 作品库 (/p/works) — 我的作品(草稿/审核中/已发布)
└── 我的 (/p/mine) — 个人中心
├── 个人信息 — 昵称/城市/性别
├── 我的报名 — 报名记录+审核状态
├── 我的收藏 — 收藏的作品
└── 子女管理 — 添加子女/创建子女账号/切换身份
```
### 公众端权限码
| 权限码 | 用途 |
|--------|------|
| activity:read | 浏览活动 |
| registration:create | 报名活动 |
| work:create | 创建作品 |
| child:manage | 子女管理 |
### 无需认证的接口(@Public
- `GET /public/activities` — 活动列表
- `GET /public/activities/:id` — 活动详情
- `GET /public/gallery` — 作品广场
- `GET /public/gallery/recommended` — 推荐作品
- `GET /public/gallery/:id` — 作品详情
- `GET /public/tags` — 标签列表
- `GET /public/tags/hot` — 热门标签
- `POST /public/auth/register` — 注册
- `POST /public/auth/login` — 登录
---
## 五、完整菜单 ID 对照表
| ID | 名称 | 父ID | 组件 | 所属端 |
|----|------|------|------|--------|
| 1 | 工作台(旧) | — | workbench/Index | ~~废弃~~ |
| 2 | 学校管理 | — | — | ~~已剥离~~ |
| 3-8 | 学校子菜单 | 2 | school/* | ~~已剥离~~ |
| **9** | **活动管理** | — | — | **租户端** |
| 10 | 活动列表 | 9 | contests/Index | 租户端 |
| 11 | 报名管理 | 9 | contests/registrations/Index | 租户端 |
| 12 | 作品管理 | 9 | contests/works/Index | 租户端 |
| **14** | **系统设置** | — | — | **超管+租户** |
| 15 | 用户管理 | 14 | system/users/Index | 租户端 |
| 16 | 角色管理 | 14 | system/roles/Index | 租户端 |
| 17 | 菜单管理 | 14 | system/menus/Index | 超管端 |
| 18 | 数据字典 | 14 | system/dict/Index | 超管端 |
| 19 | 系统配置 | 14 | system/config/Index | 超管端 |
| 20 | 日志记录 | 14 | system/logs/Index | 超管+租户 |
| 21 | 权限管理 | 14 | system/permissions/Index | 超管端 |
| 22 | 租户管理 | 14 | system/tenants/Index | ~~超管用28/30代替~~ |
| 23 | 评委管理 | 9 | contests/judges/Index | 租户端 |
| 24 | 成果发布 | 9 | contests/results/Index | 租户端 |
| 25 | 评审进度 | 9 | contests/reviews/Progress | 租户端 |
| 26 | 评审规则 | 9 | contests/ReviewRules | 租户端 |
| 27 | 活动公告 | 9 | contests/notices/Index | 租户端 |
| **28** | **机构管理** | — | — | **超管端** |
| **29** | **用户中心** | — | — | **超管端** |
| 30 | 机构管理(列表) | 28 | system/tenants/Index | 超管端 |
| 32 | 用户管理(超管) | 29 | system/users/Index | 超管端 |
| 33 | 角色管理(超管) | 29 | system/roles/Index | 超管端 |
| **34** | **我的评审** | — | — | **评委端** |
| 35 | 评审任务 | 34 | activities/Review | 评委端 |
| 36 | 预设评语 | 34 | activities/PresetComments | 评委端 |
| **37** | **活动监管** | — | — | **超管端** |
| 38 | 全部活动 | 37 | contests/Index | 超管端 |
| 39 | 报名数据 | 37 | contests/registrations/Index | 超管端 |
| 40 | 作品数据 | 37 | contests/works/Index | 超管端 |
| 41 | 评审进度 | 37 | contests/reviews/Progress | 超管端 |
| 42 | 评委管理 | 37 | contests/judges/Index | ~~超管端不用~~ |
| 43 | 评审规则 | 37 | contests/reviews/Index | ~~超管端不用~~ |
| 44 | 活动成果 | 37 | contests/results/Index | 超管端 |
| 45 | 通知管理 | 37 | contests/notices/Index | ~~超管端不用~~ |
| **46** | **内容管理** | — | — | **超管端** |
| 47 | 作品审核 | 46 | content/WorkReview | 超管端 |
| 48 | 作品管理 | 46 | content/WorkManagement | 超管端 |
| 49 | 标签管理 | 46 | content/TagManagement | 超管端 |
| **50** | **工作台** | — | workbench/TenantDashboard | **租户端** |
| 51 | 机构信息 | 14 | system/tenant-info/Index | 租户端 |
| **52** | **数据统计** | — | — | **租户端** |
| 53 | 运营概览 | 52 | analytics/Overview | 租户端 |
| 54 | 评审分析 | 52 | analytics/Review | 租户端 |
---
## 六、技术实现要点
### 菜单加载逻辑Java: SysMenuServiceImpl.getUserMenus
```
1. 查询所有 valid_state=1 的菜单
2. 查询当前用户 tenant_id 对应的 t_sys_tenant_menu
3. 按 tenant_menus 过滤菜单;若用户角色含 judge且非超管合并评委端菜单 ID评审任务/预设评语及其父级,按 component 识别)
4. 如果是超管(isSuperAdmin):不做权限码过滤
5. 如果是普通用户:按用户权限码过滤(菜单.permission 字段匹配用户 permissions
6. 补全父菜单(确保树结构完整)
7. 构建树形结构返回
```
**⚠️ 重要**:超管也必须按 tenant_menus 过滤,不能返回全部菜单。之前的 bug 就是超管返回全部 52 个菜单导致错乱。
**评委角色**`judge` 角色须绑定「评委端权限码」中的权限(见 `JudgeRolePermissionConfigurer`),否则 `permissions` 为空会导致第 5 步过滤掉所有菜单。
### 登录时租户识别
前端通过 URL 提取 tenantCode`/gdlib/login``tenantCode=gdlib`),登录请求:
```json
POST /api/auth/login
{"username": "admin", "password": "xxx", "tenantCode": "gdlib"}
```
Java 后端 `AuthService.login` 支持两种方式确定租户:
1. `X-Tenant-Id` header数字ID优先
2. body 中的 `tenantCode`(编码),后端通过 `t_sys_tenant.code` 查找租户ID
### 前端组件映射
菜单的 `component` 字段对应前端 `menu.ts` 中的 `componentMap`。如果某个 component 路径未在 `componentMap` 中注册,控制台会输出警告。已剥离的学校管理模块会有 6 条 warning属预期行为。
### 权威来源
| 来源 | 用途 |
|------|------|
| `docs/design/menu-config.md` | **本文档**,菜单配置唯一权威 |
| `backend-java/.../JudgeRolePermissionConfigurer.java` | 评委角色权限码补全、`judge`/`gdlib` 模板复制 |
| `backend/data/menus.json` | 菜单定义(所有菜单的字段) |
| `backend/scripts/init-menus.ts` | 菜单初始化脚本SUPER_TENANT_MENUS / NORMAL_TENANT_MENUS |
| `t_sys_menu` 表 | 数据库中的菜单数据 |
| `t_sys_tenant_menu` 表 | 各端的菜单分配关系 |