ChannelsTable.js 8.7 KB

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