import { useState, useCallback, useEffect } from 'react'; import { Table, Button, Space, Form, Input, Tag, Popconfirm, Checkbox, } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, UserOutlined, SafetyCertificateOutlined, StopOutlined, CheckCircleOutlined, } from '@ant-design/icons'; import { listUsers, createUser, updateUser, deleteUser, assignRoles, type CreateUserRequest, type UpdateUserRequest, } from '../api/users'; import { listRoles, type RoleInfo } from '../api/roles'; import type { UserInfo } from '../api/auth'; import { PageContainer } from '../components/PageContainer'; import { DrawerForm } from '../components/DrawerForm'; import { useCrudDrawer } from '../hooks/useCrudDrawer'; import { usePaginatedData } from '../hooks/usePaginatedData'; import { useApiRequest } from '../hooks/useApiRequest'; import { useThemeMode } from '../hooks/useThemeMode'; const STATUS_COLOR_MAP: Record = { active: '#059669', disabled: '#dc2626', locked: '#d97706' }; const STATUS_BG_MAP: Record = { active: '#ECFDF5', disabled: '#FEF2F2', locked: '#FFFBEB' }; const STATUS_LABEL_MAP: Record = { active: '正常', disabled: '禁用', locked: '锁定' }; export default function Users() { const isDark = useThemeMode(); const { execute } = useApiRequest(); const { data: users, total, page, loading, refresh, } = usePaginatedData(async (p, pageSize, search) => { const result = await listUsers(p, pageSize, search); return { data: result.data, total: result.total }; }, 20); const [allRoles, setAllRoles] = useState([]); const [roleDrawerOpen, setRoleDrawerOpen] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const [selectedRoleIds, setSelectedRoleIds] = useState([]); const fetchRoles = useCallback(async () => { try { const result = await listRoles(); setAllRoles(result.data); } catch { /* silent */ } }, []); useEffect(() => { fetchRoles(); }, [fetchRoles]); const userDrawer = useCrudDrawer({ getId: (r) => r.id, onCreate: async (values) => { await createUser(values as unknown as CreateUserRequest); }, onUpdate: async (id, values) => { await updateUser(id, values as unknown as UpdateUserRequest); }, onSuccess: refresh, }); const handleDelete = async (id: string) => { await execute(() => deleteUser(id), '用户已删除'); refresh(); }; const handleToggleStatus = async (id: string, status: string) => { const user = users.find(u => u.id === id); if (!user) return; await execute(() => updateUser(id, { status, version: user.version }), status === 'disabled' ? '用户已禁用' : '用户已启用'); refresh(); }; const handleAssignRoles = async () => { if (!selectedUser) return; await execute(() => assignRoles(selectedUser.id, selectedRoleIds), '角色分配成功'); setRoleDrawerOpen(false); refresh(); }; const openRoleDrawer = (user: UserInfo) => { setSelectedUser(user); setSelectedRoleIds(user.roles.map((r) => r.id)); setRoleDrawerOpen(true); }; const columns = [ { title: '用户', dataIndex: 'username', key: 'username', render: (v: string, record: UserInfo) => (
{(record.display_name?.[0] || v?.[0] || 'U').toUpperCase()}
{v}
{record.display_name &&
{record.display_name}
}
), }, { title: '邮箱', dataIndex: 'email', key: 'email', render: (v?: string) => v || '-' }, { title: '电话', dataIndex: 'phone', key: 'phone', render: (v?: string) => v || '-' }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (status: string) => ( {STATUS_LABEL_MAP[status] || status} ), }, { title: '角色', dataIndex: 'roles', key: 'roles', render: (roles: RoleInfo[]) => roles.length > 0 ? roles.map((r) => {r.name}) : -, }, { title: '操作', key: 'actions', width: 240, render: (_: unknown, record: UserInfo) => ( } > refresh(p), showTotal: (t) => `共 ${t} 条记录`, }} /> {/* 新建/编辑用户 Drawer */} } disabled={!!userDrawer.editingRecord} /> {!userDrawer.editingRecord && ( )} {/* 角色分配 Drawer */} setRoleDrawerOpen(false)} onSubmit={async () => { await handleAssignRoles(); }} initialValues={{}} loading={false} width={520} columns={1} >
setSelectedRoleIds(values as string[])} style={{ display: 'flex', flexDirection: 'column', gap: 12 }} > {allRoles.map((r) => (
{r.name} {r.code}
))}
); }