// ============================================================ // 账号管理 // ============================================================ import { useState } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { Button, message, Tag, Modal, Form, Input, Select, Popconfirm, Space } from 'antd' import type { ProColumns } from '@ant-design/pro-components' import { ProTable } from '@ant-design/pro-components' import { accountService } from '@/services/accounts' import { PageHeader } from '@/components/PageHeader' import type { AccountPublic } from '@/types' const roleLabels: Record = { super_admin: '超级管理员', admin: '管理员', user: '用户', } const roleColors: Record = { super_admin: 'red', admin: 'blue', user: 'default', } const statusLabels: Record = { active: '正常', disabled: '已禁用', suspended: '已封禁', } const statusColors: Record = { active: 'green', disabled: 'default', suspended: 'red', } export default function Accounts() { const queryClient = useQueryClient() const [form] = Form.useForm() const [modalOpen, setModalOpen] = useState(false) const [editingId, setEditingId] = useState(null) const [searchParams, setSearchParams] = useState>({}) const { data, isLoading } = useQuery({ queryKey: ['accounts', searchParams], queryFn: ({ signal }) => accountService.list(searchParams, signal), }) const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => accountService.update(id, data), onSuccess: () => { message.success('更新成功') queryClient.invalidateQueries({ queryKey: ['accounts'] }) setModalOpen(false) }, onError: (err: Error) => message.error(err.message || '更新失败'), }) const statusMutation = useMutation({ mutationFn: ({ id, status }: { id: string; status: AccountPublic['status'] }) => accountService.updateStatus(id, { status }), onSuccess: () => { message.success('状态更新成功') queryClient.invalidateQueries({ queryKey: ['accounts'] }) }, onError: (err: Error) => message.error(err.message || '状态更新失败'), }) const columns: ProColumns[] = [ { title: '用户名', dataIndex: 'username', width: 120, tooltip: '搜索用户名、邮箱或显示名' }, { title: '显示名', dataIndex: 'display_name', width: 120, hideInSearch: true }, { title: '邮箱', dataIndex: 'email', width: 180 }, { title: '角色', dataIndex: 'role', width: 120, valueType: 'select', valueEnum: { super_admin: { text: '超级管理员' }, admin: { text: '管理员' }, user: { text: '用户' }, }, render: (_, record) => {roleLabels[record.role] || record.role}, }, { title: '状态', dataIndex: 'status', width: 100, valueType: 'select', valueEnum: { active: { text: '正常', status: 'Success' }, disabled: { text: '已禁用', status: 'Default' }, suspended: { text: '已封禁', status: 'Error' }, }, render: (_, record) => {statusLabels[record.status] || record.status}, }, { title: '2FA', dataIndex: 'totp_enabled', width: 80, hideInSearch: true, render: (_, record) => record.totp_enabled ? 已启用 : 未启用, }, { title: 'LLM 路由', dataIndex: 'llm_routing', width: 120, hideInSearch: true, valueType: 'select', valueEnum: { relay: { text: 'SaaS 中转', status: 'Success' }, local: { text: '本地直连', status: 'Default' }, }, }, { title: '最后登录', dataIndex: 'last_login_at', width: 180, hideInSearch: true, render: (_, record) => record.last_login_at ? new Date(record.last_login_at).toLocaleString('zh-CN') : '-', }, { title: '操作', width: 200, hideInSearch: true, render: (_, record) => ( {record.status === 'active' ? ( statusMutation.mutate({ id: record.id, status: 'disabled' })}> ) : ( statusMutation.mutate({ id: record.id, status: 'active' })}> )} ), }, ] const handleSave = async () => { const values = await form.validateFields() if (editingId) { updateMutation.mutate({ id: editingId, data: values }) } } return (
columns={columns} dataSource={data?.items ?? []} loading={isLoading} rowKey="id" search={{}} toolBarRender={() => []} onSubmit={(values) => { const filtered: Record = {} for (const [k, v] of Object.entries(values)) { if (v !== undefined && v !== null && v !== '') { // Map 'username' search field to backend 'search' param if (k === 'username') { filtered.search = String(v) } else { filtered[k] = String(v) } } } setSearchParams(filtered) }} onReset={() => setSearchParams({})} pagination={{ total: data?.total ?? 0, pageSize: data?.page_size ?? 20, current: data?.page ?? 1, showSizeChanger: false, }} /> 编辑账号} open={modalOpen} onOk={handleSave} onCancel={() => { setModalOpen(false); setEditingId(null); form.resetFields() }} confirmLoading={updateMutation.isPending} >
) }