UserList.jsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. import React, { useState, useEffect } from 'react';
  2. import { Table, Button, Space, Tag, Input, Select, message, Modal, Form, Popconfirm, Card } from 'antd';
  3. import { PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, SwapOutlined, EyeOutlined } from '@ant-design/icons';
  4. import { useNavigate } from 'react-router-dom';
  5. import request from '../../utils/request';
  6. import { USER_ROLES, USER_STATUS } from '../../utils/constants';
  7. const { Search } = Input;
  8. const { Option } = Select;
  9. const UserList = () => {
  10. const navigate = useNavigate();
  11. const [loading, setLoading] = useState(false);
  12. const [dataSource, setDataSource] = useState([]);
  13. const [pagination, setPagination] = useState({ page: 1, limit: 20, total: 0 });
  14. const [filters, setFilters] = useState({});
  15. const [createModalVisible, setCreateModalVisible] = useState(false);
  16. const [editModalVisible, setEditModalVisible] = useState(false);
  17. const [resetPasswordModalVisible, setResetPasswordModalVisible] = useState(false);
  18. const [transferModalVisible, setTransferModalVisible] = useState(false);
  19. const [currentUser, setCurrentUser] = useState(null);
  20. const [allUsers, setAllUsers] = useState([]);
  21. const [createForm] = Form.useForm();
  22. const [editForm] = Form.useForm();
  23. const [resetPasswordForm] = Form.useForm();
  24. const [transferForm] = Form.useForm();
  25. useEffect(() => {
  26. fetchData();
  27. }, [pagination.page, pagination.limit, filters]);
  28. useEffect(() => {
  29. fetchAllUsers();
  30. }, []);
  31. const fetchData = async () => {
  32. setLoading(true);
  33. try {
  34. const params = {
  35. page: pagination.page,
  36. limit: pagination.limit,
  37. ...filters
  38. };
  39. const response = await request.get('/users', { params });
  40. if (response.success) {
  41. setDataSource(response.data.users || []);
  42. setPagination(prev => ({ ...prev, total: response.data.pagination.total }));
  43. }
  44. } catch (error) {
  45. message.error('获取用户列表失败');
  46. } finally {
  47. setLoading(false);
  48. }
  49. };
  50. const fetchAllUsers = async () => {
  51. try {
  52. const response = await request.get('/users', { params: { limit: 1000 } });
  53. if (response.success) {
  54. setAllUsers(response.data.users || []);
  55. }
  56. } catch (error) {
  57. console.error('获取用户列表失败');
  58. }
  59. };
  60. const handleSearch = (value) => {
  61. setFilters(prev => ({ ...prev, keyword: value }));
  62. setPagination(prev => ({ ...prev, page: 1 }));
  63. };
  64. const handleRoleChange = (value) => {
  65. setFilters(prev => ({ ...prev, role: value }));
  66. setPagination(prev => ({ ...prev, page: 1 }));
  67. };
  68. const handleStatusChange = (value) => {
  69. setFilters(prev => ({ ...prev, status: value }));
  70. setPagination(prev => ({ ...prev, page: 1 }));
  71. };
  72. const handleCreate = async (values) => {
  73. try {
  74. const response = await request.post('/users', values);
  75. if (response.success) {
  76. message.success('用户创建成功');
  77. setCreateModalVisible(false);
  78. createForm.resetFields();
  79. fetchData();
  80. fetchAllUsers();
  81. }
  82. } catch (error) {
  83. message.error(error.message || '创建用户失败');
  84. }
  85. };
  86. const handleEdit = async (values) => {
  87. try {
  88. const response = await request.put(`/users/${currentUser.id}`, values);
  89. if (response.success) {
  90. message.success('用户信息更新成功');
  91. setEditModalVisible(false);
  92. editForm.resetFields();
  93. setCurrentUser(null);
  94. fetchData();
  95. fetchAllUsers();
  96. }
  97. } catch (error) {
  98. message.error(error.message || '更新用户失败');
  99. }
  100. };
  101. const handleResetPassword = async (values) => {
  102. try {
  103. const response = await request.post(`/users/${currentUser.id}/reset-password`, values);
  104. if (response.success) {
  105. message.success('密码重置成功');
  106. setResetPasswordModalVisible(false);
  107. resetPasswordForm.resetFields();
  108. setCurrentUser(null);
  109. }
  110. } catch (error) {
  111. message.error(error.message || '重置密码失败');
  112. }
  113. };
  114. const handleDelete = async (id) => {
  115. try {
  116. const response = await request.delete(`/users/${id}`);
  117. if (response.success) {
  118. message.success('用户已禁用');
  119. fetchData();
  120. }
  121. } catch (error) {
  122. message.error(error.message || '禁用用户失败');
  123. }
  124. };
  125. const handleTransfer = async (values) => {
  126. try {
  127. const response = await request.post('/users/transfer-customers', values);
  128. if (response.success) {
  129. message.success(response.message);
  130. setTransferModalVisible(false);
  131. transferForm.resetFields();
  132. fetchData();
  133. }
  134. } catch (error) {
  135. message.error(error.message || '转移客户失败');
  136. }
  137. };
  138. const showEditModal = (record) => {
  139. setCurrentUser(record);
  140. editForm.setFieldsValue(record);
  141. setEditModalVisible(true);
  142. };
  143. const showResetPasswordModal = (record) => {
  144. setCurrentUser(record);
  145. setResetPasswordModalVisible(true);
  146. };
  147. const columns = [
  148. {
  149. title: '用户名',
  150. dataIndex: 'username',
  151. key: 'username',
  152. fixed: 'left',
  153. width: 120,
  154. },
  155. {
  156. title: '姓名',
  157. dataIndex: 'real_name',
  158. key: 'real_name',
  159. width: 100,
  160. },
  161. {
  162. title: '角色',
  163. dataIndex: 'role',
  164. key: 'role',
  165. width: 120,
  166. render: (role) => {
  167. const roleInfo = USER_ROLES.find(r => r.value === role);
  168. return <Tag color={roleInfo?.color || 'default'}>{roleInfo?.label || role}</Tag>;
  169. },
  170. },
  171. {
  172. title: '部门',
  173. dataIndex: 'department',
  174. key: 'department',
  175. width: 100,
  176. },
  177. {
  178. title: '团队',
  179. dataIndex: 'team',
  180. key: 'team',
  181. width: 100,
  182. },
  183. {
  184. title: '邮箱',
  185. dataIndex: 'email',
  186. key: 'email',
  187. width: 180,
  188. },
  189. {
  190. title: '手机',
  191. dataIndex: 'phone',
  192. key: 'phone',
  193. width: 120,
  194. },
  195. {
  196. title: '客户数',
  197. dataIndex: 'customer_count',
  198. key: 'customer_count',
  199. width: 80,
  200. render: (count) => count || 0,
  201. },
  202. {
  203. title: '成交数',
  204. dataIndex: 'closed_count',
  205. key: 'closed_count',
  206. width: 80,
  207. render: (count) => <span style={{ color: '#52c41a' }}>{count || 0}</span>,
  208. },
  209. {
  210. title: '状态',
  211. dataIndex: 'status',
  212. key: 'status',
  213. width: 80,
  214. render: (status) => {
  215. const statusInfo = USER_STATUS.find(s => s.value === status);
  216. return <Tag color={statusInfo?.color || 'default'}>{statusInfo?.label || status}</Tag>;
  217. },
  218. },
  219. {
  220. title: '最后登录',
  221. dataIndex: 'last_login',
  222. key: 'last_login',
  223. width: 160,
  224. render: (time) => time || '从未登录',
  225. },
  226. {
  227. title: '操作',
  228. key: 'action',
  229. fixed: 'right',
  230. width: 200,
  231. render: (_, record) => (
  232. <Space size="small">
  233. <Button
  234. type="link"
  235. size="small"
  236. icon={<EyeOutlined />}
  237. onClick={() => navigate(`/users/${record.id}`)}
  238. >
  239. 详情
  240. </Button>
  241. <Button
  242. type="link"
  243. size="small"
  244. icon={<EditOutlined />}
  245. onClick={() => showEditModal(record)}
  246. >
  247. 编辑
  248. </Button>
  249. <Button
  250. type="link"
  251. size="small"
  252. icon={<KeyOutlined />}
  253. onClick={() => showResetPasswordModal(record)}
  254. >
  255. 重置密码
  256. </Button>
  257. <Popconfirm
  258. title="确定要禁用该用户吗?"
  259. onConfirm={() => handleDelete(record.id)}
  260. okText="确定"
  261. cancelText="取消"
  262. >
  263. <Button type="link" size="small" danger icon={<DeleteOutlined />}>
  264. 禁用
  265. </Button>
  266. </Popconfirm>
  267. </Space>
  268. ),
  269. },
  270. ];
  271. return (
  272. <Card>
  273. <Space direction="vertical" style={{ width: '100%' }} size="large">
  274. <Space style={{ width: '100%', justifyContent: 'space-between' }}>
  275. <Space>
  276. <Search
  277. placeholder="搜索用户名、姓名、邮箱、手机"
  278. onSearch={handleSearch}
  279. style={{ width: 300 }}
  280. allowClear
  281. />
  282. <Select
  283. placeholder="角色"
  284. style={{ width: 120 }}
  285. onChange={handleRoleChange}
  286. allowClear
  287. >
  288. {USER_ROLES.map(role => (
  289. <Option key={role.value} value={role.value}>{role.label}</Option>
  290. ))}
  291. </Select>
  292. <Select
  293. placeholder="状态"
  294. style={{ width: 100 }}
  295. onChange={handleStatusChange}
  296. allowClear
  297. >
  298. {USER_STATUS.map(status => (
  299. <Option key={status.value} value={status.value}>{status.label}</Option>
  300. ))}
  301. </Select>
  302. </Space>
  303. <Space>
  304. <Button
  305. type="default"
  306. icon={<SwapOutlined />}
  307. onClick={() => setTransferModalVisible(true)}
  308. >
  309. 转移客户
  310. </Button>
  311. <Button
  312. type="primary"
  313. icon={<PlusOutlined />}
  314. onClick={() => setCreateModalVisible(true)}
  315. >
  316. 新建用户
  317. </Button>
  318. </Space>
  319. </Space>
  320. <Table
  321. columns={columns}
  322. dataSource={dataSource}
  323. rowKey="id"
  324. loading={loading}
  325. scroll={{ x: 1600 }}
  326. pagination={{
  327. current: pagination.page,
  328. pageSize: pagination.limit,
  329. total: pagination.total,
  330. showSizeChanger: true,
  331. showTotal: (total) => `共 ${total} 条`,
  332. onChange: (page, limit) => setPagination({ ...pagination, page, limit }),
  333. }}
  334. />
  335. </Space>
  336. {/* 创建用户 Modal */}
  337. <Modal
  338. title="新建用户"
  339. open={createModalVisible}
  340. onOk={() => createForm.submit()}
  341. onCancel={() => {
  342. setCreateModalVisible(false);
  343. createForm.resetFields();
  344. }}
  345. width={600}
  346. >
  347. <Form form={createForm} onFinish={handleCreate} layout="vertical">
  348. <Form.Item
  349. label="用户名"
  350. name="username"
  351. rules={[{ required: true, message: '请输入用户名' }]}
  352. >
  353. <Input placeholder="用于登录" />
  354. </Form.Item>
  355. <Form.Item
  356. label="密码"
  357. name="password"
  358. rules={[
  359. { required: true, message: '请输入密码' },
  360. { min: 6, message: '密码至少6位' }
  361. ]}
  362. >
  363. <Input.Password placeholder="至少6位" />
  364. </Form.Item>
  365. <Form.Item
  366. label="姓名"
  367. name="real_name"
  368. rules={[{ required: true, message: '请输入姓名' }]}
  369. >
  370. <Input />
  371. </Form.Item>
  372. <Form.Item
  373. label="角色"
  374. name="role"
  375. rules={[{ required: true, message: '请选择角色' }]}
  376. >
  377. <Select>
  378. {USER_ROLES.map(role => (
  379. <Option key={role.value} value={role.value}>{role.label}</Option>
  380. ))}
  381. </Select>
  382. </Form.Item>
  383. <Form.Item label="部门" name="department">
  384. <Input />
  385. </Form.Item>
  386. <Form.Item label="团队" name="team">
  387. <Input />
  388. </Form.Item>
  389. <Form.Item label="邮箱" name="email">
  390. <Input type="email" />
  391. </Form.Item>
  392. <Form.Item label="手机" name="phone">
  393. <Input />
  394. </Form.Item>
  395. </Form>
  396. </Modal>
  397. {/* 编辑用户 Modal */}
  398. <Modal
  399. title="编辑用户"
  400. open={editModalVisible}
  401. onOk={() => editForm.submit()}
  402. onCancel={() => {
  403. setEditModalVisible(false);
  404. editForm.resetFields();
  405. setCurrentUser(null);
  406. }}
  407. width={600}
  408. >
  409. <Form form={editForm} onFinish={handleEdit} layout="vertical">
  410. <Form.Item label="姓名" name="real_name">
  411. <Input />
  412. </Form.Item>
  413. <Form.Item label="角色" name="role">
  414. <Select>
  415. {USER_ROLES.map(role => (
  416. <Option key={role.value} value={role.value}>{role.label}</Option>
  417. ))}
  418. </Select>
  419. </Form.Item>
  420. <Form.Item label="部门" name="department">
  421. <Input />
  422. </Form.Item>
  423. <Form.Item label="团队" name="team">
  424. <Input />
  425. </Form.Item>
  426. <Form.Item label="邮箱" name="email">
  427. <Input type="email" />
  428. </Form.Item>
  429. <Form.Item label="手机" name="phone">
  430. <Input />
  431. </Form.Item>
  432. <Form.Item label="状态" name="status">
  433. <Select>
  434. {USER_STATUS.map(status => (
  435. <Option key={status.value} value={status.value}>{status.label}</Option>
  436. ))}
  437. </Select>
  438. </Form.Item>
  439. </Form>
  440. </Modal>
  441. {/* 重置密码 Modal */}
  442. <Modal
  443. title="重置密码"
  444. open={resetPasswordModalVisible}
  445. onOk={() => resetPasswordForm.submit()}
  446. onCancel={() => {
  447. setResetPasswordModalVisible(false);
  448. resetPasswordForm.resetFields();
  449. setCurrentUser(null);
  450. }}
  451. >
  452. <Form form={resetPasswordForm} onFinish={handleResetPassword} layout="vertical">
  453. <Form.Item
  454. label="新密码"
  455. name="new_password"
  456. rules={[
  457. { required: true, message: '请输入新密码' },
  458. { min: 6, message: '密码至少6位' }
  459. ]}
  460. >
  461. <Input.Password />
  462. </Form.Item>
  463. </Form>
  464. </Modal>
  465. {/* 转移客户 Modal */}
  466. <Modal
  467. title="转移客户"
  468. open={transferModalVisible}
  469. onOk={() => transferForm.submit()}
  470. onCancel={() => {
  471. setTransferModalVisible(false);
  472. transferForm.resetFields();
  473. }}
  474. >
  475. <Form form={transferForm} onFinish={handleTransfer} layout="vertical">
  476. <Form.Item
  477. label="源用户"
  478. name="from_user_id"
  479. rules={[{ required: true, message: '请选择源用户' }]}
  480. >
  481. <Select
  482. showSearch
  483. placeholder="选择要转移客户的用户"
  484. optionFilterProp="children"
  485. >
  486. {allUsers.map(user => (
  487. <Option key={user.id} value={user.id}>
  488. {user.real_name} ({user.username}) - {user.customer_count || 0}个客户
  489. </Option>
  490. ))}
  491. </Select>
  492. </Form.Item>
  493. <Form.Item
  494. label="目标用户"
  495. name="to_user_id"
  496. rules={[{ required: true, message: '请选择目标用户' }]}
  497. >
  498. <Select
  499. showSearch
  500. placeholder="选择接收客户的用户"
  501. optionFilterProp="children"
  502. >
  503. {allUsers.map(user => (
  504. <Option key={user.id} value={user.id}>
  505. {user.real_name} ({user.username})
  506. </Option>
  507. ))}
  508. </Select>
  509. </Form.Item>
  510. </Form>
  511. </Modal>
  512. </Card>
  513. );
  514. };
  515. export default UserList;