feat: 接口鉴权

This commit is contained in:
mozzie 2023-03-14 17:41:11 +08:00
parent 6dcc4753ae
commit a3635ffafb
19 changed files with 311 additions and 526 deletions

View File

@ -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) => {

View File

@ -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";

View 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分钟

View File

@ -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

View 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);

View File

@ -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]);

View File

@ -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: '用户名密码错误' };
}
}
}

View File

@ -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: '用户名密码错误' };
}
}
} }

View File

@ -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;
}

View File

@ -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();
}
}; };
} }

View File

@ -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) => {

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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;

View File

@ -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;
}; };

View File

@ -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 />,

View 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 }),
};
});

View 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);
}
}
}

View 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>
);
}