RedemptionsTable.js 13 KB

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