Parcourir la source

feat "zelin:完成登陆注册页面编写"

Leonezhurui il y a 2 ans
Parent
commit
5e542bf952

+ 2 - 0
package.json

@@ -10,12 +10,14 @@
     "@types/node": "^16.18.80",
     "@types/react": "^18.2.55",
     "@types/react-dom": "^18.2.19",
+    "antd": "^5.14.2",
     "axios": "^1.6.7",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-router": "^6.22.1",
     "react-router-dom": "^6.22.1",
     "react-scripts": "5.0.1",
+    "sass": "^1.71.1",
     "typescript": "^4.9.5",
     "web-vitals": "^2.1.4",
     "zustand": "^4.5.1"

+ 0 - 1
src/apis/axios.config.ts

@@ -1,5 +1,4 @@
 import axios from "axios";
-import useStore from "@/store";
 
 const axiosInstance = axios.create({
     baseURL: 'http://localhost:8080',

+ 1 - 0
src/assets/pic/back.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="6395" height="1079" viewBox="0 0 6395 1079"><defs><clipPath id="clip-path"><rect width="6395" height="1079" transform="translate(-5391)" fill="#fff"/></clipPath><linearGradient id="linear-gradient" x1="0.747" y1="0.222" x2="0.973" y2="0.807" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#2c41b4"/><stop offset="1" stop-color="#1b4fab"/></linearGradient></defs><g id="Mask_Group_1" data-name="Mask Group 1" transform="translate(5391)" clip-path="url(#clip-path)"><g id="Group_118" data-name="Group 118" transform="translate(-419.333 -1.126)"><path id="Path_142" data-name="Path 142" d="M6271.734-6.176s-222.478,187.809-55.349,583.254c44.957,106.375,81.514,205.964,84.521,277,8.164,192.764-156.046,268.564-156.046,268.564l-653.53-26.8L5475.065-21.625Z" transform="translate(-4876.383 0)" fill="#f1f5f8"/><path id="Union_6" data-name="Union 6" d="M-2631.1,1081.8v-1.6H-8230.9V.022H-2631.1V0H-1871.4s-187.845,197.448-91.626,488.844c49.167,148.9,96.309,256.289,104.683,362.118,7.979,100.852-57.98,201.711-168.644,254.286-65.858,31.29-144.552,42.382-223.028,42.383C-2441.2,1147.632-2631.1,1081.8-2631.1,1081.8Z" transform="translate(3259.524 0.803)" fill="url(#linear-gradient)"/></g></g></svg>

+ 41 - 0
src/hooks/useCountdown.ts

@@ -0,0 +1,41 @@
+import {useCallback, useRef, useState} from "react";
+
+export function useCountdown(initCount = 60, interval = 1) {
+    const [countdown, setCountdown] = useState(0)
+    const [isStart, setStart] = useState(false)
+
+    const time = useRef()
+
+    const stop = useCallback(() => clearTimeout(time.current), [])
+
+    const start = useCallback(() => {
+        setCountdown(initCount)
+        setStart(true)
+        // @ts-ignore
+        time.current = setInterval(() => {
+                console.log(1)
+                setCountdown(
+                    (value) => {
+                        if (value - interval <= 0) {
+                            clearTimeout(time.current)
+                            setStart(false)
+                            return 0
+                        }
+                        return value - interval
+                    }
+                )
+            },
+            1000
+        )
+    }, [initCount, interval])
+
+
+    return {
+        stop,
+        start,
+        countdown,
+        isStart
+    }
+}
+
+

+ 9 - 9
src/index.css

@@ -1,13 +1,13 @@
-body {
+* {
+  padding: 0;
   margin: 0;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-    sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
 }
 
-code {
-  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-    monospace;
+body, html, #root {
+  height: 100%;
+}
+
+:root {
+  --main-color: #1677ff;
+  --boder-color: #d5d5d5;
 }

+ 0 - 0
src/pages/LoginPage/LoginPage.module.scss


+ 30 - 21
src/pages/LoginPage/LoginPage.tsx

@@ -1,4 +1,6 @@
-import {ChangeEvent, useState} from "react";
+import React, {useState} from "react";
+import "./index.scss"
+import {Login} from "@/pages/LoginPage/component/Login";
 
 interface ILoginForm {
     username: string,
@@ -11,29 +13,36 @@ const LoginPage = () => {
         password: ''
     })
 
-    const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
-        setData({
-            ...data,
-            [e.target.name]: e.target.value
-        });
-    }
-
-    const handleLogin = () => {
-        console.log(data);
-    }
+    const [isLogin, setLogin] = useState(true)
 
     return (
-        <div className="container">
-            <div className={"username-wrapper"}>
-                <span>用户名:</span>
-                <input type="text" name="username" value={data.username} onChange={handleInputChange}/>
-            </div>
-            <div>
-                <span>密码:</span>
-                <input type="text" name="password" value={data.password} onChange={handleInputChange}/>
+        <div className="login-container">
+            <div className="container-box">
+                <div className="login-logo">
+                    <div className="logo-image">
+                        Logo
+                    </div>
+                </div>
+                <div className="login-box">
+                    <div className="box-content">
+                        <div className="box-tabs">
+                            <div
+                                className={`box-tabs-item ${isLogin ? 'active' : ''}`}
+                                onClick={setLogin.bind(null, true)}
+                            >
+                                账号密码
+                            </div>
+                            <div
+                                className={`box-tabs-item ${!isLogin ? 'active' : ''}`}
+                                onClick={setLogin.bind(null, false)}
+                            >
+                                手机验证码
+                            </div>
+                        </div>
+                        <Login isCode={!isLogin}/>
+                    </div>
+                </div>
             </div>
-
-            <button onClick={handleLogin}>登陆</button>
         </div>
     )
 }

+ 85 - 0
src/pages/LoginPage/component/Login.tsx

@@ -0,0 +1,85 @@
+import {Button, Form, Input, Space} from "antd";
+import React from "react";
+import "./index.scss"
+import {useCountdown} from "@/hooks/useCountdown";
+import {useNavigate} from "react-router";
+
+interface LoginProps {
+    // 是否是手机号登录
+    isCode?: boolean
+    // 获取验证码
+    onCode?: () => void
+//     提交
+    onSubmit?: (data: { password: string, username: string }) => void
+}
+
+export function Login({isCode, onCode, onSubmit}: LoginProps) {
+    const {start, isStart, countdown} = useCountdown()
+    const nav = useNavigate()
+
+    function onClickSing() {
+        nav("/sign")
+    }
+
+    /**
+     * @description 获取验证码
+     */
+    function getCode() {
+        start()
+        onCode?.()
+        console.log(1)
+    }
+
+    /**
+     * 提交
+     * @param value
+     */
+    function onFinish(value: any) {
+        onSubmit?.(value)
+    }
+
+    return <div className="box-vessel">
+        <div className="vessel-title">
+            {!isCode ? '登录' : '手机号登录'}
+        </div>
+        <Form
+            name="basic"
+            initialValues={{remember: true}}
+            autoComplete="off"
+            onFinish={onFinish}
+        >
+            <Form.Item
+                name="username"
+                rules={[{required: true, message: isCode ? '手机号不能为空' : '账号不能为空'}]}
+            >
+                <Input placeholder="请输入账号"/>
+            </Form.Item>
+            <Form.Item
+                name="password"
+                rules={[{required: true, message: isCode ? '验证码不能为空' : '密码不能为空'}]}
+            >
+                {
+                    isCode ? <Space.Compact style={{width: '100%'}}>
+                        <Input placeholder="请输入验证码"/>
+                        <Button
+                            type="primary"
+                            onClick={getCode}
+                            disabled={isStart}
+                        >
+                            {isStart ? countdown : '获取手机验证码'}
+                        </Button>
+                    </Space.Compact> : <Input.Password placeholder="请输入密码"/>
+                }
+            </Form.Item>
+            <div className="forget">忘记密码?</div>
+            <Form.Item>
+                <Button type="primary" htmlType="submit" block>
+                    用户登录
+                </Button>
+            </Form.Item>
+            <div className="sing" onClick={onClickSing}>
+                去注册?
+            </div>
+        </Form>
+    </div>
+}

+ 24 - 0
src/pages/LoginPage/component/index.scss

@@ -0,0 +1,24 @@
+.box-vessel {
+  padding: 20px 32px;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  height: 100%;
+  flex: 1;
+
+  .forget {
+    text-align: right;
+    margin-bottom: 20px;
+    cursor: pointer;
+    color: var(--main-color)
+  }
+
+
+  .sing {
+    text-align: center;
+    font-size: 12px;
+    color: #8e8e8e;
+    cursor: pointer;
+  }
+}

+ 83 - 0
src/pages/LoginPage/index.scss

@@ -0,0 +1,83 @@
+@import "@/sass/public.scss";
+
+.login-container {
+
+  height: 100%;
+
+  &::before {
+    content: "";
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    margin-left: -48%;
+    background-image: url("../../assets/pic/back.svg");
+    background-repeat: no-repeat;
+    background-position: 100%;
+    background-size: auto 100%;
+  }
+
+  .container-box {
+    position: relative;
+    z-index: 999;
+    display: flex;
+    @extend .size-width-100;
+
+    .login-logo {
+      position: relative;
+      @extend .size-width-100;
+
+      .logo-image {
+        position: absolute;
+        @extend .position-center-center;
+        font-size: 100px;
+        color: #fff;
+      }
+    }
+
+    .login-box {
+      @extend .size-width-100;
+      position: relative;
+
+      .box-content {
+        position: absolute;
+        width: 350px;
+        @extend .position-center-center;
+        border: solid 1px var(--boder-color);
+        border-radius: 7px;
+        height: 380px;
+        display: flex;
+        flex-direction: column;
+
+        .box-tabs {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border-bottom: solid 1px var(--boder-color);
+
+          .box-tabs-item {
+            width: 50%;
+            text-align: center;
+            padding: 10px 0;
+            cursor: pointer;
+
+            &.active {
+              color: var(--main-color)
+            }
+
+            &:nth-child(1) {
+              border-right: solid 1px var(--boder-color);
+            }
+          }
+        }
+      }
+      .vessel-title {
+        margin-bottom: 20px;
+        text-align: center;
+        font-size: 20px;
+      }
+    }
+  }
+}
+

+ 23 - 0
src/pages/Sign/Sign.tsx

@@ -0,0 +1,23 @@
+import "./index.scss"
+import SignComponent from "./component/index";
+
+const Sign = () => {
+    return (
+        <div className="login-container">
+            <div className="container-box">
+                <div className="login-logo">
+                    <div className="logo-image">
+                        Logo
+                    </div>
+                </div>
+                <div className="login-box">
+                    <div className="box-content">
+                        <SignComponent/>
+                    </div>
+                </div>
+            </div>
+        </div>
+    )
+}
+
+export default Sign

+ 24 - 0
src/pages/Sign/component/index.scss

@@ -0,0 +1,24 @@
+.box-vessel {
+  padding: 20px 32px;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  height: 100%;
+  flex: 1;
+
+  .forget {
+    text-align: right;
+    margin-bottom: 20px;
+    cursor: pointer;
+    color: var(--main-color)
+  }
+
+
+  .sing {
+    text-align: center;
+    font-size: 12px;
+    color: #8e8e8e;
+    cursor: pointer;
+  }
+}

+ 79 - 0
src/pages/Sign/component/index.tsx

@@ -0,0 +1,79 @@
+import {Button, Form, Input, Space} from "antd";
+import React from "react";
+import "./index.scss"
+import {useCountdown} from "@/hooks/useCountdown";
+
+interface LoginProps {
+
+    // 获取验证码
+    onCode?: () => void
+    //     提交
+    onSubmit?: (data: { password: string, username: string }) => void
+}
+
+export default function Index({onCode, onSubmit}: LoginProps) {
+    const {start, isStart, countdown} = useCountdown()
+
+    /**
+     * @description 获取验证码
+     */
+    function getCode() {
+        start()
+        onCode?.()
+    }
+
+    /**
+     * 提交
+     * @param value
+     */
+    function onFinish(value: any) {
+        onSubmit?.(value)
+    }
+
+    return <div className="box-vessel">
+        <div className="vessel-title">
+            注册手机号
+        </div>
+        <Form
+            name="basic"
+            initialValues={{remember: true}}
+            autoComplete="off"
+            onFinish={onFinish}
+        >
+            <Form.Item
+                name="username"
+                rules={[{required: true, message: '账号不能为空'}]}
+            >
+                <Input placeholder="请输入账号"/>
+            </Form.Item>
+            <Form.Item
+                name="password"
+                rules={[{required: true, message: '密码不能为空'}]}
+            >
+                <Input placeholder="请输入密码"/>
+            </Form.Item>
+            <Form.Item
+                name="code"
+                rules={[{required: true, message: '验证码不能为空'}]}
+            >
+                {
+                    <Space.Compact style={{width: '100%'}}>
+                        <Input placeholder="请输入验证码"/>
+                        <Button
+                            type="primary"
+                            onClick={getCode}
+                            disabled={isStart}
+                        >
+                            {isStart ? countdown : '获取手机验证码'}
+                        </Button>
+                    </Space.Compact>
+                }
+            </Form.Item>
+            <Form.Item>
+                <Button type="primary" htmlType="submit" block>
+                    用户注册
+                </Button>
+            </Form.Item>
+        </Form>
+    </div>
+}

+ 83 - 0
src/pages/Sign/index.scss

@@ -0,0 +1,83 @@
+@import "../../sass/public";
+
+.login-container {
+
+  height: 100%;
+
+  &::before {
+    content: "";
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    margin-left: -48%;
+    background-image: url("../../assets/pic/back.svg");
+    background-repeat: no-repeat;
+    background-position: 100%;
+    background-size: auto 100%;
+  }
+
+  .container-box {
+    position: relative;
+    z-index: 999;
+    display: flex;
+    @extend .size-width-100;
+
+    .login-logo {
+      position: relative;
+      @extend .size-width-100;
+
+      .logo-image {
+        position: absolute;
+        @extend .position-center-center;
+        font-size: 100px;
+        color: #fff;
+      }
+    }
+
+    .login-box {
+      @extend .size-width-100;
+      position: relative;
+
+      .box-content {
+        position: absolute;
+        width: 350px;
+        @extend .position-center-center;
+        border: solid 1px var(--boder-color);
+        border-radius: 7px;
+        height: 380px;
+        display: flex;
+        flex-direction: column;
+
+        .box-tabs {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          border-bottom: solid 1px var(--boder-color);
+
+          .box-tabs-item {
+            width: 50%;
+            text-align: center;
+            padding: 10px 0;
+            cursor: pointer;
+
+            &.active {
+              color: var(--main-color)
+            }
+
+            &:nth-child(1) {
+              border-right: solid 1px var(--boder-color);
+            }
+          }
+        }
+      }
+      .vessel-title {
+        margin-bottom: 20px;
+        text-align: center;
+        font-size: 20px;
+      }
+    }
+  }
+}
+

+ 5 - 0
src/router/index.tsx

@@ -1,6 +1,7 @@
 import {createBrowserRouter} from "react-router-dom";
 import App from "@/pages/App";
 import LoginPage from "@/pages/LoginPage/LoginPage";
+import Sign from "@/pages/Sign/Sign"
 
 const router = createBrowserRouter([
     {
@@ -11,6 +12,10 @@ const router = createBrowserRouter([
         path: '/login',
         element: <LoginPage></LoginPage>
     },
+    {
+        path: '/sign',
+        element: <Sign/>
+    }
 ])
 
 export default router

+ 12 - 0
src/sass/public.scss

@@ -0,0 +1,12 @@
+.size-width-100 {
+  width: 100%;
+  height: 100%;
+}
+
+.position-center-center {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+