| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- import React, { useState, useEffect } from 'react';
- import { Table, Button, Space, Tag, Input, Select, message, Modal, Form, Popconfirm, Card } from 'antd';
- import { PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, SwapOutlined, EyeOutlined } from '@ant-design/icons';
- import { useNavigate } from 'react-router-dom';
- import request from '../../utils/request';
- import { USER_ROLES, USER_STATUS } from '../../utils/constants';
- const { Search } = Input;
- const { Option } = Select;
- const UserList = () => {
- const navigate = useNavigate();
- const [loading, setLoading] = useState(false);
- const [dataSource, setDataSource] = useState([]);
- const [pagination, setPagination] = useState({ page: 1, limit: 20, total: 0 });
- const [filters, setFilters] = useState({});
- const [createModalVisible, setCreateModalVisible] = useState(false);
- const [editModalVisible, setEditModalVisible] = useState(false);
- const [resetPasswordModalVisible, setResetPasswordModalVisible] = useState(false);
- const [transferModalVisible, setTransferModalVisible] = useState(false);
- const [currentUser, setCurrentUser] = useState(null);
- const [allUsers, setAllUsers] = useState([]);
- const [createForm] = Form.useForm();
- const [editForm] = Form.useForm();
- const [resetPasswordForm] = Form.useForm();
- const [transferForm] = Form.useForm();
- useEffect(() => {
- fetchData();
- }, [pagination.page, pagination.limit, filters]);
- useEffect(() => {
- fetchAllUsers();
- }, []);
- const fetchData = async () => {
- setLoading(true);
- try {
- const params = {
- page: pagination.page,
- limit: pagination.limit,
- ...filters
- };
- const response = await request.get('/users', { params });
- if (response.success) {
- setDataSource(response.data.users || []);
- setPagination(prev => ({ ...prev, total: response.data.pagination.total }));
- }
- } catch (error) {
- message.error('获取用户列表失败');
- } finally {
- setLoading(false);
- }
- };
- const fetchAllUsers = async () => {
- try {
- const response = await request.get('/users', { params: { limit: 1000 } });
- if (response.success) {
- setAllUsers(response.data.users || []);
- }
- } catch (error) {
- console.error('获取用户列表失败');
- }
- };
- const handleSearch = (value) => {
- setFilters(prev => ({ ...prev, keyword: value }));
- setPagination(prev => ({ ...prev, page: 1 }));
- };
- const handleRoleChange = (value) => {
- setFilters(prev => ({ ...prev, role: value }));
- setPagination(prev => ({ ...prev, page: 1 }));
- };
- const handleStatusChange = (value) => {
- setFilters(prev => ({ ...prev, status: value }));
- setPagination(prev => ({ ...prev, page: 1 }));
- };
- const handleCreate = async (values) => {
- try {
- const response = await request.post('/users', values);
- if (response.success) {
- message.success('用户创建成功');
- setCreateModalVisible(false);
- createForm.resetFields();
- fetchData();
- fetchAllUsers();
- }
- } catch (error) {
- message.error(error.message || '创建用户失败');
- }
- };
- const handleEdit = async (values) => {
- try {
- const response = await request.put(`/users/${currentUser.id}`, values);
- if (response.success) {
- message.success('用户信息更新成功');
- setEditModalVisible(false);
- editForm.resetFields();
- setCurrentUser(null);
- fetchData();
- fetchAllUsers();
- }
- } catch (error) {
- message.error(error.message || '更新用户失败');
- }
- };
- const handleResetPassword = async (values) => {
- try {
- const response = await request.post(`/users/${currentUser.id}/reset-password`, values);
- if (response.success) {
- message.success('密码重置成功');
- setResetPasswordModalVisible(false);
- resetPasswordForm.resetFields();
- setCurrentUser(null);
- }
- } catch (error) {
- message.error(error.message || '重置密码失败');
- }
- };
- const handleDelete = async (id) => {
- try {
- const response = await request.delete(`/users/${id}`);
- if (response.success) {
- message.success('用户已禁用');
- fetchData();
- }
- } catch (error) {
- message.error(error.message || '禁用用户失败');
- }
- };
- const handleTransfer = async (values) => {
- try {
- const response = await request.post('/users/transfer-customers', values);
- if (response.success) {
- message.success(response.message);
- setTransferModalVisible(false);
- transferForm.resetFields();
- fetchData();
- }
- } catch (error) {
- message.error(error.message || '转移客户失败');
- }
- };
- const showEditModal = (record) => {
- setCurrentUser(record);
- editForm.setFieldsValue(record);
- setEditModalVisible(true);
- };
- const showResetPasswordModal = (record) => {
- setCurrentUser(record);
- setResetPasswordModalVisible(true);
- };
- const columns = [
- {
- title: '用户名',
- dataIndex: 'username',
- key: 'username',
- fixed: 'left',
- width: 120,
- },
- {
- title: '姓名',
- dataIndex: 'real_name',
- key: 'real_name',
- width: 100,
- },
- {
- title: '角色',
- dataIndex: 'role',
- key: 'role',
- width: 120,
- render: (role) => {
- const roleInfo = USER_ROLES.find(r => r.value === role);
- return <Tag color={roleInfo?.color || 'default'}>{roleInfo?.label || role}</Tag>;
- },
- },
- {
- title: '部门',
- dataIndex: 'department',
- key: 'department',
- width: 100,
- },
- {
- title: '团队',
- dataIndex: 'team',
- key: 'team',
- width: 100,
- },
- {
- title: '邮箱',
- dataIndex: 'email',
- key: 'email',
- width: 180,
- },
- {
- title: '手机',
- dataIndex: 'phone',
- key: 'phone',
- width: 120,
- },
- {
- title: '客户数',
- dataIndex: 'customer_count',
- key: 'customer_count',
- width: 80,
- render: (count) => count || 0,
- },
- {
- title: '成交数',
- dataIndex: 'closed_count',
- key: 'closed_count',
- width: 80,
- render: (count) => <span style={{ color: '#52c41a' }}>{count || 0}</span>,
- },
- {
- title: '状态',
- dataIndex: 'status',
- key: 'status',
- width: 80,
- render: (status) => {
- const statusInfo = USER_STATUS.find(s => s.value === status);
- return <Tag color={statusInfo?.color || 'default'}>{statusInfo?.label || status}</Tag>;
- },
- },
- {
- title: '最后登录',
- dataIndex: 'last_login',
- key: 'last_login',
- width: 160,
- render: (time) => time || '从未登录',
- },
- {
- title: '操作',
- key: 'action',
- fixed: 'right',
- width: 200,
- render: (_, record) => (
- <Space size="small">
- <Button
- type="link"
- size="small"
- icon={<EyeOutlined />}
- onClick={() => navigate(`/users/${record.id}`)}
- >
- 详情
- </Button>
- <Button
- type="link"
- size="small"
- icon={<EditOutlined />}
- onClick={() => showEditModal(record)}
- >
- 编辑
- </Button>
- <Button
- type="link"
- size="small"
- icon={<KeyOutlined />}
- onClick={() => showResetPasswordModal(record)}
- >
- 重置密码
- </Button>
- <Popconfirm
- title="确定要禁用该用户吗?"
- onConfirm={() => handleDelete(record.id)}
- okText="确定"
- cancelText="取消"
- >
- <Button type="link" size="small" danger icon={<DeleteOutlined />}>
- 禁用
- </Button>
- </Popconfirm>
- </Space>
- ),
- },
- ];
- return (
- <Card>
- <Space direction="vertical" style={{ width: '100%' }} size="large">
- <Space style={{ width: '100%', justifyContent: 'space-between' }}>
- <Space>
- <Search
- placeholder="搜索用户名、姓名、邮箱、手机"
- onSearch={handleSearch}
- style={{ width: 300 }}
- allowClear
- />
- <Select
- placeholder="角色"
- style={{ width: 120 }}
- onChange={handleRoleChange}
- allowClear
- >
- {USER_ROLES.map(role => (
- <Option key={role.value} value={role.value}>{role.label}</Option>
- ))}
- </Select>
- <Select
- placeholder="状态"
- style={{ width: 100 }}
- onChange={handleStatusChange}
- allowClear
- >
- {USER_STATUS.map(status => (
- <Option key={status.value} value={status.value}>{status.label}</Option>
- ))}
- </Select>
- </Space>
- <Space>
- <Button
- type="default"
- icon={<SwapOutlined />}
- onClick={() => setTransferModalVisible(true)}
- >
- 转移客户
- </Button>
- <Button
- type="primary"
- icon={<PlusOutlined />}
- onClick={() => setCreateModalVisible(true)}
- >
- 新建用户
- </Button>
- </Space>
- </Space>
- <Table
- columns={columns}
- dataSource={dataSource}
- rowKey="id"
- loading={loading}
- scroll={{ x: 1600 }}
- pagination={{
- current: pagination.page,
- pageSize: pagination.limit,
- total: pagination.total,
- showSizeChanger: true,
- showTotal: (total) => `共 ${total} 条`,
- onChange: (page, limit) => setPagination({ ...pagination, page, limit }),
- }}
- />
- </Space>
- {/* 创建用户 Modal */}
- <Modal
- title="新建用户"
- open={createModalVisible}
- onOk={() => createForm.submit()}
- onCancel={() => {
- setCreateModalVisible(false);
- createForm.resetFields();
- }}
- width={600}
- >
- <Form form={createForm} onFinish={handleCreate} layout="vertical">
- <Form.Item
- label="用户名"
- name="username"
- rules={[{ required: true, message: '请输入用户名' }]}
- >
- <Input placeholder="用于登录" />
- </Form.Item>
- <Form.Item
- label="密码"
- name="password"
- rules={[
- { required: true, message: '请输入密码' },
- { min: 6, message: '密码至少6位' }
- ]}
- >
- <Input.Password placeholder="至少6位" />
- </Form.Item>
- <Form.Item
- label="姓名"
- name="real_name"
- rules={[{ required: true, message: '请输入姓名' }]}
- >
- <Input />
- </Form.Item>
- <Form.Item
- label="角色"
- name="role"
- rules={[{ required: true, message: '请选择角色' }]}
- >
- <Select>
- {USER_ROLES.map(role => (
- <Option key={role.value} value={role.value}>{role.label}</Option>
- ))}
- </Select>
- </Form.Item>
- <Form.Item label="部门" name="department">
- <Input />
- </Form.Item>
- <Form.Item label="团队" name="team">
- <Input />
- </Form.Item>
- <Form.Item label="邮箱" name="email">
- <Input type="email" />
- </Form.Item>
- <Form.Item label="手机" name="phone">
- <Input />
- </Form.Item>
- </Form>
- </Modal>
- {/* 编辑用户 Modal */}
- <Modal
- title="编辑用户"
- open={editModalVisible}
- onOk={() => editForm.submit()}
- onCancel={() => {
- setEditModalVisible(false);
- editForm.resetFields();
- setCurrentUser(null);
- }}
- width={600}
- >
- <Form form={editForm} onFinish={handleEdit} layout="vertical">
- <Form.Item label="姓名" name="real_name">
- <Input />
- </Form.Item>
- <Form.Item label="角色" name="role">
- <Select>
- {USER_ROLES.map(role => (
- <Option key={role.value} value={role.value}>{role.label}</Option>
- ))}
- </Select>
- </Form.Item>
- <Form.Item label="部门" name="department">
- <Input />
- </Form.Item>
- <Form.Item label="团队" name="team">
- <Input />
- </Form.Item>
- <Form.Item label="邮箱" name="email">
- <Input type="email" />
- </Form.Item>
- <Form.Item label="手机" name="phone">
- <Input />
- </Form.Item>
- <Form.Item label="状态" name="status">
- <Select>
- {USER_STATUS.map(status => (
- <Option key={status.value} value={status.value}>{status.label}</Option>
- ))}
- </Select>
- </Form.Item>
- </Form>
- </Modal>
- {/* 重置密码 Modal */}
- <Modal
- title="重置密码"
- open={resetPasswordModalVisible}
- onOk={() => resetPasswordForm.submit()}
- onCancel={() => {
- setResetPasswordModalVisible(false);
- resetPasswordForm.resetFields();
- setCurrentUser(null);
- }}
- >
- <Form form={resetPasswordForm} onFinish={handleResetPassword} layout="vertical">
- <Form.Item
- label="新密码"
- name="new_password"
- rules={[
- { required: true, message: '请输入新密码' },
- { min: 6, message: '密码至少6位' }
- ]}
- >
- <Input.Password />
- </Form.Item>
- </Form>
- </Modal>
- {/* 转移客户 Modal */}
- <Modal
- title="转移客户"
- open={transferModalVisible}
- onOk={() => transferForm.submit()}
- onCancel={() => {
- setTransferModalVisible(false);
- transferForm.resetFields();
- }}
- >
- <Form form={transferForm} onFinish={handleTransfer} layout="vertical">
- <Form.Item
- label="源用户"
- name="from_user_id"
- rules={[{ required: true, message: '请选择源用户' }]}
- >
- <Select
- showSearch
- placeholder="选择要转移客户的用户"
- optionFilterProp="children"
- >
- {allUsers.map(user => (
- <Option key={user.id} value={user.id}>
- {user.real_name} ({user.username}) - {user.customer_count || 0}个客户
- </Option>
- ))}
- </Select>
- </Form.Item>
- <Form.Item
- label="目标用户"
- name="to_user_id"
- rules={[{ required: true, message: '请选择目标用户' }]}
- >
- <Select
- showSearch
- placeholder="选择接收客户的用户"
- optionFilterProp="children"
- >
- {allUsers.map(user => (
- <Option key={user.id} value={user.id}>
- {user.real_name} ({user.username})
- </Option>
- ))}
- </Select>
- </Form.Item>
- </Form>
- </Modal>
- </Card>
- );
- };
- export default UserList;
|