import type { Router } from 'vue-router'; import { LOGIN_PATH } from '@vben/constants'; import { preferences, updatePreferences } from '@vben/preferences'; import { useAccessStore, useUserStore } from '@vben/stores'; import { startProgress, stopProgress } from '@vben/utils'; import { accessRoutes, coreRouteNames } from '#/router/routes'; import { useAuthStore } from '#/store'; import { NO_LOGIN_ROUTES } from '#/router/routes'; import { APP_LAYOUT_BY_PREFIX } from '#/preferences'; import { generateAccess } from './access'; /** * 通用守卫配置 * @param router */ function setupCommonGuard(router: Router) { // 记录已经加载的页面 const loadedPaths = new Set(); router.beforeEach((to) => { to.meta.loaded = loadedPaths.has(to.path); // 根据前缀切换布局(由配置驱动,可接后端/站点设置) const first = to.path.replace(/^\/+/, '').split('/')[0] as string; const desiredLayout = APP_LAYOUT_BY_PREFIX[first]; if (desiredLayout && preferences.app.layout !== desiredLayout) { updatePreferences({ app: { layout: desiredLayout as any } }); } // 页面加载进度条 if (!to.meta.loaded && preferences.transition.progress) { startProgress(); } return true; }); router.afterEach((to) => { // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行 loadedPaths.add(to.path); // 关闭页面加载进度条 if (preferences.transition.progress) { stopProgress(); } // 设置标题:路由标题 + 站点/网站名(如有) const titleParts: string[] = []; if (typeof to.meta?.title === 'string') titleParts.push(to.meta.title); // 这里可扩展注入 store 的网站/站点名 const websiteName = ''; if (websiteName) titleParts.push(websiteName); if (titleParts.length) document.title = titleParts.join(' - '); }); } /** * 权限访问守卫配置 * @param router */ function setupAccessGuard(router: Router) { router.beforeEach(async (to, from) => { const accessStore = useAccessStore(); const userStore = useUserStore(); const authStore = useAuthStore(); // 基本路由,这些路由不需要进入权限拦截 if (coreRouteNames.includes(to.name as string)) { if (to.path === LOGIN_PATH && accessStore.accessToken) { return decodeURIComponent( (to.query?.redirect as string) || userStore.userInfo?.homePath || preferences.app.defaultHomePath, ); } return true; } // accessToken 检查 if (!accessStore.accessToken) { // 明确声明忽略权限访问权限,则可以访问 if (to.meta.ignoreAccess) { return true; } // addon 定义的免登录白名单 if (NO_LOGIN_ROUTES?.includes(to.path)) { return true; } // 没有访问权限,跳转登录页面 if (to.fullPath !== LOGIN_PATH) { return { path: LOGIN_PATH, // 如不需要,直接删除 query query: to.fullPath === preferences.app.defaultHomePath ? {} : { redirect: encodeURIComponent(to.fullPath) }, // 携带当前跳转的页面,登录后重新跳转该页面 replace: true, }; } return to; } // 是否已经生成过动态路由 if (accessStore.isAccessChecked) { return true; } // 生成路由表 // 当前登录用户拥有的角色标识列表 const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); const userRoles = userInfo.roles ?? []; // 生成菜单和路由 const { accessibleMenus, accessibleRoutes } = await generateAccess({ roles: userRoles, router, // 则会在菜单中显示,但是访问会被重定向到403 routes: accessRoutes, }); // 保存菜单信息和路由信息 accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessRoutes(accessibleRoutes); accessStore.setIsAccessChecked(true); // 计算首次进入首页路径 let redirectPath: string; if (from.query.redirect) { redirectPath = from.query.redirect as string; } else if (to.path === preferences.app.defaultHomePath) { redirectPath = preferences.app.defaultHomePath; } else if (userInfo.homePath && to.path === userInfo.homePath) { redirectPath = userInfo.homePath; } else { redirectPath = to.fullPath; } return { ...router.resolve(decodeURIComponent(redirectPath)), replace: true, }; }); } /** * 项目守卫配置 * @param router */ function createRouterGuard(router: Router) { /** 通用 */ setupCommonGuard(router); /** 权限访问 */ setupAccessGuard(router); } export { createRouterGuard };