UsersTable.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. import React, { useEffect, useState } from 'react';
  2. import { API, showError, showSuccess } from '../helpers';
  3. import {
  4. Button,
  5. Form,
  6. Popconfirm,
  7. Space,
  8. Table,
  9. Tag,
  10. Tooltip,
  11. } from '@douyinfe/semi-ui';
  12. import { ITEMS_PER_PAGE } from '../constants';
  13. import { renderGroup, renderNumber, renderQuota } from '../helpers/render';
  14. import AddUser from '../pages/User/AddUser';
  15. import EditUser from '../pages/User/EditUser';
  16. function renderRole(role) {
  17. switch (role) {
  18. case 1:
  19. return <Tag size='large'>普通用户</Tag>;
  20. case 10:
  21. return (
  22. <Tag color='yellow' size='large'>
  23. 管理员
  24. </Tag>
  25. );
  26. case 100:
  27. return (
  28. <Tag color='orange' size='large'>
  29. 超级管理员
  30. </Tag>
  31. );
  32. default:
  33. return (
  34. <Tag color='red' size='large'>
  35. 未知身份
  36. </Tag>
  37. );
  38. }
  39. }
  40. const UsersTable = () => {
  41. const columns = [
  42. {
  43. title: 'ID',
  44. dataIndex: 'id',
  45. },
  46. {
  47. title: '用户名',
  48. dataIndex: 'username',
  49. },
  50. {
  51. title: '分组',
  52. dataIndex: 'group',
  53. render: (text, record, index) => {
  54. return <div>{renderGroup(text)}</div>;
  55. },
  56. },
  57. {
  58. title: '统计信息',
  59. dataIndex: 'info',
  60. render: (text, record, index) => {
  61. return (
  62. <div>
  63. <Space spacing={1}>
  64. <Tooltip content={'剩余额度'}>
  65. <Tag color='white' size='large'>
  66. {renderQuota(record.quota)}
  67. </Tag>
  68. </Tooltip>
  69. <Tooltip content={'已用额度'}>
  70. <Tag color='white' size='large'>
  71. {renderQuota(record.used_quota)}
  72. </Tag>
  73. </Tooltip>
  74. <Tooltip content={'调用次数'}>
  75. <Tag color='white' size='large'>
  76. {renderNumber(record.request_count)}
  77. </Tag>
  78. </Tooltip>
  79. </Space>
  80. </div>
  81. );
  82. },
  83. },
  84. {
  85. title: '邀请信息',
  86. dataIndex: 'invite',
  87. render: (text, record, index) => {
  88. return (
  89. <div>
  90. <Space spacing={1}>
  91. <Tooltip content={'邀请人数'}>
  92. <Tag color='white' size='large'>
  93. {renderNumber(record.aff_count)}
  94. </Tag>
  95. </Tooltip>
  96. <Tooltip content={'邀请总收益'}>
  97. <Tag color='white' size='large'>
  98. {renderQuota(record.aff_history_quota)}
  99. </Tag>
  100. </Tooltip>
  101. <Tooltip content={'邀请人ID'}>
  102. {record.inviter_id === 0 ? (
  103. <Tag color='white' size='large'>
  104. </Tag>
  105. ) : (
  106. <Tag color='white' size='large'>
  107. {record.inviter_id}
  108. </Tag>
  109. )}
  110. </Tooltip>
  111. </Space>
  112. </div>
  113. );
  114. },
  115. },
  116. {
  117. title: '角色',
  118. dataIndex: 'role',
  119. render: (text, record, index) => {
  120. return <div>{renderRole(text)}</div>;
  121. },
  122. },
  123. {
  124. title: '状态',
  125. dataIndex: 'status',
  126. render: (text, record, index) => {
  127. return (
  128. <div>
  129. {record.DeletedAt !== null ? (
  130. <Tag color='red'>已注销</Tag>
  131. ) : (
  132. renderStatus(text)
  133. )}
  134. </div>
  135. );
  136. },
  137. },
  138. {
  139. title: '',
  140. dataIndex: 'operate',
  141. render: (text, record, index) => (
  142. <div>
  143. {record.DeletedAt !== null ? (
  144. <></>
  145. ) : (
  146. <>
  147. <Popconfirm
  148. title='确定?'
  149. okType={'warning'}
  150. onConfirm={() => {
  151. manageUser(record.username, 'promote', record);
  152. }}
  153. >
  154. <Button theme='light' type='warning' style={{ marginRight: 1 }}>
  155. 提升
  156. </Button>
  157. </Popconfirm>
  158. <Popconfirm
  159. title='确定?'
  160. okType={'warning'}
  161. onConfirm={() => {
  162. manageUser(record.username, 'demote', record);
  163. }}
  164. >
  165. <Button
  166. theme='light'
  167. type='secondary'
  168. style={{ marginRight: 1 }}
  169. >
  170. 降级
  171. </Button>
  172. </Popconfirm>
  173. {record.status === 1 ? (
  174. <Button
  175. theme='light'
  176. type='warning'
  177. style={{ marginRight: 1 }}
  178. onClick={async () => {
  179. manageUser(record.username, 'disable', record);
  180. }}
  181. >
  182. 禁用
  183. </Button>
  184. ) : (
  185. <Button
  186. theme='light'
  187. type='secondary'
  188. style={{ marginRight: 1 }}
  189. onClick={async () => {
  190. manageUser(record.username, 'enable', record);
  191. }}
  192. disabled={record.status === 3}
  193. >
  194. 启用
  195. </Button>
  196. )}
  197. <Button
  198. theme='light'
  199. type='tertiary'
  200. style={{ marginRight: 1 }}
  201. onClick={() => {
  202. setEditingUser(record);
  203. setShowEditUser(true);
  204. }}
  205. >
  206. 编辑
  207. </Button>
  208. <Popconfirm
  209. title='确定是否要注销此用户?'
  210. content='相当于删除用户,此修改将不可逆'
  211. okType={'danger'}
  212. position={'left'}
  213. onConfirm={() => {
  214. manageUser(record.username, 'delete', record).then(() => {
  215. removeRecord(record.id);
  216. });
  217. }}
  218. >
  219. <Button theme='light' type='danger' style={{ marginRight: 1 }}>
  220. 注销
  221. </Button>
  222. </Popconfirm>
  223. </>
  224. )}
  225. </div>
  226. ),
  227. },
  228. ];
  229. const [users, setUsers] = useState([]);
  230. const [loading, setLoading] = useState(true);
  231. const [activePage, setActivePage] = useState(1);
  232. const [searchKeyword, setSearchKeyword] = useState('');
  233. const [searching, setSearching] = useState(false);
  234. const [searchGroup, setSearchGroup] = useState('');
  235. const [groupOptions, setGroupOptions] = useState([]);
  236. const [userCount, setUserCount] = useState(ITEMS_PER_PAGE);
  237. const [showAddUser, setShowAddUser] = useState(false);
  238. const [showEditUser, setShowEditUser] = useState(false);
  239. const [editingUser, setEditingUser] = useState({
  240. id: undefined,
  241. });
  242. const setCount = (data) => {
  243. if (data.length >= activePage * ITEMS_PER_PAGE) {
  244. setUserCount(data.length + 1);
  245. } else {
  246. setUserCount(data.length);
  247. }
  248. };
  249. const removeRecord = (key) => {
  250. let newDataSource = [...users];
  251. if (key != null) {
  252. let idx = newDataSource.findIndex((data) => data.id === key);
  253. if (idx > -1) {
  254. // update deletedAt
  255. newDataSource[idx].DeletedAt = new Date();
  256. setUsers(newDataSource);
  257. }
  258. }
  259. };
  260. const loadUsers = async (startIdx) => {
  261. const res = await API.get(`/api/user/?p=${startIdx}`);
  262. const { success, message, data } = res.data;
  263. if (success) {
  264. if (startIdx === 0) {
  265. setUsers(data);
  266. setCount(data);
  267. } else {
  268. let newUsers = users;
  269. newUsers.push(...data);
  270. setUsers(newUsers);
  271. setCount(newUsers);
  272. }
  273. } else {
  274. showError(message);
  275. }
  276. setLoading(false);
  277. };
  278. const onPaginationChange = (e, { activePage }) => {
  279. (async () => {
  280. if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
  281. // In this case we have to load more data and then append them.
  282. await loadUsers(activePage - 1);
  283. }
  284. setActivePage(activePage);
  285. })();
  286. };
  287. useEffect(() => {
  288. loadUsers(0)
  289. .then()
  290. .catch((reason) => {
  291. showError(reason);
  292. });
  293. fetchGroups().then();
  294. }, []);
  295. const manageUser = async (username, action, record) => {
  296. const res = await API.post('/api/user/manage', {
  297. username,
  298. action,
  299. });
  300. const { success, message } = res.data;
  301. if (success) {
  302. showSuccess('操作成功完成!');
  303. let user = res.data.data;
  304. let newUsers = [...users];
  305. if (action === 'delete') {
  306. } else {
  307. record.status = user.status;
  308. record.role = user.role;
  309. }
  310. setUsers(newUsers);
  311. } else {
  312. showError(message);
  313. }
  314. };
  315. const renderStatus = (status) => {
  316. switch (status) {
  317. case 1:
  318. return <Tag size='large'>已激活</Tag>;
  319. case 2:
  320. return (
  321. <Tag size='large' color='red'>
  322. 已封禁
  323. </Tag>
  324. );
  325. default:
  326. return (
  327. <Tag size='large' color='grey'>
  328. 未知状态
  329. </Tag>
  330. );
  331. }
  332. };
  333. const searchUsers = async (searchKeyword, searchGroup) => {
  334. if (searchKeyword === '' && searchGroup === '') {
  335. // if keyword is blank, load files instead.
  336. await loadUsers(0);
  337. setActivePage(1);
  338. return;
  339. }
  340. setSearching(true);
  341. const res = await API.get(`/api/user/search?keyword=${searchKeyword}&group=${searchGroup}`);
  342. const { success, message, data } = res.data;
  343. if (success) {
  344. setUsers(data);
  345. setActivePage(1);
  346. } else {
  347. showError(message);
  348. }
  349. setSearching(false);
  350. };
  351. const handleKeywordChange = async (value) => {
  352. setSearchKeyword(value.trim());
  353. };
  354. const sortUser = (key) => {
  355. if (users.length === 0) return;
  356. setLoading(true);
  357. let sortedUsers = [...users];
  358. sortedUsers.sort((a, b) => {
  359. return ('' + a[key]).localeCompare(b[key]);
  360. });
  361. if (sortedUsers[0].id === users[0].id) {
  362. sortedUsers.reverse();
  363. }
  364. setUsers(sortedUsers);
  365. setLoading(false);
  366. };
  367. const handlePageChange = (page) => {
  368. setActivePage(page);
  369. if (page === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
  370. // In this case we have to load more data and then append them.
  371. loadUsers(page - 1).then((r) => {});
  372. }
  373. };
  374. const pageData = users.slice(
  375. (activePage - 1) * ITEMS_PER_PAGE,
  376. activePage * ITEMS_PER_PAGE,
  377. );
  378. const closeAddUser = () => {
  379. setShowAddUser(false);
  380. };
  381. const closeEditUser = () => {
  382. setShowEditUser(false);
  383. setEditingUser({
  384. id: undefined,
  385. });
  386. };
  387. const refresh = async () => {
  388. if (searchKeyword === '') {
  389. await loadUsers(activePage - 1);
  390. } else {
  391. await searchUsers();
  392. }
  393. };
  394. const fetchGroups = async () => {
  395. try {
  396. let res = await API.get(`/api/group/`);
  397. // add 'all' option
  398. // res.data.data.unshift('all');
  399. if (res === undefined) {
  400. return;
  401. }
  402. setGroupOptions(
  403. res.data.data.map((group) => ({
  404. label: group,
  405. value: group,
  406. })),
  407. );
  408. } catch (error) {
  409. showError(error.message);
  410. }
  411. };
  412. return (
  413. <>
  414. <AddUser
  415. refresh={refresh}
  416. visible={showAddUser}
  417. handleClose={closeAddUser}
  418. ></AddUser>
  419. <EditUser
  420. refresh={refresh}
  421. visible={showEditUser}
  422. handleClose={closeEditUser}
  423. editingUser={editingUser}
  424. ></EditUser>
  425. <Form
  426. onSubmit={() => {
  427. searchUsers(searchKeyword, searchGroup);
  428. }}
  429. labelPosition='left'
  430. >
  431. <div style={{ display: 'flex' }}>
  432. <Space>
  433. <Form.Input
  434. label='搜索关键字'
  435. icon='search'
  436. field='keyword'
  437. iconPosition='left'
  438. placeholder='搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...'
  439. value={searchKeyword}
  440. loading={searching}
  441. onChange={(value) => handleKeywordChange(value)}
  442. />
  443. <Form.Select
  444. field='group'
  445. label='分组'
  446. optionList={groupOptions}
  447. onChange={(value) => {
  448. setSearchGroup(value);
  449. searchUsers(searchKeyword, value);
  450. }}
  451. />
  452. <Button
  453. label='查询'
  454. type='primary'
  455. htmlType='submit'
  456. className='btn-margin-right'
  457. style={{ marginRight: 8 }}
  458. >
  459. 查询
  460. </Button>
  461. </Space>
  462. </div>
  463. </Form>
  464. <Table
  465. columns={columns}
  466. dataSource={pageData}
  467. pagination={{
  468. currentPage: activePage,
  469. pageSize: ITEMS_PER_PAGE,
  470. total: userCount,
  471. pageSizeOpts: [10, 20, 50, 100],
  472. onPageChange: handlePageChange,
  473. }}
  474. loading={loading}
  475. />
  476. <Button
  477. theme='light'
  478. type='primary'
  479. style={{ marginRight: 8 }}
  480. onClick={() => {
  481. setShowAddUser(true);
  482. }}
  483. >
  484. 添加用户
  485. </Button>
  486. </>
  487. );
  488. };
  489. export default UsersTable;