Files
ftb-project-management/apps/web/lib/members.ts
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

67 lines
1.5 KiB
TypeScript

export interface Department {
id: string;
name: string;
parentId?: string;
order: number;
createdAt: string;
}
export interface Member {
id: string;
name: string;
departmentId: string;
roleId: string;
phone: string;
email: string;
password: string;
createdAt: string;
}
export interface RoleItem {
id: string;
name: string;
description?: string;
createdAt: string;
isSystem?: boolean;
permissions: string[];
}
export interface PasswordRule {
prefix: string;
length: number;
includeUppercase: boolean;
includeLowercase: boolean;
includeNumbers: boolean;
includeSpecial: boolean;
}
export const DEFAULT_PASSWORD_RULE: PasswordRule = {
prefix: 'Ftb',
length: 8,
includeUppercase: true,
includeLowercase: true,
includeNumbers: true,
includeSpecial: false,
};
export function generatePassword(rule: PasswordRule): string {
let chars = '';
if (rule.includeUppercase) chars += 'ABCDEFGHJKLMNPQRSTUVWXYZ';
if (rule.includeLowercase) chars += 'abcdefghjkmnpqrstuvwxyz';
if (rule.includeNumbers) chars += '23456789';
if (rule.includeSpecial) chars += '!@#$%&*';
if (!chars) chars = 'abcdefghjkmnpqrstuvwxyz23456789';
const randomLength = Math.max(rule.length - rule.prefix.length, 4);
let result = rule.prefix;
for (let i = 0; i < randomLength; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
export function maskPhone(phone: string): string {
if (!phone || phone.length < 7) return phone;
return phone.slice(0, 3) + '****' + phone.slice(7);
}