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>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
# 加班/版本/项目模块小幅调整设计
|
||||
|
||||
## Context
|
||||
|
||||
针对已上线的加班记录、版本列表、项目列表、全局日期校验做一组明确的小幅调整。所有需求点边界已通过澄清确认,方案直接,无需架构层面权衡。
|
||||
|
||||
## 决策摘要
|
||||
|
||||
| 项 | 决策 |
|
||||
|---|---|
|
||||
| 加班"已提交"定义 | 创建即提交,所有记录不可编辑只可删除 |
|
||||
| "创建日期"取值 | `OvertimeRecord.createdAt`(表单提交日期) |
|
||||
| 加班人字段 | 默认回显 `useAuthStore` 当前用户 + `disabled` 不可改 |
|
||||
| 版本列表移除范围 | 顶部 9 个状态 tab + 表格"状态"列 |
|
||||
| 项目页"未达 100%"取值 | 整体进度(与版本页同源算法) < 100% |
|
||||
| 日期校验提示 | 提交时验证 + 表单项下方红色文案 |
|
||||
|
||||
## 1. 加班记录模块(`apps/web/app/overtime/page.tsx`)
|
||||
|
||||
**字段调整**:
|
||||
- `OvertimeModal` 加 `useAuthStore` 引用,初始化 `person = useAuthStore(s => s.user?.name) ?? ''`
|
||||
- 加班人 input 加 `disabled` + 灰底(`bg-zinc-50`)
|
||||
- 编辑时仍以 record 的 `person` 为准(虽然这条路径会被移除)
|
||||
|
||||
**列表表头**:
|
||||
- 在"备注"列前插入"创建日期"列
|
||||
- 单元格读 `r.createdAt`,已是 `YYYY-MM-DD` 格式直接显示
|
||||
|
||||
**编辑功能移除**:
|
||||
- 删除:`editing` state、`handleEdit` 函数、表格"编辑"按钮、`OvertimeModal` 的 `initial` 编辑分支(保留 `initial: null` 用于校验空表单状态,或直接简化签名移除 initial)
|
||||
- `handleCreate` 简化为 `setShowModal(true)`
|
||||
- `OvertimeModal` props 删除 `initial`,简化为新建专用
|
||||
|
||||
**日期校验**:
|
||||
- 提交时检查 `endTime > startTime`,失败设 `formError = '结束时间必须晚于开始时间'`
|
||||
- 在结束时间 input 下方显示红色文案 `<p className="text-[11px] text-red-500 mt-1">{formError}</p>`
|
||||
|
||||
## 2. 全局日期校验
|
||||
|
||||
**通用错误提示组件**:新增 `apps/web/components/FieldError.tsx`
|
||||
|
||||
```tsx
|
||||
interface Props { message?: string }
|
||||
export function FieldError({ message }: Props) {
|
||||
if (!message) return null;
|
||||
return <p className="mt-1 text-[11px] text-red-500">{message}</p>;
|
||||
}
|
||||
```
|
||||
|
||||
**应用范围**(提交时校验,下方红色文案):
|
||||
|
||||
- `apps/web/app/overtime/page.tsx` — OvertimeModal 结束时间下方
|
||||
- `apps/web/components/version/PlanTab.tsx` — 计划开始/结束日期对(按现有结构定位)
|
||||
- `apps/web/components/dev-task/DevTaskCreateModal.tsx` — 已有 `startBeforeEnd` 校验,统一改为用 `<FieldError>` 显示在预计截止下方
|
||||
- `apps/web/components/dev-task/DevTaskDetailDrawer.tsx` — 详情抽屉编辑(如有日期对编辑)
|
||||
|
||||
**跳过**:
|
||||
- `apps/web/app/versions/page.tsx` 的 NewVersionModal 仅有截止日期,无开始
|
||||
- `apps/web/components/dev-task/WorklogPanel.tsx` 仅单 date 字段
|
||||
|
||||
## 3. 版本列表(`apps/web/app/versions/page.tsx`)
|
||||
|
||||
**移除**:
|
||||
- `STATUS_TABS` 常量(line 39-50)
|
||||
- 顶部状态 tab JSX 块(line 275-281)
|
||||
- 表头"状态"列(line 346)
|
||||
- 表格"状态"单元格(line 419-424)
|
||||
- `statusFilter` state 和过滤分支(保留 search/projectFilter/priorityFilter 三个)
|
||||
|
||||
**保留**:项目筛选下拉、优先级筛选下拉、搜索框
|
||||
|
||||
**整体进度** 列保留——它本身就反映状态信息。
|
||||
|
||||
## 4. 项目页(`apps/web/app/projects/page.tsx`)
|
||||
|
||||
**计算 versionProgressMap**:
|
||||
- `ProjectsPage` 加同款 `versionProgressMap` useMemo(与 versions/page.tsx line 141-212 同源算法)
|
||||
- 需要 import `useVersionPlanStore`、`useDevTaskStore`、`useTestCaseStore`、`useRequirementStore`、`STATUS_PROGRESS`、`getEstimateHours`
|
||||
- 抽出公共函数 `apps/web/lib/version-progress.ts`:
|
||||
|
||||
```ts
|
||||
export function calcVersionProgress(
|
||||
versionId: string,
|
||||
plans: VersionPlan[],
|
||||
requirements: Requirement[],
|
||||
devTasks: DevTask[],
|
||||
testCases: TestCase[],
|
||||
): number;
|
||||
|
||||
export function buildVersionProgressMap(
|
||||
versions: VersionWithContext[],
|
||||
plans, requirements, devTasks, testCases,
|
||||
): Record<string, number>;
|
||||
```
|
||||
|
||||
把 versions/page.tsx 现有逻辑替换为调用这个工具,避免两份。
|
||||
|
||||
**ProjectRow 改动**:
|
||||
- 接收 `versionProgressMap: Record<string, number>` prop
|
||||
- `activeVersions = filter(v => (versionProgressMap[v.id] ?? 0) < 100)`
|
||||
- `releasedCount` 改为 `completedCount = filter(v => (versionProgressMap[v.id] ?? 0) >= 100)`
|
||||
- "+N 已发布"改为"+N 已完成"
|
||||
|
||||
**Legend 调整**:
|
||||
- 现有图例 `开发中 / 规划中 / 已发布` 颜色与版本状态绑定,VersionChip 也是。继续保留 chip 内部颜色逻辑,仅过滤维度变了。Legend 文案保持不变(这是 chip 颜色含义说明)。
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
overview / requirements / plans / devTasks / testCases
|
||||
│
|
||||
└── lib/version-progress.ts
|
||||
│
|
||||
├── buildVersionProgressMap(versions, ...)
|
||||
│ └── Record<versionId, percent>
|
||||
│
|
||||
├── versions/page.tsx 整体进度列
|
||||
└── projects/page.tsx ProjectRow 过滤未完成版本
|
||||
```
|
||||
|
||||
## 实施清单
|
||||
|
||||
新增:
|
||||
- `apps/web/components/FieldError.tsx`
|
||||
- `apps/web/lib/version-progress.ts`
|
||||
|
||||
修改:
|
||||
- `apps/web/app/overtime/page.tsx`(加班人禁用 + 创建日期列 + 移除编辑 + 日期校验)
|
||||
- `apps/web/components/version/PlanTab.tsx`(日期校验)
|
||||
- `apps/web/components/dev-task/DevTaskCreateModal.tsx`(统一校验文案样式)
|
||||
- `apps/web/components/dev-task/DevTaskDetailDrawer.tsx`(如有日期对编辑则加校验)
|
||||
- `apps/web/app/versions/page.tsx`(移除状态 tab/列 + 改用 version-progress.ts)
|
||||
- `apps/web/app/projects/page.tsx`(加 progressMap + 改 ProjectRow 过滤口径)
|
||||
|
||||
## 验证
|
||||
|
||||
1. `pnpm type-check` 0 错误
|
||||
2. `pnpm build` 14 个页面成功
|
||||
3. 加班记录:新建表单加班人为当前登录用户名且不可改;列表有"创建日期"列;任何记录无"编辑"按钮,仅"删除"
|
||||
4. 加班/计划/开发任务表单:截止 < 开始时提交,下方显示红色文案,不能提交
|
||||
5. 版本列表:顶部无状态 tab;表头无状态列;只有项目+优先级两个下拉
|
||||
6. 项目列表:每行显示进度未达 100% 的版本 chip,已完成数量在末尾"+N 已完成"
|
||||
254
docs/superpowers/specs/2026-06-16-role-permission-design.md
Normal file
254
docs/superpowers/specs/2026-06-16-role-permission-design.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# 角色权限设计
|
||||
|
||||
## 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<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` 不可改 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
|
||||
// 路由级
|
||||
<RouteGuard permission="product:view">
|
||||
<ProductsPage />
|
||||
</RouteGuard>
|
||||
|
||||
// 按钮级
|
||||
<PermissionGuard permission="product:create" fallback={null}>
|
||||
<button>新建产品</button>
|
||||
</PermissionGuard>
|
||||
|
||||
// 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`
|
||||
- 无权限:`<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-2):Sidebar 看不到成员/角色菜单;进版本详情看不到测试用例/Bug 的 manage 按钮(但能看 view)
|
||||
6. 切换设计师(孙七 m-5):UI 设计 Tab 全权,开发任务 Tab 只读
|
||||
7. localStorage 旧 RoleItem 无 permissions 字段:启动后自动补默认值,UI 正常渲染
|
||||
Reference in New Issue
Block a user