/** * @description 路由守卫和权限检查 * @author yslg * @since 2025-10-07 */ import type { Router, NavigationGuardNext, RouteLocationNormalized } from 'vue-router'; import type { Store } from 'vuex'; import { AuthState } from '@/store/modules/auth'; /** * 白名单路由 - 无需登录即可访问 */ const WHITE_LIST = [ '/login', '/register', '/forgot-password', '/home', '/404', '/403', '/500' ]; /** * 设置路由守卫 * @param router Vue Router实例 * @param store Vuex Store实例 */ export function setupRouterGuards(router: Router, store: Store) { // 全局前置守卫 router.beforeEach(async (to, from, next) => { // 开始页面加载进度条 startProgress(); try { await handleRouteGuard(to, from, next, store); } catch (error) { console.error('路由守卫执行失败:', error); // 发生错误时跳转到500页面 next('/500'); } }); // 全局后置钩子 router.afterEach((to) => { // 结束页面加载进度条 finishProgress(); // 设置页面标题 setPageTitle(to.meta?.title as string); }); // 全局解析守卫(在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后调用) router.beforeResolve(async (to, from, next) => { // 这里可以处理一些最终的权限检查或数据预加载 next(); }); } /** * 处理路由守卫逻辑 */ async function handleRouteGuard( to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext, store: Store ) { const authState: AuthState = store.state.auth; const isAuthenticated = store.getters['auth/isAuthenticated']; // 检查是否在白名单中 if (isInWhiteList(to.path)) { return next(); } // 检查用户是否已登录 if (!isAuthenticated) { // 未登录,重定向到登录页 return next({ path: '/login', query: { redirect: to.fullPath // 记录用户想要访问的页面 } }); } // 用户已登录,检查是否需要生成动态路由 // 注意:通常情况下路由应该在 main.ts 初始化时就已经生成 // 这里主要处理登录后首次生成路由的情况 if (!authState.routesLoaded && authState.menus && authState.menus.length > 0) { try { console.log('[路由守卫] 路由未加载,开始生成动态路由'); // 生成动态路由 await store.dispatch('auth/generateRoutes'); console.log('[路由守卫] 动态路由生成成功,重新导航'); // 重新导航到目标路由 return next({ ...to, replace: true }); } catch (error) { console.error('[路由守卫] 生成动态路由失败:', error); // 清除认证信息并跳转到登录页 store.commit('auth/CLEAR_AUTH'); return next('/login'); } } // 检查页面权限 const hasPermission = await checkPagePermission(to, store); if (!hasPermission) { // 无权限访问,跳转到403页面 return next('/403'); } // 所有检查通过,继续导航 next(); } /** * 检查路径是否在白名单中 */ function isInWhiteList(path: string): boolean { return WHITE_LIST.some(whitePath => { if (whitePath.endsWith('*')) { // 支持通配符匹配 const prefix = whitePath.slice(0, -1); return path.startsWith(prefix); } return path === whitePath; }); } /** * 检查页面权限 */ async function checkPagePermission( route: RouteLocationNormalized, store: Store ): Promise { // 如果路由元信息中没有要求权限,则允许访问 if (route.meta?.requiresAuth === false) { return true; } // 检查路由是否需要特定权限 const requiredPermissions = route.meta?.permissions as string[] | undefined; if (!requiredPermissions || requiredPermissions.length === 0) { // 无特定权限要求,但需要登录,已经在前面检查过登录状态 return true; } // 检查用户是否有所需权限 const hasPermission = store.getters['auth/hasAnyPermission']; return hasPermission(requiredPermissions); } /** * 设置页面标题 */ function setPageTitle(title?: string) { const appTitle = '校园新闻管理系统'; document.title = title ? `${title} - ${appTitle}` : appTitle; } /** * 开始进度条(可以集成 NProgress 或其他进度条库) */ function startProgress() { // TODO: 集成进度条库,如 NProgress // NProgress.start(); } /** * 完成进度条 */ function finishProgress() { // TODO: 集成进度条库,如 NProgress // NProgress.done(); } /** * Token自动刷新中间件 */ export function setupTokenRefresh(store: Store) { // 设置定时器自动刷新Token setInterval(async () => { const authState: AuthState = store.state.auth; if (!authState.token || !authState.loginDomain) { return; } // 检查Token是否快要过期(例如:提前5分钟刷新) const tokenExpireTime = authState.loginDomain.tokenExpireTime; if (tokenExpireTime) { const expireTime = new Date(tokenExpireTime).getTime(); const currentTime = Date.now(); const fiveMinutes = 5 * 60 * 1000; if (expireTime - currentTime <= fiveMinutes) { try { await store.dispatch('auth/refreshToken'); console.log('Token自动刷新成功'); } catch (error) { console.error('Token自动刷新失败:', error); } } } }, 60 * 1000); // 每分钟检查一次 } /** * 权限检查工具函数 */ export class PermissionChecker { private store: Store; constructor(store: Store) { this.store = store; } /** * 检查是否有指定权限 */ hasPermission(permissionCode: string): boolean { return this.store.getters['auth/hasPermission'](permissionCode); } /** * 检查是否有任意一个权限 */ hasAnyPermission(permissionCodes: string[]): boolean { return this.store.getters['auth/hasAnyPermission'](permissionCodes); } /** * 检查是否有所有权限 */ hasAllPermissions(permissionCodes: string[]): boolean { return this.store.getters['auth/hasAllPermissions'](permissionCodes); } /** * 检查是否有指定角色 */ hasRole(roleCode: string): boolean { const userRoles = this.store.getters['auth/userRoles']; return userRoles.some((role: any) => role.code === roleCode); } /** * 检查是否有任意一个角色 */ hasAnyRole(roleCodes: string[]): boolean { return roleCodes.some(code => this.hasRole(code)); } }