'use client'; import * as React from 'react'; import { useState } from 'react'; import { SiteHeader, PageInfo } from '@/components/site-header'; import { IconPlus, IconEdit, IconTrash, IconSearch, IconUserShield, IconShield, IconEye, IconSettings, IconCheck, IconX, IconChevronDown, IconChevronRight, } from '@tabler/icons-react'; import { Button } from '@nice/ui/components/button'; import { Input } from '@nice/ui/components/input'; import { Badge } from '@nice/ui/components/badge'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@nice/ui/components/dropdown-menu'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@nice/ui/components/dialog'; import { Label } from '@nice/ui/components/label'; import { Textarea } from '@nice/ui/components/textarea'; import { Switch } from '@nice/ui/components/switch'; import { ScrollArea } from '@nice/ui/components/scroll-area'; import { Checkbox } from '@nice/ui/components/checkbox'; import { toast } from '@nice/ui/components/sonner'; import { SystemRole, SystemRoleConfig, SystemPermission, SystemPermissionMeta, PermissionGroup, PermissionUtils, PermissionGroupNames, PermissionLevelBadge, PermissionLevelLabel, } from '@fenghuo/common'; import { useQuery } from '@tanstack/react-query'; import { useTRPC, useRole } from '@fenghuo/client'; import { Role } from '@fenghuo/db'; import { useSetPageInfo } from '@/components/providers/dashboard-provider'; // 模拟角色数据 type RoleData = Role & { userCount?: number; }; // 远程数据将从 API 获取,这里不再使用模拟数据 // 权限分组组件 interface PermissionGroupProps { group: PermissionGroup; permissions: SystemPermission[]; selectedPermissions: Set; onPermissionToggle: (permission: SystemPermission) => void; onGroupToggle: (permissions: SystemPermission[], shouldSelect: boolean) => void; disabled?: boolean; } function PermissionGroupComponent({ group, permissions, selectedPermissions, onPermissionToggle, onGroupToggle, disabled = false, }: PermissionGroupProps) { const [isExpanded, setIsExpanded] = useState(true); const groupPermissions = permissions.filter((p) => SystemPermissionMeta[p]?.group === group); if (groupPermissions.length === 0) return null; const allSelected = groupPermissions.length > 0 && groupPermissions.every((p) => selectedPermissions.has(p)); const someSelected = groupPermissions.some((p) => selectedPermissions.has(p)); const handleGroupToggle = () => { if (disabled) return; onGroupToggle(groupPermissions, !allSelected); }; return (
setIsExpanded(!isExpanded)} >
e.stopPropagation()}>
{PermissionGroupNames[group]} {someSelected ? `${groupPermissions.filter((p) => selectedPermissions.has(p)).length}/` : ''} {groupPermissions.length}
{disabled && ( 锁定 )}
{isExpanded && (
{groupPermissions.map((permission) => { const meta = SystemPermissionMeta[permission]; const isSelected = selectedPermissions.has(permission); return (
onPermissionToggle(permission)} disabled={disabled} className={disabled ? 'opacity-40' : ''} />
{meta.name}
{meta.description}
{PermissionLevelLabel[meta.level]}
); })}
)}
); } export default function PermissionsPage() { // 状态管理 const [searchTerm, setSearchTerm] = useState(''); const [roles, setRoles] = useState([]); const [showCreateDialog, setShowCreateDialog] = useState(false); const [showEditDialog, setShowEditDialog] = useState(false); const [editingRole, setEditingRole] = useState(null); // 表单状态 const [formData, setFormData] = useState({ name: '', slug: '', description: '', isActive: true, }); const [selectedPermissions, setSelectedPermissions] = useState>(new Set()); useSetPageInfo({ title: '权限管理', subtitle: '管理系统角色和权限分配', }) // tRPC & TanStack Query const trpc = useTRPC(); const roleMutations = useRole(); // 获取角色列表(包含用户数量统计) const { data: rolePagination } = useQuery( trpc.role.findManyWithPagination.queryOptions({ page: 1, pageSize: 100, select: { id: true, name: true, slug: true, description: true, permissions: true, isSystem: true, isActive: true, createdAt: true, updatedAt: true, _count: { select: { users: { where: { deletedAt: null }, }, }, }, }, }), ); // 同步远程数据到本地状态 React.useEffect(() => { if (rolePagination?.items) { console.log('rolePagination', rolePagination); const rolesWithUserCount = rolePagination.items.map((role: any) => ({ ...role, userCount: role._count?.users || 0, _count: undefined, // 移除 _count 字段,避免混淆 })); setRoles(rolesWithUserCount as RoleData[]); } }, [rolePagination]); // 获取所有权限与分组(仅初始化时计算) const allPermissions = React.useMemo(() => Object.values(SystemPermission) as SystemPermission[], []); const permissionGroups = React.useMemo(() => Object.values(PermissionGroup) as PermissionGroup[], []); // 根据搜索关键字过滤角色 const filteredRoles = React.useMemo( () => roles.filter( (role) => role.name.toLowerCase().includes(searchTerm.toLowerCase()) || role.description?.toLowerCase().includes(searchTerm.toLowerCase()), ), [roles, searchTerm], ); // 权限切换 const handlePermissionToggle = (permission: SystemPermission) => { const newSelected = new Set(selectedPermissions); if (newSelected.has(permission)) { newSelected.delete(permission); } else { newSelected.add(permission); } setSelectedPermissions(newSelected); }; // Batch toggle permissions for a group const handleGroupPermissionToggle = (permissions: SystemPermission[], shouldSelect: boolean) => { setSelectedPermissions((prev) => { const newSelected = new Set(prev); if (shouldSelect) { permissions.forEach((p) => newSelected.add(p)); } else { permissions.forEach((p) => newSelected.delete(p)); } return newSelected; }); }; // 重置表单 const resetForm = () => { setFormData({ name: '', slug: '', description: '', isActive: true, }); setSelectedPermissions(new Set()); setEditingRole(null); }; // 创建角色 const handleCreateRole = () => { setShowCreateDialog(true); resetForm(); }; // 编辑角色 const handleEditRole = (role: RoleData) => { setEditingRole(role); setFormData({ name: role.name, slug: role.slug, description: role.description || '', isActive: role.isActive, }); setSelectedPermissions(new Set(role.permissions as SystemPermission[])); setShowEditDialog(true); }; // 删除角色 const handleDeleteRole = (role: RoleData) => { if (role.isSystem) { toast.error('系统角色不能删除'); return; } if (confirm(`确定要删除角色"${role.name}"吗?`)) { roleMutations.softDeleteByIds.mutate( { ids: [role.id] }, { onSuccess: () => { toast.success('角色删除成功'); }, onError: (error) => { toast.error('删除失败: ' + error.message); }, }, ); } }; // 保存角色 const handleSaveRole = () => { if (!formData.name.trim()) { toast.error('请输入角色名称'); return; } if (!formData.slug.trim()) { toast.error('请输入角色标识'); return; } if (selectedPermissions.size === 0) { toast.error('请至少选择一个权限'); return; } const onSuccess = (result: any) => { // 根据操作类型更新本地列表,避免等待重新拉取 setRoles((prev) => { if (editingRole) { return prev.map((r) => (r.id === (result?.id || editingRole.id) ? { ...r, ...result } : r)); } return [...prev, result]; }); toast.success(editingRole ? '角色更新成功' : '角色创建成功'); setShowCreateDialog(false); setShowEditDialog(false); resetForm(); }; const onError = (error: any) => { toast.error('操作失败: ' + error.message); }; if (editingRole) { roleMutations.update.mutate( { where: { id: editingRole.id }, data: { name: formData.name, slug: formData.slug, description: formData.description, permissions: Array.from(selectedPermissions), isActive: formData.isActive, }, }, { onSuccess, onError }, ); } else { roleMutations.create.mutate( { data: { name: formData.name, slug: formData.slug, description: formData.description, permissions: Array.from(selectedPermissions), isActive: formData.isActive, isSystem: false, }, }, { onSuccess, onError }, ); } // 后续逻辑在 onSuccess 内处理 return; }; // 切换角色状态 const handleToggleRoleStatus = (role: RoleData) => { if (role.isSystem && role.slug === 'super_admin') { toast.error('超级管理员角色不能禁用'); return; } roleMutations.update.mutate( { where: { id: role.id }, data: { isActive: !role.isActive }, }, { onSuccess: () => { toast.success(role.isActive ? '角色已禁用' : '角色已启用'); }, onError: (error) => { toast.error('操作失败: ' + error.message); }, }, ); }; return (
{/* 工具栏 */}
setSearchTerm(e.target.value)} className="pl-10" />
{/* 角色列表 */}
{filteredRoles.map((role) => (
{/* 角色头部 */}
{role.name} {role.isSystem && ( 系统 )} {!role.isActive && ( 已禁用 )}
/{role.slug}
handleEditRole(role)} className="cursor-pointer"> 编辑角色 handleToggleRoleStatus(role)} className="cursor-pointer"> {role.isActive ? ( <> 禁用角色 ) : ( <> 启用角色 )} {!role.isSystem && ( <> handleDeleteRole(role)} className="text-red-600 focus:text-red-600 dark:text-red-400 dark:focus:text-red-400 cursor-pointer" > 删除角色 )}
{/* 角色描述 */} {role.description &&

{role.description}

} {/* 统计信息 */}
用户数: {role.userCount || 0} 权限数:{' '} {role.permissions.length}
{/* 权限预览 */}
权限预览
{role.permissions.slice(0, 6).map((permission) => { const meta = SystemPermissionMeta[permission]; return ( {meta?.name || permission} ); })} {role.permissions.length > 6 && ( +{role.permissions.length - 6} )}
))}
{filteredRoles.length === 0 && (
{searchTerm ? '未找到匹配的角色' : '暂无角色数据'}
)}
{/* 创建角色对话框 */} 创建新角色 {/* 卡片采用两列布局:基本信息左,权限配置右;在小屏依旧垂直 */}

基本信息

setFormData((prev) => ({ ...prev, name: e.target.value }))} placeholder="如:内容编辑" className="bg-white dark:bg-gray-800" />
setFormData((prev) => ({ ...prev, slug: e.target.value }))} placeholder="如:content_editor" className="bg-white dark:bg-gray-800" />