Skip to content

路由系统

概述

Naive Admin Max 的路由分为静态路由动态路由两部分。静态路由在应用初始化时注册,包含登录页、异常页等无需权限的页面;动态路由在用户登录后由后端菜单数据驱动生成。

静态路由

静态路由定义在 src/router/routes/staticRoutes.ts,应用启动时即注册,无需鉴权即可访问。

typescript
// src/router/routes/staticRoutes.ts
export const staticRoutes = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/login/index.vue'),
    meta: { title: '登录' },
  },
  {
    path: '/403',
    name: 'Forbidden',
    component: () => import('@/views/error/403.vue'),
    meta: { title: '403' },
  },
  {
    path: '/404',
    name: 'NotFound',
    component: () => import('@/views/error/404.vue'),
    meta: { title: '404' },
  },
  {
    path: '/500',
    name: 'ServerError',
    component: () => import('@/views/error/500.vue'),
    meta: { title: '500' },
  },
]

动态路由

动态路由在用户登录成功后,由 stores/user.ts 根据后端菜单接口返回的数据动态生成并挂载。

工作原理

  1. 用户登录成功,userStore 调用菜单接口获取菜单树
  2. 将菜单项转换为 Vue Router 路由记录
  3. 菜单项的 component 字段对应 src/views/ 下的相对路径
  4. 使用 import.meta.glob 实现路由组件懒加载
typescript
// 使用 import.meta.glob 懒加载视图组件
const viewModules = import.meta.glob('@/views/**/*.vue')

// 将后端菜单项转换为路由
function transformMenuToRoutes(menus: MenuItem[]): RouteRecordRaw[] {
  return menus.map((menu) => ({
    path: menu.path,
    name: menu.name,
    component: viewModules[`/src/views/${menu.component}.vue`],
    meta: {
      title: menu.title,
      icon: menu.icon,
      keepAlive: menu.keepAlive,
      actionList: menu.actionList, // 按钮权限码
    },
    children: menu.children ? transformMenuToRoutes(menu.children) : undefined,
  }))
}

路由懒加载

所有动态路由组件通过 import.meta.glob 预收集路径,实际加载时使用动态 import(),确保按需加载,减少初始包体积。

路由守卫

路由守卫在 src/router/index.ts 中配置,主要处理以下逻辑:

鉴权拦截

  • 未登录用户访问受保护路由 → 重定向至 /login?redirect=当前路径
  • 已登录用户访问 /login → 重定向至首页

动态路由加载

  • 首次进入受保护路由时,触发动态路由加载
  • 加载完成后调用 router.addRoute() 挂载路由

进度条

  • 路由切换时显示顶部进度条(基于 nprogress
  • 切换完成后自动隐藏
typescript
router.beforeEach(async (to, from, next) => {
  NProgress.start()

  const token = localStorage.getItem(VITE_TOKEN_KEY)

  if (token) {
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      // 首次进入时加载动态路由
      if (!userStore.menusLoaded) {
        await userStore.fetchUserInfo()
        // 动态路由已添加,重新导航
        next({ ...to, replace: true })
      } else {
        next()
      }
    }
  } else {
    // 白名单路由直接放行
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
    }
  }
})

router.afterEach(() => {
  NProgress.done()
})

注意

动态路由挂载后需要重新导航(next({ ...to, replace: true })),否则首次访问会命中 404。

Released under the MIT License.