/** * 路由守卫 * 处理路由权限验证、登录状态检查等 */ import { Router, RouteLocationNormalized, NavigationGuardNext } from 'vue-router' import { ElMessage } from 'element-plus' import { authManager } from '@/utils/auth' import { loadingManager } from '@/utils/loadingManager' import { checkTeamMembership, checkCourseAccess, clearPermissionCache } from '@/utils/permissionChecker' // 白名单路由(不需要登录) const WHITE_LIST = ['/login', '/register', '/404'] // 需要特殊权限的路由映射 const ROUTE_PERMISSIONS: Record = { '/admin': ['admin'], '/manager': ['manager', 'admin'], '/analysis': ['manager', 'admin'] } /** * 设置路由守卫 */ export function setupRouterGuard(router: Router) { // 全局前置守卫 router.beforeEach(async (to, from, next) => { // 显示页面加载状态 loadingManager.start('page-loading', { message: '页面加载中...', background: 'rgba(255, 255, 255, 0.8)' }) try { await handleRouteGuard(to, from, next) } catch (error) { console.error('Route guard error:', error) ElMessage.error('页面加载失败,请重试') next('/404') } }) // 全局后置守卫 router.afterEach((to, from) => { // 隐藏页面加载状态 loadingManager.stop('page-loading') // 设置页面标题 document.title = getPageTitle(to.meta?.title as string) // 记录页面访问日志 if (process.env.NODE_ENV === 'development') { console.log(`[Router] Navigate from ${from.path} to ${to.path}`) } }) // 路由错误处理 router.onError((error) => { console.error('Router error:', error) ElMessage.error('路由加载失败') loadingManager.stop('page-loading') }) } /** * 处理路由守卫逻辑 */ async function handleRouteGuard( to: RouteLocationNormalized, _from: RouteLocationNormalized, next: NavigationGuardNext ) { const { path } = to // 白名单路由直接通过 if (WHITE_LIST.includes(path)) { // 如果已登录用户访问登录页,重定向到首页 if (path === '/login' && authManager.isAuthenticated()) { next(authManager.getDefaultRoute()) return } next() return } // 检查登录状态 if (!authManager.isAuthenticated()) { ElMessage.warning('请先登录') next(`/login?redirect=${encodeURIComponent(path)}`) return } // 检查token是否过期 if (authManager.isTokenExpired()) { try { // 尝试刷新token await authManager.refreshToken() } catch (error) { ElMessage.error('登录已过期,请重新登录') next(`/login?redirect=${encodeURIComponent(path)}`) return } } // 检查路由权限 if (!checkRoutePermission(path)) { ElMessage.error('您没有访问此页面的权限') // 根据用户角色重定向到合适的页面 next(authManager.getDefaultRoute()) return } // 检查特殊路由规则(先进行同步检查) if (!checkSpecialRouteRules(to)) { ElMessage.error('访问被拒绝') next(authManager.getDefaultRoute()) return } // 异步权限检查(团队和课程权限) const hasSpecialAccess = await checkSpecialRouteRulesAsync(to) if (!hasSpecialAccess) { ElMessage.error('您没有访问此资源的权限') next(authManager.getDefaultRoute()) return } next() } /** * 检查路由权限 */ function checkRoutePermission(path: string): boolean { // 检查是否可以访问路由 if (!authManager.canAccessRoute(path)) { return false } // 检查特定路由的权限要求 for (const [routePrefix, roles] of Object.entries(ROUTE_PERMISSIONS)) { if (path.startsWith(routePrefix)) { const userRole = authManager.getUserRole() if (!userRole || !roles.includes(userRole)) { return false } } } return true } /** * 检查特殊路由规则(异步版本) */ async function checkSpecialRouteRulesAsync(to: RouteLocationNormalized): Promise { const { path, params } = to // 检查用户ID参数权限(只能访问自己的数据,管理员除外) if (params.userId && !authManager.isAdmin()) { const currentUser = authManager.getCurrentUser() if (currentUser && String(params.userId) !== String(currentUser.id)) { return false } } // 检查团队ID参数权限 if (params.teamId && !authManager.isAdmin()) { const teamId = Number(params.teamId) if (!isNaN(teamId)) { const isMember = await checkTeamMembership(teamId) if (!isMember) { return false } } } // 检查课程访问权限 if (path.includes('/course/') && params.courseId) { const courseId = Number(params.courseId) if (!isNaN(courseId)) { const hasAccess = await checkCourseAccess(courseId) if (!hasAccess) { return false } } } return true } /** * 检查特殊路由规则(同步版本,用于简单检查) */ function checkSpecialRouteRules(to: RouteLocationNormalized): boolean { const { params } = to // 检查用户ID参数权限(只能访问自己的数据,管理员除外) if (params.userId && !authManager.isAdmin()) { const currentUser = authManager.getCurrentUser() if (currentUser && String(params.userId) !== String(currentUser.id)) { return false } } return true } /** * 获取页面标题 */ function getPageTitle(title?: string): string { const appTitle = '考培练系统' return title ? `${title} - ${appTitle}` : appTitle } /** * 动态添加路由(用于角色权限路由) */ export function addDynamicRoutes(router: Router) { const userRole = authManager.getUserRole() if (!userRole) return // 根据角色动态添加路由 const dynamicRoutes = getDynamicRoutesByRole(userRole) dynamicRoutes.forEach(route => { router.addRoute(route) }) } /** * 根据角色获取动态路由 */ function getDynamicRoutesByRole(role: string) { const routes: any[] = [] // 根据角色添加不同的路由 switch (role) { case 'admin': // 管理员可以访问所有路由 break case 'manager': // 管理者路由 break case 'trainee': // 学员路由 break } return routes } /** * 检查页面权限(组件内使用) */ export function checkPagePermission(permissions: string[]): boolean { if (!permissions || permissions.length === 0) { return true } const userRole = authManager.getUserRole() if (!userRole) return false return permissions.includes(userRole) || authManager.isAdmin() } /** * 权限指令(v-permission) */ export const permissionDirective = { mounted(el: HTMLElement, binding: any) { const { value } = binding if (value && !checkPagePermission(Array.isArray(value) ? value : [value])) { el.style.display = 'none' } }, updated(el: HTMLElement, binding: any) { const { value } = binding if (value && !checkPagePermission(Array.isArray(value) ? value : [value])) { el.style.display = 'none' } else { el.style.display = '' } } } /** * 路由元信息接口扩展 */ declare module 'vue-router' { interface RouteMeta { title?: string icon?: string hidden?: boolean roles?: string[] permissions?: string[] keepAlive?: boolean affix?: boolean breadcrumb?: boolean activeMenu?: string } }