/* eslint-disable max-lines-per-function, complexity -- 列表页负责主从联动、筛选、菜单与五类弹窗编排,集中便于追踪数据流 */ import { Button, Input, Switch } from '@douyinfe/semi-ui-19'; import { Permission, type RoleDetail, type RoleListItem } from '@lingxi/shared'; import { notifySuccess } from '@lingxi/ui'; import { IconAdd, IconSearch } from '@lingxi/ui/icons'; import type { ReactNode } from 'react'; import { useMemo, useState } from 'react'; import { useNavigate } from 'react-router'; import { Can } from '@/components/Can'; import { useCreateRoleMutation, useDeleteRoleMutation, usePermissionTreeQuery, useRoleDetailQuery, useRoleListQuery, useUpdateRoleMutation, } from '@/hooks/queries/use-roles'; import { usePermission } from '@/hooks/use-permission'; import { roleDetailPath, ROUTES } from '@/routes'; import { CopyRoleDialog, DeleteRoleDialog, DisableRoleDialog, RenameRoleDialog, } from './role-dialogs'; import { DEFAULT_ROLE_LIST_FILTERS, filterRoles, hasActiveRoleFilters, type RoleListFilters, summarizeRoles, } from './role-display'; import { RoleDetailPanel } from './RoleDetailPanel'; import { RoleListPanel, type RoleRowAction } from './RoleListPanel'; /** 角色管理列表查询口径:角色 ≤ 20,一次拉全量后本地筛选(分页由列表卡兜底) */ const ROLE_LIST_QUERY = { limit: 100, status: 'all', sort: 'created_at', order: 'desc', } as const; type DialogState = { kind: 'disable' | 'delete' | 'rename' | 'copy'; role: RoleListItem } | null; function RoleFilterBar({ filters, stats, onChange, onReset, }: { filters: RoleListFilters; stats: ReturnType; onChange: (next: Partial) => void; onReset: () => void; }): ReactNode { return (
} placeholder="搜索角色名称或说明" aria-label="搜索角色" showClear onChange={(value) => { onChange({ keyword: value }); }} />
{[ { value: 'all' as const, label: '全部', count: stats.total }, { value: 'active' as const, label: '启用', count: stats.active }, { value: 'disabled' as const, label: '停用', count: stats.disabled }, ].map((option) => ( ))}
绑定用户 {stats.boundUsers} 特殊权限授予 {stats.specialGrants} {hasActiveRoleFilters(filters) ? ( ) : null}
); } /** 角色管理列表页:左角色列表 + 右只读详情面板(/system/roles) */ export function RolesPage(): ReactNode { const navigate = useNavigate(); const canManage = usePermission(Permission.ROLE_MANAGE); const tree = usePermissionTreeQuery(); const listQuery = useRoleListQuery(ROLE_LIST_QUERY); const roles = useMemo(() => listQuery.data?.items ?? [], [listQuery.data]); const [filters, setFilters] = useState(DEFAULT_ROLE_LIST_FILTERS); const [selectedId, setSelectedId] = useState(null); const [dialog, setDialog] = useState(null); const filteredRoles = useMemo(() => filterRoles(roles, filters), [roles, filters]); const stats = useMemo(() => summarizeRoles(roles), [roles]); const effectiveSelectedId = filteredRoles.some((role) => role.id === selectedId) ? selectedId : (filteredRoles[0]?.id ?? null); const detail = useRoleDetailQuery(effectiveSelectedId); const copySource = useRoleDetailQuery(dialog?.kind === 'copy' ? dialog.role.id : null); const updateRole = useUpdateRoleMutation(); const deleteRole = useDeleteRoleMutation(); const createRole = useCreateRoleMutation(); const openDialog = (kind: 'disable' | 'delete' | 'rename' | 'copy', role: RoleListItem): void => { setDialog({ kind, role }); }; const handleRowAction = (action: RoleRowAction, role: RoleListItem): void => { setSelectedId(role.id); if (action === 'view' || action === 'edit') { void navigate(roleDetailPath(role.id)); return; } if (action === 'enable') { updateRole.mutate( { roleId: role.id, input: { status: 'enabled' } }, { onSuccess: () => { notifySuccess(`「${role.name}」已启用,${String(role.userCount)} 名用户下一次请求生效`); }, }, ); return; } openDialog(action, role); }; const selectedDetail: RoleDetail | undefined = detail.data; const activeRole = dialog?.role ?? selectedDetail ?? null; return (

角色管理

配置功能权限、数据范围与特殊权限,变更即时生效并留痕

{ setFilters((current) => ({ ...current, ...next })); }} onReset={() => { setFilters(DEFAULT_ROLE_LIST_FILTERS); }} />
{ void navigate(ROUTES.system.roleNew); }} onResetFilters={() => { setFilters(DEFAULT_ROLE_LIST_FILTERS); }} onRetry={() => { void listQuery.refetch(); }} /> { void detail.refetch(); }} onEdit={(roleId) => { void navigate(roleDetailPath(roleId)); }} onCopy={(role) => { openDialog('copy', role); }} onRename={(role) => { openDialog('rename', role); }} onDisable={(role) => { openDialog('disable', role); }} onEnable={(role) => { updateRole.mutate( { roleId: role.id, input: { status: 'enabled' } }, { onSuccess: () => { notifySuccess(`「${role.name}」已启用`); }, }, ); }} />
{ setDialog(null); }} onConfirm={(reason) => { if (dialog === null) { return; } updateRole.mutate( { roleId: dialog.role.id, input: { status: 'disabled', ...(reason === '' ? {} : { reason }) }, }, { onSuccess: () => { setDialog(null); notifySuccess( `「${dialog.role.name}」已停用,${String(dialog.role.userCount)} 名用户下一次请求生效`, ); }, }, ); }} /> { setDialog(null); }} onConfirm={() => { if (dialog === null) { return; } deleteRole.mutate(dialog.role.id, { onSuccess: () => { setSelectedId(null); setDialog(null); notifySuccess('角色已删除'); }, }); }} /> { setDialog(null); }} onConfirm={(name) => { if (dialog === null) { return; } updateRole.mutate( { roleId: dialog.role.id, input: { name } }, { onSuccess: () => { setDialog(null); notifySuccess('角色名称已更新'); }, }, ); }} /> { setDialog(null); }} onConfirm={(name) => { const source = copySource.data; if (source === undefined) { return; } createRole.mutate( { name, description: source.description, permissions: [...source.permissions], dataScope: { type: source.dataScope.type, ...(source.dataScope.targetIds === undefined ? {} : { targetIds: [...source.dataScope.targetIds] }), }, }, { onSuccess: (result) => { setDialog(null); notifySuccess('已创建「' + result.role.name + '」,可在编辑器中继续调整权限'); void navigate(roleDetailPath(result.role.id)); }, }, ); }} />
); }