From a3635ffafb8438a15e377377083f9ba8552b7d3d Mon Sep 17 00:00:00 2001 From: mozzie Date: Tue, 14 Mar 2023 17:41:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8E=A5=E5=8F=A3=E9=89=B4=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/admin/src/api/request.ts | 21 +- apps/admin/src/router/Guard.tsx | 2 +- apps/server/src/config/base.config.ts | 7 + apps/server/src/config/config.default.ts | 30 +- apps/server/src/config/white.api.ts | 7 + apps/server/src/configuration.ts | 11 +- .../server/src/controller/admin.controller.ts | 25 -- apps/server/src/controller/user.controller.ts | 56 +++- apps/server/src/dto/user.dto.ts | 8 + apps/server/src/middleware/auth.middleware.ts | 38 ++- apps/web/src/api/request.ts | 18 ++ apps/web/src/components/Nav/index.less | 261 ------------------ apps/web/src/components/Nav/index.tsx | 128 +-------- apps/web/src/components/Nav/loginModal.tsx | 50 ---- apps/web/src/router/Guard.tsx | 20 +- apps/web/src/router/index.tsx | 7 + apps/web/src/store/modal.store.ts | 9 + apps/web/src/view/Login/index.less | 55 ++++ apps/web/src/view/Login/index.tsx | 84 ++++++ 19 files changed, 311 insertions(+), 526 deletions(-) create mode 100644 apps/server/src/config/base.config.ts create mode 100644 apps/server/src/config/white.api.ts delete mode 100644 apps/server/src/controller/admin.controller.ts delete mode 100644 apps/web/src/components/Nav/loginModal.tsx create mode 100644 apps/web/src/store/modal.store.ts create mode 100644 apps/web/src/view/Login/index.less create mode 100644 apps/web/src/view/Login/index.tsx diff --git a/apps/admin/src/api/request.ts b/apps/admin/src/api/request.ts index bb6638b..793f608 100644 --- a/apps/admin/src/api/request.ts +++ b/apps/admin/src/api/request.ts @@ -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) => { diff --git a/apps/admin/src/router/Guard.tsx b/apps/admin/src/router/Guard.tsx index 8190c57..04c0a32 100644 --- a/apps/admin/src/router/Guard.tsx +++ b/apps/admin/src/router/Guard.tsx @@ -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"; diff --git a/apps/server/src/config/base.config.ts b/apps/server/src/config/base.config.ts new file mode 100644 index 0000000..f64b7f1 --- /dev/null +++ b/apps/server/src/config/base.config.ts @@ -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分钟 diff --git a/apps/server/src/config/config.default.ts b/apps/server/src/config/config.default.ts index b7f3559..7940c98 100644 --- a/apps/server/src/config/config.default.ts +++ b/apps/server/src/config/config.default.ts @@ -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 diff --git a/apps/server/src/config/white.api.ts b/apps/server/src/config/white.api.ts new file mode 100644 index 0000000..d487eb5 --- /dev/null +++ b/apps/server/src/config/white.api.ts @@ -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); diff --git a/apps/server/src/configuration.ts b/apps/server/src/configuration.ts index 901355c..c967e50 100644 --- a/apps/server/src/configuration.ts +++ b/apps/server/src/configuration.ts @@ -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]); diff --git a/apps/server/src/controller/admin.controller.ts b/apps/server/src/controller/admin.controller.ts deleted file mode 100644 index 69ed50e..0000000 --- a/apps/server/src/controller/admin.controller.ts +++ /dev/null @@ -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: '用户名密码错误' }; - } - } -} diff --git a/apps/server/src/controller/user.controller.ts b/apps/server/src/controller/user.controller.ts index 76a4e68..833777e 100644 --- a/apps/server/src/controller/user.controller.ts +++ b/apps/server/src/controller/user.controller.ts @@ -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: '用户名密码错误' }; + } + } } diff --git a/apps/server/src/dto/user.dto.ts b/apps/server/src/dto/user.dto.ts index bbe80c0..8dc4dc3 100644 --- a/apps/server/src/dto/user.dto.ts +++ b/apps/server/src/dto/user.dto.ts @@ -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; +} diff --git a/apps/server/src/middleware/auth.middleware.ts b/apps/server/src/middleware/auth.middleware.ts index bf2be30..be26791 100644 --- a/apps/server/src/middleware/auth.middleware.ts +++ b/apps/server/src/middleware/auth.middleware.ts @@ -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 { + @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(); + } }; } diff --git a/apps/web/src/api/request.ts b/apps/web/src/api/request.ts index de4191f..1a47de1 100644 --- a/apps/web/src/api/request.ts +++ b/apps/web/src/api/request.ts @@ -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) => { diff --git a/apps/web/src/components/Nav/index.less b/apps/web/src/components/Nav/index.less index ab6d866..de52543 100644 --- a/apps/web/src/components/Nav/index.less +++ b/apps/web/src/components/Nav/index.less @@ -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; - } - } -} diff --git a/apps/web/src/components/Nav/index.tsx b/apps/web/src/components/Nav/index.tsx index 507594e..627bf79 100644 --- a/apps/web/src/components/Nav/index.tsx +++ b/apps/web/src/components/Nav/index.tsx @@ -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("useSms"); - let [countdown, setCountdown] = useState(DURATION); - const timer = useRef(); - - 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 (
@@ -85,78 +34,7 @@ function Nav() {
- setVisible(true)}>登录 - setVisible(false)} - > -
-
-

setLoginMethod("useSms")} - > - 验证码登录{" "} -

-
- - setSmsLoginForm((p) => ({ - ...p, - user_login: e.target.value, - })) - } - /> -
- - setSmsLoginForm((p) => ({ - ...p, - user_pass: e.target.value, - })) - } - placeholder="验证码" - > - -
-
- -
- {/* 扫码 */} -
-
-

setLoginMethod("useQRCode")} - onClick={() => Message.info("开发中... 😀 ")} - > - 扫码登录 -

-
开发中……
-
-
-
-
+ navigate("/login")}>登录
diff --git a/apps/web/src/components/Nav/loginModal.tsx b/apps/web/src/components/Nav/loginModal.tsx deleted file mode 100644 index 34fab97..0000000 --- a/apps/web/src/components/Nav/loginModal.tsx +++ /dev/null @@ -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 ( - setVisible(false)} - > -
- - - - - - -
-
- ); -} - -export default LoginModal; diff --git a/apps/web/src/router/Guard.tsx b/apps/web/src/router/Guard.tsx index d4618de..17a4b83 100644 --- a/apps/web/src/router/Guard.tsx +++ b/apps/web/src/router/Guard.tsx @@ -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 = ( +
+ +
+); + 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 ( -
- -
- ); + if (!sign && needAuth) return Result403; return props.children; }; diff --git a/apps/web/src/router/index.tsx b/apps/web/src/router/index.tsx index 1381579..95eb407 100644 --- a/apps/web/src/router/index.tsx +++ b/apps/web/src/router/index.tsx @@ -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: , + name: "登录", + invisible: true, + }, { path: "/", element: , diff --git a/apps/web/src/store/modal.store.ts b/apps/web/src/store/modal.store.ts new file mode 100644 index 0000000..43a567f --- /dev/null +++ b/apps/web/src/store/modal.store.ts @@ -0,0 +1,9 @@ +import { create } from "zustand"; + +export const useLoginModalStore = create((set) => { + return { + visible: false, + setVisible: () => set({ visible: true }), + setInVisible: () => set({ visible: false }), + }; +}); diff --git a/apps/web/src/view/Login/index.less b/apps/web/src/view/Login/index.less new file mode 100644 index 0000000..bdce686 --- /dev/null +++ b/apps/web/src/view/Login/index.less @@ -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); + } + } +} diff --git a/apps/web/src/view/Login/index.tsx b/apps/web/src/view/Login/index.tsx new file mode 100644 index 0000000..7367193 --- /dev/null +++ b/apps/web/src/view/Login/index.tsx @@ -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("useSms"); + let [countdown, setCountdown] = useState(DURATION); + const timer = useRef(); + + 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 ( +
+
+
+ + setSmsLoginForm((p) => ({ + ...p, + user_login: e.target.value, + })) + } + /> +
+ + setSmsLoginForm((p) => ({ + ...p, + user_pass: e.target.value, + })) + } + placeholder="验证码" + > + +
+
+ +
+
+ ); +}