import { useState, useMemo, useCallback } from 'react'; import { Crown, Database, Code, Eye, ChevronRight, Check, Save, Search, X, Copy, Users, Lock, BarChart3, XCircle, CheckCircle2, MinusCircle, Sparkles, ChevronDown, } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { permissionTreeData } from '@/lib/mock'; /* ───────── Types ───────── */ interface Role { id: string; name: string; description: string; icon: string; iconClass: string; permissions: string[]; userCount: number; permissionCount: number; } interface PermissionChange { id: string; label: string; action: 'added' | 'removed'; } interface PermissionPreset { key: string; name: string; description: string; icon: React.ElementType; permissions: string[]; } /* ───────── Constants ───────── */ const iconMap: Record = { Crown, Database, Code, Eye }; const PERMISSION_PRESETS: PermissionPreset[] = [ { key: 'dba', name: 'DBA 模板', description: '数据库管理员:完整的数据源管理、工单审批、查询数据权限', icon: Database, permissions: ['dashboard', 'mysql', 'postgres', 'redis', 'mongo', 'tickets', 'ticket_create', 'ticket_approve', 'ticket_execute', 'ticket_rollback', 'queries', 'query_create', 'query_export', 'query_view_all', 'audit-rules', 'audit-logs', 'workflow'], }, { key: 'developer', name: '开发模板', description: '开发人员:工单提交、查询执行、数据导出', icon: Code, permissions: ['dashboard', 'tickets', 'ticket_create', 'queries', 'query_create', 'query_export'], }, { key: 'auditor', name: '审计模板', description: '审计人员:查看查询日志、审计规则、审批流程', icon: Eye, permissions: ['dashboard', 'queries', 'query_view_all', 'audit-rules', 'audit-logs', 'workflow'], }, { key: 'admin', name: '管理员模板', description: '超级管理员:系统全部权限', icon: Crown, permissions: ['dashboard', 'mysql', 'postgres', 'redis', 'mongo', 'tickets', 'ticket_create', 'ticket_approve', 'ticket_execute', 'ticket_rollback', 'queries', 'query_create', 'query_export', 'query_view_all', 'roles', 'users', 'audit-rules', 'audit-logs', 'workflow', 'auto-task', 'announcement', 'config-platform', 'notifications'], }, ]; /* ───────── Helpers ───────── */ function collectAllIds(nodes: typeof permissionTreeData): string[] { const ids: string[] = []; const walk = (list: typeof permissionTreeData) => { list.forEach(n => { ids.push(n.id); if (n.children) walk(n.children); }); }; walk(nodes); return ids; } function searchTree(nodes: typeof permissionTreeData, query: string): typeof permissionTreeData { if (!query.trim()) return nodes; const lower = query.toLowerCase(); return nodes.reduce((acc, node) => { const matchesSelf = node.label.toLowerCase().includes(lower); const filteredChildren = node.children ? searchTree(node.children, query) : []; if (matchesSelf || filteredChildren.length > 0) { acc.push({ ...node, children: filteredChildren.length > 0 ? filteredChildren : node.children }); } return acc; }, []); } /* ───────── Main Component ───────── */ export default function RolesPage() { const [checkedKeys, setCheckedKeys] = useState>(new Set()); const [originalKeys, setOriginalKeys] = useState>(new Set()); const [searchQuery, setSearchQuery] = useState(''); const [showDiff, setShowDiff] = useState(false); const [showPresets, setShowPresets] = useState(false); const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' } | null>(null); const hasChanges = useMemo(() => { const allIds = collectAllIds(permissionTreeData); return allIds.some(id => checkedKeys.has(id) !== originalKeys.has(id)); }, [checkedKeys, originalKeys]); const changes = useMemo(() => { const allIds = collectAllIds(permissionTreeData); const idToLabel: Record = {}; const buildLabelMap = (nodes: typeof permissionTreeData) => { nodes.forEach(n => { idToLabel[n.id] = n.label; if (n.children) buildLabelMap(n.children); }); }; buildLabelMap(permissionTreeData); const result: PermissionChange[] = []; allIds.forEach(id => { if (checkedKeys.has(id) && !originalKeys.has(id)) { result.push({ id, label: idToLabel[id] || id, action: 'added' }); } else if (!checkedKeys.has(id) && originalKeys.has(id)) { result.push({ id, label: idToLabel[id] || id, action: 'removed' }); } }); return result; }, [originalKeys, checkedKeys]); const filteredTree = useMemo(() => searchTree(permissionTreeData, searchQuery), [searchQuery]); const togglePermission = useCallback((id: string) => { setCheckedKeys(prev => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); }, []); const toggleChildren = useCallback((node: typeof permissionTreeData[0], checked: boolean) => { setCheckedKeys(prev => { const next = new Set(prev); const toggleNode = (n: typeof node) => { if (checked) next.add(n.id); else next.delete(n.id); n.children?.forEach(toggleNode); }; toggleNode(node); return next; }); }, []); const applyPreset = (preset: PermissionPreset) => { setCheckedKeys(new Set(preset.permissions)); setOriginalKeys(new Set(preset.permissions)); setShowPresets(false); setToast({ message: `已应用「${preset.name}」预设`, type: 'success' }); setTimeout(() => setToast(null), 2000); }; const handleSave = () => { setOriginalKeys(new Set(checkedKeys)); setToast({ message: '权限配置已保存(演示模式)', type: 'success' }); setTimeout(() => setToast(null), 2000); }; const handleDiscard = () => { setCheckedKeys(new Set(originalKeys)); setShowDiff(false); setToast({ message: '已撤销未保存的更改', type: 'error' }); setTimeout(() => setToast(null), 2000); }; return (
{/* Toast */} {toast && (
{toast.type === 'success' ? : } {toast.message}
)} {/* Header */}

角色权限管理

配置 RBAC 角色权限,控制用户对系统功能和数据的访问

{/* Empty state info */}

角色数据暂未接入后端 API,当前为权限配置演示模式。您可以使用下方的权限树和预设模板来规划角色权限方案。

{/* Permission Config */} 权限配置
{/* Presets */}
{showPresets && ( <>
setShowPresets(false)} />
{PERMISSION_PRESETS.map(preset => { const Icon = preset.icon; return ( ); })}
)}
{/* Diff Toggle */} {hasChanges && ( )} {/* Discard */} {hasChanges && ( )} {/* Save */}
{/* Permission Tree */}
{/* Search */}
setSearchQuery(e.target.value)} placeholder="搜索权限名称..." className="w-full pl-10 pr-4 py-2 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm placeholder:text-[var(--dg-text-muted)] focus:border-blue-400 focus:outline-none" /> {searchQuery && ( )}
{/* Tree */}
{filteredTree.map(node => ( ))} {filteredTree.length === 0 && (
未找到匹配的权限
)}
{/* Diff Panel */} {showDiff && hasChanges && (

变更详情

{changes.map(change => (
{change.action === 'added' ? : } {change.label}
))}
新增 {changes.filter(c => c.action === 'added').length} 移除 {changes.filter(c => c.action === 'removed').length}
)}
); } /* ───────── Permission Node Component ───────── */ function PermissionNodeComponent({ node, checkedKeys, onToggle, onToggleChildren, depth }: { node: typeof permissionTreeData[0]; checkedKeys: Set; onToggle: (id: string) => void; onToggleChildren: (node: typeof permissionTreeData[0], checked: boolean) => void; depth: number; }) { const [expanded, setExpanded] = useState(true); const isChecked = checkedKeys.has(node.id); const hasChildren = node.children && node.children.length > 0; const allChildrenChecked = hasChildren && node.children!.every(c => checkedKeys.has(c.id)); const someChildrenChecked = hasChildren && node.children!.some(c => checkedKeys.has(c.id)); return (
{ if (hasChildren) setExpanded(!expanded); }} > {hasChildren && ( )} {!hasChildren &&
}
{ e.stopPropagation(); if (hasChildren) onToggleChildren(node, !allChildrenChecked); else onToggle(node.id); }} > {(isChecked || allChildrenChecked || someChildrenChecked) && }
{node.label}
{hasChildren && expanded && (
{node.children!.map(child => ( ))}
)}
); }