UsersTable.js 14 KB

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