|
|
@@ -0,0 +1,184 @@
|
|
|
+import React, { useState } from 'react';
|
|
|
+import { Link, useParams } from 'react-router-dom';
|
|
|
+import {
|
|
|
+ List,
|
|
|
+ Row,
|
|
|
+ Col,
|
|
|
+ Input,
|
|
|
+ Button,
|
|
|
+ Popconfirm,
|
|
|
+ Space,
|
|
|
+ Typography,
|
|
|
+ message,
|
|
|
+} from 'antd';
|
|
|
+import {
|
|
|
+ PlusOutlined,
|
|
|
+ AimOutlined,
|
|
|
+ EditOutlined,
|
|
|
+ StarFilled,
|
|
|
+ StarOutlined,
|
|
|
+ DeleteOutlined,
|
|
|
+} from '@ant-design/icons';
|
|
|
+import { useGetDict } from '../apis/dicts';
|
|
|
+import type { WordResult } from '../apis/words';
|
|
|
+import { useUpdateWord, useDeleteWord } from '../apis/words';
|
|
|
+import WordDrawer from '../components/WordDrawer';
|
|
|
+import styles from './DictPage.module.css';
|
|
|
+
|
|
|
+const { Text } = Typography;
|
|
|
+
|
|
|
+const DictPage: React.FC = () => {
|
|
|
+ const [search, setSearch] = useState('');
|
|
|
+ const [initialWord, setInitialWord] = useState<WordResult>();
|
|
|
+ const [open, setOpen] = useState(false);
|
|
|
+
|
|
|
+ const { dictID = '' } = useParams();
|
|
|
+
|
|
|
+ const {
|
|
|
+ data: { dict, words } = {},
|
|
|
+ refresh,
|
|
|
+ mutate,
|
|
|
+ } = useGetDict({
|
|
|
+ id: parseInt(dictID),
|
|
|
+ });
|
|
|
+ const updateWord = useUpdateWord();
|
|
|
+ const deleteWord = useDeleteWord();
|
|
|
+
|
|
|
+ const mutateWordsList = (word: WordResult, index: number) =>
|
|
|
+ dict &&
|
|
|
+ words &&
|
|
|
+ mutate({
|
|
|
+ dict,
|
|
|
+ words: {
|
|
|
+ total: words.total,
|
|
|
+ list: [
|
|
|
+ ...words.list.slice(0, index),
|
|
|
+ word,
|
|
|
+ ...words.list.slice(index + 1),
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!dict || !words) {
|
|
|
+ return <></>;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className={styles.wrapper}>
|
|
|
+ <List
|
|
|
+ bordered
|
|
|
+ header={
|
|
|
+ <Row justify="space-between">
|
|
|
+ <Col>
|
|
|
+ <Input
|
|
|
+ allowClear
|
|
|
+ placeholder="搜索"
|
|
|
+ onChange={e => setSearch(e.target.value)}
|
|
|
+ />
|
|
|
+ </Col>
|
|
|
+ <Col>
|
|
|
+ <Button
|
|
|
+ type="link"
|
|
|
+ icon={<PlusOutlined />}
|
|
|
+ onClick={() => {
|
|
|
+ setInitialWord(undefined);
|
|
|
+ setOpen(true);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 创建新单词
|
|
|
+ </Button>
|
|
|
+ <Link to={`/dicts/${dictID}/memo`}>
|
|
|
+ <Button type="link" icon={<AimOutlined />}>
|
|
|
+ 开始复习
|
|
|
+ </Button>
|
|
|
+ </Link>
|
|
|
+ </Col>
|
|
|
+ </Row>
|
|
|
+ }
|
|
|
+ dataSource={
|
|
|
+ search
|
|
|
+ ? words.list.filter(
|
|
|
+ word =>
|
|
|
+ word.value.includes(search) ||
|
|
|
+ word.meaning.includes(search) ||
|
|
|
+ word.extra.includes(search),
|
|
|
+ )
|
|
|
+ : words.list
|
|
|
+ }
|
|
|
+ renderItem={(word, index) => (
|
|
|
+ <List.Item
|
|
|
+ actions={[
|
|
|
+ <Button
|
|
|
+ key="update"
|
|
|
+ type="link"
|
|
|
+ icon={<EditOutlined />}
|
|
|
+ onClick={() => {
|
|
|
+ setInitialWord(word);
|
|
|
+ setOpen(true);
|
|
|
+ }}
|
|
|
+ />,
|
|
|
+ <Button
|
|
|
+ key="star"
|
|
|
+ type="link"
|
|
|
+ danger
|
|
|
+ icon={word.star ? <StarFilled /> : <StarOutlined />}
|
|
|
+ onClick={() =>
|
|
|
+ void (async () => {
|
|
|
+ try {
|
|
|
+ const newWord = { ...word, star: !word.star };
|
|
|
+ mutateWordsList(newWord, index);
|
|
|
+ await updateWord(newWord);
|
|
|
+ void message.success('更新成功');
|
|
|
+ } catch (err) {
|
|
|
+ if (err instanceof Error) {
|
|
|
+ void message.error(err.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })()
|
|
|
+ }
|
|
|
+ />,
|
|
|
+ <Popconfirm
|
|
|
+ key="delete"
|
|
|
+ title="确定删除该单词?"
|
|
|
+ onConfirm={() =>
|
|
|
+ void (async () => {
|
|
|
+ try {
|
|
|
+ await deleteWord({ id: word.id });
|
|
|
+ void message.success('删除成功');
|
|
|
+ refresh();
|
|
|
+ } catch (err) {
|
|
|
+ if (err instanceof Error) {
|
|
|
+ void message.error(err.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })()
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <Button type="link" danger icon={<DeleteOutlined />} />
|
|
|
+ </Popconfirm>,
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <List.Item.Meta
|
|
|
+ title={
|
|
|
+ <Space size="middle">
|
|
|
+ {word.value}
|
|
|
+ <Text type="secondary">{word.extra}</Text>
|
|
|
+ </Space>
|
|
|
+ }
|
|
|
+ description={word.meaning}
|
|
|
+ />
|
|
|
+ </List.Item>
|
|
|
+ )}
|
|
|
+ ></List>
|
|
|
+ <WordDrawer
|
|
|
+ dictID={parseInt(dictID)}
|
|
|
+ initialWord={initialWord}
|
|
|
+ refresh={refresh}
|
|
|
+ open={open}
|
|
|
+ onClose={() => setOpen(false)}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default DictPage;
|