feat: 接口鉴权
This commit is contained in:
parent
6dcc4753ae
commit
a3635ffafb
|
@ -11,7 +11,6 @@ const instance = axios.create(config);
|
|||
|
||||
instance.interceptors.request.use(
|
||||
(config) => {
|
||||
console.log(config);
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
|
@ -22,10 +21,22 @@ instance.interceptors.request.use(
|
|||
// Add a response interceptor
|
||||
instance.interceptors.response.use(
|
||||
(response) => {
|
||||
if (response.data.code === 10000)
|
||||
message.success(`接口: ${response.config.url}, 请求成功`);
|
||||
if (response.data.code === 20000)
|
||||
message.error(`接口: ${response.config.url}, 遇到错误`);
|
||||
const { msg, code } = response.data;
|
||||
switch (code) {
|
||||
case 10000:
|
||||
message.success(`接口: ${response.config.url}, 请求成功`);
|
||||
break;
|
||||
case 20000:
|
||||
message.error(`接口: ${response.config.url}, 遇到错误`);
|
||||
break;
|
||||
case 40000:
|
||||
message.error(msg);
|
||||
window.location.href = "/";
|
||||
break;
|
||||
default:
|
||||
// TODO ...
|
||||
break;
|
||||
}
|
||||
return response?.data;
|
||||
},
|
||||
(error) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import Cookie from "js-cookie";
|
||||
|
||||
|
|
7
apps/server/src/config/base.config.ts
Normal file
7
apps/server/src/config/base.config.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export const globalPrefix = '/api/v1';
|
||||
|
||||
export const adminSign = '_sign_admin';
|
||||
export const adminSignExpired = 60 * 1000 * 10; // 10分钟
|
||||
|
||||
export const webSign = '_sign_web';
|
||||
export const webSignExpired = 60 * 1000 * 10; // 10分钟
|
|
@ -2,40 +2,14 @@ import { MidwayAppInfo, MidwayConfig } from '@midwayjs/core';
|
|||
import { uploadWhiteList } from '@midwayjs/upload';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'path';
|
||||
|
||||
// '.jpg',
|
||||
// '.jpeg',
|
||||
// '.png',
|
||||
// '.gif',
|
||||
// '.bmp',
|
||||
// '.wbmp',
|
||||
// '.webp',
|
||||
// '.tif',
|
||||
// '.psd',
|
||||
// '.svg',
|
||||
// '.js',
|
||||
// '.jsx',
|
||||
// '.json',
|
||||
// '.css',
|
||||
// '.less',
|
||||
// '.html',
|
||||
// '.htm',
|
||||
// '.xml',
|
||||
// '.pdf',
|
||||
// '.zip',
|
||||
// '.gz',
|
||||
// '.tgz',
|
||||
// '.gzip',
|
||||
// '.mp3',
|
||||
// '.mp4',
|
||||
// '.avi',
|
||||
import { globalPrefix } from './base.config';
|
||||
|
||||
export default (appInfo: MidwayAppInfo): MidwayConfig => {
|
||||
return {
|
||||
keys: '1676532942172_2248',
|
||||
koa: {
|
||||
port: 7001,
|
||||
globalPrefix: '/api/v1',
|
||||
globalPrefix,
|
||||
},
|
||||
upload: {
|
||||
// mode: UploadMode, 默认为file,即上传到服务器临时目录,可以配置为 stream
|
||||
|
|
7
apps/server/src/config/white.api.ts
Normal file
7
apps/server/src/config/white.api.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { globalPrefix } from './base.config';
|
||||
|
||||
export const whiteApis = [
|
||||
'/user/admin/auth',
|
||||
'/user/web/auth',
|
||||
'/course/select/all',
|
||||
].map(api => globalPrefix + api);
|
|
@ -1,4 +1,9 @@
|
|||
import { Configuration, App } from '@midwayjs/core';
|
||||
import {
|
||||
Configuration,
|
||||
App,
|
||||
Inject,
|
||||
MidwayDecoratorService,
|
||||
} from '@midwayjs/core';
|
||||
import * as koa from '@midwayjs/koa';
|
||||
import * as validate from '@midwayjs/validate';
|
||||
import * as info from '@midwayjs/info';
|
||||
|
@ -14,7 +19,6 @@ import { ReportMiddleware } from './middleware/report.middleware';
|
|||
import { AuthMiddleware } from './middleware/auth.middleware';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@Configuration({
|
||||
imports: [
|
||||
koa,
|
||||
|
@ -34,6 +38,9 @@ export class ContainerLifeCycle {
|
|||
@App()
|
||||
app: koa.Application;
|
||||
|
||||
@Inject()
|
||||
decoratorService: MidwayDecoratorService;
|
||||
|
||||
async onReady() {
|
||||
// add middleware
|
||||
this.app.useMiddleware([ReportMiddleware, AuthMiddleware]);
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import { Body, Controller, Inject, Post } from '@midwayjs/core';
|
||||
import { Context } from '@midwayjs/koa';
|
||||
import { BizCode } from '../biz/code';
|
||||
import { createToken } from '../util/encrypt';
|
||||
|
||||
@Controller('/admin')
|
||||
export class AdminController {
|
||||
@Inject()
|
||||
ctx: Context;
|
||||
|
||||
@Post('/user/auth')
|
||||
async AdminAuth(@Body() params) {
|
||||
const { username, password } = params as any;
|
||||
const token = createToken({ login: true });
|
||||
if (username === 'admin' && password === '123123') {
|
||||
this.ctx.cookies.set('_sign_admin', token, {
|
||||
expires: new Date(Date.now() + 10 * 60 * 1000),
|
||||
httpOnly: false,
|
||||
});
|
||||
this.ctx.body = { code: BizCode.OK };
|
||||
} else {
|
||||
this.ctx.body = { code: BizCode.ERROR, msg: '用户名密码错误' };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,15 @@
|
|||
import { Body, Context, Controller, Inject, Post } from '@midwayjs/core';
|
||||
import { Body, Controller, Inject, Post } from '@midwayjs/core';
|
||||
import { Context } from '@midwayjs/koa';
|
||||
import { BizCode } from '../biz/code';
|
||||
import { UserWebAuthDTO } from '../dto/user.dto';
|
||||
import {
|
||||
adminSign,
|
||||
adminSignExpired,
|
||||
webSign,
|
||||
webSignExpired,
|
||||
} from '../config/base.config';
|
||||
import { UserAdminAuthDTO, UserWebAuthDTO } from '../dto/user.dto';
|
||||
import { UserService } from '../service/user.service';
|
||||
import { md5 } from '../util/encrypt';
|
||||
import { createToken, md5 } from '../util/encrypt';
|
||||
|
||||
@Controller('/user')
|
||||
export class UserController {
|
||||
|
@ -12,6 +19,9 @@ export class UserController {
|
|||
@Inject()
|
||||
userService: UserService;
|
||||
|
||||
/**
|
||||
* 用户前台登录
|
||||
*/
|
||||
@Post('/web/auth')
|
||||
async webAuth(@Body() params: UserWebAuthDTO) {
|
||||
try {
|
||||
|
@ -19,12 +29,28 @@ export class UserController {
|
|||
if (userExist?.id) {
|
||||
const { user_pass, ...rest } = userExist;
|
||||
const passValid = userExist.user_pass === md5(params.user_pass);
|
||||
return passValid
|
||||
? { code: BizCode.OK, msg: '欢迎回来', data: { ...rest } }
|
||||
: { code: BizCode.ERROR, msg: '密码错误' };
|
||||
const token = createToken({ ...rest, hasLogin: true });
|
||||
if (passValid) {
|
||||
this.ctx.cookies.set(webSign, token, {
|
||||
expires: new Date(Date.now() + webSignExpired),
|
||||
httpOnly: false,
|
||||
});
|
||||
return {
|
||||
code: BizCode.OK,
|
||||
msg: '欢迎回来',
|
||||
data: { ...rest },
|
||||
};
|
||||
} else {
|
||||
return { code: BizCode.ERROR, msg: '密码错误' };
|
||||
}
|
||||
} else {
|
||||
const createUser = await this.userService.save(params);
|
||||
const { user_pass, ...rest } = createUser;
|
||||
const token = createToken({ ...rest, hasLogin: true });
|
||||
this.ctx.cookies.set(webSign, token, {
|
||||
expires: new Date(Date.now() + webSignExpired),
|
||||
httpOnly: false,
|
||||
});
|
||||
return {
|
||||
code: BizCode.OK,
|
||||
data: { ...rest },
|
||||
|
@ -36,4 +62,22 @@ export class UserController {
|
|||
return { code: BizCode.ERROR, msg: '[error] web/auth error' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
*/
|
||||
@Post('/admin/auth')
|
||||
async AdminAuth(@Body() params: UserAdminAuthDTO) {
|
||||
const { username, password } = params;
|
||||
const token = createToken({ hasLogin: true });
|
||||
if (username === 'admin' && password === '123123') {
|
||||
this.ctx.cookies.set(adminSign, token, {
|
||||
expires: new Date(Date.now() + adminSignExpired),
|
||||
httpOnly: false,
|
||||
});
|
||||
return { code: BizCode.OK };
|
||||
} else {
|
||||
return { code: BizCode.ERROR, msg: '用户名密码错误' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,3 +8,11 @@ export class UserWebAuthDTO {
|
|||
@Rule(RuleType.string().required())
|
||||
user_pass: string;
|
||||
}
|
||||
|
||||
export class UserAdminAuthDTO {
|
||||
@Rule(RuleType.string().required())
|
||||
username: string;
|
||||
|
||||
@Rule(RuleType.string().required())
|
||||
password: string;
|
||||
}
|
||||
|
|
|
@ -1,21 +1,35 @@
|
|||
import { Middleware, IMiddleware } from '@midwayjs/core';
|
||||
import {
|
||||
Middleware,
|
||||
IMiddleware,
|
||||
App,
|
||||
IMidwayApplication,
|
||||
} from '@midwayjs/core';
|
||||
import { NextFunction, Context } from '@midwayjs/koa';
|
||||
// import { BizCode } from '../biz/code';
|
||||
// import { decodeToken } from '../util/encrypt';
|
||||
import { BizCode } from '../biz/code';
|
||||
import { adminSign, webSign } from '../config/base.config';
|
||||
import { whiteApis } from '../config/white.api';
|
||||
import { decodeToken } from '../util/encrypt';
|
||||
|
||||
@Middleware()
|
||||
export class AuthMiddleware implements IMiddleware<Context, NextFunction> {
|
||||
@App()
|
||||
app: IMidwayApplication;
|
||||
|
||||
resolve() {
|
||||
return async (ctx: Context, next: NextFunction) => {
|
||||
// const isAdminUrl = ctx.url;
|
||||
// console.log('isAdminUrl', isAdminUrl);
|
||||
// if (isAdminUrl) {
|
||||
// const signToken = ctx.cookies.get('_sign_admin');
|
||||
// const { login } = decodeToken(signToken);
|
||||
// if (!login) return { code: BizCode.AUTH };
|
||||
// }
|
||||
|
||||
await next();
|
||||
const isWhiteApi = whiteApis.some(api => ctx.url.indexOf(api) > -1);
|
||||
if (!isWhiteApi) {
|
||||
const token = ctx.cookies.get(adminSign) ?? ctx.cookies.get(webSign);
|
||||
try {
|
||||
const { hasLogin } = decodeToken(token);
|
||||
if (!hasLogin) return { code: BizCode.AUTH, msg: '身份验证错误' };
|
||||
await next();
|
||||
} catch (error) {
|
||||
return { code: BizCode.AUTH, msg: '身份验证错误' };
|
||||
}
|
||||
} else {
|
||||
await next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// import { message } from "antd";
|
||||
import { Message } from "@arco-design/web-react";
|
||||
import axios from "axios";
|
||||
import { useLoginModalStore } from "../store/modal.store";
|
||||
|
||||
const config = {
|
||||
baseURL: "",
|
||||
|
@ -22,6 +24,22 @@ instance.interceptors.request.use(
|
|||
// Add a response interceptor
|
||||
instance.interceptors.response.use(
|
||||
(response) => {
|
||||
const { msg, code } = response.data;
|
||||
switch (code) {
|
||||
case 10000:
|
||||
// Message.success(`接口: ${response.config.url}, 请求成功`);
|
||||
break;
|
||||
case 20000:
|
||||
// Message.error(`接口: ${response.config.url}, 遇到错误`);
|
||||
break;
|
||||
case 40000:
|
||||
Message.error(msg);
|
||||
// console.log('登录')
|
||||
break;
|
||||
default:
|
||||
// TODO ...
|
||||
break;
|
||||
}
|
||||
return response?.data;
|
||||
},
|
||||
(error) => {
|
||||
|
|
|
@ -64,264 +64,3 @@ header {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arco-modal {
|
||||
background: transparent;
|
||||
.arco-modal-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-structor {
|
||||
height: 550px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0.8;
|
||||
background-repeat: no-repeat;
|
||||
background-position: left bottom;
|
||||
background-size: 500px;
|
||||
background-image: url("/bg2.avif");
|
||||
}
|
||||
|
||||
.login-sms {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 65%;
|
||||
z-index: 5;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.slide-up {
|
||||
top: 5%;
|
||||
transform: translate(-50%, 0%);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.slide-up .form-holder,
|
||||
&.slide-up .submit-btn {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&.slide-up .form-title {
|
||||
font-size: 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.slide-up .form-title span {
|
||||
margin-right: 5px;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
|
||||
span {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.sms-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
button {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-holder {
|
||||
border-radius: 6px;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
margin-top: 50px;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.input {
|
||||
border: 0;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
display: block;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
padding: 4px 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
&::input-placeholder {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
margin: 15px auto;
|
||||
padding: 15px 45px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
letter-spacing: 10px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transition: all 0.3s ease;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-qrcode {
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #fff;
|
||||
z-index: 5;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: -20px;
|
||||
transform: translate(-50%, 0);
|
||||
background-color: #fff;
|
||||
width: 200%;
|
||||
height: 250px;
|
||||
border-radius: 50%;
|
||||
z-index: 4;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.center {
|
||||
position: absolute;
|
||||
top: calc(50% - 10%);
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 65%;
|
||||
z-index: 5;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.form-title {
|
||||
color: #000;
|
||||
font-weight: 400;
|
||||
font-size: 1.7em;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.form-holder {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin-top: 50px;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.input {
|
||||
border: 0;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
display: block;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
padding: 4px 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
&::input-placeholder {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: rgb(107, 146, 164);
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
margin: 15px auto;
|
||||
padding: 15px 45px;
|
||||
width: 100%;
|
||||
letter-spacing: 10px;
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transition: all 0.3s ease;
|
||||
background-color: rgba(107, 146, 164, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.slide-up {
|
||||
top: 90%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.slide-up .center {
|
||||
top: 10%;
|
||||
transform: translate(-50%, 0%);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.slide-up .form-holder,
|
||||
&.slide-up .submit-btn {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&.slide-up .form-title {
|
||||
font-size: 1em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.slide-up .form-title span {
|
||||
margin-right: 5px;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,62 +1,11 @@
|
|||
import "./index.less";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { commonRouters, menuRouters } from "../../router";
|
||||
import {
|
||||
Input,
|
||||
Modal,
|
||||
Form,
|
||||
Message,
|
||||
Button,
|
||||
Space,
|
||||
Radio,
|
||||
Tooltip,
|
||||
} from "@arco-design/web-react";
|
||||
import { menuRouters } from "../../router";
|
||||
import { Input } from "@arco-design/web-react";
|
||||
const InputSearch = Input.Search;
|
||||
const RadioGroup = Radio.Group;
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { userLogin } from "../../api";
|
||||
|
||||
const defaultForm = {
|
||||
user_login: "",
|
||||
user_pass: "",
|
||||
};
|
||||
|
||||
type TLoginMethod = "useQRCode" | "useSms";
|
||||
|
||||
const DURATION = 3; // 验证码倒计时
|
||||
|
||||
function Nav() {
|
||||
const navigate = useNavigate();
|
||||
const [smsLoginForm, setSmsLoginForm] = useState(defaultForm);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loginMethod, setLoginMethod] = useState<TLoginMethod>("useSms");
|
||||
let [countdown, setCountdown] = useState(DURATION);
|
||||
const timer = useRef<any>();
|
||||
|
||||
const onClickSmsBtn = () => {
|
||||
setTimeout(() => {
|
||||
Message.success("验证码已发送");
|
||||
timer.current = setInterval(() => setCountdown(countdown--), 1000);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const onClickSmsLogin = () => {
|
||||
userLogin(smsLoginForm).then((res: any) => {
|
||||
const { code, data, msg } = res;
|
||||
if (code === 10000) {
|
||||
Message.success(msg);
|
||||
console.log(data);
|
||||
}
|
||||
if (code === 20000) Message.error(msg);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (countdown === 0) {
|
||||
clearInterval(timer.current);
|
||||
setCountdown(DURATION);
|
||||
}
|
||||
}, [countdown]);
|
||||
|
||||
return (
|
||||
<header>
|
||||
|
@ -85,78 +34,7 @@ function Nav() {
|
|||
<InputSearch allowClear placeholder="搜索" style={{ width: 150 }} />
|
||||
</div>
|
||||
<div className="end">
|
||||
<span onClick={() => setVisible(true)}>登录</span>
|
||||
<Modal
|
||||
style={{ width: "350px" }}
|
||||
visible={visible}
|
||||
closeIcon={false}
|
||||
footer={null}
|
||||
onCancel={() => setVisible(false)}
|
||||
>
|
||||
<div className="form-structor">
|
||||
<div
|
||||
className={`login-sms ${
|
||||
loginMethod === "useSms" ? "" : "slide-up"
|
||||
}`}
|
||||
>
|
||||
<h2
|
||||
className="form-title"
|
||||
onClick={() => setLoginMethod("useSms")}
|
||||
>
|
||||
验证码登录{" "}
|
||||
</h2>
|
||||
<div className="form-holder">
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
placeholder="手机"
|
||||
onChange={(e) =>
|
||||
setSmsLoginForm((p) => ({
|
||||
...p,
|
||||
user_login: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<div className="sms-group">
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
onChange={(e) =>
|
||||
setSmsLoginForm((p) => ({
|
||||
...p,
|
||||
user_pass: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="验证码"
|
||||
></input>
|
||||
<Button type="text" onClick={onClickSmsBtn}>
|
||||
{countdown === DURATION ? "获取" : countdown + "s"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<button className="submit-btn" onClick={onClickSmsLogin}>
|
||||
登录
|
||||
</button>
|
||||
</div>
|
||||
{/* 扫码 */}
|
||||
<div
|
||||
className={`login-qrcode ${
|
||||
loginMethod === "useQRCode" ? "" : "slide-up"
|
||||
}`}
|
||||
>
|
||||
<div className="center">
|
||||
<h2
|
||||
className="form-title"
|
||||
// onClick={() => setLoginMethod("useQRCode")}
|
||||
onClick={() => Message.info("开发中... 😀 ")}
|
||||
>
|
||||
扫码登录
|
||||
</h2>
|
||||
<div className="form-holder">开发中……</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<span onClick={() => navigate("/login")}>登录</span>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
import { Form, Input, Message, Modal } from "@arco-design/web-react";
|
||||
import FormItem from "@arco-design/web-react/es/Form/form-item";
|
||||
import { useState } from "react";
|
||||
|
||||
function LoginModal() {
|
||||
const [loginForm] = Form.useForm();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
|
||||
const onOk = () => {
|
||||
loginForm.validate().then((res) => {
|
||||
setConfirmLoading(true);
|
||||
setTimeout(() => {
|
||||
Message.success("Success !");
|
||||
setVisible(false);
|
||||
setConfirmLoading(false);
|
||||
}, 1500);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
style={{ width: "400px" }}
|
||||
visible={visible}
|
||||
onOk={onOk}
|
||||
okText="登录"
|
||||
cancelText="取消"
|
||||
closeIcon={false}
|
||||
confirmLoading={confirmLoading}
|
||||
onCancel={() => setVisible(false)}
|
||||
>
|
||||
<Form form={loginForm} layout="vertical">
|
||||
<FormItem field="username" rules={[{ required: true }]} label="">
|
||||
<Input placeholder="用户名/手机号" />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
required
|
||||
field="password"
|
||||
rules={[{ required: true }]}
|
||||
style={{ marginBottom: 0 }}
|
||||
label=""
|
||||
>
|
||||
<Input placeholder="密码" type="password" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default LoginModal;
|
|
@ -1,31 +1,29 @@
|
|||
import { Button, Result } from "@arco-design/web-react";
|
||||
import { useEffect } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
interface IGuardProps {
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
const Result403 = (
|
||||
<div style={{ paddingTop: 100 }}>
|
||||
<Result status="403" subTitle="无权访问,请登录" />
|
||||
</div>
|
||||
);
|
||||
|
||||
const needAuthList = ["course/detail"];
|
||||
|
||||
export const Guard = (props: IGuardProps) => {
|
||||
const location = useLocation();
|
||||
const sessionExist = !!Cookies.get("_session");
|
||||
const sign = Cookies.get("_sign_web");
|
||||
const needAuth = needAuthList.some((p) => location.pathname.indexOf(p) > -1);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("location.pathname changed 拦截", location.pathname);
|
||||
if (location.pathname.indexOf("course/detail/") > -1) {
|
||||
console.log("进入课程详情页");
|
||||
}
|
||||
}, [location.pathname]);
|
||||
|
||||
if (needAuth && !sessionExist)
|
||||
return (
|
||||
<div style={{ paddingTop: 100 }}>
|
||||
<Result status="403" subTitle="无权访问,请登录" />
|
||||
</div>
|
||||
);
|
||||
if (!sign && needAuth) return Result403;
|
||||
return props.children;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { lazy } from "react";
|
||||
import Course from "../view/Course";
|
||||
import { Login } from "../view/Login";
|
||||
|
||||
export interface IRouteMenuItem {
|
||||
path: string;
|
||||
|
@ -12,6 +13,12 @@ interface IRoute extends IRouteMenuItem {
|
|||
}
|
||||
|
||||
export const commonRouters: IRoute[] = [
|
||||
{
|
||||
path: "/login",
|
||||
element: <Login />,
|
||||
name: "登录",
|
||||
invisible: true,
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
element: <Course />,
|
||||
|
|
9
apps/web/src/store/modal.store.ts
Normal file
9
apps/web/src/store/modal.store.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { create } from "zustand";
|
||||
|
||||
export const useLoginModalStore = create((set) => {
|
||||
return {
|
||||
visible: false,
|
||||
setVisible: () => set({ visible: true }),
|
||||
setInVisible: () => set({ visible: false }),
|
||||
};
|
||||
});
|
55
apps/web/src/view/Login/index.less
Normal file
55
apps/web/src/view/Login/index.less
Normal file
|
@ -0,0 +1,55 @@
|
|||
.form {
|
||||
border-radius: 6px;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
margin-top: 50px;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.sms-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
button {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
margin: 15px auto;
|
||||
padding: 15px 45px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
letter-spacing: 10px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transition: all 0.3s ease;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
border: 0;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
display: block;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
padding: 4px 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
&::input-placeholder {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
84
apps/web/src/view/Login/index.tsx
Normal file
84
apps/web/src/view/Login/index.tsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { userLogin } from "../../api";
|
||||
import { Message, Button } from "@arco-design/web-react";
|
||||
import "./index.less";
|
||||
|
||||
const defaultForm = {
|
||||
user_login: "",
|
||||
user_pass: "",
|
||||
};
|
||||
|
||||
type TLoginMethod = "useQRCode" | "useSms";
|
||||
|
||||
const DURATION = 3; // 验证码倒计时
|
||||
|
||||
export function Login() {
|
||||
const [smsLoginForm, setSmsLoginForm] = useState(defaultForm);
|
||||
const [loginMethod, setLoginMethod] = useState<TLoginMethod>("useSms");
|
||||
let [countdown, setCountdown] = useState(DURATION);
|
||||
const timer = useRef<any>();
|
||||
|
||||
const onClickSmsBtn = () => {
|
||||
setTimeout(() => {
|
||||
Message.success("验证码已发送");
|
||||
timer.current = setInterval(() => setCountdown(countdown--), 1000);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const onClickSmsLogin = () => {
|
||||
userLogin(smsLoginForm).then((res: any) => {
|
||||
const { code, data, msg } = res;
|
||||
if (code === 10000) {
|
||||
Message.success(msg);
|
||||
console.log(data);
|
||||
}
|
||||
if (code === 20000) Message.error(msg);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (countdown === 0) {
|
||||
clearInterval(timer.current);
|
||||
setCountdown(DURATION);
|
||||
}
|
||||
}, [countdown]);
|
||||
|
||||
return (
|
||||
<div className="container" style={{ textAlign: "center", paddingTop: 100 }}>
|
||||
<div style={{ width: 350, display: "inline-block" }}>
|
||||
<div className="form">
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
placeholder="手机"
|
||||
onChange={(e) =>
|
||||
setSmsLoginForm((p) => ({
|
||||
...p,
|
||||
user_login: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<div className="sms-group">
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
onChange={(e) =>
|
||||
setSmsLoginForm((p) => ({
|
||||
...p,
|
||||
user_pass: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="验证码"
|
||||
></input>
|
||||
<Button type="text" onClick={onClickSmsBtn}>
|
||||
{countdown === DURATION ? "获取" : countdown + "s"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<button className="submit-btn" onClick={onClickSmsLogin}>
|
||||
登录
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user