feat: 移动端开发完成 - 包含完整的移动端应用和Web管理后台
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user