242 lines
7.5 KiB
TypeScript
242 lines
7.5 KiB
TypeScript
import { useRef, useState } from 'react'
|
|
import { ProTable } from '@ant-design/pro-components'
|
|
import type { ProColumns, ActionType } from '@ant-design/pro-components'
|
|
import { Button, Tag, message, Tooltip } from 'antd'
|
|
import { PlusOutlined, ReloadOutlined } from '@ant-design/icons'
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
import type { AdminUser } from '@/types/admin'
|
|
import { ADMIN_ROLE_LABELS, ADMIN_ROLE_COLORS } from '@/constants/roles'
|
|
import { getAdminUsers, deleteAdminUser } from '@/services/admin-api'
|
|
import DetailDrawer from '@/components/DetailDrawer'
|
|
import ConfirmDangerModal from '@/components/ConfirmDangerModal'
|
|
|
|
const statusLabelMap: Record<string, string> = {
|
|
ACTIVE: '正常',
|
|
DISABLED: '已禁用',
|
|
}
|
|
|
|
const statusColorMap: Record<string, string> = {
|
|
ACTIVE: 'green',
|
|
DISABLED: 'red',
|
|
}
|
|
|
|
export default function UserManagement() {
|
|
const actionRef = useRef<ActionType>(undefined)
|
|
const queryClient = useQueryClient()
|
|
const [detailOpen, setDetailOpen] = useState(false)
|
|
const [selectedUser, setSelectedUser] = useState<AdminUser | null>(null)
|
|
const [deleteTarget, setDeleteTarget] = useState<AdminUser | null>(null)
|
|
|
|
const { data, isLoading } = useQuery({
|
|
queryKey: ['admin-users'],
|
|
queryFn: () => getAdminUsers({ page: 1, limit: 20 }),
|
|
})
|
|
|
|
const deleteMutation = useMutation({
|
|
mutationFn: (id: string) => deleteAdminUser(id),
|
|
onSuccess: () => {
|
|
message.success('已删除')
|
|
queryClient.invalidateQueries({ queryKey: ['admin-users'] })
|
|
},
|
|
onError: () => message.error('删除失败'),
|
|
})
|
|
|
|
const columns: ProColumns<AdminUser>[] = [
|
|
{
|
|
title: '姓名',
|
|
dataIndex: 'displayName',
|
|
width: 130,
|
|
ellipsis: true,
|
|
},
|
|
{
|
|
title: '邮箱',
|
|
dataIndex: 'email',
|
|
width: 200,
|
|
ellipsis: true,
|
|
},
|
|
{
|
|
title: '角色',
|
|
dataIndex: 'role',
|
|
width: 120,
|
|
valueType: 'select',
|
|
valueEnum: {
|
|
SUPER_ADMIN: { text: '超级管理员' },
|
|
ADMIN: { text: '管理员' },
|
|
OPERATIONS: { text: '运营人员' },
|
|
DEVELOPER: { text: '开发者' },
|
|
READONLY: { text: '只读用户' },
|
|
},
|
|
render: (_, record) => (
|
|
<Tag color={ADMIN_ROLE_COLORS[record.role]}>{ADMIN_ROLE_LABELS[record.role]}</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
width: 90,
|
|
valueType: 'select',
|
|
valueEnum: {
|
|
ACTIVE: { text: '正常' },
|
|
DISABLED: { text: '已禁用' },
|
|
},
|
|
render: (_, record) => (
|
|
<Tag color={statusColorMap[record.status]}>{statusLabelMap[record.status]}</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '最后登录',
|
|
dataIndex: 'lastLoginAt',
|
|
width: 160,
|
|
search: false,
|
|
render: (_, record) =>
|
|
record.lastLoginAt
|
|
? new Date(record.lastLoginAt).toLocaleString('zh-CN', {
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
})
|
|
: '-',
|
|
},
|
|
{
|
|
title: '创建时间',
|
|
dataIndex: 'createdAt',
|
|
width: 160,
|
|
search: false,
|
|
render: (_, record) =>
|
|
new Date(record.createdAt).toLocaleString('zh-CN', {
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
}),
|
|
},
|
|
{
|
|
title: '操作',
|
|
valueType: 'option',
|
|
width: 100,
|
|
render: (_, record) => [
|
|
<a
|
|
key="view"
|
|
onClick={() => {
|
|
setSelectedUser(record)
|
|
setDetailOpen(true)
|
|
}}
|
|
>
|
|
详情
|
|
</a>,
|
|
<Tooltip key="delete" title={record.role === 'SUPER_ADMIN' ? '不可删除超级管理员' : ''}>
|
|
<a
|
|
style={{ color: record.role === 'SUPER_ADMIN' ? '#ccc' : '#ff4d4f' }}
|
|
onClick={() => {
|
|
if (record.role !== 'SUPER_ADMIN') {
|
|
setDeleteTarget(record)
|
|
}
|
|
}}
|
|
>
|
|
删除
|
|
</a>
|
|
</Tooltip>,
|
|
],
|
|
},
|
|
]
|
|
|
|
return (
|
|
<>
|
|
<ProTable<AdminUser>
|
|
actionRef={actionRef}
|
|
columns={columns}
|
|
dataSource={data?.items || []}
|
|
loading={isLoading}
|
|
rowKey="id"
|
|
search={false}
|
|
options={false}
|
|
pagination={
|
|
data
|
|
? {
|
|
current: data.page,
|
|
pageSize: data.limit,
|
|
total: data.total,
|
|
showSizeChanger: true,
|
|
showTotal: (total) => `共 ${total} 条`,
|
|
}
|
|
: false
|
|
}
|
|
headerTitle="管理员列表"
|
|
toolbar={{
|
|
actions: [
|
|
<Button key="new" type="primary" icon={<PlusOutlined />}>
|
|
新建管理员
|
|
</Button>,
|
|
<Button
|
|
key="refresh"
|
|
icon={<ReloadOutlined />}
|
|
onClick={() => queryClient.invalidateQueries({ queryKey: ['admin-users'] })}
|
|
>
|
|
刷新
|
|
</Button>,
|
|
],
|
|
}}
|
|
/>
|
|
|
|
<DetailDrawer
|
|
open={detailOpen}
|
|
onClose={() => setDetailOpen(false)}
|
|
title="管理员详情"
|
|
>
|
|
{selectedUser && (
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
<div>
|
|
<div style={{ fontSize: 12, color: '#999', marginBottom: 4 }}>姓名</div>
|
|
<div style={{ fontSize: 14 }}>{selectedUser.displayName}</div>
|
|
</div>
|
|
<div>
|
|
<div style={{ fontSize: 12, color: '#999', marginBottom: 4 }}>邮箱</div>
|
|
<div style={{ fontSize: 14 }}>{selectedUser.email}</div>
|
|
</div>
|
|
<div>
|
|
<div style={{ fontSize: 12, color: '#999', marginBottom: 4 }}>角色</div>
|
|
<Tag color={ADMIN_ROLE_COLORS[selectedUser.role]}>{ADMIN_ROLE_LABELS[selectedUser.role]}</Tag>
|
|
</div>
|
|
<div>
|
|
<div style={{ fontSize: 12, color: '#999', marginBottom: 4 }}>状态</div>
|
|
<Tag color={statusColorMap[selectedUser.status]}>{statusLabelMap[selectedUser.status]}</Tag>
|
|
</div>
|
|
<div>
|
|
<div style={{ fontSize: 12, color: '#999', marginBottom: 4 }}>两步验证</div>
|
|
<div style={{ fontSize: 14 }}>{selectedUser.twoFactorEnabled ? '已启用' : '未启用'}</div>
|
|
</div>
|
|
<div>
|
|
<div style={{ fontSize: 12, color: '#999', marginBottom: 4 }}>最后登录时间</div>
|
|
<div style={{ fontSize: 14 }}>{selectedUser.lastLoginAt || '-'}</div>
|
|
</div>
|
|
<div>
|
|
<div style={{ fontSize: 12, color: '#999', marginBottom: 4 }}>最后登录 IP</div>
|
|
<div style={{ fontSize: 14 }}>{selectedUser.lastLoginIp || '-'}</div>
|
|
</div>
|
|
<div>
|
|
<div style={{ fontSize: 12, color: '#999', marginBottom: 4 }}>创建时间</div>
|
|
<div style={{ fontSize: 14 }}>{selectedUser.createdAt}</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</DetailDrawer>
|
|
|
|
<ConfirmDangerModal
|
|
open={!!deleteTarget}
|
|
onCancel={() => setDeleteTarget(null)}
|
|
onConfirm={() => {
|
|
if (deleteTarget) {
|
|
deleteMutation.mutate(deleteTarget.id)
|
|
setDeleteTarget(null)
|
|
}
|
|
}}
|
|
title="删除管理员"
|
|
description="此操作不可撤销。请输入管理员邮箱确认删除:"
|
|
targetName={deleteTarget?.email || ''}
|
|
loading={deleteMutation.isPending}
|
|
/>
|
|
</>
|
|
)
|
|
}
|