import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'; import { ElMessage } from 'element-plus'; import { useNProgress } from '@vueuse/integrations/useNProgress'; import { createRouter, createWebHistory } from 'vue-router'; import { ROUTER_WHITE_LIST } from '@/config'; import { checkPagePermission } from '@/config/permission'; import { errorRouter, layoutRouter, staticRouter } from '@/routers/modules/staticRouter'; import { useUserStore } from '@/stores'; import { useDesignStore } from '@/stores/modules/design'; // 创建页面加载进度条,提升用户体验 const { start, done } = useNProgress(0, { showSpinner: false, trickleSpeed: 200, minimum: 0.3, easing: 'ease', speed: 500, }); // 创建路由实例 const router = createRouter({ history: createWebHistory(), routes: [...layoutRouter, ...staticRouter, ...errorRouter], strict: false, scrollBehavior: () => ({ left: 0, top: 0 }), }); // 预加载标记,防止重复预加载 const preloadedComponents = new Set(); /** * 预加载路由组件 * 提前加载可能访问的路由组件,减少路由切换时的等待时间 */ function preloadRouteComponents() { // 预加载核心路由组件 const coreRoutes = [ '/chat/conversation', '/chat/image', '/chat/video', '/chat/agent', '/console/user', '/model-library', ]; // 延迟预加载,避免影响首屏加载 setTimeout(() => { coreRoutes.forEach(path => { const route = router.resolve(path); if (route.matched.length > 0) { const component = route.matched[route.matched.length - 1].components?.default; if (typeof component === 'function' && !preloadedComponents.has(component)) { preloadedComponents.add(component); // 异步预加载,不阻塞主线程 requestIdleCallback?.(() => { (component as () => Promise)().catch(() => {}); }) || setTimeout(() => { (component as () => Promise)().catch(() => {}); }, 100); } } }); }, 2000); } // 首屏加载完成后开始预加载 if (typeof window !== 'undefined') { window.addEventListener('load', preloadRouteComponents); } // 路由前置守卫 router.beforeEach( async ( to: RouteLocationNormalized, _from: RouteLocationNormalized, next: NavigationGuardNext, ) => { // 1. 开始显示进度条 start(); // 2. 设置页面标题 document.title = (to.meta.title as string) || (import.meta.env.VITE_WEB_TITLE as string); // 3. 设置布局(使用 setTimeout 避免阻塞导航) const layout = to.meta?.layout || 'default'; setTimeout(() => { try { const designStore = useDesignStore(); designStore._setLayout(layout); } catch (e) { // 忽略 store 初始化错误 } }, 0); // 4. 检查路由是否存在(404 处理) // 如果 to.matched 为空且 to.name 不存在,说明路由未匹配 if (to.matched.length === 0 || (to.matched.length === 1 && to.matched[0].path === '/:pathMatch(.*)*')) { // 404 路由已定义在 errorRouter 中,这里不需要额外处理 } // 5. 白名单检查(跳过权限验证) if (ROUTER_WHITE_LIST.some(path => { // 支持通配符匹配 if (path.includes(':')) { const pattern = path.replace(/:\w+/g, '[^/]+'); const regex = new RegExp(`^${pattern}$`); return regex.test(to.path); } return path === to.path; })) { return next(); } // 6. 获取用户状态(延迟加载,避免阻塞) let userStore; try { userStore = useUserStore(); } catch (e) { // Store 未初始化,允许继续 return next(); } // 7. Token 检查(用户认证) if (!userStore.token) { userStore.clearUserInfo(); return next({ path: '/', replace: true }); } // 8. 页面权限检查 const userName = userStore.userInfo?.user?.userName; const hasPermission = checkPagePermission(to.path, userName); if (!hasPermission) { ElMessage.warning('您没有权限访问该页面'); return next({ path: '/403', replace: true }); } // 9. 放行路由 next(); }, ); // 路由跳转错误 router.onError((error) => { // 结束全屏动画 done(); console.warn('路由错误', error.message); }); // 后置路由 router.afterEach(() => { // 结束全屏动画 done(); }); export default router;