Browse Source

feat: add user register and login

RegMs If 3 years ago
parent
commit
51e10478a5
4 changed files with 157 additions and 29 deletions
  1. 101 0
      src/components/UserDrawer.tsx
  2. 0 5
      src/layout/PageLayout.module.css
  3. 56 15
      src/layout/PageLayout.tsx
  4. 0 9
      src/types/dicts.ts

+ 101 - 0
src/components/UserDrawer.tsx

@@ -0,0 +1,101 @@
+import React, { useState } from 'react';
+import { Drawer, Form, Input, Button, message } from 'antd';
+import type { DrawerProps } from 'antd';
+import { useAuth } from '../apis/AuthProvider';
+import { useRegister, useLogin } from '../apis/users';
+
+enum Mode {
+  Register,
+  Login,
+}
+
+interface UserDrawerProps extends DrawerProps {
+  onClose: () => void;
+}
+
+const UserDrawer: React.FC<UserDrawerProps> = props => {
+  const [mode, setMode] = useState<Mode>(Mode.Login);
+
+  const { refresh } = useAuth();
+  const register = useRegister();
+  const login = useLogin();
+
+  return (
+    <Drawer
+      title={mode === Mode.Register ? '注册' : '登录'}
+      destroyOnClose
+      {...props}
+    >
+      <Form
+        name="user"
+        labelCol={{ span: 6 }}
+        wrapperCol={{ span: 18 }}
+        onFinish={(values: { name: string; password: string }) =>
+          void (async () => {
+            try {
+              if (mode === Mode.Register) {
+                await register(values);
+                void message.success('注册成功');
+              }
+              await login(values);
+              void message.success('登录成功');
+              refresh();
+              props.onClose();
+            } catch (err) {
+              if (err instanceof Error) {
+                void message.error(err.message);
+              }
+            }
+          })()
+        }
+      >
+        <Form.Item
+          label="用户名"
+          name="name"
+          rules={[
+            { required: true, message: '请输入用户名' },
+            { min: 2, max: 16, message: '用户名长度应在 4 到 16 之间' },
+            { pattern: /^\w+$/, message: '用户名应仅包含字母、数字和下划线' },
+          ]}
+          validateTrigger="onBlur"
+        >
+          <Input placeholder="请输入用户名" />
+        </Form.Item>
+        <Form.Item
+          label="密码"
+          name="password"
+          rules={[
+            { required: true, message: '请输入密码' },
+            { min: 2, max: 16, message: '密码长度应在 4 到 16 之间' },
+          ]}
+          validateTrigger="onBlur"
+        >
+          <Input.Password placeholder="请输入密码" />
+        </Form.Item>
+        <Form.Item label=" " colon={false}>
+          {mode === Mode.Register ? (
+            <>
+              <Button htmlType="submit" type="primary">
+                注册
+              </Button>
+              <Button type="link" onClick={() => setMode(Mode.Login)}>
+                已有账号?返回登录
+              </Button>
+            </>
+          ) : (
+            <>
+              <Button htmlType="submit" type="primary">
+                登录
+              </Button>
+              <Button type="link" onClick={() => setMode(Mode.Register)}>
+                没有账号?前往注册
+              </Button>
+            </>
+          )}
+        </Form.Item>
+      </Form>
+    </Drawer>
+  );
+};
+
+export default UserDrawer;

+ 0 - 5
src/layout/PageLayout.module.css

@@ -1,8 +1,3 @@
-.layout {
-  width: 100vw;
-  height: 100vh;
-}
-
 .header {
   background-color: white;
   box-shadow: 0px 4px 4px -2px lightgray;

+ 56 - 15
src/layout/PageLayout.tsx

@@ -1,22 +1,63 @@
-import React from 'react';
-import { Outlet } from 'react-router-dom';
-import { Layout, Typography } from 'antd';
+import React, { useState, useEffect } from 'react';
+import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom';
+import { Layout, Row, Col, Typography, Button, Result } from 'antd';
+import { ArrowLeftOutlined } from '@ant-design/icons';
+import { useAuth } from '../apis/AuthProvider';
+import UserDrawer from '../components/UserDrawer';
 import styles from './PageLayout.module.css';
 
 const { Header, Content } = Layout;
 const { Text } = Typography;
 
-const PageLayout: React.FC = () => (
-  <Layout className={styles.layout}>
-    <Header className={styles.header}>
-      <Text className={styles.title} strong>
-        Woord
-      </Text>
-    </Header>
-    <Content className={styles.content}>
-      <Outlet />
-    </Content>
-  </Layout>
-);
+const PageLayout: React.FC = () => {
+  const [open, setOpen] = useState(false);
+
+  const { pathname } = useLocation();
+  const navigate = useNavigate();
+
+  useEffect(() => {
+    if (pathname === '/') {
+      navigate('/dicts');
+    }
+  }, [pathname, navigate]);
+
+  const { user } = useAuth();
+
+  return (
+    <Layout>
+      <Header className={styles.header}>
+        <Row gutter={8}>
+          <Col>
+            <Link to={pathname.slice(0, pathname.lastIndexOf('/'))}>
+              <Button
+                type="link"
+                icon={<ArrowLeftOutlined />}
+                disabled={pathname === '/dicts'}
+              />
+            </Link>
+          </Col>
+          <Col flex={1}>
+            <Text className={styles.title} strong>
+              Woord
+            </Text>
+          </Col>
+          <Col>
+            {user ? (
+              user.name
+            ) : (
+              <Button shape="round" onClick={() => setOpen(true)}>
+                登录
+              </Button>
+            )}
+          </Col>
+        </Row>
+      </Header>
+      <Content className={styles.content}>
+        {user ? <Outlet /> : <Result status="403" subTitle="请先登录" />}
+      </Content>
+      <UserDrawer open={open} onClose={() => setOpen(false)} />
+    </Layout>
+  );
+};
 
 export default PageLayout;

+ 0 - 9
src/types/dicts.ts

@@ -1,9 +0,0 @@
-export interface Word {
-  value: string;
-  meaning: string;
-  extra: string;
-}
-
-export type Dict = {
-  [field in keyof Word as `${field}Title`]: string;
-} & { words: (Word & { star: boolean })[] };