UsersTable.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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. </>
  209. )}
  210. <Popconfirm
  211. title='确定是否要删除此用户?'
  212. content='硬删除,此修改将不可逆'
  213. okType={'danger'}
  214. position={'left'}
  215. onConfirm={() => {
  216. manageUser(record.username, 'delete', record).then(() => {
  217. removeRecord(record.id);
  218. });
  219. }}
  220. >
  221. <Button theme='light' type='danger' style={{ marginRight: 1 }}>
  222. 删除
  223. </Button>
  224. </Popconfirm>
  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 [userCount, setUserCount] = useState(ITEMS_PER_PAGE);
  235. const [showAddUser, setShowAddUser] = useState(false);
  236. const [showEditUser, setShowEditUser] = useState(false);
  237. const [editingUser, setEditingUser] = useState({
  238. id: undefined,
  239. });
  240. const setCount = (data) => {
  241. if (data.length >= activePage * ITEMS_PER_PAGE) {
  242. setUserCount(data.length + 1);
  243. } else {
  244. setUserCount(data.length);
  245. }
  246. };
  247. const removeRecord = (key) => {
  248. console.log(key);
  249. let newDataSource = [...users];
  250. if (key != null) {
  251. let idx = newDataSource.findIndex((data) => data.id === key);
  252. if (idx > -1) {
  253. newDataSource.splice(idx, 1);
  254. setUsers(newDataSource);
  255. }
  256. }
  257. };
  258. const loadUsers = async (startIdx) => {
  259. const res = await API.get(`/api/user/?p=${startIdx}`);
  260. const { success, message, data } = res.data;
  261. if (success) {
  262. if (startIdx === 0) {
  263. setUsers(data);
  264. setCount(data);
  265. } else {
  266. let newUsers = users;
  267. newUsers.push(...data);
  268. setUsers(newUsers);
  269. setCount(newUsers);
  270. }
  271. } else {
  272. showError(message);
  273. }
  274. setLoading(false);
  275. };
  276. const onPaginationChange = (e, { activePage }) => {
  277. (async () => {
  278. if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
  279. // In this case we have to load more data and then append them.
  280. await loadUsers(activePage - 1);
  281. }
  282. setActivePage(activePage);
  283. })();
  284. };
  285. useEffect(() => {
  286. loadUsers(0)
  287. .then()
  288. .catch((reason) => {
  289. showError(reason);
  290. });
  291. }, []);
  292. const manageUser = async (username, action, record) => {
  293. const res = await API.post('/api/user/manage', {
  294. username,
  295. action,
  296. });
  297. const { success, message } = res.data;
  298. if (success) {
  299. showSuccess('操作成功完成!');
  300. let user = res.data.data;
  301. let newUsers = [...users];
  302. if (action === 'delete') {
  303. } else {
  304. record.status = user.status;
  305. record.role = user.role;
  306. }
  307. setUsers(newUsers);
  308. } else {
  309. showError(message);
  310. }
  311. };
  312. const renderStatus = (status) => {
  313. switch (status) {
  314. case 1:
  315. return <Tag size='large'>已激活</Tag>;
  316. case 2:
  317. return (
  318. <Tag size='large' color='red'>
  319. 已封禁
  320. </Tag>
  321. );
  322. default:
  323. return (
  324. <Tag size='large' color='grey'>
  325. 未知状态
  326. </Tag>
  327. );
  328. }
  329. };
  330. const searchUsers = async () => {
  331. if (searchKeyword === '') {
  332. // if keyword is blank, load files instead.
  333. await loadUsers(0);
  334. setActivePage(1);
  335. return;
  336. }
  337. setSearching(true);
  338. const res = await API.get(`/api/user/search?keyword=${searchKeyword}`);
  339. const { success, message, data } = res.data;
  340. if (success) {
  341. setUsers(data);
  342. setActivePage(1);
  343. } else {
  344. showError(message);
  345. }
  346. setSearching(false);
  347. };
  348. const handleKeywordChange = async (value) => {
  349. setSearchKeyword(value.trim());
  350. };
  351. const sortUser = (key) => {
  352. if (users.length === 0) return;
  353. setLoading(true);
  354. let sortedUsers = [...users];
  355. sortedUsers.sort((a, b) => {
  356. return ('' + a[key]).localeCompare(b[key]);
  357. });
  358. if (sortedUsers[0].id === users[0].id) {
  359. sortedUsers.reverse();
  360. }
  361. setUsers(sortedUsers);
  362. setLoading(false);
  363. };
  364. const handlePageChange = (page) => {
  365. setActivePage(page);
  366. if (page === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
  367. // In this case we have to load more data and then append them.
  368. loadUsers(page - 1).then((r) => {});
  369. }
  370. };
  371. const pageData = users.slice(
  372. (activePage - 1) * ITEMS_PER_PAGE,
  373. activePage * ITEMS_PER_PAGE,
  374. );
  375. const closeAddUser = () => {
  376. setShowAddUser(false);
  377. };
  378. const closeEditUser = () => {
  379. setShowEditUser(false);
  380. setEditingUser({
  381. id: undefined,
  382. });
  383. };
  384. const refresh = async () => {
  385. if (searchKeyword === '') {
  386. await loadUsers(activePage - 1);
  387. } else {
  388. await searchUsers();
  389. }
  390. };
  391. return (
  392. <>
  393. <AddUser
  394. refresh={refresh}
  395. visible={showAddUser}
  396. handleClose={closeAddUser}
  397. ></AddUser>
  398. <EditUser
  399. refresh={refresh}
  400. visible={showEditUser}
  401. handleClose={closeEditUser}
  402. editingUser={editingUser}
  403. ></EditUser>
  404. <Form onSubmit={searchUsers}>
  405. <Form.Input
  406. label='搜索关键字'
  407. icon='search'
  408. field='keyword'
  409. iconPosition='left'
  410. placeholder='搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...'
  411. value={searchKeyword}
  412. loading={searching}
  413. onChange={(value) => handleKeywordChange(value)}
  414. />
  415. </Form>
  416. <Table
  417. columns={columns}
  418. dataSource={pageData}
  419. pagination={{
  420. currentPage: activePage,
  421. pageSize: ITEMS_PER_PAGE,
  422. total: userCount,
  423. pageSizeOpts: [10, 20, 50, 100],
  424. onPageChange: handlePageChange,
  425. }}
  426. loading={loading}
  427. />
  428. <Button
  429. theme='light'
  430. type='primary'
  431. style={{ marginRight: 8 }}
  432. onClick={() => {
  433. setShowAddUser(true);
  434. }}
  435. >
  436. 添加用户
  437. </Button>
  438. </>
  439. );
  440. };
  441. export default UsersTable;