DictPage.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import React, { useState, useMemo } from 'react';
  2. import { Link, useParams } from 'react-router-dom';
  3. import {
  4. List,
  5. Row,
  6. Col,
  7. Input,
  8. Button,
  9. Popconfirm,
  10. Space,
  11. Typography,
  12. message,
  13. } from 'antd';
  14. import {
  15. PlusOutlined,
  16. UploadOutlined,
  17. AimOutlined,
  18. EditOutlined,
  19. StarFilled,
  20. StarOutlined,
  21. DeleteOutlined,
  22. } from '@ant-design/icons';
  23. import { useGetDict } from '../apis/dicts';
  24. import type { WordResult } from '../apis/words';
  25. import { useUpdateWord, useDeleteWord } from '../apis/words';
  26. import WordModal from '../components/WordModal';
  27. import UploadModal from '../components/UploadModal';
  28. import styles from './DictPage.module.css';
  29. const { Text } = Typography;
  30. const DictPage: React.FC = () => {
  31. const [search, setSearch] = useState('');
  32. const [initialWord, setInitialWord] = useState<WordResult>();
  33. const [wordModalOpen, setWordModalOpen] = useState(false);
  34. const [uploadModalOpen, setUploadModalOpen] = useState(false);
  35. const { dictID = '' } = useParams();
  36. const {
  37. data: { dict, words } = {},
  38. loading,
  39. refresh,
  40. mutate,
  41. } = useGetDict({
  42. id: parseInt(dictID),
  43. });
  44. const updateWord = useUpdateWord();
  45. const deleteWord = useDeleteWord();
  46. const filteredWordsList = useMemo(
  47. () =>
  48. search
  49. ? words?.list.filter(
  50. word =>
  51. word.value.includes(search) ||
  52. word.meaning.includes(search) ||
  53. (dict?.extraTitle && word.extra.includes(search)),
  54. )
  55. : words?.list,
  56. [search, dict?.extraTitle, words?.list],
  57. );
  58. const mutateWordsList = (newWord: WordResult) => {
  59. if (dict && words) {
  60. const index = words.list.findIndex(value => value.id === newWord.id);
  61. mutate({
  62. dict,
  63. words: {
  64. total: words.total,
  65. list: [
  66. ...words.list.slice(0, index),
  67. newWord,
  68. ...words.list.slice(index + 1),
  69. ],
  70. },
  71. });
  72. }
  73. };
  74. return (
  75. <div className={styles.wrapper}>
  76. <List
  77. bordered
  78. header={
  79. <Row justify="space-between">
  80. <Col>
  81. <Input
  82. allowClear
  83. placeholder="搜索"
  84. onChange={e => setSearch(e.target.value)}
  85. />
  86. </Col>
  87. {dict && (
  88. <Col>
  89. <Button
  90. type="link"
  91. icon={<PlusOutlined />}
  92. onClick={() => {
  93. setInitialWord(undefined);
  94. setWordModalOpen(true);
  95. }}
  96. >
  97. 创建新单词
  98. </Button>
  99. <Button
  100. type="link"
  101. icon={<UploadOutlined />}
  102. onClick={() => setUploadModalOpen(true)}
  103. >
  104. 上传 Excel
  105. </Button>
  106. <Link to={`/dicts/${dictID}/memo`}>
  107. <Button type="link" icon={<AimOutlined />}>
  108. 开始复习
  109. </Button>
  110. </Link>
  111. </Col>
  112. )}
  113. </Row>
  114. }
  115. loading={loading}
  116. dataSource={filteredWordsList}
  117. renderItem={word => (
  118. <List.Item
  119. actions={[
  120. <Button
  121. key="update"
  122. type="link"
  123. size="small"
  124. icon={<EditOutlined />}
  125. onClick={() => {
  126. setInitialWord(word);
  127. setWordModalOpen(true);
  128. }}
  129. />,
  130. <Button
  131. key="star"
  132. type="link"
  133. danger
  134. size="small"
  135. icon={word.star ? <StarFilled /> : <StarOutlined />}
  136. onClick={() =>
  137. void (async () => {
  138. try {
  139. const newWord = { ...word, star: !word.star };
  140. mutateWordsList(newWord);
  141. await updateWord(newWord);
  142. } catch (err) {
  143. if (err instanceof Error) {
  144. void message.error(err.message);
  145. }
  146. }
  147. })()
  148. }
  149. />,
  150. <Popconfirm
  151. key="delete"
  152. title="确定删除该单词?"
  153. onConfirm={() =>
  154. void (async () => {
  155. try {
  156. await deleteWord({ id: word.id });
  157. void message.success('删除成功');
  158. refresh();
  159. } catch (err) {
  160. if (err instanceof Error) {
  161. void message.error(err.message);
  162. }
  163. }
  164. })()
  165. }
  166. >
  167. <Button
  168. type="link"
  169. danger
  170. size="small"
  171. icon={<DeleteOutlined />}
  172. />
  173. </Popconfirm>,
  174. ]}
  175. >
  176. <List.Item.Meta
  177. title={
  178. <Space size="middle">
  179. {word.value}
  180. {dict?.extraTitle && (
  181. <Text type="secondary">{word.extra}</Text>
  182. )}
  183. </Space>
  184. }
  185. description={word.meaning}
  186. />
  187. </List.Item>
  188. )}
  189. pagination={{}}
  190. />
  191. {dict && (
  192. <>
  193. <WordModal
  194. dict={dict}
  195. initialWord={initialWord}
  196. refresh={refresh}
  197. open={wordModalOpen}
  198. onCancel={() => setWordModalOpen(false)}
  199. />
  200. <UploadModal
  201. dict={dict}
  202. refresh={refresh}
  203. open={uploadModalOpen}
  204. onCancel={() => setUploadModalOpen(false)}
  205. />
  206. </>
  207. )}
  208. </div>
  209. );
  210. };
  211. export default DictPage;