Files
ftb-project-management/docs/superpowers/specs/2026-06-16-role-permission-design.md
Script Generator c2f2fed82c feat: 加班/版本/项目调整 + 角色权限设计
加班记录:
- 加班人默认回显当前用户且不可改
- 列表加创建日期列
- 移除编辑功能(创建即提交,仅可删除)
- 提交时校验结束>开始

版本列表:
- 移除顶部 9 个状态 tab + 表格状态列
- 仅保留搜索、项目、优先级筛选

项目列表:
- 行内显示进度未达 100% 的版本(与版本页同源算法)
- 新增 lib/version-progress.ts 抽出公共进度计算

角色权限:
- RoleItem 加 permissions 字段
- 新建 lib/permissions.ts: 14 组 37 个权限点 + 默认 5 角色映射 + hasPermission
- 角色表单内嵌权限矩阵(主模块 4 件套 + Tab 二档 + Bug Tab 4 档)+ 全选/反选/仅查看快捷
- 新建 components/auth/Guard.tsx: RouteGuard + PermissionGuard + useHasPermission + AccessDenied
- Sidebar 菜单按 view 权限过滤
- 7 个主路由(products/projects/versions/requirements/overtime/admin/members/roles)包 RouteGuard
- 版本详情 Tab 按 view 权限过滤,自动跳转到第一个有权限的 Tab
- 默认未登录/无角色按只读最严格
- 超管 role-admin: ['*'] 通配符,permissions 不可改

通用:
- 新建 components/FieldError.tsx 统一表单错误文案
- PlanTab 提交时校验结束>开始

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-16 19:15:28 +08:00

10 KiB
Raw Permalink Blame History

角色权限设计

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-adminpermissions: ['*']

(2) 产品经理 role-pm

  • 全权product / project / version / requirement / version.req / version.product_plan
  • 仅查看overtimeview/ memberview/ roleview/ version.research / version.ui_plan / version.devtask / version.testcase / version.bug

(3) 开发工程师 role-dev

  • 全权version.devtask
  • Bug Tabview + 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.bug4 档全权)
  • 加班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.tsRoleItempermissions: string[](必填,默认 []

apps/web/lib/permissions.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<string, string[]> = {
  '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 不可改 permissionsupdateRole 拦截)

4. UI 改动

(A) 角色表单apps/web/app/admin/roles/page.tsx

RoleModal 加权限区块(在 description 下方。Modal 宽度 max-w-xsmax-w-2xl

布局:

[全选] [反选] [仅查看]   ← 顶部快捷按钮

主模块
─────────────────────────
产品          ☐查看 ☐创建 ☐编辑 ☐删除
项目          ☐查看 ☐创建 ☐编辑 ☐删除
版本          ☐查看 ☐创建 ☐编辑 ☐删除
需求池        ☐查看 ☐创建 ☐编辑 ☐删除
加班记录      ☐查看 ☐创建 ☐删除 ☐导出
成员          ☐查看 ☐创建 ☐编辑 ☐删除
角色          ☐查看 ☐创建 ☐编辑 ☐删除

版本详情 Tab
─────────────────────────
需求 Tab      ☐查看 ☐管理
调研 Tab      ☐查看 ☐管理
产品方案 Tab  ☐查看 ☐管理
UI 设计 Tab   ☐查看 ☐管理
开发任务 Tab  ☐查看 ☐管理
测试用例 Tab  ☐查看 ☐管理
Bug Tab       ☐查看 ☐创建 ☐编辑 ☐删除
  • 系统角色(isSystem)表单顶部黄色提示"超管权限不可修改",所有 checkbox 设 disabled
  • 编辑态:initial.permissions 默认勾选

(B) 全局拦截层apps/web/components/auth/Guard.tsx 新增)

// 路由级
<RouteGuard permission="product:view">
  <ProductsPage />
</RouteGuard>

// 按钮级
<PermissionGuard permission="product:create" fallback={null}>
  <button>新建产品</button>
</PermissionGuard>

// hook
const canEdit = useHasPermission('product:edit');

useHasPermission 实现:从 useAuthStore 取 user.roleIduseMemberStore 查 role.permissionshasPermission

(C) 接入清单

路由级

  • /products product:view
  • /projects project:view
  • /versions version:view
  • /requirements requirement:view
  • /overtime overtime:view
  • /admin/members member:view
  • /admin/roles role:view
  • 无权限:<AccessDenied /> 占位组件(不跳转,避免循环)
  • /workspace 不拦截

按钮级

  • 各页面"新建/编辑/删除/导出"按钮包 PermissionGuard
  • OvertimeModal 内的"删除原因"按钮要 overtime:delete

Sidebar 菜单过滤

  • 7 个主菜单按 <module>:view 过滤——无权限的菜单不显示(不灰显)
  • 工作区组永远显示(与我相关)

版本详情 Tab 过滤apps/web/app/versions/[id]/page.tsx

  • Tab 列表渲染时按 version.<tab>: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-2Sidebar 看不到成员/角色菜单;进版本详情看不到测试用例/Bug 的 manage 按钮(但能看 view
  6. 切换设计师(孙七 m-5UI 设计 Tab 全权,开发任务 Tab 只读
  7. localStorage 旧 RoleItem 无 permissions 字段启动后自动补默认值UI 正常渲染