# 角色权限设计 ## Context 当前系统有 5 个默认角色(超管/产品经理/开发/测试/设计师),但 `RoleItem` 没有 `permissions` 字段,新增角色表单也只能填名字+描述。所有页面/按钮都没做权限拦截,任何登录用户都能进所有模块、做所有操作。 本期目标:给角色加权限字段、为默认 5 角色按职能分配权限、新增角色 UI 支持勾选权限、并且把权限拦截一并接入到 7 个主模块 + 版本详情 7 个 Tab + 各按钮。 ## 决策摘要 | 项 | 决策 | |---|---| | 权限粒度 | 细粒度(view/create/edit/delete + 加班特殊操作 export) | | 模块清单 | 7 主模块 + 7 个版本详情 Tab;与我相关不纳入 | | 数据结构 | `RoleItem.permissions: string[]`,`module:action` 形式(推荐方案 A) | | 超管标记 | `permissions: ['*']` 通配符 | | Bug Tab 粒度 | 单独拆 4 档(view/create/edit/delete),其他 Tab 二档 | | 默认 5 角色映射 | 按职能划分(详见下方) | | 新增角色默认值 | 全部不勾选(最小授权) | | 新增角色 UI | 现有表单内嵌权限矩阵 + 顶部"全选/反选/仅查看"快捷 | | 拦截接入范围 | 本期一并接入:路由级 + 按钮级 + Sidebar 菜单 + Tab 级 | | 未登录/无角色 | 默认按只读(最严格) | | 权限点总数 | 37 个 | | localStorage 迁移 | fetchMembers 启动检测旧 RoleItem 无 permissions 字段则补默认值 | ## 1. 权限点字典(37 个) **主模块(4 件套,加班加 export)**: | 模块 | 权限点 | |---|---| | 产品 product | view / create / edit / delete | | 项目 project | view / create / edit / delete | | 版本 version | view / create / edit / delete | | 需求池 requirement | view / create / edit / delete | | 加班记录 overtime | view / create / delete / export | | 成员 member | view / create / edit / delete | | 角色 role | view / create / edit / delete | **版本详情 Tab(其他二档,Bug 单独 4 档)**: | Tab | 权限点 | |---|---| | 需求 Tab `version.req` | view / manage | | 调研 Tab `version.research` | view / manage | | 产品方案 Tab `version.product_plan` | view / manage | | UI 设计 Tab `version.ui_plan` | view / manage | | 开发任务 Tab `version.devtask` | view / manage | | 测试用例 Tab `version.testcase` | view / manage | | Bug Tab `version.bug` | view / create / edit / delete | **说明**:Tab 二档因为 Tab 内部操作高度耦合(manage = 该 Tab 内全部增删改)。Bug 单独拆 4 档因为开发与测试在 Bug 流程上职能不同:测试创建/关闭,开发改状态/修复,测试验证。 ## 2. 默认 5 角色权限映射 **(1) 超级管理员 `role-admin`** — `permissions: ['*']` **(2) 产品经理 `role-pm`**: - 全权:product / project / version / requirement / version.req / version.product_plan - 仅查看:overtime(view)/ member(view)/ role(view)/ version.research / version.ui_plan / version.devtask / version.testcase / version.bug **(3) 开发工程师 `role-dev`**: - 全权:version.devtask - Bug Tab:view + edit - 加班:view + create - 仅查看:product / project / version / requirement / version.req / version.research / version.product_plan / version.ui_plan / version.testcase - 不可:member / role / overtime export+delete / 主模块的 create/edit/delete **(4) 测试工程师 `role-test`**: - 全权:version.testcase / version.bug(4 档全权) - 加班:view + create - 仅查看:product / project / version / requirement / version.req / version.research / version.product_plan / version.ui_plan / version.devtask - 不可:同开发 **(5) 设计师 `role-design`**: - 全权:version.ui_plan - 加班:view + create - 仅查看:product / project / version / requirement / version.req / version.research / version.product_plan / version.devtask / version.testcase / version.bug - 不可:同开发 ## 3. 数据结构 + Store **`apps/web/lib/members.ts`**:`RoleItem` 加 `permissions: string[]`(必填,默认 `[]`) **`apps/web/lib/permissions.ts`** 新增: ```ts export type Permission = string; export interface ActionDef { action: 'view' | 'create' | 'edit' | 'delete' | 'manage' | 'export'; label: string; permission: string; } export interface PermissionGroup { module: string; moduleLabel: string; category: 'main' | 'version_tab'; actions: ActionDef[]; } export const PERMISSION_GROUPS: PermissionGroup[] = [/* 14 组 */]; export const ALL_PERMISSIONS: string[] = /* 37 个展开 */; export const DEFAULT_ROLE_PERMISSIONS: Record = { 'role-admin': ['*'], 'role-pm': [...], 'role-dev': [...], 'role-test': [...], 'role-design': [...], }; export function hasPermission(role: RoleItem | undefined, permission: string): boolean { if (!role) return false; if (role.permissions.includes('*')) return true; return role.permissions.includes(permission); } ``` **`apps/web/stores/useMemberStore.ts`**: - `PRESET_ROLES` 5 条加 `permissions` - `fetchMembers` 加迁移:旧 RoleItem 无 permissions 字段时补默认值 - `createRole/updateRole` 接收 `permissions: string[]` - 超管 `role-admin` 不可改 permissions(`updateRole` 拦截) ## 4. UI 改动 **(A) 角色表单**(`apps/web/app/admin/roles/page.tsx`) `RoleModal` 加权限区块(在 description 下方)。Modal 宽度 `max-w-xs` → `max-w-2xl`。 布局: ``` [全选] [反选] [仅查看] ← 顶部快捷按钮 主模块 ───────────────────────── 产品 ☐查看 ☐创建 ☐编辑 ☐删除 项目 ☐查看 ☐创建 ☐编辑 ☐删除 版本 ☐查看 ☐创建 ☐编辑 ☐删除 需求池 ☐查看 ☐创建 ☐编辑 ☐删除 加班记录 ☐查看 ☐创建 ☐删除 ☐导出 成员 ☐查看 ☐创建 ☐编辑 ☐删除 角色 ☐查看 ☐创建 ☐编辑 ☐删除 版本详情 Tab ───────────────────────── 需求 Tab ☐查看 ☐管理 调研 Tab ☐查看 ☐管理 产品方案 Tab ☐查看 ☐管理 UI 设计 Tab ☐查看 ☐管理 开发任务 Tab ☐查看 ☐管理 测试用例 Tab ☐查看 ☐管理 Bug Tab ☐查看 ☐创建 ☐编辑 ☐删除 ``` - 系统角色(`isSystem`)表单顶部黄色提示"超管权限不可修改",所有 checkbox 设 `disabled` - 编辑态:`initial.permissions` 默认勾选 **(B) 全局拦截层**(`apps/web/components/auth/Guard.tsx` 新增) ```tsx // 路由级 // 按钮级 // hook const canEdit = useHasPermission('product:edit'); ``` `useHasPermission` 实现:从 `useAuthStore` 取 user.roleId,从 `useMemberStore` 查 role.permissions,调 `hasPermission`。 **(C) 接入清单** **路由级**: - `/products` `product:view` - `/projects` `project:view` - `/versions` `version:view` - `/requirements` `requirement:view` - `/overtime` `overtime:view` - `/admin/members` `member:view` - `/admin/roles` `role:view` - 无权限:`` 占位组件(不跳转,避免循环) - `/workspace` 不拦截 **按钮级**: - 各页面"新建/编辑/删除/导出"按钮包 `PermissionGuard` - `OvertimeModal` 内的"删除原因"按钮要 `overtime:delete` **Sidebar 菜单过滤**: - 7 个主菜单按 `:view` 过滤——无权限的菜单不显示(不灰显) - 工作区组永远显示(与我相关) **版本详情 Tab 过滤**(`apps/web/app/versions/[id]/page.tsx`): - Tab 列表渲染时按 `version.:view` 过滤——无权限的 Tab 不渲染 - Tab 内"新建/管理"按钮按对应 manage / 4 档权限拦截 **(D) 默认状态处理** - mock 用户已登录但 role 找不到 / 用户未登录:当前组件 `useHasPermission` 返回 `false`,所有页面/菜单/按钮都显示无权限——最严格只读 - 现有 mock 数据 `m-8 陈十` 是 `role-admin`,登录该用户即超管全权 - 切换其他 mock 用户(PM/开发/测试/设计)测试不同视图 ## 数据流 ``` useAuthStore.user │ └─ user.roleId │ └─ useMemberStore.roles.find(r => r.id === user.roleId) │ └─ role.permissions: string[] │ ├─ useHasPermission(perm) ─→ 按钮/Guard ├─ Sidebar 菜单过滤 ─→ 顶级菜单 └─ Tab 过滤 ─→ 版本详情 ``` ## 实施清单 新增: - `apps/web/lib/permissions.ts` — 权限字典 + DEFAULT_ROLE_PERMISSIONS + hasPermission - `apps/web/components/auth/Guard.tsx` — RouteGuard + PermissionGuard + useHasPermission + AccessDenied 修改: - `apps/web/lib/members.ts` — RoleItem 加 permissions - `apps/web/stores/useMemberStore.ts` — PRESET_ROLES 补 permissions + 迁移逻辑 + 超管保护 - `apps/web/app/admin/roles/page.tsx` — RoleModal 加权限矩阵 + 顶部快捷 - `apps/web/components/layout/Sidebar.tsx` — 菜单按 view 过滤 - `apps/web/app/products/page.tsx` — RouteGuard + 按钮拦截 - `apps/web/app/projects/page.tsx` — 同上 - `apps/web/app/versions/page.tsx` — 同上 - `apps/web/app/requirements/page.tsx` — 同上 - `apps/web/app/overtime/page.tsx` — 同上 + export 按钮拦截 - `apps/web/app/admin/members/page.tsx` — 同上 - `apps/web/app/versions/[id]/page.tsx` — Tab 过滤 + Tab 内按钮拦截 ## 验证 1. `pnpm type-check` 0 错误 2. `pnpm build` 14 个页面成功 3. 角色页:超管显示"系统不可修改"+ checkbox disabled;其他 4 个角色显示已勾选权限 4. 新建角色:勾选"产品-查看 + 加班-查看"保存,应能在新角色下看到对应菜单 5. 切换 mock 用户为开发工程师(如李四 m-2):Sidebar 看不到成员/角色菜单;进版本详情看不到测试用例/Bug 的 manage 按钮(但能看 view) 6. 切换设计师(孙七 m-5):UI 设计 Tab 全权,开发任务 Tab 只读 7. localStorage 旧 RoleItem 无 permissions 字段:启动后自动补默认值,UI 正常渲染