From 85aba29ced073a3831f704c7713083732c8d6bc6 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Thu, 21 Dec 2023 18:36:31 +0800 Subject: [PATCH] Recode login page --- .../css/pages/{login.scss => login-.scss} | 0 src/assets/css/pages/sign.scss | 161 ++++++++ src/assets/svg/password.svg | 1 + src/pages/{Login.tsx => Login-.tsx} | 2 +- src/pages/Sign.tsx | 347 ++++++++++++++++++ src/pages/UserFramework.tsx | 13 +- src/router/index.tsx | 22 +- 7 files changed, 540 insertions(+), 6 deletions(-) rename src/assets/css/pages/{login.scss => login-.scss} (100%) create mode 100644 src/assets/css/pages/sign.scss create mode 100644 src/assets/svg/password.svg rename src/pages/{Login.tsx => Login-.tsx} (99%) create mode 100644 src/pages/Sign.tsx diff --git a/src/assets/css/pages/login.scss b/src/assets/css/pages/login-.scss similarity index 100% rename from src/assets/css/pages/login.scss rename to src/assets/css/pages/login-.scss diff --git a/src/assets/css/pages/sign.scss b/src/assets/css/pages/sign.scss new file mode 100644 index 0000000..93ddf2a --- /dev/null +++ b/src/assets/css/pages/sign.scss @@ -0,0 +1,161 @@ +@use "@/assets/css/mixins" as mixins; +@use "@/assets/css/constants" as constants; + +[data-component=sign] { + background-color: #D2D0DD; + user-select: none; + + a:hover { + color: constants.$production-color; + } + + .sign-box { + position: relative; + background-color: constants.$origin-color; + width: 900px; + height: 600px; + overflow: hidden; + border-radius: 12px; + + .left, .right { + opacity: 1; + animation: 1s ease; + @include mixins.unique-keyframes { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } + } + + > * { + width: 100%; + height: 100%; + } + + &.hidden { + opacity: 0; + animation: 1s ease; + @include mixins.unique-keyframes { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } + } + } + + > * { + .title { + margin-bottom: 20px; + transform: translateY(-10px); + + .primary { + font-size: 2.4em; + font-weight: bolder; + color: constants.$production-color; + } + + .secondary { + font-size: 1.2em; + } + } + + .form { + width: 300px; + + .addition { + justify-content: space-between; + margin-bottom: 14px; + + > * { + flex: 0 0 auto; + } + } + + .footer { + text-align: center; + + a { + color: constants.$production-color; + } + } + } + } + + + .sign-up { + + } + + .sign-in { + + } + } + + .cover { + position: absolute; + height: 100%; + width: 50%; + background-color: #F3F4F8; + animation: 0.8s ease; + @include mixins.unique-keyframes { + 0% { + left: 50%; + } + + 100% { + left: 0; + } + } + + .ball-box { + position: relative; + overflow: hidden; + + background-color: #F1F2F7; + + .ball { + position: absolute; + width: 128px; + height: 128px; + background-color: constants.$production-color; + border-radius: 50%; + bottom: 0; + left: 50%; + transform: translateX(-50%) translateY(50%); + } + } + + .mask { + transform: rotateZ(180deg); + filter: blur(12px); + + .ball { + width: 140px; + height: 140px; + } + } + } + + &.switch { + .cover { + left: 50%; + animation: 0.8s ease; + @include mixins.unique-keyframes { + 0% { + left: 0; + } + + 100% { + left: 50%; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/assets/svg/password.svg b/src/assets/svg/password.svg new file mode 100644 index 0000000..acdcbb3 --- /dev/null +++ b/src/assets/svg/password.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pages/Login.tsx b/src/pages/Login-.tsx similarity index 99% rename from src/pages/Login.tsx rename to src/pages/Login-.tsx index b64c68a..42e3362 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login-.tsx @@ -1,5 +1,5 @@ import React from 'react' -import '@/assets/css/pages/login.scss' +import '@/assets/css/pages/login-.scss' import { PERMISSION_LOGIN_SUCCESS, PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR, diff --git a/src/pages/Sign.tsx b/src/pages/Sign.tsx new file mode 100644 index 0000000..8622291 --- /dev/null +++ b/src/pages/Sign.tsx @@ -0,0 +1,347 @@ +import React, { useState } from 'react' +import '@/assets/css/pages/sign.scss' +import FitFullscreen from '@/components/common/FitFullscreen' +import FitCenter from '@/components/common/FitCenter' +import FlexBox from '@/components/common/FlexBox' +import { useNavigate } from 'react-router' +import Icon from '@ant-design/icons' +import { getUserInfo, login, setToken } from '@/util/auth.tsx' +import { + PERMISSION_LOGIN_SUCCESS, + PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR, + PERMISSION_USER_DISABLE, + PERMISSION_USERNAME_NOT_FOUND +} from '@/constants/common.constants.ts' +import { AppContext } from '@/App.tsx' +import { utcToLocalTime } from '@/util/datetime.tsx' +import { useUpdatedEffect } from '@/util/hooks.tsx' + +const SignUp: React.FC = () => { + const navigate = useNavigate() + const [searchParams] = useSearchParams() + + return ( +
+ + +
Welcome join us
+ + navigate( + `/login${ + searchParams.toString() ? `?${searchParams.toString()}` : '' + }` + ) + } + > + 登录 + + + + +
+
+
+ ) +} + +const Forget: React.FC = () => { + const navigate = useNavigate() + const [searchParams] = useSearchParams() + const [isLoading, setIsLoading] = useState(false) + const [isFinish, setIsFinish] = useState(false) + + const handleOnFinish = () => { + setIsFinish(true) + } + + const handleOnRetry = () => { + setIsFinish(false) + } + + return ( +
+ + +
+
找回密码
+
Retrieve password
+
+ + {!isFinish ? ( + <> + + } + placeholder={'邮箱'} + disabled={isLoading} + /> + + + + 确    定 + + + + ) : ( +
+ 我们发送了一封包含找回密码链接的邮件到您的邮箱里,如未收到,可能被归为垃圾邮件,请仔细检查。 + 重新发送 +
+ )} + + +
+
+
+
+ ) +} + +const SignIn: React.FC = () => { + const { refreshRouter } = useContext(AppContext) + const navigate = useNavigate() + const [searchParams] = useSearchParams() + const [isSigningIn, setIsSigningIn] = useState(false) + + const handleOnFinish = (loginForm: LoginForm) => { + if (isSigningIn) { + return + } + setIsSigningIn(true) + + void login(loginForm.account, loginForm.password) + .then((res) => { + const response = res.data + const { code, data } = response + switch (code) { + case PERMISSION_LOGIN_SUCCESS: + setToken(data?.token ?? '') + void message.success('登录成功') + setTimeout(() => { + void getUserInfo().then((user) => { + refreshRouter() + if (searchParams.has('redirect')) { + navigate(searchParams.get('redirect') ?? '/') + } else { + navigate('/') + } + + notification.success({ + message: '欢迎回来', + description: ( + <> + + 你好 {user.userInfo.nickname} + +
+ + 最近登录: + {user.lastLoginTime + ? `${utcToLocalTime(user.lastLoginTime)}【${ + user.lastLoginIp + }】` + : '无'} + + + ), + placement: 'topRight' + }) + }) + }, 1500) + break + case PERMISSION_USERNAME_NOT_FOUND: + case PERMISSION_LOGIN_USERNAME_PASSWORD_ERROR: + void message.error( + <> + 账号密码错误,请重试 + + ) + setIsSigningIn(false) + break + case PERMISSION_USER_DISABLE: + void message.error( + <> + 该用户已被禁用 + + ) + setIsSigningIn(false) + break + default: + void message.error( + <> + 服务器出错了 + + ) + setIsSigningIn(false) + } + }) + .catch(() => { + setIsSigningIn(false) + }) + } + + return ( +
+ + +
+
欢迎回来
+
Welcome back
+
+ + + } + placeholder={'邮箱/用户名'} + disabled={isSigningIn} + /> + + + } + placeholder={'密码'} + disabled={isSigningIn} + /> + + + 记住密码 + { + navigate( + `/forget${ + searchParams.toString() + ? `?${searchParams.toString()}` + : '' + }` + ) + }} + > + 忘记密码? + + + + + 登    录 + + + + +
+
+
+ ) +} + +const Sign: React.FC = () => { + const lastPage = useRef('none') + const currentPage = useRef('none') + const match = useMatches().reduce((_, second) => second) + const [isSwitch, setIsSwitch] = useState(false) + + const leftPage = ['register', 'forget'] + + useUpdatedEffect(() => { + lastPage.current = currentPage.current + currentPage.current = match.id + + setIsSwitch(leftPage.includes(currentPage.current)) + }, [match.id]) + + const leftComponent = () => { + switch (leftPage.includes(currentPage.current) ? currentPage.current : lastPage.current) { + case 'forget': + return + default: + return + } + } + + const rightComponent = () => { + switch (leftPage.includes(currentPage.current) ? lastPage.current : currentPage.current) { + default: + return + } + } + + return ( + <> + + + +
{leftComponent()}
+
+ {rightComponent()} +
+ +
+
+
+
+
+
+
+
+ + + + + + ) +} + +export default Sign diff --git a/src/pages/UserFramework.tsx b/src/pages/UserFramework.tsx index 6a4e6da..5e60a66 100644 --- a/src/pages/UserFramework.tsx +++ b/src/pages/UserFramework.tsx @@ -16,15 +16,20 @@ const ToolsFramework: React.FC = () => { + + {hasPathPermission('/system') ? ( - - ) : undefined + ) : undefined} + + } > diff --git a/src/router/index.tsx b/src/router/index.tsx index 9e90791..3714009 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -6,17 +6,37 @@ import user from '@/router/user' import tools from '@/router/tools' import { getAuthRoute, mapJsonToRoute, setTitle } from '@/util/route' +const lazySignPage = React.lazy(() => import('@/pages/Sign')) + const root: RouteJsonObject[] = [ { path: '/', absolutePath: '/', component: React.lazy(() => import('@/AuthRoute')), children: [ + { + path: 'register', + absolutePath: '/register', + id: 'register', + component: lazySignPage + }, + { + path: 'confirm', + absolutePath: '/confirm', + id: 'confirm', + component: lazySignPage + }, + { + path: 'forget', + absolutePath: '/forget', + id: 'forget', + component: lazySignPage + }, { path: 'login', absolutePath: '/login', id: 'login', - component: React.lazy(() => import('@/pages/Login')) + component: lazySignPage }, { path: 'loading',