Bladeren bron

feat: add dict detail page

RegMs If 3 jaren geleden
bovenliggende
commit
3dca063097
3 gewijzigde bestanden met toevoegingen van 270 en 0 verwijderingen
  1. 83 0
      src/components/WordDrawer.tsx
  2. 3 0
      src/pages/DictPage.module.css
  3. 184 0
      src/pages/DictPage.tsx

+ 83 - 0
src/components/WordDrawer.tsx

@@ -0,0 +1,83 @@
+import React from 'react';
+import { Drawer, Form, Input, Button, message } from 'antd';
+import type { DrawerProps } from 'antd';
+import type { WordResult } from '../apis/words';
+import { useCreateWord, useUpdateWord } from '../apis/words';
+
+interface WordDrawerProps extends DrawerProps {
+  dictID: number;
+  initialWord?: WordResult;
+  refresh: () => void;
+  onClose: () => void;
+}
+
+const WordDrawer: React.FC<WordDrawerProps> = ({
+  dictID,
+  initialWord,
+  refresh,
+  ...props
+}) => {
+  const createWord = useCreateWord();
+  const updateWord = useUpdateWord();
+
+  return (
+    <Drawer
+      title={initialWord ? '更新单词' : '创建新单词'}
+      destroyOnClose
+      {...props}
+    >
+      <Form
+        name="word"
+        labelCol={{ span: 6 }}
+        wrapperCol={{ span: 18 }}
+        initialValues={initialWord}
+        onFinish={(values: { value: string; meaning: string; extra: string }) =>
+          void (async () => {
+            try {
+              if (initialWord) {
+                await updateWord({ ...initialWord, ...values });
+                void message.success('更新成功');
+              } else {
+                await createWord({ ...values, dictID });
+                void message.success('创建成功');
+              }
+              refresh();
+              props.onClose();
+            } catch (err) {
+              if (err instanceof Error) {
+                void message.error(err.message);
+              }
+            }
+          })()
+        }
+      >
+        <Form.Item
+          label="单词"
+          name="value"
+          rules={[{ required: true, message: '请输入单词' }]}
+          validateTrigger="onBlur"
+        >
+          <Input placeholder="请输入单词(如:good)" />
+        </Form.Item>
+        <Form.Item
+          label="词义"
+          name="meaning"
+          rules={[{ required: true, message: '请输入词义' }]}
+          validateTrigger="onBlur"
+        >
+          <Input placeholder="请输入词义(如:好的)" />
+        </Form.Item>
+        <Form.Item label="附加" name="extra">
+          <Input placeholder="请输入附加(如:[ɡʊd])" />
+        </Form.Item>
+        <Form.Item label=" " colon={false}>
+          <Button htmlType="submit" type="primary">
+            {initialWord ? '更新' : '创建'}
+          </Button>
+        </Form.Item>
+      </Form>
+    </Drawer>
+  );
+};
+
+export default WordDrawer;

+ 3 - 0
src/pages/DictPage.module.css

@@ -0,0 +1,3 @@
+.wrapper {
+  padding: 32px;
+}

+ 184 - 0
src/pages/DictPage.tsx

@@ -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;