feat: 接口鉴权
This commit is contained in:
parent
6dcc4753ae
commit
a3635ffafb
|
@ -11,7 +11,6 @@ const instance = axios.create(config);
|
||||||
|
|
||||||
instance.interceptors.request.use(
|
instance.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
console.log(config);
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
@ -22,10 +21,22 @@ instance.interceptors.request.use(
|
||||||
// Add a response interceptor
|
// Add a response interceptor
|
||||||
instance.interceptors.response.use(
|
instance.interceptors.response.use(
|
||||||
(response) => {
|
(response) => {
|
||||||
if (response.data.code === 10000)
|
const { msg, code } = response.data;
|
||||||
|
switch (code) {
|
||||||
|
case 10000:
|
||||||
message.success(`接口: ${response.config.url}, 请求成功`);
|
message.success(`接口: ${response.config.url}, 请求成功`);
|
||||||
if (response.data.code === 20000)
|
break;
|
||||||
|
case 20000:
|
||||||
message.error(`接口: ${response.config.url}, 遇到错误`);
|
message.error(`接口: ${response.config.url}, 遇到错误`);
|
||||||
|
break;
|
||||||
|
case 40000:
|
||||||
|
message.error(msg);
|
||||||
|
window.location.href = "/";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// TODO ...
|
||||||
|
break;
|
||||||
|
}
|
||||||
return response?.data;
|
return response?.data;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import Cookie from "js-cookie";
|
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 { uploadWhiteList } from '@midwayjs/upload';
|
||||||
import { tmpdir } from 'os';
|
import { tmpdir } from 'os';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
import { globalPrefix } from './base.config';
|
||||||
// '.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',
|
|
||||||
|
|
||||||
export default (appInfo: MidwayAppInfo): MidwayConfig => {
|
export default (appInfo: MidwayAppInfo): MidwayConfig => {
|
||||||
return {
|
return {
|
||||||
keys: '1676532942172_2248',
|
keys: '1676532942172_2248',
|
||||||
koa: {
|
koa: {
|
||||||
port: 7001,
|
port: 7001,
|
||||||
globalPrefix: '/api/v1',
|
globalPrefix,
|
||||||
},
|
},
|
||||||
upload: {
|
upload: {
|
||||||
// mode: UploadMode, 默认为file,即上传到服务器临时目录,可以配置为 stream
|
// 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 koa from '@midwayjs/koa';
|
||||||
import * as validate from '@midwayjs/validate';
|
import * as validate from '@midwayjs/validate';
|
||||||
import * as info from '@midwayjs/info';
|
import * as info from '@midwayjs/info';
|
||||||
|
@ -14,7 +19,6 @@ import { ReportMiddleware } from './middleware/report.middleware';
|
||||||
import { AuthMiddleware } from './middleware/auth.middleware';
|
import { AuthMiddleware } from './middleware/auth.middleware';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@Configuration({
|
@Configuration({
|
||||||
imports: [
|
imports: [
|
||||||
koa,
|
koa,
|
||||||
|
@ -34,6 +38,9 @@ export class ContainerLifeCycle {
|
||||||
@App()
|
@App()
|
||||||
app: koa.Application;
|
app: koa.Application;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
decoratorService: MidwayDecoratorService;
|
||||||
|
|
||||||
async onReady() {
|
async onReady() {
|
||||||
// add middleware
|
// add middleware
|
||||||
this.app.useMiddleware([ReportMiddleware, AuthMiddleware]);
|
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 { 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 { UserService } from '../service/user.service';
|
||||||
import { md5 } from '../util/encrypt';
|
import { createToken, md5 } from '../util/encrypt';
|
||||||
|
|
||||||
@Controller('/user')
|
@Controller('/user')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
|
@ -12,6 +19,9 @@ export class UserController {
|
||||||
@Inject()
|
@Inject()
|
||||||
userService: UserService;
|
userService: UserService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户前台登录
|
||||||
|
*/
|
||||||
@Post('/web/auth')
|
@Post('/web/auth')
|
||||||
async webAuth(@Body() params: UserWebAuthDTO) {
|
async webAuth(@Body() params: UserWebAuthDTO) {
|
||||||
try {
|
try {
|
||||||
|
@ -19,12 +29,28 @@ export class UserController {
|
||||||
if (userExist?.id) {
|
if (userExist?.id) {
|
||||||
const { user_pass, ...rest } = userExist;
|
const { user_pass, ...rest } = userExist;
|
||||||
const passValid = userExist.user_pass === md5(params.user_pass);
|
const passValid = userExist.user_pass === md5(params.user_pass);
|
||||||
return passValid
|
const token = createToken({ ...rest, hasLogin: true });
|
||||||
? { code: BizCode.OK, msg: '欢迎回来', data: { ...rest } }
|
if (passValid) {
|
||||||
: { code: BizCode.ERROR, msg: '密码错误' };
|
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 {
|
} else {
|
||||||
const createUser = await this.userService.save(params);
|
const createUser = await this.userService.save(params);
|
||||||
const { user_pass, ...rest } = createUser;
|
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 {
|
return {
|
||||||
code: BizCode.OK,
|
code: BizCode.OK,
|
||||||
data: { ...rest },
|
data: { ...rest },
|
||||||
|
@ -36,4 +62,22 @@ export class UserController {
|
||||||
return { code: BizCode.ERROR, msg: '[error] web/auth error' };
|
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())
|
@Rule(RuleType.string().required())
|
||||||
user_pass: string;
|
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 { NextFunction, Context } from '@midwayjs/koa';
|
||||||
// import { BizCode } from '../biz/code';
|
import { BizCode } from '../biz/code';
|
||||||
// import { decodeToken } from '../util/encrypt';
|
import { adminSign, webSign } from '../config/base.config';
|
||||||
|
import { whiteApis } from '../config/white.api';
|
||||||
|
import { decodeToken } from '../util/encrypt';
|
||||||
|
|
||||||
@Middleware()
|
@Middleware()
|
||||||
export class AuthMiddleware implements IMiddleware<Context, NextFunction> {
|
export class AuthMiddleware implements IMiddleware<Context, NextFunction> {
|
||||||
|
@App()
|
||||||
|
app: IMidwayApplication;
|
||||||
|
|
||||||
resolve() {
|
resolve() {
|
||||||
return async (ctx: Context, next: NextFunction) => {
|
return async (ctx: Context, next: NextFunction) => {
|
||||||
// const isAdminUrl = ctx.url;
|
const isWhiteApi = whiteApis.some(api => ctx.url.indexOf(api) > -1);
|
||||||
// console.log('isAdminUrl', isAdminUrl);
|
if (!isWhiteApi) {
|
||||||
// if (isAdminUrl) {
|
const token = ctx.cookies.get(adminSign) ?? ctx.cookies.get(webSign);
|
||||||
// const signToken = ctx.cookies.get('_sign_admin');
|
try {
|
||||||
// const { login } = decodeToken(signToken);
|
const { hasLogin } = decodeToken(token);
|
||||||
// if (!login) return { code: BizCode.AUTH };
|
if (!hasLogin) return { code: BizCode.AUTH, msg: '身份验证错误' };
|
||||||
// }
|
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
|
} catch (error) {
|
||||||
|
return { code: BizCode.AUTH, msg: '身份验证错误' };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await next();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
// import { message } from "antd";
|
// import { message } from "antd";
|
||||||
|
import { Message } from "@arco-design/web-react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { useLoginModalStore } from "../store/modal.store";
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
baseURL: "",
|
baseURL: "",
|
||||||
|
@ -22,6 +24,22 @@ instance.interceptors.request.use(
|
||||||
// Add a response interceptor
|
// Add a response interceptor
|
||||||
instance.interceptors.response.use(
|
instance.interceptors.response.use(
|
||||||
(response) => {
|
(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;
|
return response?.data;
|
||||||
},
|
},
|
||||||
(error) => {
|
(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 "./index.less";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { commonRouters, menuRouters } from "../../router";
|
import { menuRouters } from "../../router";
|
||||||
import {
|
import { Input } from "@arco-design/web-react";
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
Form,
|
|
||||||
Message,
|
|
||||||
Button,
|
|
||||||
Space,
|
|
||||||
Radio,
|
|
||||||
Tooltip,
|
|
||||||
} from "@arco-design/web-react";
|
|
||||||
const InputSearch = Input.Search;
|
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() {
|
function Nav() {
|
||||||
const navigate = useNavigate();
|
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 (
|
return (
|
||||||
<header>
|
<header>
|
||||||
|
@ -85,78 +34,7 @@ function Nav() {
|
||||||
<InputSearch allowClear placeholder="搜索" style={{ width: 150 }} />
|
<InputSearch allowClear placeholder="搜索" style={{ width: 150 }} />
|
||||||
</div>
|
</div>
|
||||||
<div className="end">
|
<div className="end">
|
||||||
<span onClick={() => setVisible(true)}>登录</span>
|
<span onClick={() => navigate("/login")}>登录</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>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</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 { Button, Result } from "@arco-design/web-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
interface IGuardProps {
|
interface IGuardProps {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Result403 = (
|
||||||
|
<div style={{ paddingTop: 100 }}>
|
||||||
|
<Result status="403" subTitle="无权访问,请登录" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const needAuthList = ["course/detail"];
|
const needAuthList = ["course/detail"];
|
||||||
|
|
||||||
export const Guard = (props: IGuardProps) => {
|
export const Guard = (props: IGuardProps) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const sessionExist = !!Cookies.get("_session");
|
const sign = Cookies.get("_sign_web");
|
||||||
const needAuth = needAuthList.some((p) => location.pathname.indexOf(p) > -1);
|
const needAuth = needAuthList.some((p) => location.pathname.indexOf(p) > -1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("location.pathname changed 拦截", location.pathname);
|
console.log("location.pathname changed 拦截", location.pathname);
|
||||||
if (location.pathname.indexOf("course/detail/") > -1) {
|
|
||||||
console.log("进入课程详情页");
|
|
||||||
}
|
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
if (needAuth && !sessionExist)
|
if (!sign && needAuth) return Result403;
|
||||||
return (
|
|
||||||
<div style={{ paddingTop: 100 }}>
|
|
||||||
<Result status="403" subTitle="无权访问,请登录" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
return props.children;
|
return props.children;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { lazy } from "react";
|
import { lazy } from "react";
|
||||||
import Course from "../view/Course";
|
import Course from "../view/Course";
|
||||||
|
import { Login } from "../view/Login";
|
||||||
|
|
||||||
export interface IRouteMenuItem {
|
export interface IRouteMenuItem {
|
||||||
path: string;
|
path: string;
|
||||||
|
@ -12,6 +13,12 @@ interface IRoute extends IRouteMenuItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const commonRouters: IRoute[] = [
|
export const commonRouters: IRoute[] = [
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
element: <Login />,
|
||||||
|
name: "登录",
|
||||||
|
invisible: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
element: <Course />,
|
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