import { lazy, Suspense, memo, useEffect } from 'react' import { Routes, Route, Navigate, useLocation } from 'react-router' import { Toaster } from '@/components/ui/sonner' import { isLoggedIn, getUser } from '@/lib/auth-storage' import { routes, findRouteMeta } from '@/router/routes' import ErrorBoundary from '@/components/ErrorBoundary' import { LicenseProvider } from '@/hooks/useLicense' import { useTranslation } from 'react-i18next' const Layout = lazy(() => import('@/components/Layout')) // 路由守卫 - 未登录跳登录页并携带来源路径(登录成功后回跳,见 lib/redirect.ts) const RequireAuth = memo(function RequireAuth({ children }: { children: React.ReactNode }) { const location = useLocation() if (!isLoggedIn()) { const from = location.pathname + location.search return } if (getUser()?.role !== 'admin' && location.pathname !== '/logs') return // 动态导入Layout以避免在登录页面加载它 return {children} }) // 懒加载布局包装器(Layout chunk 加载期间的整屏占位) function LazyLayout({ children }: { children: React.ReactNode }) { return ( }> {children} ) } // 整屏加载占位 function AppLoadingFallback() { return (
) } // document.title 由路由 meta 单源驱动(meta.title 为 i18n key,common 命名空间) function useDocumentTitle() { const location = useLocation() const { t, i18n } = useTranslation() useEffect(() => { const titleKey = findRouteMeta(location.pathname)?.title const title = titleKey ? t(titleKey) : '' document.title = title && location.pathname !== '/login' ? `${title} · LokiStack` : 'LokiStack' }, [location.pathname, t, i18n.language]) } /** * App 根组件 * - 路由配置集中在 router/routes.tsx(meta 驱动标题/菜单) * - 页面全部 React.lazy 懒加载实现代码分割 * - 页面级 Suspense 在 Layout 的内容区,切换页面时布局不闪 */ const App = memo(function App() { useDocumentTitle() return ( {routes.map((route) => ( {/* 页面级错误边界:单页崩溃/chunk 加载失败只影响当前页,侧栏与其它路由仍可用 */} {} ) : ( }> ) } /> ))} ) }) export default App