登录初步

This commit is contained in:
禺狨 2023-03-23 16:23:58 +08:00
parent c97a2529a8
commit 4e03c1d82d
17 changed files with 572 additions and 35 deletions

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<title>Vite + React + TS</title>
<title>PlayEdu</title>
</head>
<body>
<div id="root"></div>

View File

@ -1,7 +1,6 @@
#root {
max-width: 1280px;
width: 100%;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

View File

@ -0,0 +1,83 @@
@font-face {
font-family: "iconfont"; /* Project id 3943555 */
src: url('iconfont.woff2?t=1679275935231') format('woff2'),
url('iconfont.woff?t=1679275935231') format('woff'),
url('iconfont.ttf?t=1679275935231') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-waterprint:before {
content: "\e747";
}
.icon-adduser:before {
content: "\e743";
}
.icon-upvideo:before {
content: "\e744";
}
.icon-onlinelesson:before {
content: "\e745";
}
.icon-department:before {
content: "\e746";
}
.icon-icon-drag:before {
content: "\e740";
}
.icon-icon-edit:before {
content: "\e741";
}
.icon-icon-delete:before {
content: "\e742";
}
.icon-icon-video:before {
content: "\e73f";
}
.icon-icon-home:before {
content: "\e737";
}
.icon-icon-category:before {
content: "\e738";
}
.icon-icon-file:before {
content: "\e739";
}
.icon-icon-study:before {
content: "\e73a";
}
.icon-icon-user:before {
content: "\e73b";
}
.icon-icon-setting:before {
content: "\e73c";
}
.icon-icon-password:before {
content: "\e73d";
}
.icon-a-icon-logout:before {
content: "\e73e";
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

View File

@ -0,0 +1,21 @@
import React from "react";
import { Layout } from "antd";
export const Footer: React.FC = () => {
return (
<Layout.Footer
style={{
width: "100%",
backgroundColor: "#ffffff",
height: 130,
textAlign: "center",
paddingBottom: 100,
}}
>
<i
style={{ fontSize: 30, color: "#cccccc" }}
className="iconfont icon-waterprint"
></i>
</Layout.Footer>
);
};

2
src/compenents/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from "./footer";
export * from "./no-header";

View File

@ -0,0 +1,29 @@
.app-header {
background-color: #f6f6f6;
box-sizing: border-box;
-moz-box-sizing: border-box;
/* Firefox */
-webkit-box-sizing: border-box;
/* Safari */
padding: 0px 24px;
}
.main-header {
width: 1200px;
height: 60px;
line-height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 auto;
}
.App-logo {
width: 124px;
height: 40px;
}
.top-main {
display: flex;
align-items: center;
}

View File

@ -0,0 +1,19 @@
import React from "react";
import styles from "./index.module.scss";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { Image } from "antd";
import logo from "../../assets/logo.png";
export const NoHeader: React.FC = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
return (
<div className={styles["app-header"]}>
<div className={styles["main-header"]}>
<img src={logo} className={styles["App-logo"]} />
</div>
</div>
);
};

View File

@ -1,3 +1,7 @@
@import "./assets/iconfont/iconfont.css";
$primaryColor: #ff4d4f;
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
@ -67,3 +71,184 @@ button:focus-visible {
background-color: #f9f9f9;
}
}
.w-250px {
width: 250px;
}
.w-300px {
width: 300px;
}
.w-350px {
width: 350px;
}
.w-400px {
width: 200px;
}
.w-450px {
width: 200px;
}
.w-500px {
width: 200px;
}
.mr-5 {
margin-right: 5px;
}
.ml-5 {
margin-left: 5px;
}
.ml-8 {
margin-left: 8px;
}
.mt-10 {
margin-top: 10px;
}
.ml-15 {
margin-left: 15px;
}
.ml-120 {
margin-left: 120px;
}
.ml-16 {
margin-left: 16px;
}
.mr-16 {
margin-right: 16px;
}
.mb-8 {
margin-bottom: 8px;
}
.mb-10 {
margin-bottom: 10px;
}
.mt-24 {
margin-top: 24px;
}
.mb-24 {
margin-bottom: 24px;
}
.mr-24 {
margin-right: 24px;
}
.mb-28 {
margin-bottom: 28px;
}
.mt-50 {
margin-top: 50px;
}
.mb-50 {
margin-bottom: 50px;
}
.helper-text {
height: 24px;
font-size: 12px;
font-weight: 400;
color: rgba(0, 0, 0, 0.45);
line-height: 24px;
}
.float-left {
width: 100%;
height: auto;
float: left;
}
.d-flex {
display: flex;
align-items: center;
}
.j-flex {
display: flex;
justify-content: center;
}
.d-j-flex {
display: flex;
align-items: center;
justify-content: center;
}
.j-r-flex {
display: flex;
justify-content: right;
}
.j-b-flex {
display: flex;
align-items: center;
justify-content: space-between;
}
.c-flex {
display: flex;
flex-direction: column;
}
.c-a-flex {
display: flex;
flex-direction: column;
align-items: center;
}
.flex-1 {
flex: 1;
}
.primary {
color: $primaryColor;
}
.c-yellow {
color: #e1a500;
}
.c-success {
color: #04c877;
}
.c-green {
color: #00cc66;
}
.c-red {
color: $primaryColor;
}
.login-box {
.ant-btn {
font-size: 18px !important;
font-weight: 500 !important;
}
.ant-input,
.ant-input-password {
font-weight: 400;
font-size: 18px !important;
background-color: #f6f6f6;
&::placeholder {
color: rgba(0, 0, 0, 0.45);
}
}
}

View File

@ -0,0 +1,73 @@
.login-content {
width: 100%;
float: left;
height: 100vh;
background-color: #fff;
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
.title {
width: 120px;
height: auto;
font-size: 30px;
font-weight: 600;
color: #333333;
line-height: 30px;
border-bottom: 4px solid #ff4d4f;
box-sizing: border-box;
padding-bottom: 10px;
margin: 0 auto;
margin-top: 100px;
}
.login-box {
width: 1200px;
height: 366px;
background: #ffffff;
display: flex;
margin: 0 auto;
margin-top: 80px;
.left-box {
width: 595px;
height: 100%;
box-sizing: border-box;
padding: 33px 60px;
.icon {
width: 475px;
height: 300px;
}
}
.right-box {
width: 520px;
height: 100%;
box-sizing: border-box;
border-left: 1px solid #d8d8d8;
padding: 0px 60px;
display: flex;
flex-direction: column;
.captcha-box {
width: 125px;
height: 54px;
margin-left: 15px;
border-radius: 8px;
background-color: rgba(#ff4d4f, 0.1);
display: flex;
.catpcha-loading-box {
width: 125px;
height: 54px;
line-height: 54px;
text-align: center;
}
.captcha {
width: 125px;
height: 54px;
border: none;
cursor: pointer;
border-radius: 8px;
}
}
}
}
}

View File

@ -1,40 +1,166 @@
import { Button } from "antd";
import { Spin, Input, Button, message } from "antd";
import React, { useState, useEffect } from "react";
import styles from "./index.module.scss";
import banner from "../../assets/images/login/banner.png";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { loginAction, logoutAction } from "../../store/user/loginUserSlice";
import { login, system, user } from "../../api/index";
import { setToken } from "../../utils/index";
import { Footer, NoHeader } from "../../compenents";
const LoginPage = () => {
const LoginPage: React.FC = () => {
const dispatch = useDispatch();
const loginState = useSelector((state: any) => {
return state.loginUser.value;
});
return (
<>
<Button
onClick={() => {
dispatch(
loginAction({
user: {
name: "霸王",
},
})
);
}}
>
</Button>
const navigate = useNavigate();
const [loading, setLoading] = useState<boolean>(false);
const [image, setImage] = useState<string>("");
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [captchaVal, setCaptchaVal] = useState<string>("");
const [captchaKey, setCaptchaKey] = useState<string>("");
const [captchaLoading, setCaptchaLoading] = useState(true);
{loginState.isLogin && (
<Button
onClick={() => {
dispatch(logoutAction());
}}
>
{loginState.user.name}
</Button>
)}
</>
useEffect(() => {
fetchImageCaptcha();
}, []);
const fetchImageCaptcha = () => {
setCaptchaLoading(true);
system.imageCaptcha().then((res: any) => {
setImage(res.data.image);
setCaptchaKey(res.data.key);
setCaptchaLoading(false);
});
};
const loginSubmit = (e: any) => {
if (!email) {
message.error("请输入学员邮箱账号");
return;
}
if (!password) {
message.error("请输入密码");
return;
}
if (!captchaVal) {
message.error("请输入图形验证码");
return;
}
if (loading) {
return;
}
handleSubmit();
};
const keyUp = (e: any) => {
if (e.keyCode === 13) {
loginSubmit(e);
}
};
const handleSubmit = () => {
if (loading) {
return;
}
setLoading(true);
login
.login(email, password, captchaKey, captchaVal)
.then((res: any) => {
const token = res.data.token;
setToken(token);
getUser();
})
.catch((e) => {
setLoading(false);
setCaptchaVal("");
fetchImageCaptcha();
});
};
const getUser = () => {
user.detail().then((res: any) => {
const data = res.data;
dispatch(logoutAction(data.user));
setLoading(false);
navigate("/");
});
};
return (
<div className={styles["login-content"]}>
<div className={styles["top-content"]}>
<NoHeader></NoHeader>
<div className={styles["title"]}></div>
<div className={styles["login-box"]}>
<div className={styles["left-box"]}>
<img className={styles["icon"]} src={banner} alt="" />
</div>
<div className={styles["right-box"]}>
<div className="login-box d-flex">
<Input
value={email}
onChange={(e) => {
setEmail(e.target.value);
}}
style={{ width: 400, height: 54 }}
placeholder="请输入学员邮箱账号"
onKeyUp={(e) => keyUp(e)}
/>
</div>
<div className="login-box d-flex mt-50">
<Input.Password
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
style={{ width: 400, height: 54 }}
placeholder="请输入密码"
/>
</div>
<div className="login-box d-flex mt-50">
<Input
value={captchaVal}
style={{ width: 260, height: 54 }}
placeholder="请输入图形验证码"
onChange={(e) => {
setCaptchaVal(e.target.value);
}}
onKeyUp={(e) => keyUp(e)}
/>
<div className={styles["captcha-box"]}>
{captchaLoading && (
<div className={styles["catpcha-loading-box"]}>
<Spin size="small" />
</div>
)}
{!captchaLoading && (
<img
className={styles["captcha"]}
onClick={fetchImageCaptcha}
src={image}
/>
)}
</div>
</div>
<div className="login-box d-flex mt-50">
<Button
style={{ width: 400, height: 54 }}
type="primary"
onClick={loginSubmit}
loading={loading}
>
</Button>
</div>
</div>
</div>
</div>
<div className={styles["footer"]}>
<Footer></Footer>
</div>
</div>
);
};
export default LoginPage;
export default LoginPage;