动态路由的具体实现(一篇搞定)

560 阅读5分钟

该项目 git 地址

  • 结合项目源码更容易看懂 代码更完整

动态路由理解

  • 为什么要使用动态路由呢

    • 主要还是权限管理这一块吧 到时候写一篇完整的权限管理文章 这里简单说说 动态路由
    • 比如 一个 admin 管理端 不同角色是有不同权限的 也就是说部分角色的部分功能是没法使用的 具体到页面的话 就是有一些 menu 没办法点击 或者 一部分路由是没有办法跳转的
  • 三种实现 以及区别

  • 登录后可以取得 userInfo 角色和信息 会得到一个 userMenus

    • 这个就是一个权限 一张动态路由表
    • 控制的的菜单 只有你有这个权限 才会显示这个路由
  • 方式一:我以前的话可能使用 v-if 来判断这个菜单是否展示

    • 但是这样是写死的 全都要注册上去 只是不展示

      • 如果手动改路径 那么就会展示 不安全 很早以前我自己就是这么写的 那个时候不知道还有其他方式
    • 但是现在可以使用动态路由来渲染 如果手动输入地址 因为路由没有注册 所以不会出现页面 还能跳转到本不应该出现的页面 就不会出现错误页面的现象 可以直接 跳转到 not-found

  • 方式二:

    • 可以在前端使用数组 对不同的身份创建对应的路由映射表
    • 但是有一个弊端 如果有新增的角色 就需要该前端代码重新部署了
  • 方式三:

    • 菜单动态生成路由

    • 1.菜单中有加载组件的我名称

      • components: Rple.vue (路径和名称必须前后端一致)
      • 就是跟后端协商传递的值要做限制
      • 菜单中只有 url
      • 前端中已经配置好 路径和组件的映射关系
      • 然后会根据这个路径来加载 组件
  • 下面是 方式三的具体实现思路

动态路由具体实现思路

  • 根据登录的角色

  • 获取登录信息 和 路由菜单

    • 所有的路由是要提前注册好的

    • image.png

    • 后台传过来的数据是什么样的呢

      • 是一个数组 可以二级 三级等
      • userMenus {
          {
            children : [], // 子级菜单
            icon: '', // 菜单图标 
            id: '', // 
            type: '', // 用于判断第几级菜单
            url: '', // 路径
            name: '' // 菜单名称
          }
        }
        
      • 要注册的路由 起始本质就是一个对象
      • export default {
          path: '',
          name: '',
          component: () => import('@views'),
          children: []
        }
        // 具体实现
        const dashboard = () => import('@/views/main/analysis/dashboard/dashboard.vue')
        export default {
          path: '/main/analysis/dashboard',
          name: 'dashboard',
          component: dashboard,
          children: [],
        }
        ​
        
      • 到时候就只需要将后端接收的数据动态注册到路由中就行了
  • 根绝获取到的后台的动态路由来添加进路由列表

    • 所以 router 文件夹里面刚开始只用注册 .login .mian .not found

    • 其他的路由 新建一个函数获取完了之后在动态注册进去

      • 获取到的树要怎么转换并且注册成路由呢?
      • 使用路由映射表
        先通过遍历 生成一个所有路由的数组
        然后通过获取的 userMenus 在所有的路由对象中查找 find 使用 url 来判断是否查找到
        把该路由添加进一个新的 路由数组中
          这里可以使用递归来遍历 二级 或者三级路由
        最后使用 router.addRouter 将这个动态路由注册进去
        
    • 这里先封装一个工具函数 utils map-menus.ts

      • import { RouteRecordRaw } from 'vue-router'export function mapMenusToRoutes(userMenus: any[]): RouteRecordRaw[] {
          const routes: RouteRecordRaw[] = []
        ​
          // 1.先去加载默认所有的 routes
          const allRoutes: RouteRecordRaw[] = []
          // 加载文件夹 true 会递归查找下面的文件夹// webpack
          // const routeFiles = require.context('../router/main', true, /.ts/)
          // 里面是所有的 ts 文件的路径 相对于 main 的
          // .production/goods/goods.ts
          //
          // routeFiles.keys().forEach((key: string) => {
          //   // 拼接出路由使用的路径
          //   const route = require('../router/main' + key.split('.')[1])
          //   allRoutes.push(route.default)
          // })
          const routeFiles = import.meta.globEager('@/router/main/**/*.ts')
          console.log(routeFiles)
          for (const path in routeFiles) {
            allRoutes.push(routeFiles[path].default)
          }
        ​
          // 2.根据菜单获取需要添加的routes
          // userMenus:
          // type === 1 -> children -> type === 1
          // type === 2 -> url -> route
          const _recurseGetRoute = (menus: any[]) => {
            for (const menu of menus) {
              if (menu.type === 2) {
                const route = allRoutes.find((route) => route.path === menu.url)
                if (route) routes.push(route)
              } else {
                _recurseGetRoute(menu.children)
              }
            }
          }
        ​
          _recurseGetRoute(userMenus)
        ​
          return routes
        }
        ​
        
        • console.log(routeFiles) 的信息
        • image.png
    • 因为 我获取的每个角色的信息是使用 vuex 来进行管理的 所以更新路由的函数在 store 下的 login 中

      • 为什么需要更新 userMenus 呢

        • 1.因为在登录页面跳转完了之后 第一次进入首页 是可以调用了获取了 useMenus 的信息来 加载左侧的 menu 导航栏的

          • 但是当你刷新页面之后 需要重新获取数据加载侧边导航栏
        • 2.如果没有注册路由的话 点击 menu 直接就会跳转到 not-found 页面

    • store login login.ts

      • mutations: {
          changeUserMenus(state, userMenus: any) {
            state.userMenus = userMenus
        ​
            console.log('注册动态路由')
        ​
            // 使用公路函数获取路由映射表
            const routes = mapMenusToRoutes(userMenus)
        ​
            // 将routes => router.main.children 路由映射表添加到路由中去
            routes.forEach((route) => {
              router.addRoute('main', route)
            })
          }
        },
        actions: {
          // 重新获取信息的异步函数
          loadLocalLogin({ commit }) {
            const token = localCache.getCache('token')
            if (token) {
              commit('changeToken', token)
            }
            const userInfo = localCache.getCache('userInfo')
            if (userInfo) {
              commit('changeUserInfo', userInfo)
            }
            // 用于更新路由信息    
            const userMenus = localCache.getCache('userMenus')
            if (userMenus) {
              commit('changeUserMenus', userMenus)
            }
          }
        }
        
    • store index.ts

      • // 重新获取登陆后的信息 动态路由 界面刷新时使用
        export function setupStore() {
          store.dispatch('login/loadLocalLogin')
        }
        
    • main.ts

      • import { setupStore } from './store'
        ​
        // 刷新后重新获取 userMenus
        setupStore()
        ​
        /*
        + 每次刷新都会重新执行这个文件
        + 那么就会执行 app.use(router) 获取到当前的 path
          + 这时 就会接着 去匹配路径 router.routes
          + 但是这个时候还没有执行路由守卫的 因为路由守卫是一个回调函数
            + 真正要跳转的时候才会执行
          + 那么这时候 匹配到的是 not-found
          + 所以 setupStore() 一定要在 app.use(router) 之前执行注册
        */
        app.use(router)
        
      • \