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:
@@ -27,6 +27,7 @@ import { useBugStore } from '@/stores/useBugStore';
|
||||
import { useAuthStore } from '@/stores/useAuthStore';
|
||||
import { useMemberStore } from '@/stores/useMemberStore';
|
||||
import { calcGroupProgress as calcDevTaskProgress, getActualHours, getEstimateHours, aggregateDevTaskHours, devTaskIntervals } from '@/lib/dev-task';
|
||||
import { hasPermission } from '@/lib/permissions';
|
||||
import { calcWorkHours, formatWorkHours, calcTwoMetrics } from '@/lib/work-hours';
|
||||
import { testCaseIntervals } from '@/lib/test-case';
|
||||
import { bugIntervals } from '@/lib/bug';
|
||||
@@ -42,14 +43,14 @@ const PRIORITY_STYLE: Record<string, string> = {
|
||||
};
|
||||
|
||||
const TABS = [
|
||||
{ key: 'overview', label: '概览' },
|
||||
{ key: 'requirements', label: '关联需求' },
|
||||
{ key: 'research', label: '调研' },
|
||||
{ key: 'product', label: '产品方案' },
|
||||
{ key: 'ui', label: 'UI设计' },
|
||||
{ key: 'tasks', label: '开发任务' },
|
||||
{ key: 'testcases', label: '测试用例' },
|
||||
{ key: 'bugs', label: 'BUG' },
|
||||
{ key: 'overview', label: '概览', permission: null as string | null },
|
||||
{ key: 'requirements', label: '关联需求', permission: 'version.req:view' },
|
||||
{ key: 'research', label: '调研', permission: 'version.research:view' },
|
||||
{ key: 'product', label: '产品方案', permission: 'version.product_plan:view' },
|
||||
{ key: 'ui', label: 'UI设计', permission: 'version.ui_plan:view' },
|
||||
{ key: 'tasks', label: '开发任务', permission: 'version.devtask:view' },
|
||||
{ key: 'testcases', label: '测试用例', permission: 'version.testcase:view' },
|
||||
{ key: 'bugs', label: 'BUG', permission: 'version.bug:view' },
|
||||
];
|
||||
|
||||
export default function VersionDetailPage() {
|
||||
@@ -64,8 +65,18 @@ export default function VersionDetailPage() {
|
||||
const { testCases, fetchTestCases, deleteTestCase } = useTestCaseStore();
|
||||
const { bugs, fetchBugs, deleteBug } = useBugStore();
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const { members: allMembers } = useMemberStore();
|
||||
const { members: allMembers, roles } = useMemberStore();
|
||||
const currentRole = useMemo(() => roles.find((r) => r.id === user?.roleId), [roles, user?.roleId]);
|
||||
const visibleTabs = useMemo(
|
||||
() => TABS.filter((t) => t.permission === null || hasPermission(currentRole, t.permission)),
|
||||
[currentRole],
|
||||
);
|
||||
const [activeTab, setActiveTab] = useState('overview');
|
||||
useEffect(() => {
|
||||
if (visibleTabs.length > 0 && !visibleTabs.find((t) => t.key === activeTab)) {
|
||||
setActiveTab(visibleTabs[0].key);
|
||||
}
|
||||
}, [visibleTabs, activeTab]);
|
||||
const [showMemberModal, setShowMemberModal] = useState(false);
|
||||
|
||||
useEffect(() => { fetchOverview(); }, [fetchOverview]);
|
||||
@@ -212,7 +223,7 @@ export default function VersionDetailPage() {
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="flex items-center gap-0 border-b border-[var(--line)] bg-[var(--bg-card)] px-5">
|
||||
{TABS.map((tab) => (
|
||||
{visibleTabs.map((tab) => (
|
||||
<button
|
||||
key={tab.key}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
|
||||
Reference in New Issue
Block a user