RedemptionsTable.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import React, { useEffect, useState } from 'react';
  2. import { Button, Form, Label, Message, Pagination, Table } from 'semantic-ui-react';
  3. import { Link } from 'react-router-dom';
  4. import { API, copy, showError, showInfo, showSuccess, showWarning, timestamp2string } from '../helpers';
  5. import { ITEMS_PER_PAGE } from '../constants';
  6. function renderTimestamp(timestamp) {
  7. return (
  8. <>
  9. {timestamp2string(timestamp)}
  10. </>
  11. );
  12. }
  13. function renderStatus(status) {
  14. switch (status) {
  15. case 1:
  16. return <Label basic color='green'>未使用</Label>;
  17. case 2:
  18. return <Label basic color='red'> 已禁用 </Label>;
  19. case 3:
  20. return <Label basic color='grey'> 已使用 </Label>;
  21. default:
  22. return <Label basic color='black'> 未知状态 </Label>;
  23. }
  24. }
  25. const RedemptionsTable = () => {
  26. const [redemptions, setRedemptions] = useState([]);
  27. const [loading, setLoading] = useState(true);
  28. const [activePage, setActivePage] = useState(1);
  29. const [searchKeyword, setSearchKeyword] = useState('');
  30. const [searching, setSearching] = useState(false);
  31. const loadRedemptions = async (startIdx) => {
  32. const res = await API.get(`/api/redemption/?p=${startIdx}`);
  33. const { success, message, data } = res.data;
  34. if (success) {
  35. if (startIdx === 0) {
  36. setRedemptions(data);
  37. } else {
  38. let newRedemptions = redemptions;
  39. newRedemptions.push(...data);
  40. setRedemptions(newRedemptions);
  41. }
  42. } else {
  43. showError(message);
  44. }
  45. setLoading(false);
  46. };
  47. const onPaginationChange = (e, { activePage }) => {
  48. (async () => {
  49. if (activePage === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) {
  50. // In this case we have to load more data and then append them.
  51. await loadRedemptions(activePage - 1);
  52. }
  53. setActivePage(activePage);
  54. })();
  55. };
  56. useEffect(() => {
  57. loadRedemptions(0)
  58. .then()
  59. .catch((reason) => {
  60. showError(reason);
  61. });
  62. }, []);
  63. const manageRedemption = async (id, action, idx) => {
  64. let data = { id };
  65. let res;
  66. switch (action) {
  67. case 'delete':
  68. res = await API.delete(`/api/redemption/${id}/`);
  69. break;
  70. case 'enable':
  71. data.status = 1;
  72. res = await API.put('/api/redemption/?status_only=true', data);
  73. break;
  74. case 'disable':
  75. data.status = 2;
  76. res = await API.put('/api/redemption/?status_only=true', data);
  77. break;
  78. }
  79. const { success, message } = res.data;
  80. if (success) {
  81. showSuccess('操作成功完成!');
  82. let redemption = res.data.data;
  83. let newRedemptions = [...redemptions];
  84. let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
  85. if (action === 'delete') {
  86. newRedemptions[realIdx].deleted = true;
  87. } else {
  88. newRedemptions[realIdx].status = redemption.status;
  89. }
  90. setRedemptions(newRedemptions);
  91. } else {
  92. showError(message);
  93. }
  94. };
  95. const searchRedemptions = async () => {
  96. if (searchKeyword === '') {
  97. // if keyword is blank, load files instead.
  98. await loadRedemptions(0);
  99. setActivePage(1);
  100. return;
  101. }
  102. setSearching(true);
  103. const res = await API.get(`/api/redemption/search?keyword=${searchKeyword}`);
  104. const { success, message, data } = res.data;
  105. if (success) {
  106. setRedemptions(data);
  107. setActivePage(1);
  108. } else {
  109. showError(message);
  110. }
  111. setSearching(false);
  112. };
  113. const handleKeywordChange = async (e, { value }) => {
  114. setSearchKeyword(value.trim());
  115. };
  116. const sortRedemption = (key) => {
  117. if (redemptions.length === 0) return;
  118. setLoading(true);
  119. let sortedRedemptions = [...redemptions];
  120. sortedRedemptions.sort((a, b) => {
  121. return ('' + a[key]).localeCompare(b[key]);
  122. });
  123. if (sortedRedemptions[0].id === redemptions[0].id) {
  124. sortedRedemptions.reverse();
  125. }
  126. setRedemptions(sortedRedemptions);
  127. setLoading(false);
  128. };
  129. return (
  130. <>
  131. <Form onSubmit={searchRedemptions}>
  132. <Form.Input
  133. icon='search'
  134. fluid
  135. iconPosition='left'
  136. placeholder='搜索兑换码的 ID 和名称 ...'
  137. value={searchKeyword}
  138. loading={searching}
  139. onChange={handleKeywordChange}
  140. />
  141. </Form>
  142. <Table basic>
  143. <Table.Header>
  144. <Table.Row>
  145. <Table.HeaderCell
  146. style={{ cursor: 'pointer' }}
  147. onClick={() => {
  148. sortRedemption('id');
  149. }}
  150. >
  151. ID
  152. </Table.HeaderCell>
  153. <Table.HeaderCell
  154. style={{ cursor: 'pointer' }}
  155. onClick={() => {
  156. sortRedemption('name');
  157. }}
  158. >
  159. 名称
  160. </Table.HeaderCell>
  161. <Table.HeaderCell
  162. style={{ cursor: 'pointer' }}
  163. onClick={() => {
  164. sortRedemption('status');
  165. }}
  166. >
  167. 状态
  168. </Table.HeaderCell>
  169. <Table.HeaderCell
  170. style={{ cursor: 'pointer' }}
  171. onClick={() => {
  172. sortRedemption('quota');
  173. }}
  174. >
  175. 额度
  176. </Table.HeaderCell>
  177. <Table.HeaderCell
  178. style={{ cursor: 'pointer' }}
  179. onClick={() => {
  180. sortRedemption('created_time');
  181. }}
  182. >
  183. 创建时间
  184. </Table.HeaderCell>
  185. <Table.HeaderCell
  186. style={{ cursor: 'pointer' }}
  187. onClick={() => {
  188. sortRedemption('redeemed_time');
  189. }}
  190. >
  191. 兑换时间
  192. </Table.HeaderCell>
  193. <Table.HeaderCell>操作</Table.HeaderCell>
  194. </Table.Row>
  195. </Table.Header>
  196. <Table.Body>
  197. {redemptions
  198. .slice(
  199. (activePage - 1) * ITEMS_PER_PAGE,
  200. activePage * ITEMS_PER_PAGE
  201. )
  202. .map((redemption, idx) => {
  203. if (redemption.deleted) return <></>;
  204. return (
  205. <Table.Row key={redemption.id}>
  206. <Table.Cell>{redemption.id}</Table.Cell>
  207. <Table.Cell>{redemption.name ? redemption.name : '无'}</Table.Cell>
  208. <Table.Cell>{renderStatus(redemption.status)}</Table.Cell>
  209. <Table.Cell>{redemption.quota}</Table.Cell>
  210. <Table.Cell>{renderTimestamp(redemption.created_time)}</Table.Cell>
  211. <Table.Cell>{redemption.redeemed_time ? renderTimestamp(redemption.redeemed_time) : "尚未兑换"} </Table.Cell>
  212. <Table.Cell>
  213. <div>
  214. <Button
  215. size={'small'}
  216. positive
  217. onClick={async () => {
  218. if (await copy(redemption.key)) {
  219. showSuccess('已复制到剪贴板!');
  220. } else {
  221. showWarning('无法复制到剪贴板,请手动复制,已将兑换码填入搜索框。')
  222. setSearchKeyword(redemption.key);
  223. }
  224. }}
  225. >
  226. 复制
  227. </Button>
  228. <Button
  229. size={'small'}
  230. negative
  231. onClick={() => {
  232. manageRedemption(redemption.id, 'delete', idx);
  233. }}
  234. >
  235. 删除
  236. </Button>
  237. <Button
  238. size={'small'}
  239. disabled={redemption.status === 3} // used
  240. onClick={() => {
  241. manageRedemption(
  242. redemption.id,
  243. redemption.status === 1 ? 'disable' : 'enable',
  244. idx
  245. );
  246. }}
  247. >
  248. {redemption.status === 1 ? '禁用' : '启用'}
  249. </Button>
  250. <Button
  251. size={'small'}
  252. as={Link}
  253. to={'/redemption/edit/' + redemption.id}
  254. >
  255. 编辑
  256. </Button>
  257. </div>
  258. </Table.Cell>
  259. </Table.Row>
  260. );
  261. })}
  262. </Table.Body>
  263. <Table.Footer>
  264. <Table.Row>
  265. <Table.HeaderCell colSpan='8'>
  266. <Button size='small' as={Link} to='/redemption/add' loading={loading}>
  267. 添加新的兑换码
  268. </Button>
  269. <Pagination
  270. floated='right'
  271. activePage={activePage}
  272. onPageChange={onPaginationChange}
  273. size='small'
  274. siblingRange={1}
  275. totalPages={
  276. Math.ceil(redemptions.length / ITEMS_PER_PAGE) +
  277. (redemptions.length % ITEMS_PER_PAGE === 0 ? 1 : 0)
  278. }
  279. />
  280. </Table.HeaderCell>
  281. </Table.Row>
  282. </Table.Footer>
  283. </Table>
  284. </>
  285. );
  286. };
  287. export default RedemptionsTable;