登录页面和我的页面初步
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||||
<title>PlayEdu</title>
|
<title>PlayEdu</title>
|
||||||
</head>
|
</head>
|
||||||
|
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
1
public/js/DPlayer.min.js
vendored
Normal file
1
public/js/xg/hls.min.js
vendored
Normal file
21
public/js/xg/index.js
Normal file
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
@ -41,7 +41,6 @@ export class HttpClient {
|
|||||||
return Promise.resolve(response);
|
return Promise.resolve(response);
|
||||||
} else {
|
} else {
|
||||||
Toast.show({
|
Toast.show({
|
||||||
icon: "fail",
|
|
||||||
content: msg,
|
content: msg,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -52,7 +51,6 @@ export class HttpClient {
|
|||||||
let status = error.response.status;
|
let status = error.response.status;
|
||||||
if (status === 401) {
|
if (status === 401) {
|
||||||
Toast.show({
|
Toast.show({
|
||||||
icon: "fail",
|
|
||||||
content: "请重新登录",
|
content: "请重新登录",
|
||||||
});
|
});
|
||||||
GoLogin();
|
GoLogin();
|
||||||
|
BIN
src/assets/images/commen/icon-back-n.png
Normal file
After Width: | Height: | Size: 355 B |
BIN
src/assets/images/commen/icon-back.png
Normal file
After Width: | Height: | Size: 355 B |
BIN
src/assets/images/commen/icon-more.png
Normal file
After Width: | Height: | Size: 490 B |
BIN
src/assets/images/login/banner.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
src/assets/images/login/bg.png
Normal file
After Width: | Height: | Size: 115 KiB |
@ -21,7 +21,7 @@ export const TabBarFooter: React.FC = () => {
|
|||||||
) : (
|
) : (
|
||||||
<i
|
<i
|
||||||
style={{ fontSize: 30, color: "#cccccc" }}
|
style={{ fontSize: 30, color: "#cccccc" }}
|
||||||
className="iconfont icon-waterprint"
|
className="iconfont icon-icon-shouye"
|
||||||
></i>
|
></i>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -35,6 +35,12 @@ code {
|
|||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-body {
|
||||||
|
width: 100%;
|
||||||
|
float: left;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.adm-tab-bar-item-title {
|
.adm-tab-bar-item-title {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
.login-content {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
background-image: url("../../assets//images/login/bg.png");
|
||||||
|
background-size: 100% 100%;
|
||||||
|
.top-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 150px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0px 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.form-box {
|
||||||
|
width: 100%;
|
||||||
|
float: left;
|
||||||
|
height: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0px 20px;
|
||||||
|
margin-top: 30px;
|
||||||
|
.input-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 109px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
.input-item {
|
||||||
|
width: 100%;
|
||||||
|
height: 54px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(0, 0, 0, 0.3);
|
||||||
|
line-height: 54px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0px 15px;
|
||||||
|
}
|
||||||
|
.line {
|
||||||
|
width: auto;
|
||||||
|
height: 1px;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
margin: 0px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.captcha-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 54px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
.input-item {
|
||||||
|
width: 200px;
|
||||||
|
height: 54px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0px 15px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(0, 0, 0, 0.3);
|
||||||
|
line-height: 54px;
|
||||||
|
}
|
||||||
|
.captcha-button {
|
||||||
|
width: 125px;
|
||||||
|
height: 54px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
.catpcha-loading-box {
|
||||||
|
width: 125px;
|
||||||
|
height: 54px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.captcha {
|
||||||
|
width: 125px;
|
||||||
|
height: 54px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 54px;
|
||||||
|
margin-top: 30px;
|
||||||
|
.primary-button {
|
||||||
|
width: 100%;
|
||||||
|
height: 54px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.support-box {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 90px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(0, 0, 0, 0.3);
|
||||||
|
line-height: 12px;
|
||||||
|
margin-top: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,39 +1,165 @@
|
|||||||
import { Button } from "antd-mobile";
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Button, Toast, SpinLoading, Input, Image } from "antd-mobile";
|
||||||
|
import styles from "./index.module.scss";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { login, system, user } from "../../api/index";
|
||||||
|
import { setToken } from "../../utils/index";
|
||||||
import { loginAction, logoutAction } from "../../store/user/loginUserSlice";
|
import { loginAction, logoutAction } from "../../store/user/loginUserSlice";
|
||||||
|
import banner from "../../assets/images/login/banner.png";
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
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);
|
||||||
const loginState = useSelector((state: any) => {
|
const loginState = useSelector((state: any) => {
|
||||||
return state.loginUser.value;
|
return state.loginUser.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<>
|
fetchImageCaptcha();
|
||||||
<Button
|
document.title = "登录";
|
||||||
onClick={() => {
|
}, []);
|
||||||
dispatch(
|
|
||||||
loginAction({
|
|
||||||
user: {
|
|
||||||
name: "霸王",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
登录吧
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{loginState.isLogin && (
|
const fetchImageCaptcha = () => {
|
||||||
<Button
|
setCaptchaLoading(true);
|
||||||
onClick={() => {
|
system.imageCaptcha().then((res: any) => {
|
||||||
dispatch(logoutAction());
|
setImage(res.data.image);
|
||||||
}}
|
setCaptchaKey(res.data.key);
|
||||||
>
|
setCaptchaLoading(false);
|
||||||
{loginState.user.name}
|
});
|
||||||
</Button>
|
};
|
||||||
)}
|
|
||||||
</>
|
const loginSubmit = (e: any) => {
|
||||||
|
if (!email) {
|
||||||
|
Toast.show({
|
||||||
|
content: "请输入学员邮箱账号",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!password) {
|
||||||
|
Toast.show({
|
||||||
|
content: "请输入密码",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!captchaVal) {
|
||||||
|
Toast.show({
|
||||||
|
content: "请输入图形验证码",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (captchaVal.length < 4) {
|
||||||
|
Toast.show({
|
||||||
|
content: "图形验证码错误",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
|
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(loginAction(data));
|
||||||
|
setLoading(false);
|
||||||
|
navigate("/member", { replace: true });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles["login-content"]}>
|
||||||
|
<div className={styles["top-content"]}>
|
||||||
|
<div className={styles["title"]}>学员登录</div>
|
||||||
|
<Image src={banner} width={150} height={150} />
|
||||||
|
</div>
|
||||||
|
<div className={styles["form-box"]}>
|
||||||
|
<div className={styles["input-box"]}>
|
||||||
|
<Input
|
||||||
|
className={styles["input-item"]}
|
||||||
|
placeholder="请输入学员邮箱账号"
|
||||||
|
value={email}
|
||||||
|
onChange={(val) => {
|
||||||
|
setEmail(val);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className={styles["line"]}></div>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
className={styles["input-item"]}
|
||||||
|
placeholder="请输入密码"
|
||||||
|
value={password}
|
||||||
|
onChange={(val) => {
|
||||||
|
setPassword(val);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles["captcha-box"]}>
|
||||||
|
<Input
|
||||||
|
value={captchaVal}
|
||||||
|
className={styles["input-item"]}
|
||||||
|
placeholder="请输入图形验证码"
|
||||||
|
onChange={(val) => {
|
||||||
|
setCaptchaVal(val);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className={styles["captcha-button"]}>
|
||||||
|
{captchaLoading && (
|
||||||
|
<div className={styles["catpcha-loading-box"]}>
|
||||||
|
<SpinLoading color="primary" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!captchaLoading && (
|
||||||
|
<Image
|
||||||
|
className={styles["captcha"]}
|
||||||
|
onClick={fetchImageCaptcha}
|
||||||
|
src={image}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles["button-box"]}>
|
||||||
|
<Button
|
||||||
|
className={styles["primary-button"]}
|
||||||
|
disabled={captchaVal === "" || email === "" || password === ""}
|
||||||
|
color="primary"
|
||||||
|
loading={loading}
|
||||||
|
onClick={loginSubmit}
|
||||||
|
>
|
||||||
|
登 录
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles["support-box"]}>「PlayEdu提供技术支持」</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
11
src/pages/member/index.module.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
.support-box {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 90px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: rgba(0, 0, 0, 0.3);
|
||||||
|
line-height: 12px;
|
||||||
|
margin-top: 200px;
|
||||||
|
}
|
32
src/pages/member/index.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { user } from "../../api/index";
|
||||||
|
import styles from "./index.module.scss";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { TabBarFooter } from "../../components";
|
||||||
|
import moreIcon from "../../assets/images/commen/icon-more.png";
|
||||||
|
|
||||||
|
const MemberPage = () => {
|
||||||
|
const systemConfig = useSelector((state: any) => state.systemConfig.value);
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [tabKey, setTabKey] = useState(0);
|
||||||
|
const departments = useSelector(
|
||||||
|
(state: any) => state.loginUser.value.departments
|
||||||
|
);
|
||||||
|
const currentDepId = useSelector(
|
||||||
|
(state: any) => state.loginUser.value.currentDepId
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = "我的";
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="main-body">
|
||||||
|
<div className={styles["content-box"]}>我的</div>
|
||||||
|
<div className={styles["support-box"]}>「PlayEdu提供技术支持」</div>
|
||||||
|
<TabBarFooter></TabBarFooter>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MemberPage;
|
@ -6,6 +6,7 @@ import { getToken } from "../utils";
|
|||||||
import { InitPage } from "../pages/init";
|
import { InitPage } from "../pages/init";
|
||||||
import IndexPage from "../pages/index/index";
|
import IndexPage from "../pages/index/index";
|
||||||
import LoginPage from "../pages/login";
|
import LoginPage from "../pages/login";
|
||||||
|
import MemberPage from "../pages/member/index";
|
||||||
import PrivateRoute from "../components/private-route";
|
import PrivateRoute from "../components/private-route";
|
||||||
|
|
||||||
let RootPage: any = null;
|
let RootPage: any = null;
|
||||||
@ -36,12 +37,16 @@ const routes: RouteObject[] = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
element: <IndexPage />,
|
element: <PrivateRoute Component={<IndexPage />} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/login",
|
path: "/login",
|
||||||
element: <LoginPage />,
|
element: <LoginPage />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/member",
|
||||||
|
element: <PrivateRoute Component={<MemberPage />} />,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|