391 lines
10 KiB
TypeScript
391 lines
10 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import {
|
|
Table,
|
|
Card,
|
|
Button,
|
|
Space,
|
|
Tag,
|
|
Modal,
|
|
Form,
|
|
Input,
|
|
Select,
|
|
message,
|
|
Typography,
|
|
Row,
|
|
Col,
|
|
Statistic
|
|
} from 'antd';
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
import {
|
|
PlusOutlined,
|
|
EditOutlined,
|
|
DeleteOutlined,
|
|
SearchOutlined,
|
|
UserOutlined,
|
|
TeamOutlined,
|
|
CrownOutlined
|
|
} from '@ant-design/icons';
|
|
import { useLoading } from '@/store';
|
|
import { formatDate } from '@/utils';
|
|
|
|
const { Title } = Typography;
|
|
const { Option } = Select;
|
|
|
|
interface User {
|
|
id: string;
|
|
name: string;
|
|
email: string;
|
|
phone: string;
|
|
role: 'admin' | 'translator' | 'user';
|
|
status: 'active' | 'inactive' | 'suspended';
|
|
registeredAt: Date;
|
|
lastLoginAt: Date;
|
|
}
|
|
|
|
const UserList = () => {
|
|
const [users, setUsers] = useState<User[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [modalVisible, setModalVisible] = useState(false);
|
|
const [editingUser, setEditingUser] = useState<User | null>(null);
|
|
const [searchText, setSearchText] = useState('');
|
|
const [form] = Form.useForm();
|
|
const { setLoading: setGlobalLoading } = useLoading();
|
|
|
|
// 模拟数据
|
|
const mockUsers: User[] = [
|
|
{
|
|
id: '1',
|
|
name: '张三',
|
|
email: 'zhangsan@example.com',
|
|
phone: '13800138001',
|
|
role: 'admin',
|
|
status: 'active',
|
|
registeredAt: new Date('2023-01-15'),
|
|
lastLoginAt: new Date('2024-01-15'),
|
|
},
|
|
{
|
|
id: '2',
|
|
name: '李四',
|
|
email: 'lisi@example.com',
|
|
phone: '13800138002',
|
|
role: 'translator',
|
|
status: 'active',
|
|
registeredAt: new Date('2023-02-20'),
|
|
lastLoginAt: new Date('2024-01-14'),
|
|
},
|
|
{
|
|
id: '3',
|
|
name: '王五',
|
|
email: 'wangwu@example.com',
|
|
phone: '13800138003',
|
|
role: 'user',
|
|
status: 'inactive',
|
|
registeredAt: new Date('2023-03-10'),
|
|
lastLoginAt: new Date('2024-01-10'),
|
|
},
|
|
];
|
|
|
|
const columns: ColumnsType<User> = [
|
|
{
|
|
title: '姓名',
|
|
dataIndex: 'name',
|
|
key: 'name',
|
|
filteredValue: searchText ? [searchText] : null,
|
|
onFilter: (value, record) =>
|
|
record.name.toLowerCase().includes(String(value).toLowerCase()) ||
|
|
record.email.toLowerCase().includes(String(value).toLowerCase()),
|
|
},
|
|
{
|
|
title: '邮箱',
|
|
dataIndex: 'email',
|
|
key: 'email',
|
|
},
|
|
{
|
|
title: '电话',
|
|
dataIndex: 'phone',
|
|
key: 'phone',
|
|
},
|
|
{
|
|
title: '角色',
|
|
dataIndex: 'role',
|
|
key: 'role',
|
|
render: (role: string) => {
|
|
const roleMap = {
|
|
admin: { color: 'red', text: '管理员', icon: <CrownOutlined /> },
|
|
translator: { color: 'blue', text: '译员', icon: <TeamOutlined /> },
|
|
user: { color: 'green', text: '用户', icon: <UserOutlined /> },
|
|
};
|
|
const roleInfo = roleMap[role as keyof typeof roleMap];
|
|
return (
|
|
<Tag color={roleInfo.color} icon={roleInfo.icon}>
|
|
{roleInfo.text}
|
|
</Tag>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
key: 'status',
|
|
render: (status: string) => {
|
|
const statusMap = {
|
|
active: { color: 'green', text: '活跃' },
|
|
inactive: { color: 'orange', text: '不活跃' },
|
|
suspended: { color: 'red', text: '已暂停' },
|
|
};
|
|
const statusInfo = statusMap[status as keyof typeof statusMap];
|
|
return <Tag color={statusInfo.color}>{statusInfo.text}</Tag>;
|
|
},
|
|
},
|
|
{
|
|
title: '注册时间',
|
|
dataIndex: 'registeredAt',
|
|
key: 'registeredAt',
|
|
render: (date: Date) => formatDate(date),
|
|
},
|
|
{
|
|
title: '最后登录',
|
|
dataIndex: 'lastLoginAt',
|
|
key: 'lastLoginAt',
|
|
render: (date: Date) => formatDate(date),
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
render: (_, record) => (
|
|
<Space size="middle">
|
|
<Button
|
|
type="link"
|
|
icon={<EditOutlined />}
|
|
onClick={() => handleEdit(record)}
|
|
>
|
|
编辑
|
|
</Button>
|
|
<Button
|
|
type="link"
|
|
danger
|
|
icon={<DeleteOutlined />}
|
|
onClick={() => handleDelete(record.id)}
|
|
>
|
|
删除
|
|
</Button>
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
const handleEdit = (user: User) => {
|
|
setEditingUser(user);
|
|
form.setFieldsValue(user);
|
|
setModalVisible(true);
|
|
};
|
|
|
|
const handleDelete = (userId: string) => {
|
|
Modal.confirm({
|
|
title: '确认删除',
|
|
content: '确定要删除这个用户吗?',
|
|
onOk: () => {
|
|
setUsers(users.filter(user => user.id !== userId));
|
|
message.success('删除成功');
|
|
},
|
|
});
|
|
};
|
|
|
|
const handleSubmit = async (values: any) => {
|
|
try {
|
|
setLoading(true);
|
|
if (editingUser) {
|
|
// 编辑用户
|
|
setUsers(users.map(user =>
|
|
user.id === editingUser.id ? { ...user, ...values } : user
|
|
));
|
|
message.success('更新成功');
|
|
} else {
|
|
// 新增用户
|
|
const newUser: User = {
|
|
id: Date.now().toString(),
|
|
...values,
|
|
registeredAt: new Date(),
|
|
lastLoginAt: new Date(),
|
|
};
|
|
setUsers([...users, newUser]);
|
|
message.success('添加成功');
|
|
}
|
|
setModalVisible(false);
|
|
form.resetFields();
|
|
setEditingUser(null);
|
|
} catch (error) {
|
|
message.error('操作失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setModalVisible(false);
|
|
form.resetFields();
|
|
setEditingUser(null);
|
|
};
|
|
|
|
useEffect(() => {
|
|
setGlobalLoading(true);
|
|
// 模拟数据加载
|
|
setTimeout(() => {
|
|
setUsers(mockUsers);
|
|
setGlobalLoading(false);
|
|
}, 1000);
|
|
}, [setGlobalLoading]);
|
|
|
|
const stats = {
|
|
total: users.length,
|
|
active: users.filter(u => u.status === 'active').length,
|
|
translators: users.filter(u => u.role === 'translator').length,
|
|
admins: users.filter(u => u.role === 'admin').length,
|
|
};
|
|
|
|
return (
|
|
<div style={{ padding: '24px' }}>
|
|
<Title level={2}>用户管理</Title>
|
|
|
|
{/* 统计卡片 */}
|
|
<Row gutter={16} style={{ marginBottom: '24px' }}>
|
|
<Col span={6}>
|
|
<Card>
|
|
<Statistic
|
|
title="总用户数"
|
|
value={stats.total}
|
|
prefix={<UserOutlined />}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Card>
|
|
<Statistic
|
|
title="活跃用户"
|
|
value={stats.active}
|
|
prefix={<TeamOutlined />}
|
|
valueStyle={{ color: '#3f8600' }}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Card>
|
|
<Statistic
|
|
title="译员数量"
|
|
value={stats.translators}
|
|
prefix={<TeamOutlined />}
|
|
valueStyle={{ color: '#1890ff' }}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Card>
|
|
<Statistic
|
|
title="管理员"
|
|
value={stats.admins}
|
|
prefix={<CrownOutlined />}
|
|
valueStyle={{ color: '#cf1322' }}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
|
|
<Card>
|
|
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
|
|
<Space>
|
|
<Input
|
|
placeholder="搜索用户..."
|
|
prefix={<SearchOutlined />}
|
|
value={searchText}
|
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchText(e.target.value)}
|
|
style={{ width: 300 }}
|
|
/>
|
|
</Space>
|
|
<Button
|
|
type="primary"
|
|
icon={<PlusOutlined />}
|
|
onClick={() => setModalVisible(true)}
|
|
>
|
|
添加用户
|
|
</Button>
|
|
</div>
|
|
|
|
<Table
|
|
columns={columns}
|
|
dataSource={users}
|
|
rowKey="id"
|
|
loading={loading}
|
|
pagination={{
|
|
pageSize: 10,
|
|
showSizeChanger: true,
|
|
showQuickJumper: true,
|
|
showTotal: (total) => `共 ${total} 条记录`,
|
|
}}
|
|
/>
|
|
</Card>
|
|
|
|
<Modal
|
|
title={editingUser ? '编辑用户' : '添加用户'}
|
|
open={modalVisible}
|
|
onOk={() => form.submit()}
|
|
onCancel={handleCancel}
|
|
confirmLoading={loading}
|
|
>
|
|
<Form
|
|
form={form}
|
|
layout="vertical"
|
|
onFinish={handleSubmit}
|
|
>
|
|
<Form.Item
|
|
label="姓名"
|
|
name="name"
|
|
rules={[{ required: true, message: '请输入姓名' }]}
|
|
>
|
|
<Input />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label="邮箱"
|
|
name="email"
|
|
rules={[
|
|
{ required: true, message: '请输入邮箱' },
|
|
{ type: 'email', message: '请输入有效邮箱' },
|
|
]}
|
|
>
|
|
<Input />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label="电话"
|
|
name="phone"
|
|
rules={[{ required: true, message: '请输入电话' }]}
|
|
>
|
|
<Input />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label="角色"
|
|
name="role"
|
|
rules={[{ required: true, message: '请选择角色' }]}
|
|
>
|
|
<Select>
|
|
<Option value="user">用户</Option>
|
|
<Option value="translator">译员</Option>
|
|
<Option value="admin">管理员</Option>
|
|
</Select>
|
|
</Form.Item>
|
|
<Form.Item
|
|
label="状态"
|
|
name="status"
|
|
rules={[{ required: true, message: '请选择状态' }]}
|
|
>
|
|
<Select>
|
|
<Option value="active">活跃</Option>
|
|
<Option value="inactive">不活跃</Option>
|
|
<Option value="suspended">已暂停</Option>
|
|
</Select>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default UserList;
|