Add login to ToolsFramework #35
@@ -1,5 +1,6 @@
|
|||||||
import { getLoginStatus } from '@/utils/auth.ts'
|
import { getLoginStatus } from '@/utils/auth.ts'
|
||||||
import { PRODUCTION_NAME } from '@/constants/Common.constants.ts'
|
import { PRODUCTION_NAME } from '@/constants/Common.constants.ts'
|
||||||
|
import { getRedirectUrl } from '@/utils/common.ts'
|
||||||
|
|
||||||
const AuthRoute = () => {
|
const AuthRoute = () => {
|
||||||
const matches = useMatches()
|
const matches = useMatches()
|
||||||
@@ -16,9 +17,7 @@ const AuthRoute = () => {
|
|||||||
if (matches.some(({ handle }) => (handle as RouteHandle)?.auth) && !isLogin) {
|
if (matches.some(({ handle }) => (handle as RouteHandle)?.auth) && !isLogin) {
|
||||||
return (
|
return (
|
||||||
<Navigate
|
<Navigate
|
||||||
to={`/login?redirect=${encodeURIComponent(
|
to={getRedirectUrl('/login', `${lastMatch.pathname}${location.search}`)}
|
||||||
`${lastMatch.pathname}${location.search}`
|
|
||||||
)}`}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
$origin-color: white;
|
$origin-color: white;
|
||||||
$main-color: #4E47BB;
|
$main-color: #4E47BB;
|
||||||
$secondary-color: #BAB8E5;
|
$secondary-color: #BAB8E5;
|
||||||
|
$error-color: #ff4d4f;
|
||||||
|
$error-secondary-color: #ff7875;
|
||||||
|
$blue-color: #1677ff;
|
||||||
$active-color: #EBECFFD;
|
$active-color: #EBECFFD;
|
||||||
$background-color: #F5F5F5;
|
$background-color: #F5F5F5;
|
||||||
$font-main-color: #4D4D4D;
|
$font-main-color: #4D4D4D;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ body {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
transition: all .3s;
|
transition: all .3s;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -21,6 +20,7 @@ body {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
color: constants.$main-color;
|
color: constants.$main-color;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.icon-box {
|
.icon-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -153,12 +153,10 @@ body {
|
|||||||
animation: 0.3s ease;
|
animation: 0.3s ease;
|
||||||
@include mixins.unique-keyframes {
|
@include mixins.unique-keyframes {
|
||||||
0% {
|
0% {
|
||||||
display: block;
|
|
||||||
transform: translateX(-10px);
|
transform: translateX(-10px);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
display: block;
|
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -188,7 +186,7 @@ body {
|
|||||||
padding: 8px 14px;
|
padding: 8px 14px;
|
||||||
color: constants.$main-color;
|
color: constants.$main-color;
|
||||||
|
|
||||||
.icon-box {
|
.icon-user {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -205,10 +203,27 @@ body {
|
|||||||
|
|
||||||
.text {
|
.text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-left: 8px;
|
padding-left: 10px;
|
||||||
font-size: 1.4em;
|
font-size: 1.4em;
|
||||||
color: constants.$font-main-color;
|
color: constants.$font-main-color;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
|
|
||||||
|
a{
|
||||||
|
color: constants.$main-color;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-exit {
|
||||||
|
font-size: constants.$SIZE_ICON_XS;
|
||||||
|
color: constants.$error-color;
|
||||||
|
padding: 6px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: constants.$background-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,9 +249,50 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
position: relative;
|
||||||
.text {
|
.text {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.submenu-exit {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
padding-left: 6px;
|
||||||
|
left: 100%;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: constants.$origin-color;
|
||||||
|
|
||||||
|
.icon-exit {
|
||||||
|
padding: 4px 8px;
|
||||||
|
&:hover {
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: constants.$background-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hide {
|
||||||
|
display: none!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .submenu-exit {
|
||||||
|
display: block;
|
||||||
|
animation: 0.3s ease;
|
||||||
|
@include mixins.unique-keyframes {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-10px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/assets/svg/exit.svg
Normal file
1
src/assets/svg/exit.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M835.669333 554.666667h-473.173333A42.453333 42.453333 0 0 1 320 512a42.666667 42.666667 0 0 1 42.474667-42.666667h473.173333l-161.813333-161.834666a42.666667 42.666667 0 0 1 60.330666-60.330667l234.666667 234.666667a42.666667 42.666667 0 0 1 0 60.330666l-234.666667 234.666667a42.666667 42.666667 0 0 1-60.330666-60.330667L835.669333 554.666667zM554.666667 42.666667a42.666667 42.666667 0 1 1 0 85.333333H149.525333C137.578667 128 128 137.578667 128 149.482667v725.034666C128 886.4 137.6 896 149.525333 896H554.666667a42.666667 42.666667 0 1 1 0 85.333333H149.525333A106.816 106.816 0 0 1 42.666667 874.517333V149.482667A106.773333 106.773333 0 0 1 149.525333 42.666667H554.666667z" /></svg>
|
||||||
|
After Width: | Height: | Size: 779 B |
@@ -4,9 +4,15 @@ import '@/assets/css/pages/tools-framework.scss'
|
|||||||
import Icon from '@ant-design/icons'
|
import Icon from '@ant-design/icons'
|
||||||
import { toolsJsonObjects } from '@/router/tools.tsx'
|
import { toolsJsonObjects } from '@/router/tools.tsx'
|
||||||
import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar.tsx'
|
import HideScrollbar, { HideScrollbarElement } from '@/components/common/HideScrollbar.tsx'
|
||||||
import { getLocalStorage, setLocalStorage } from '@/utils/common.ts'
|
import { getLocalStorage, getRedirectUrl, setLocalStorage } from '@/utils/common.ts'
|
||||||
|
import { getLoginStatus, logout } from '@/utils/auth.ts'
|
||||||
|
import { NavLink, Outlet } from 'react-router-dom'
|
||||||
|
|
||||||
const ToolsFramework: React.FC = () => {
|
const ToolsFramework: React.FC = () => {
|
||||||
|
const matches = useMatches()
|
||||||
|
const lastMatch = matches.reduce((_, second) => second)
|
||||||
|
const location = useLocation()
|
||||||
|
const navigate = useNavigate()
|
||||||
const hideScrollbarRef = useRef<HideScrollbarElement>(null)
|
const hideScrollbarRef = useRef<HideScrollbarElement>(null)
|
||||||
const [submenuTop, setSubmenuTop] = useState(0)
|
const [submenuTop, setSubmenuTop] = useState(0)
|
||||||
const [submenuLeft, setSubmenuLeft] = useState(0)
|
const [submenuLeft, setSubmenuLeft] = useState(0)
|
||||||
@@ -37,6 +43,21 @@ const ToolsFramework: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClickAvatar = () => {
|
||||||
|
if (getLoginStatus()) {
|
||||||
|
navigate(`${lastMatch.pathname}${location.search}`)
|
||||||
|
} else {
|
||||||
|
navigate(getRedirectUrl('/login', `${lastMatch.pathname}${location.search}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
logout()
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload()
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FitFullScreen className={'flex-horizontal'}>
|
<FitFullScreen className={'flex-horizontal'}>
|
||||||
@@ -183,13 +204,42 @@ const ToolsFramework: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className={'separate'} style={{ marginTop: 0, marginBottom: 0 }} />
|
<div className={'separate'} style={{ marginTop: 0, marginBottom: 0 }} />
|
||||||
<div className={'footer'}>
|
<div className={'footer'}>
|
||||||
<span className={'icon-box'}>
|
<span className={'icon-user'} onClick={handleClickAvatar}>
|
||||||
<Icon component={IconFatwebUser} />
|
<Icon component={IconFatwebUser} />
|
||||||
</span>
|
</span>
|
||||||
<span className={'text'}>未登录</span>
|
<span hidden={getLoginStatus()} className={'text'}>
|
||||||
|
未
|
||||||
|
<NavLink
|
||||||
|
to={getRedirectUrl(
|
||||||
|
'/login',
|
||||||
|
`${lastMatch.pathname}${location.search}`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</NavLink>
|
||||||
|
</span>
|
||||||
|
<span hidden={!getLoginStatus()} className={'text'}>
|
||||||
|
已登录
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
hidden={!getLoginStatus()}
|
||||||
|
className={`submenu-exit${!getLoginStatus() ? ' hide' : ''}`}
|
||||||
|
>
|
||||||
|
<div className={'content'}>
|
||||||
|
<span
|
||||||
|
hidden={!getLoginStatus()}
|
||||||
|
className={'icon-exit'}
|
||||||
|
onClick={handleLogout}
|
||||||
|
>
|
||||||
|
<Icon component={IconFatwebExit} />
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'right-panel'}></div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={'right-panel'}>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
</FitFullScreen>
|
</FitFullScreen>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -309,4 +309,9 @@ const tools: RouteObject[] = toolsJsonObjects.map((value) => ({
|
|||||||
}))
|
}))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
tools.push({
|
||||||
|
path: '*',
|
||||||
|
element: <Navigate to="/tools" replace />
|
||||||
|
})
|
||||||
|
|
||||||
export default tools
|
export default tools
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ service.interceptors.request.use(
|
|||||||
) {
|
) {
|
||||||
await axios
|
await axios
|
||||||
.get(import.meta.env.VITE_API_TOKEN_URL, {
|
.get(import.meta.env.VITE_API_TOKEN_URL, {
|
||||||
headers: { token }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
})
|
})
|
||||||
.then((value: AxiosResponse<_Response<Token>>) => {
|
.then((value: AxiosResponse<_Response<Token>>) => {
|
||||||
const response = value.data
|
const response = value.data
|
||||||
@@ -53,7 +53,7 @@ service.interceptors.request.use(
|
|||||||
}
|
}
|
||||||
|
|
||||||
token = getToken()
|
token = getToken()
|
||||||
config.headers.set('token', token)
|
config.headers.set('Authorization', `Bearer ${token}`)
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { clearLocalStorage, getCaptcha, getLocalStorage, setLocalStorage } from './common'
|
import { getCaptcha, getLocalStorage, removeLocalStorage, setLocalStorage } from './common'
|
||||||
import { SYSTEM_OK, TOKEN_NAME } from '@/constants/Common.constants'
|
import { SYSTEM_OK, TOKEN_NAME } from '@/constants/Common.constants'
|
||||||
import request from '@/services'
|
import request from '@/services'
|
||||||
|
|
||||||
@@ -12,8 +12,9 @@ export async function login(username: string, password: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function logout(): void {
|
export function logout(): void {
|
||||||
void request.get('/logout').finally(() => {
|
void request.post('/logout').finally(() => {
|
||||||
clearLocalStorage()
|
removeLocalStorage('userInfo')
|
||||||
|
removeLocalStorage(TOKEN_NAME)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -134,3 +134,7 @@ function randomFloat(start: number, end: number): number {
|
|||||||
function randomColor(start: number, end: number): string {
|
function randomColor(start: number, end: number): string {
|
||||||
return `rgb(${randomInt(start, end)},${randomInt(start, end)},${randomInt(start, end)})`
|
return `rgb(${randomInt(start, end)},${randomInt(start, end)},${randomInt(start, end)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getRedirectUrl = (path: string, redirectUrl: string): string => {
|
||||||
|
return `${path}?redirect=${encodeURIComponent(redirectUrl)}`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user