Skip to content

路由配置与守卫

路由守卫机制

路由守卫是控制路由访问权限和用户体验的核心机制,包含用户认证、权限验证、动态路由生成等功能。

核心功能

  • 用户认证: 验证用户登录状态
  • 权限控制: 检查用户访问权限
  • 动态路由: 根据权限生成可访问路由
  • 状态管理: 自动获取用户信息和权限数据
  • 用户体验: 进度条、页面标题、路由重定向

前置守卫 (beforeEach)

执行流程

typescript
router.beforeEach(async (to, from, next) => {
  // 1. 开启进度条
  NProgress.start()
  
  // 2. 检查用户登录状态
  if (!isLoggedIn.value) {
    // 未登录处理逻辑
    return handleUnauthenticated(to, next)
  }
  
  // 3. 已登录用户处理
  if (to.path === '/login') {
    return next({ path: '/' }) // 重定向到首页
  }
  
  // 4. 白名单检查
  if (isInWhiteList(to.path)) {
    return next()
  }
  
  // 5. 权限验证
  if (userStore.roles.length > 0) {
    return canAccessRoute(to) ? next() : next('/403')
  }
  
  // 6. 获取用户信息和生成动态路由
  await initUserAndRoutes()
  next({ ...to, replace: true })
})

白名单配置

不需要登录即可访问的页面:

typescript
const WHITE_LIST = [
  '/login',           // 登录页
  '/register',        // 注册页
  '/socialCallback',  // 社交登录回调
  '/register*',       // 注册相关页面(通配符)
  '/register/*',      // 注册子页面
  '/401',             // 未授权页面
  '/home'             // 前台首页
]

const isInWhiteList = (path: string) => {
  return WHITE_LIST.some(pattern => isPathMatch(pattern, path))
}

用户信息获取

typescript
// 防重复获取机制
let isFetchingUserInfo = false

const initUserAndRoutes = async () => {
  if (isFetchingUserInfo) return
  
  isFetchingUserInfo = true
  
  try {
    // 获取用户信息
    const [fetchUserErr] = await userStore.fetchUserInfo()
    if (fetchUserErr) {
      await userStore.logoutUser()
      return next({ path: '/' })
    }
    
    // 生成动态路由
    const [generateRoutesErr, accessRoutes] = await permissionStore.generateRoutes()
    if (generateRoutesErr) {
      return next('/403')
    }
    
    // 添加路由到路由表
    accessRoutes.forEach(route => {
      if (!isHttp(route.path)) {
        router.addRoute(route)
      }
    })
  } finally {
    isFetchingUserInfo = false
  }
}

后置守卫 (afterEach)

功能实现

typescript
router.afterEach((to) => {
  // 结束进度条
  NProgress.done()
  
  // 设置页面标题
  const themeStore = useThemeStore()
  if (to.meta.title) {
    themeStore.setTitle(to.meta.title as string)
  }
})

进度条配置

NProgress 设置

typescript
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

// 进度条配置
NProgress.configure({ 
  showSpinner: false  // 隐藏加载图标
})

使用场景

  • 路由切换时自动显示/隐藏
  • 长时间异步操作的视觉反馈
  • 提升用户体验的重要工具

权限验证

useAuth 权限钩子

typescript
const { canAccessRoute, isLoggedIn } = useAuth()

// 检查路由访问权限
if (canAccessRoute(to)) {
  next()
} else {
  next('/403')
}

权限检查逻辑

typescript
const canAccessRoute = (route: RouteLocationNormalized) => {
  // 1. 检查路由是否需要权限
  if (!route.meta?.permissions && !route.meta?.roles) {
    return true
  }
  
  // 2. 检查用户权限
  const userPermissions = userStore.permissions
  const userRoles = userStore.roles
  
  // 3. 权限验证
  if (route.meta.permissions) {
    return route.meta.permissions.some(permission => 
      userPermissions.includes(permission)
    )
  }
  
  // 4. 角色验证
  if (route.meta.roles) {
    return route.meta.roles.some(role => 
      userRoles.includes(role)
    )
  }
  
  return false
}

错误处理

常见错误场景

typescript
// 1. 用户信息获取失败
const [fetchUserErr] = await userStore.fetchUserInfo()
if (fetchUserErr) {
  // 自动注销并重定向
  await userStore.logoutUser()
  return next({ path: '/' })
}

// 2. 动态路由生成失败
const [generateRoutesErr] = await permissionStore.generateRoutes()
if (generateRoutesErr) {
  showMsgError(generateRoutesErr)
  return next('/403')
}

// 3. 网络异常处理
if (!logoutErr) {
  return next({ path: '/' })
} else {
  showNotifyError({
    title: '系统提示',
    message: '后端服务未启动或异常,请检查!',
    duration: 10000
  })
}

路由重置

resetRouter 函数

typescript
export const resetRouter = () => {
  // 创建新的路由实例
  const newRouter = createRouter({
    history: createWebHistory(SystemConfig.app.contextPath),
    routes: constantRoutes,
    scrollBehavior(to, from, savedPosition) {
      if (savedPosition) return savedPosition
      return { top: 0 }
    }
  })
  
  // 重置路由匹配器
  router.matcher = newRouter.matcher
}

使用场景

  • 用户退出登录时清除动态路由
  • 权限变更后重新初始化路由
  • 角色切换时更新可访问路由

最佳实践

1. 路由守卫优化

typescript
// ❌ 避免在守卫中进行大量同步操作
router.beforeEach((to, from, next) => {
  // 大量同步计算...
  next()
})

// ✅ 使用异步操作和缓存
router.beforeEach(async (to, from, next) => {
  const cachedResult = getCachedResult()
  if (!cachedResult) {
    await computeAsync()
  }
  next()
})

2. 权限检查缓存

typescript
// 缓存权限检查结果,避免重复计算
const permissionCache = new Map()

const canAccessRoute = (route) => {
  const cacheKey = `${route.path}-${userStore.roles.join(',')}`
  if (permissionCache.has(cacheKey)) {
    return permissionCache.get(cacheKey)
  }
  
  const result = checkPermissions(route)
  permissionCache.set(cacheKey, result)
  return result
}

3. 导航失败处理

typescript
// 处理导航失败的情况
router.push('/target').catch(failure => {
  if (isNavigationFailure(failure, NavigationFailureType.cancelled)) {
    // 处理导航取消
  }
})