"use client"; /** * rbac-manager(admin / main) * * 角色权限管理。左侧角色列表,右侧权限矩阵(行=权限,列=角色)。 * 勾选/取消勾选时调用 mutation 保存该角色的权限集合。 * * 关联:portal-shell spec §5.6 统一 Hook */ import { useMemo, useState } from "react"; import { usePermissions, useRoles, useUpdateRolePermissions, } from "@/lib/api/admin"; import { PluginSkeleton } from "@/shell/PluginLoader"; import type { PluginProps } from "@/lib/types"; export default function RbacManager(_props: PluginProps): React.ReactElement { const [selectedRoleId, setSelectedRoleId] = useState(""); const [busyRoleId, setBusyRoleId] = useState(null); const rolesResult = useRoles(); const permsResult = usePermissions(); const { run: updatePermissions } = useUpdateRolePermissions(); const roles = rolesResult.data ?? []; const permissions = permsResult.data ?? []; // 选中角色默认取第一个 const effectiveRoleId = selectedRoleId.length > 0 ? selectedRoleId : roles.length > 0 ? (roles[0]?.id ?? "") : ""; // 当前角色 → 权限 ID 集合 const rolePermissionMap = useMemo(() => { const map = new Map>(); for (const role of roles) { const set = new Set(); for (const p of role.permissions) { set.add(p.id); } map.set(role.id, set); } return map; }, [roles]); const handleToggle = async ( roleId: string, permissionId: string, ): Promise => { const current = rolePermissionMap.get(roleId); if (current === undefined) { return; } const next = new Set(current); if (next.has(permissionId)) { next.delete(permissionId); } else { next.add(permissionId); } setBusyRoleId(roleId); try { await updatePermissions(roleId, Array.from(next)); await rolesResult.refetch(); } finally { setBusyRoleId(null); } }; if (rolesResult.loading && !rolesResult.data) { return ; } if (roles.length === 0) { return (

角色权限

暂无角色数据

); } return (

角色权限

{/* 左侧角色列表 */}

角色

    {roles.map((role) => { const isSelected = role.id === effectiveRoleId; return (
  • ); })}
{/* 右侧权限矩阵 */}
{permissions.length === 0 ? (

暂无权限数据

) : ( {roles.map((role) => ( ))} {permissions.map((perm) => ( {roles.map((role) => { const checked = rolePermissionMap.get(role.id)?.has(perm.id) ?? false; const isBusy = busyRoleId === role.id; return ( ); })} ))}
权限 {role.name}

{perm.name}

{perm.resource} / {perm.action}

handleToggle(role.id, perm.id)} aria-label={`${role.name} - ${perm.name}`} />
)}
); }