加班记录: - 加班人默认回显当前用户且不可改 - 列表加创建日期列 - 移除编辑功能(创建即提交,仅可删除) - 提交时校验结束>开始 版本列表: - 移除顶部 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>
67 lines
1.5 KiB
TypeScript
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);
|
|
}
|