RedemptionsTable.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. import React, { useEffect, useState } from 'react';
  2. import {
  3. API,
  4. copy,
  5. showError,
  6. showSuccess,
  7. timestamp2string,
  8. } from '../helpers';
  9. import { ITEMS_PER_PAGE } from '../constants';
  10. import { renderQuota } from '../helpers/render';
  11. import {
  12. Button, Divider,
  13. Form,
  14. Modal,
  15. Popconfirm,
  16. Popover,
  17. Table,
  18. Tag,
  19. } from '@douyinfe/semi-ui';
  20. import EditRedemption from '../pages/Redemption/EditRedemption';
  21. function renderTimestamp(timestamp) {
  22. return <>{timestamp2string(timestamp)}</>;
  23. }
  24. function renderStatus(status) {
  25. switch (status) {
  26. case 1:
  27. return (
  28. <Tag color='green' size='large'>
  29. 未使用
  30. </Tag>
  31. );
  32. case 2:
  33. return (
  34. <Tag color='red' size='large'>
  35. {' '}
  36. 已禁用{' '}
  37. </Tag>
  38. );
  39. case 3:
  40. return (
  41. <Tag color='grey' size='large'>
  42. {' '}
  43. 已使用{' '}
  44. </Tag>
  45. );
  46. default:
  47. return (
  48. <Tag color='black' size='large'>
  49. {' '}
  50. 未知状态{' '}
  51. </Tag>
  52. );
  53. }
  54. }
  55. const RedemptionsTable = () => {
  56. const columns = [
  57. {
  58. title: 'ID',
  59. dataIndex: 'id',
  60. },
  61. {
  62. title: '名称',
  63. dataIndex: 'name',
  64. },
  65. {
  66. title: '状态',
  67. dataIndex: 'status',
  68. key: 'status',
  69. render: (text, record, index) => {
  70. return <div>{renderStatus(text)}</div>;
  71. },
  72. },
  73. {
  74. title: '额度',
  75. dataIndex: 'quota',
  76. render: (text, record, index) => {
  77. return <div>{renderQuota(parseInt(text))}</div>;
  78. },
  79. },
  80. {
  81. title: '创建时间',
  82. dataIndex: 'created_time',
  83. render: (text, record, index) => {
  84. return <div>{renderTimestamp(text)}</div>;
  85. },
  86. },
  87. {
  88. title: '兑换人ID',
  89. dataIndex: 'used_user_id',
  90. render: (text, record, index) => {
  91. return <div>{text === 0 ? '无' : text}</div>;
  92. },
  93. },
  94. {
  95. title: '',
  96. dataIndex: 'operate',
  97. render: (text, record, index) => (
  98. <div>
  99. <Popover content={record.key} style={{ padding: 20 }} position='top'>
  100. <Button theme='light' type='tertiary' style={{ marginRight: 1 }}>
  101. 查看
  102. </Button>
  103. </Popover>
  104. <Button
  105. theme='light'
  106. type='secondary'
  107. style={{ marginRight: 1 }}
  108. onClick={async (text) => {
  109. await copyText(record.key);
  110. }}
  111. >
  112. 复制
  113. </Button>
  114. <Popconfirm
  115. title='确定是否要删除此兑换码?'
  116. content='此修改将不可逆'
  117. okType={'danger'}
  118. position={'left'}
  119. onConfirm={() => {
  120. manageRedemption(record.id, 'delete', record).then(() => {
  121. removeRecord(record.key);
  122. });
  123. }}
  124. >
  125. <Button theme='light' type='danger' style={{ marginRight: 1 }}>
  126. 删除
  127. </Button>
  128. </Popconfirm>
  129. {record.status === 1 ? (
  130. <Button
  131. theme='light'
  132. type='warning'
  133. style={{ marginRight: 1 }}
  134. onClick={async () => {
  135. manageRedemption(record.id, 'disable', record);
  136. }}
  137. >
  138. 禁用
  139. </Button>
  140. ) : (
  141. <Button
  142. theme='light'
  143. type='secondary'
  144. style={{ marginRight: 1 }}
  145. onClick={async () => {
  146. manageRedemption(record.id, 'enable', record);
  147. }}
  148. disabled={record.status === 3}
  149. >
  150. 启用
  151. </Button>
  152. )}
  153. <Button
  154. theme='light'
  155. type='tertiary'
  156. style={{ marginRight: 1 }}
  157. onClick={() => {
  158. setEditingRedemption(record);
  159. setShowEdit(true);
  160. }}
  161. disabled={record.status !== 1}
  162. >
  163. 编辑
  164. </Button>
  165. </div>
  166. ),
  167. },
  168. ];
  169. const [redemptions, setRedemptions] = useState([]);
  170. const [loading, setLoading] = useState(true);
  171. const [activePage, setActivePage] = useState(1);
  172. const [searchKeyword, setSearchKeyword] = useState('');
  173. const [searching, setSearching] = useState(false);
  174. const [tokenCount, setTokenCount] = useState(ITEMS_PER_PAGE);
  175. const [selectedKeys, setSelectedKeys] = useState([]);
  176. const [editingRedemption, setEditingRedemption] = useState({
  177. id: undefined,
  178. });
  179. const [showEdit, setShowEdit] = useState(false);
  180. const closeEdit = () => {
  181. setShowEdit(false);
  182. };
  183. // const setCount = (data) => {
  184. // if (data.length >= (activePage) * ITEMS_PER_PAGE) {
  185. // setTokenCount(data.length + 1);
  186. // } else {
  187. // setTokenCount(data.length);
  188. // }
  189. // }
  190. const setRedemptionFormat = (redeptions) => {
  191. // for (let i = 0; i < redeptions.length; i++) {
  192. // redeptions[i].key = '' + redeptions[i].id;
  193. // }
  194. // data.key = '' + data.id
  195. setRedemptions(redeptions);
  196. if (redeptions.length >= activePage * ITEMS_PER_PAGE) {
  197. setTokenCount(redeptions.length + 1);
  198. } else {
  199. setTokenCount(redeptions.length);
  200. }
  201. };
  202. const loadRedemptions = async (startIdx) => {
  203. const res = await API.get(`/api/redemption/?p=${startIdx}`);
  204. const { success, message, data } = res.data;
  205. if (success) {
  206. if (startIdx === 0) {
  207. setRedemptionFormat(data);
  208. } else {
  209. let newRedemptions = redemptions;
  210. newRedemptions.push(...data);
  211. setRedemptionFormat(newRedemptions);
  212. }
  213. } else {
  214. showError(message);
  215. }
  216. setLoading(false);
  217. };
  218. const removeRecord = (key) => {
  219. let newDataSource = [...redemptions];
  220. if (key != null) {
  221. let idx = newDataSource.findIndex((data) => data.key === key);
  222. if (idx > -1) {
  223. newDataSource.splice(idx, 1);
  224. setRedemptions(newDataSource);
  225. }
  226. }
  227. };
  228. const copyText = async (text) => {
  229. if (await copy(text)) {
  230. showSuccess('已复制到剪贴板!');
  231. } else {
  232. // setSearchKeyword(text);
  233. Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text });
  234. }
  235. };
  236. const onPaginationChange = (e, { activePage }) => {
  237. (async () => {
  238. if (activePage === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) {
  239. // In this case we have to load more data and then append them.
  240. await loadRedemptions(activePage - 1);
  241. }
  242. setActivePage(activePage);
  243. })();
  244. };
  245. useEffect(() => {
  246. loadRedemptions(0)
  247. .then()
  248. .catch((reason) => {
  249. showError(reason);
  250. });
  251. }, []);
  252. const refresh = async () => {
  253. await loadRedemptions(activePage - 1);
  254. };
  255. const manageRedemption = async (id, action, record) => {
  256. let data = { id };
  257. let res;
  258. switch (action) {
  259. case 'delete':
  260. res = await API.delete(`/api/redemption/${id}/`);
  261. break;
  262. case 'enable':
  263. data.status = 1;
  264. res = await API.put('/api/redemption/?status_only=true', data);
  265. break;
  266. case 'disable':
  267. data.status = 2;
  268. res = await API.put('/api/redemption/?status_only=true', data);
  269. break;
  270. }
  271. const { success, message } = res.data;
  272. if (success) {
  273. showSuccess('操作成功完成!');
  274. let redemption = res.data.data;
  275. let newRedemptions = [...redemptions];
  276. // let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
  277. if (action === 'delete') {
  278. } else {
  279. record.status = redemption.status;
  280. }
  281. setRedemptions(newRedemptions);
  282. } else {
  283. showError(message);
  284. }
  285. };
  286. const searchRedemptions = async () => {
  287. if (searchKeyword === '') {
  288. // if keyword is blank, load files instead.
  289. await loadRedemptions(0);
  290. setActivePage(1);
  291. return;
  292. }
  293. setSearching(true);
  294. const res = await API.get(
  295. `/api/redemption/search?keyword=${searchKeyword}`,
  296. );
  297. const { success, message, data } = res.data;
  298. if (success) {
  299. setRedemptions(data);
  300. setActivePage(1);
  301. } else {
  302. showError(message);
  303. }
  304. setSearching(false);
  305. };
  306. const handleKeywordChange = async (value) => {
  307. setSearchKeyword(value.trim());
  308. };
  309. const sortRedemption = (key) => {
  310. if (redemptions.length === 0) return;
  311. setLoading(true);
  312. let sortedRedemptions = [...redemptions];
  313. sortedRedemptions.sort((a, b) => {
  314. return ('' + a[key]).localeCompare(b[key]);
  315. });
  316. if (sortedRedemptions[0].id === redemptions[0].id) {
  317. sortedRedemptions.reverse();
  318. }
  319. setRedemptions(sortedRedemptions);
  320. setLoading(false);
  321. };
  322. const handlePageChange = (page) => {
  323. setActivePage(page);
  324. if (page === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) {
  325. // In this case we have to load more data and then append them.
  326. loadRedemptions(page - 1).then((r) => {});
  327. }
  328. };
  329. let pageData = redemptions.slice(
  330. (activePage - 1) * ITEMS_PER_PAGE,
  331. activePage * ITEMS_PER_PAGE,
  332. );
  333. const rowSelection = {
  334. onSelect: (record, selected) => {},
  335. onSelectAll: (selected, selectedRows) => {},
  336. onChange: (selectedRowKeys, selectedRows) => {
  337. setSelectedKeys(selectedRows);
  338. },
  339. };
  340. const handleRow = (record, index) => {
  341. if (record.status !== 1) {
  342. return {
  343. style: {
  344. background: 'var(--semi-color-disabled-border)',
  345. },
  346. };
  347. } else {
  348. return {};
  349. }
  350. };
  351. return (
  352. <>
  353. <EditRedemption
  354. refresh={refresh}
  355. editingRedemption={editingRedemption}
  356. visiable={showEdit}
  357. handleClose={closeEdit}
  358. ></EditRedemption>
  359. <Form onSubmit={searchRedemptions}>
  360. <Form.Input
  361. label='搜索关键字'
  362. field='keyword'
  363. icon='search'
  364. iconPosition='left'
  365. placeholder='关键字(id或者名称)'
  366. value={searchKeyword}
  367. loading={searching}
  368. onChange={handleKeywordChange}
  369. />
  370. </Form>
  371. <Divider style={{margin:'5px 0 15px 0'}}/>
  372. <div>
  373. <Button
  374. theme='light'
  375. type='primary'
  376. style={{ marginRight: 8 }}
  377. onClick={() => {
  378. setEditingRedemption({
  379. id: undefined,
  380. });
  381. setShowEdit(true);
  382. }}
  383. >
  384. 添加兑换码
  385. </Button>
  386. <Button
  387. label='复制所选兑换码'
  388. type='warning'
  389. onClick={async () => {
  390. if (selectedKeys.length === 0) {
  391. showError('请至少选择一个兑换码!');
  392. return;
  393. }
  394. let keys = '';
  395. for (let i = 0; i < selectedKeys.length; i++) {
  396. keys += selectedKeys[i].name + ' ' + selectedKeys[i].key + '\n';
  397. }
  398. await copyText(keys);
  399. }}
  400. >
  401. 复制所选兑换码到剪贴板
  402. </Button>
  403. </div>
  404. <Table
  405. style={{ marginTop: 20 }}
  406. columns={columns}
  407. dataSource={pageData}
  408. pagination={{
  409. currentPage: activePage,
  410. pageSize: ITEMS_PER_PAGE,
  411. total: tokenCount,
  412. // showSizeChanger: true,
  413. // pageSizeOptions: [10, 20, 50, 100],
  414. formatPageText: (page) =>
  415. `第 ${page.currentStart} - ${page.currentEnd} 条,共 ${redemptions.length} 条`,
  416. // onPageSizeChange: (size) => {
  417. // setPageSize(size);
  418. // setActivePage(1);
  419. // },
  420. onPageChange: handlePageChange,
  421. }}
  422. loading={loading}
  423. rowSelection={rowSelection}
  424. onRow={handleRow}
  425. ></Table>
  426. </>
  427. );
  428. };
  429. export default RedemptionsTable;