路由与安全导航
2026/1/17大约 2 分钟
路由与安全导航
使用react-router来实现路由导航和保护路由。
实例
const Page = () => {
const {i18n} = useTranslation()
const {theme} = useAppStore()
return (
<BrowserRouter>
<ConfigProvider
locale={i18n.language === 'zh' ? zhCN : enUS}
theme={{
algorithm: [theme === 'dark' ? darkAlgorithm : defaultAlgorithm],
}}
>
<App className="w-full h-full theme">
<StaticMessage/>
<RouterPage/>
</App>
</ConfigProvider>
</BrowserRouter>
)
}export const RouterPage = () => {
return (
<>
<Routes>
<Route path="" element={<Guards/>}>
<Route path="/home" element={<Home/>}/>
<Route path="/lottery" element={<Lottery/>}></Route>
<Route path="/manage/lottery" element={<LotteryManage/>}></Route>
<Route path="/manage/role" element={<RoleManage/>}></Route>
<Route path="/manage/user" element={<UserManage/>}></Route>
<Route path="/manage/movie" element={<UserManage/>}></Route>
<Route path="/manage/page" element={<PageManage/>}></Route>
</Route>
<Route path="/login" element={<Login/>}></Route>
</Routes>
</>
)
}在routerPage 的初始路由添加守卫组件 Guards 来保护路由。
import {message} from '@/components/StaticMessage'
import Layout from '@/layout'
import {TOKEN} from '@/utils/config'
import {getLocalInfo} from '@/utils/localStorage'
import {Spin} from 'antd'
import {useEffect, useMemo, useState} from 'react'
import {useTranslation} from 'react-i18next'
import {useLocation, useNavigate, useOutlet} from 'react-router'
import {useUserStore} from '@/stores/userStore.ts'
// 同步方式获取token
function getTokenSync() {
try {
return getLocalInfo<string>(TOKEN) || ''
} catch {
return ''
}
}
function Guards() {
// 路由嵌套中的外部
const outlet = useOutlet()
const location = useLocation()
const whiteList = ['/login', '/home']
const {permissions} = useUserStore(state => state)
const navigate = useNavigate()
const {t} = useTranslation()
// 同步检查权限,避免异步导致的页面闪动
const {token, isValid, shouldRedirect, redirectPath} = useMemo(() => {
const token = getTokenSync()
const isLoginRoute = location.pathname === '/login'
// 有token且访问登录页,需要重定向到首页
if (token && isLoginRoute) {
const redirect = new URLSearchParams(location.search).get('redirect')
return {
token,
isValid: false,
shouldRedirect: true,
redirectPath: redirect || '/',
}
}
// 无token且访问非登录页,需要重定向到登录页
if (!token && !isLoginRoute) {
const param = location.pathname?.length > 1 ? `?redirect=${location.pathname}${location.search}` : ''
return {
token,
isValid: false,
shouldRedirect: true,
redirectPath: `/login${param}`,
}
}
// 如果不在白名单中并且没有访问权限 那就回到公共首页
if (token && !whiteList.includes(location.pathname) && !permissions.includes(location.pathname)) {
return {
token,
isValid: false,
shouldRedirect: true,
redirectPath: `/home`,
}
}
// 其他情况正常渲染
return {token, isValid: !!token || isLoginRoute, shouldRedirect: false, redirectPath: ''}
}, [location.pathname, location.search])
const [redirected, setRedirected] = useState<boolean>(false)
useEffect(() => {
if (shouldRedirect) {
// 执行重定向
navigate(redirectPath, {replace: true})
setRedirected(true)
// 如果是跳转到登录页,显示提示信息
if (redirectPath.startsWith('/login') && location.pathname !== '/') {
message.warning({
content: t('public.noLoginVisit'),
key: 'noLoginVisit',
})
}
return
}
setRedirected(false)
return () => {
}
}, [shouldRedirect, redirectPath, navigate, location.pathname, t])
// 重定向时不渲染任何内容
if (shouldRedirect || redirected) {
// 这是一个加载页
return (
<div className="absolute left-50% top-50% -translate-x-1/2 -translate-y-1/2 text-center">
<Spin spinning={true}/>
</div>
)
}
/** 渲染页面 */
const renderPage = () => {
// 访问登录页且有token,但useEffect还没执行跳转时
if (location.pathname === '/login' && token) {
return <div>{outlet}</div>
}
// 有权限访问其他页面,渲染布局
if (isValid && token) {
return <Layout/>
}
// 无权限或跳转情况,渲染登录页
return <div>{outlet}</div>
}
return <>{renderPage()}</>
}
export default Guards