215 lines
5.0 KiB
TypeScript
215 lines
5.0 KiB
TypeScript
|
|
import { createBrowserRouter, Navigate, useLocation } from 'react-router-dom'
|
|||
|
|
import { Suspense, lazy } from 'react'
|
|||
|
|
import { useAuthStore, ROLES } from '@/stores/authStore'
|
|||
|
|
|
|||
|
|
// 路由守卫组件
|
|||
|
|
export function AuthGuard({ children }: { children: React.ReactNode }) {
|
|||
|
|
const { isAuthenticated, isLoading } = useAuthStore()
|
|||
|
|
const location = useLocation()
|
|||
|
|
|
|||
|
|
if (isLoading) {
|
|||
|
|
return (
|
|||
|
|
<div className="flex h-screen items-center justify-center">
|
|||
|
|
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!isAuthenticated) {
|
|||
|
|
return <Navigate to="/login" state={{ from: location }} replace />
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return <>{children}</>
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function RoleGuard({
|
|||
|
|
children,
|
|||
|
|
roles
|
|||
|
|
}: {
|
|||
|
|
children: React.ReactNode
|
|||
|
|
roles: string[]
|
|||
|
|
}) {
|
|||
|
|
const { hasAnyRole, isAuthenticated, isLoading } = useAuthStore()
|
|||
|
|
const location = useLocation()
|
|||
|
|
|
|||
|
|
if (isLoading) {
|
|||
|
|
return (
|
|||
|
|
<div className="flex h-screen items-center justify-center">
|
|||
|
|
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!isAuthenticated) {
|
|||
|
|
return <Navigate to="/login" state={{ from: location }} replace />
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!hasAnyRole(roles)) {
|
|||
|
|
return <Navigate to="/403" replace />
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return <>{children}</>
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function AdminGuard({ children }: { children: React.ReactNode }) {
|
|||
|
|
return (
|
|||
|
|
<RoleGuard roles={[ROLES.SUPER_ADMIN, ROLES.ADMIN]}>
|
|||
|
|
{children}
|
|||
|
|
</RoleGuard>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function ModeratorGuard({ children }: { children: React.ReactNode }) {
|
|||
|
|
return (
|
|||
|
|
<RoleGuard roles={[ROLES.SUPER_ADMIN, ROLES.ADMIN, ROLES.MODERATOR]}>
|
|||
|
|
{children}
|
|||
|
|
</RoleGuard>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 公开路由守卫(已登录用户重定向到dashboard)
|
|||
|
|
export function PublicGuard({ children }: { children: React.ReactNode }) {
|
|||
|
|
const { isAuthenticated, isLoading } = useAuthStore()
|
|||
|
|
|
|||
|
|
if (isLoading) {
|
|||
|
|
return (
|
|||
|
|
<div className="flex h-screen items-center justify-center">
|
|||
|
|
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (isAuthenticated) {
|
|||
|
|
return <Navigate to="/dashboard" replace />
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return <>{children}</>
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载指示器组件
|
|||
|
|
function PageLoader() {
|
|||
|
|
return (
|
|||
|
|
<div className="flex h-full items-center justify-center p-8">
|
|||
|
|
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 懒加载页面组件
|
|||
|
|
const Login = lazy(() => import('@/pages/Login'))
|
|||
|
|
const Dashboard = lazy(() => import('@/pages/Dashboard'))
|
|||
|
|
const Users = lazy(() => import('@/pages/Users'))
|
|||
|
|
const Roles = lazy(() => import('@/pages/Roles'))
|
|||
|
|
const Posts = lazy(() => import('@/pages/Posts'))
|
|||
|
|
const Comments = lazy(() => import('@/pages/Comments'))
|
|||
|
|
const Groups = lazy(() => import('@/pages/Groups'))
|
|||
|
|
const Forbidden = lazy(() => import('@/pages/Forbidden'))
|
|||
|
|
const NotFound = lazy(() => import('@/pages/NotFound'))
|
|||
|
|
|
|||
|
|
// 布局组件
|
|||
|
|
import MainLayout from '@/components/Layout/MainLayout'
|
|||
|
|
|
|||
|
|
// 创建路由
|
|||
|
|
export const router = createBrowserRouter([
|
|||
|
|
{
|
|||
|
|
path: '/login',
|
|||
|
|
element: (
|
|||
|
|
<PublicGuard>
|
|||
|
|
<Suspense fallback={<PageLoader />}>
|
|||
|
|
<Login />
|
|||
|
|
</Suspense>
|
|||
|
|
</PublicGuard>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
path: '/',
|
|||
|
|
element: (
|
|||
|
|
<AuthGuard>
|
|||
|
|
<MainLayout />
|
|||
|
|
</AuthGuard>
|
|||
|
|
),
|
|||
|
|
children: [
|
|||
|
|
{
|
|||
|
|
index: true,
|
|||
|
|
element: <Navigate to="/dashboard" replace />,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
path: 'dashboard',
|
|||
|
|
element: (
|
|||
|
|
<Suspense fallback={<PageLoader />}>
|
|||
|
|
<Dashboard />
|
|||
|
|
</Suspense>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
path: 'users',
|
|||
|
|
element: (
|
|||
|
|
<AdminGuard>
|
|||
|
|
<Suspense fallback={<PageLoader />}>
|
|||
|
|
<Users />
|
|||
|
|
</Suspense>
|
|||
|
|
</AdminGuard>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
path: 'roles',
|
|||
|
|
element: (
|
|||
|
|
<AdminGuard>
|
|||
|
|
<Suspense fallback={<PageLoader />}>
|
|||
|
|
<Roles />
|
|||
|
|
</Suspense>
|
|||
|
|
</AdminGuard>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
path: 'posts',
|
|||
|
|
element: (
|
|||
|
|
<ModeratorGuard>
|
|||
|
|
<Suspense fallback={<PageLoader />}>
|
|||
|
|
<Posts />
|
|||
|
|
</Suspense>
|
|||
|
|
</ModeratorGuard>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
path: 'comments',
|
|||
|
|
element: (
|
|||
|
|
<ModeratorGuard>
|
|||
|
|
<Suspense fallback={<PageLoader />}>
|
|||
|
|
<Comments />
|
|||
|
|
</Suspense>
|
|||
|
|
</ModeratorGuard>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
path: 'groups',
|
|||
|
|
element: (
|
|||
|
|
<ModeratorGuard>
|
|||
|
|
<Suspense fallback={<PageLoader />}>
|
|||
|
|
<Groups />
|
|||
|
|
</Suspense>
|
|||
|
|
</ModeratorGuard>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
path: '/403',
|
|||
|
|
element: (
|
|||
|
|
<Suspense fallback={<PageLoader />}>
|
|||
|
|
<Forbidden />
|
|||
|
|
</Suspense>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
path: '*',
|
|||
|
|
element: (
|
|||
|
|
<Suspense fallback={<PageLoader />}>
|
|||
|
|
<NotFound />
|
|||
|
|
</Suspense>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
export default router
|