diff --git a/apps/web/src/app/(auth)/layout.tsx b/apps/web/src/app/(auth)/layout.tsx index a60e9c8..f825035 100644 --- a/apps/web/src/app/(auth)/layout.tsx +++ b/apps/web/src/app/(auth)/layout.tsx @@ -1,37 +1,111 @@ +'use client'; + +import type { MenuTreeNode } from '@seclusion/shared'; import Link from 'next/link'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { Suspense, useEffect, useState } from 'react'; import { ThemeToggle } from '@/components/layout/ThemeToggle'; import { siteConfig } from '@/config/site'; +import { getUserMenusAndPermissions } from '@/services/permission.service'; +import { useAuthStore, usePermissionStore } from '@/stores'; -export default function AuthLayout({ - children, -}: { - children: React.ReactNode; -}) { +function AuthRedirect() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { token, isHydrated: authHydrated } = useAuthStore(); + const { isLoaded, isExpired, setPermissionData, canAccessRoute, menus } = usePermissionStore(); + const [hasRedirected, setHasRedirected] = useState(false); + + // 加载权限数据 + useEffect(() => { + if (!authHydrated || !token) return; + + if (!isLoaded || isExpired()) { + getUserMenusAndPermissions() + .then((data) => { + setPermissionData(data); + }) + .catch((error) => { + console.error('加载权限数据失败:', error); + setPermissionData({ menus: [], permissions: [], isSuperAdmin: false }); + }); + } + }, [authHydrated, token, isLoaded, isExpired, setPermissionData]); + + // 执行智能重定向(只执行一次) + useEffect(() => { + // 未登录、权限未加载或已经执行过重定向,则不处理 + if (!authHydrated || !token || !isLoaded || hasRedirected) return; + + const targetRedirect = searchParams.get('redirect') || '/dashboard'; + + // 检查目标路由是否可访问 + if (canAccessRoute(targetRedirect)) { + setHasRedirected(true); + router.replace(targetRedirect); + return; + } + + // 目标路由不可访问,找到第一个可访问的菜单路径 + const findFirstAccessiblePath = (menuList: MenuTreeNode[]): string | null => { + for (const menu of menuList) { + // 跳过隐藏菜单、外部链接和没有路径的菜单 + if (!menu.isHidden && menu.path && !menu.isExternal) { + return menu.path; + } + // 递归查找子菜单 + if (menu.children && menu.children.length > 0) { + const childPath = findFirstAccessiblePath(menu.children); + if (childPath) return childPath; + } + } + return null; + }; + + const firstPath = findFirstAccessiblePath(menus); + + setHasRedirected(true); + if (firstPath) { + router.replace(firstPath); + } else { + // 没有任何可访问的路由,跳转到无权限页面 + router.replace('/403'); + } + }, [authHydrated, token, isLoaded, hasRedirected, searchParams, canAccessRoute, menus, router]); + + return null; +} + +export default function AuthLayout({ children }: { children: React.ReactNode }) { return ( -
- {/* 顶部导航 */} -
-
- - {siteConfig.name} - - -
-
+ <> + + + +
+ {/* 顶部导航 */} +
+
+ + {siteConfig.name} + + +
+
- {/* 主内容区 */} -
-
{children}
-
+ {/* 主内容区 */} +
+
{children}
+
- {/* 页脚 */} -
-
- © {new Date().getFullYear()} {siteConfig.name}. All rights - reserved. -
-
-
+ {/* 页脚 */} + +
+ ); } diff --git a/apps/web/src/app/(dashboard)/layout.tsx b/apps/web/src/app/(dashboard)/layout.tsx index 0289f45..7c8a8fb 100644 --- a/apps/web/src/app/(dashboard)/layout.tsx +++ b/apps/web/src/app/(dashboard)/layout.tsx @@ -5,7 +5,6 @@ import { useEffect } from 'react'; import { Header } from '@/components/layout/Header'; import { Sidebar, MobileSidebar } from '@/components/layout/Sidebar'; -import { getRoutePermission, isPublicRoute } from '@/config/route-permissions'; import { getLoginUrl } from '@/lib/auth'; import { getUserMenusAndPermissions } from '@/services/permission.service'; import { useAuthStore, usePermissionStore } from '@/stores'; @@ -14,7 +13,8 @@ export default function DashboardLayout({ children }: { children: React.ReactNod const pathname = usePathname(); const router = useRouter(); const { token, isHydrated: authHydrated } = useAuthStore(); - const { isLoaded, setPermissionData, hasPermission, clearPermissionData } = usePermissionStore(); + const { isLoaded, isExpired, setPermissionData, canAccessRoute, clearPermissionData } = + usePermissionStore(); // 登录状态检查:未登录时重定向到登录页 useEffect(() => { @@ -26,8 +26,8 @@ export default function DashboardLayout({ children }: { children: React.ReactNod return; } - // 已登录且权限未加载时,加载权限数据 - if (!isLoaded) { + // 已登录且权限未加载或已过期时,加载/刷新权限数据 + if (!isLoaded || isExpired()) { getUserMenusAndPermissions() .then((data) => { setPermissionData(data); @@ -38,24 +38,17 @@ export default function DashboardLayout({ children }: { children: React.ReactNod setPermissionData({ menus: [], permissions: [], isSuperAdmin: false }); }); } - }, [authHydrated, token, isLoaded, pathname, router, setPermissionData, clearPermissionData]); + }, [authHydrated, token, isLoaded, isExpired, pathname, router, setPermissionData, clearPermissionData]); // 路由权限检查 useEffect(() => { if (!authHydrated || !isLoaded) return; - // 公开路由不检查权限 - if (isPublicRoute(pathname)) return; - - // 获取路由所需权限 - const requiredPermission = getRoutePermission(pathname); - if (!requiredPermission) return; - - // 检查权限 - if (!hasPermission(requiredPermission)) { + // 检查是否可以访问当前路由 + if (!canAccessRoute(pathname)) { router.replace('/403'); } - }, [authHydrated, isLoaded, pathname, hasPermission, router]); + }, [authHydrated, isLoaded, pathname, canAccessRoute, router]); return (