From fd13bf94702dce768552684db367ca1430a7b1ea Mon Sep 17 00:00:00 2001 From: dark Date: Sat, 14 Feb 2026 11:03:39 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=8F=9C=E5=8D=95=E7=AE=A1=E7=90=86+?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E7=AE=A1=E7=90=86+=E6=9C=BA=E6=9E=84?= =?UTF-8?q?=E7=AE=A1=E7=90=86=20=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/2026-02-14-menu-role-org-design.md | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 docs/plans/2026-02-14-menu-role-org-design.md diff --git a/docs/plans/2026-02-14-menu-role-org-design.md b/docs/plans/2026-02-14-menu-role-org-design.md new file mode 100644 index 0000000..3cf2dda --- /dev/null +++ b/docs/plans/2026-02-14-menu-role-org-design.md @@ -0,0 +1,287 @@ +# 菜单管理 + 角色管理 + 机构管理 设计文档 + +## 需求概述 + +为 BASE 管理面板添加三大功能模块: + +1. **菜单管理**:动态菜单替代硬编码侧边栏,菜单分"默认"和"配置"两种类型,按角色分配 +2. **角色管理**:支持自定义角色,角色按机构分配(同一用户在不同机构可有不同角色) +3. **机构管理**:树形结构的组织架构,用户与机构多对多关系 + +## 核心设计决策 + +| 决策点 | 选择 | 说明 | +|--------|------|------| +| 菜单分配方式 | 按角色分配 | 角色绑定菜单,用户通过角色获得菜单权限 | +| 角色类型 | 支持自定义角色 | 除系统内置角色外,可创建新角色 | +| 机构结构 | 树形(多级父子) | 支持 总公司→分公司→部门→小组 | +| 用户-机构关系 | 多对多 | 用户可属于多个机构 | +| 角色作用域 | 按机构分配 | 同一用户在不同机构可有不同角色 | +| 机构上下文 | 当前机构切换器 | 用户登录后选择工作机构,菜单和权限随之变化 | + +## 数据模型 + +### 新增表 + +#### menus(菜单表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | uint, PK | 自增主键 | +| parent_id | uint | 父菜单ID,0为顶级 | +| name | varchar(50) | 菜单名称 | +| path | varchar(200) | 前端路由路径 | +| icon | varchar(50) | 图标名称(lucide-react) | +| component | varchar(200) | 前端组件路径 | +| type | varchar(20) | 'default' 或 'config' | +| sort_order | int | 排序序号 | +| visible | bool | 是否在侧边栏显示 | +| status | int | 1=启用, 0=禁用 | +| created_at | datetime | | +| updated_at | datetime | | + +#### roles(角色表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | uint, PK | 自增主键 | +| name | varchar(50) | 角色显示名称 | +| code | varchar(50), unique | 角色编码,如 super_admin | +| description | varchar(255) | 角色描述 | +| is_system | bool | 系统内置角色不可删除 | +| sort_order | int | 排序 | +| status | int | 1=启用, 0=禁用 | +| created_at | datetime | | +| updated_at | datetime | | + +#### role_menus(角色-菜单关联表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | uint, PK | | +| role_id | uint | 角色ID | +| menu_id | uint | 菜单ID | + +#### organizations(机构表,树形) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | uint, PK | | +| parent_id | uint | 父机构ID,0为顶级 | +| name | varchar(100) | 机构名称 | +| code | varchar(50) | 机构编码 | +| leader | varchar(50) | 负责人 | +| phone | varchar(20) | 联系电话 | +| email | varchar(100) | 联系邮箱 | +| sort_order | int | 排序 | +| status | int | 1=启用, 0=禁用 | +| created_at | datetime | | +| updated_at | datetime | | + +#### user_organizations(用户-机构-角色关联表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | uint, PK | | +| user_id | uint | 用户ID | +| org_id | uint | 机构ID | +| role_id | uint | 该用户在该机构的角色ID | +| created_at | datetime | | + +### 现有表改动 + +**users 表**: +- 保留 `role` 字段作为无机构时的默认角色 +- 新增 `current_org_id` (uint):记住用户上次选择的机构 + +**JWT Claims**: +- 新增 `currentOrgId` (uint64) +- `role` 字段值来源改为:有当前机构时用机构角色,否则用默认角色 + +### 种子数据 + +**系统内置角色**(is_system=true): + +| code | name | sort_order | +|------|------|------------| +| super_admin | 超级管理员 | 1 | +| admin | 管理员 | 2 | +| user | 普通用户 | 3 | +| guest | 访客 | 4 | + +**默认菜单**: + +| name | path | icon | type | sort_order | +|------|------|------|------|------------| +| 我的 | /my | User | default | 1 | +| 仪表盘 | /dashboard | LayoutDashboard | config | 2 | +| 用户管理 | /users | Users | config | 3 | +| 文件管理 | /files | FolderOpen | config | 4 | +| 角色管理 | /roles | Shield | config | 5 | +| 菜单管理 | /menus | Menu | config | 6 | +| 机构管理 | /organizations | Building2 | config | 7 | +| 设置 | /settings | Settings | default | 8 | + +**super_admin 角色**自动绑定所有菜单(default + config)。 +**user 角色**只绑定 default 类型菜单(我的 + 设置)。 + +## 核心流程 + +### 登录流程 + +``` +用户登录 + → JWT签发 (userId, username, email, role=默认角色, currentOrgId=user.current_org_id) + → 前端拿到 token + → GET /profile/orgs 获取用户机构列表 + → 如果有机构 && current_org_id > 0 → 使用该机构 + → 如果有机构 && current_org_id == 0 → 使用第一个机构 + → 如果无机构 → 使用默认角色 + → GET /menus/current 获取当前角色的菜单 + → 渲染动态侧边栏 +``` + +### 机构切换 + +``` +用户点击机构切换器选择新机构 + → PUT /profile/current-org { orgId: xxx } + → 后端:更新 user.current_org_id,查 user_organizations 获取新角色 + → 后端:重新签发 JWT (含新角色和新orgId) + → 前端:更新 token,刷新菜单,刷新页面 +``` + +### 权限校验 + +``` +请求到达 + → Auth middleware: 解析 JWT,注入 userId, role, currentOrgId 到 context + → Authz middleware: Casbin enforce(role, path, method) + → 通过 → handler + → 拒绝 → 403 +``` + +## API 设计 + +### 菜单管理 + +| 方法 | 路径 | 说明 | 权限 | +|------|------|------|------| +| GET | /api/v1/menus/current | 当前用户可见菜单(树形) | 所有登录用户 | +| GET | /api/v1/menus | 全部菜单列表(树形) | admin+ | +| POST | /api/v1/menu | 创建菜单 | super_admin | +| PUT | /api/v1/menu/:id | 更新菜单 | super_admin | +| DELETE | /api/v1/menu/:id | 删除菜单 | super_admin | + +### 角色管理 + +| 方法 | 路径 | 说明 | 权限 | +|------|------|------|------| +| GET | /api/v1/roles | 角色列表 | admin+ | +| POST | /api/v1/role | 创建角色 | super_admin | +| PUT | /api/v1/role/:id | 更新角色 | super_admin | +| DELETE | /api/v1/role/:id | 删除角色(非系统角色) | super_admin | +| GET | /api/v1/role/:id/menus | 获取角色的菜单ID列表 | admin+ | +| PUT | /api/v1/role/:id/menus | 设置角色的菜单(全量替换) | super_admin | + +### 机构管理 + +| 方法 | 路径 | 说明 | 权限 | +|------|------|------|------| +| GET | /api/v1/organizations | 机构列表(树形) | admin+ | +| POST | /api/v1/organization | 创建机构 | super_admin | +| PUT | /api/v1/organization/:id | 更新机构 | admin+ | +| DELETE | /api/v1/organization/:id | 删除机构(无子机构) | super_admin | +| GET | /api/v1/organization/:id/members | 机构成员列表 | admin+ | +| POST | /api/v1/organization/:id/member | 添加成员(含角色) | admin+ | +| PUT | /api/v1/organization/:id/member/:userId | 更新成员角色 | admin+ | +| DELETE | /api/v1/organization/:id/member/:userId | 移除成员 | admin+ | + +### 用户上下文 + +| 方法 | 路径 | 说明 | 权限 | +|------|------|------|------| +| GET | /api/v1/profile/orgs | 我的机构列表(含各机构角色) | 所有登录用户 | +| PUT | /api/v1/profile/current-org | 切换当前机构(返回新token) | 所有登录用户 | + +## 前端设计 + +### 新页面 + +1. **我的** (`/my`):个人信息面板,显示所属机构列表、当前角色 +2. **菜单管理** (`/menus`):树形表格展示菜单,支持拖拽排序、启用/禁用 +3. **角色管理** (`/roles`):角色列表 + 菜单分配弹窗(树形勾选) +4. **机构管理** (`/organizations`):树形表格 + 成员管理抽屉(添加/移除成员、分配角色) + +### 侧边栏改造 + +- **当前**:`Sidebar.tsx` 中硬编码 `navItems` 数组 +- **改造后**: + - 登录后从 `GET /menus/current` 获取菜单树 + - `AuthContext` 存储 `userMenus` state + - `Sidebar` 组件从 context 读取菜单动态渲染 + - 顶部 Logo 区域下方增加机构切换下拉框 + +### AuthContext 扩展 + +新增 state: +- `currentOrg: { id, name } | null` +- `userOrgs: Array<{ orgId, orgName, roleId, roleName }>` +- `userMenus: MenuTree[]` + +新增方法: +- `switchOrg(orgId: number): Promise` — 切换机构,刷新 token 和菜单 +- `refreshMenus(): Promise` — 重新获取菜单 + +### 机构切换器 + +位置:侧边栏 Logo 区域下方 +样式:下拉选择框,显示当前机构名称 +行为:切换时调用 `switchOrg()`,自动刷新菜单和页面数据 + +## Casbin 策略更新 + +新增资源的 Casbin 策略需要同步更新,自定义角色创建时由管理员配置具体权限。 + +系统内置角色默认策略: +- **super_admin**:所有 API 端点的所有方法 +- **admin**:菜单/角色/机构的读取 + 机构成员管理 +- **user**:仅 /menus/current, /profile/orgs, /profile/current-org +- **guest**:仅 /menus/current + +## 文件变更预估 + +### 新建文件(后端 ~20 个) +- `model/menu_entity.go`, `model/menu_model.go` +- `model/role_entity.go`, `model/role_model.go` +- `model/role_menu_model.go` +- `model/organization_entity.go`, `model/organization_model.go` +- `model/user_organization_entity.go`, `model/user_organization_model.go` +- `api/menu.api`, `api/role.api`, `api/organization.api` +- `internal/logic/menu/*.go` (5 个 logic) +- `internal/logic/role/*.go` (6 个 logic) +- `internal/logic/organization/*.go` (7 个 logic) +- `internal/logic/profile/` (2 个新 logic) + +### 修改文件(后端 ~8 个) +- `model/user_entity.go` — 增加 current_org_id +- `internal/util/jwt/jwt.go` — Claims 增加 currentOrgId +- `internal/svc/servicecontext.go` — 新模型注入 + 种子数据 +- `base.api` — 新路由组 +- `internal/middleware/authmiddleware.go` — 注入 currentOrgId +- `internal/middleware/authzmiddleware.go` — 适配新策略 +- 各登录/注册 logic — GenerateToken 调用更新 + +### 新建文件(前端 ~6 个) +- `pages/MyPage.tsx` +- `pages/MenuManagementPage.tsx` +- `pages/RoleManagementPage.tsx` +- `pages/OrganizationManagementPage.tsx` +- `components/layout/OrgSwitcher.tsx` + +### 修改文件(前端 ~5 个) +- `App.tsx` — 新路由 +- `components/layout/Sidebar.tsx` — 动态菜单 +- `contexts/AuthContext.tsx` — 机构/菜单 state +- `services/api.ts` — 新 API 方法 +- `types/index.ts` — 新类型定义