vue3+Ts动态路由,权限管理

动态路由部分 (整个路由的跳转根据name进行匹配,而不是path)

  • 1.先分析前端定义好的路由结构,后端返回的菜单数据

    • 以下是前端定义好的路由结构
        [
            {
                    path: 'analysis',
                    name: 'analysis',
                    component: abalysis,
                    meta:{
                    title: '系统总览'
                    },
                    children: [
                            {
                                path: 'overview',
                                name: 'overview',
                                meta:{
                                title: '核心技术'
                                },
                                component: overview
                            },
                            {
                                path: 'abshboard',
                                name: 'abshboard',
                                meta:{
                                title: '商品统计'
                                },
                                component: abshboard
                            }
                        ]
                },
                // ...... 其他路由结构
        ]
      
    • 以下是后端返回的菜单数据
         [
             {
                 "id": 38,
                 "name": "系统总览",
                 "type": 1,
                 "url": "/analysis",
                 "routeName":"analysis",
                 "icon": "el-icon-monitor",
                 "sort": 1,
                 "children": [
                     {
                         "id": 39,
                         "url": "/analysis/overview",
                         "routeName":"overview",
                         "name": "核心技术",
                         "sort": 106,
                         "type": 2,
                         "children": null,
                         "perentId": 38
                     },
                     {
                         "id": 40,
                         "url": "/analysis/abshboard",
                         "routeName":"abshboard",
                         "name": "商品统计",
                         "sort": 107,
                         "type": 2,
                         "children": null,
                         "perentId": 38
                     }
                 ]
             },
             // .....其他菜单数据
         ]
      
    • 然后就根据独有的字段和后端匹配出需要添加的动态路由
    • 这里用的是前端路由里的meta.title字段和后端返回的name字段进行匹配,当然其他相同字段也可以用于匹配内容
  • 2.根据用户菜单递归获取需要添加的动态路由

        /** 
        * @param { userMenus : <Array>}  后端获取的当前身份用户菜单
        * @param { localRouter : RouteRecordRaw[]}  本地定义好的全部路由
        * @return { realRouter : RouteRecordRaw[]}  根据用户菜单获取需要添加的动态路由
        * 递归找出用户菜单中对应的本地路由
        **/ 
    
        export function mapMenusToRoutes3(userMenus: any[], localRouter: RouteRecordRaw[]): RouteRecordRaw[] {
            const realRouter: RouteRecordRaw[] = [];
            localRouter.forEach(item => {
                    userMenus.forEach(menu => {
                        if (item.meta?.title == menu.name) {
                            if (item.children && item.children.length > 0) {                       
                                mapMenusToRoutes3(menu.children,item.children)
                            }
                            realRouter.push(item)
                        }
                    })
                })
            return realRouter;
        }
    
  • 3.接着将比对好的路由结构添加默认路由 (当我们进入到他的主路由后,自动重定向到他的第一个子路由)

    /**
     * @param { realRouter : <Array>}  根据用户菜单获取的需要添加的动态路由
     * 设置点击下拉菜单时 将路由重定向到当前菜单的 第一个 子路由
    **/ 
    export function mapMenusToFirstPath(realRouter: RouteRecordRaw[]) {
        realRouter.forEach(item => {
            if (item.children && item.children.length > 0) {
                item.redirect = {name: item.children[0].name}
                mapMenusToFirstPath(item.children)
            }
        })
    }
    
  • 4.最后将比对好的路由结构添加到路由实例中

    • 首先调用路由比对函数,将用户菜单和本地路由进行比对,获取到需要添加的动态路由
    • 接着获取到本地的静态路由,这个静态路由是直接定义在路由文件里的,但是还没有挂在到路由实例上,找到这个静态路由里的主页面然后将动态路由添加到主页面下。
    • 添加重定向路由 让每个菜单都能直接跳转到第一个子路由
    • 最后将比对好的路由结构添加到路由实例中 ,这里为什么用[0]是因为我们只添加了一个主页面
    • 静态路由如下
        // 准备动态加载的路由
            export const asyncRoutes = [
            {
                path: '/',
                component: Main,
                name: 'main',
                mata:{
                title: '首页',
                requireAuth: true
                },
                children:[
                ]
            }
            ]
      
    • 添加路由代码如下 ,这个代码就可以在登录完成之后,跳转页面之前,的时候进行,
          // 动态添加路由  调用路由比对函数
          const routes: RouteRecordRaw[] = mapMenusToRoutes3(userMenus.value,localRouter) // 将菜单映射成路由
          /*
          添加默认路由  
          这是获取到到本地的静态路由,找到主页面然后将动态路由添加到主页面下。
          asyncRoutes 是本地的静态路由,MainContainer 是主页面路由,children 是主页面下的子路由
          */ 
          let MainContainer = asyncRoutes.find( item => item.path == '/' && item.name == "main")
      
          // 如果主页面存在,则将动态路由添加到主页面下
          if (MainContainer){
              let children :any[] = MainContainer.children;
              children.push(...routes);
          }
          // 调用重定向路由函数 让每个菜单都能直接跳转到第一个子路由
          mapMenusToFirstPath(asyncRoutes) 
      
          //   将比对好的路由结构添加到路由实例中 ,这里为什么用[0]是因为我们只添加了一个主页面
          router.addRoute(asyncRoutes[0]);
      
  • 5.解决路由刷新后,页面丢失的问题

    • 路由丢失的原因,就是因为我们动态添加的路由原本没有在路由实例中,所以刷新页面后,路由实例就丢失了
    • 所以我们只需要再重新添加一遍路由信息就可以了
    • 先定义一个添加路由的函数
          // 刷新页面时,重新加载所有静态信息,包括路由
          function loadAllStaticInfo() {
              const neWtoken = localCache.getCache('token')
              const neWuserinfo = localCache.getCache('userinfo')
              const neWuserMenus = localCache.getCache('userMenus')
              if (neWtoken && neWuserinfo && neWuserMenus) {
              
              token.value = neWtoken
              userinfo.value = neWuserinfo
              userMenus.value = neWuserMenus
              
              // 刷新后 再次动态添加路由
              // const localRouter = loadLocalRouter() // 获取本地路由
              const routes: RouteRecordRaw[] = mapMenusToRoutes3(userMenus.value, localRouter) // 将菜单映射成路由
              let MainContainer = asyncRoutes.find( item => item.path == '/' && item.name == "main")
              if (MainContainer){
                  let children :any[] = MainContainer.children;
                  children.push(...routes);
                  
              }
              mapMenusToFirstPath(asyncRoutes)  // 添加重定向路由 让每个菜单都能直接跳转到第一个子路由
      
              router.addRoute(asyncRoutes[0]);
              //routes.forEach(route => router.addRoute("main",route)) // 添加到路由中
      
              }
          }
      
    • 上面代码的基本思路就是判断用户是否登录,如果登录了就拿到本地缓存里的菜单数据,然后重新添加到路由实例中,这里获取本地缓存的函数是封装过的,所以看着不一样,但是原理是一样的
    • 下面这一步就是要考虑在什么时候调用这个函数,因为刷新页面后,路由实例会丢失,所以我们需要在路由实例丢失后,调用这个函数
    • 我选择在主入口文件main.ts中调用这个函数, 但是要注意的是,这个函数要在pinia挂载之后调用,因为我们整个登录的逻辑都是在pinia中实现的。然后就是要在router实例挂在之前调用这个函数,让我们的动态路由确保已经添加到路由实例中,这样刷新页面后,路由实例就不会丢失了
    • 代码如下:我这里是单独在stores 文件夹下创建了一个index.ts文件,用来挂载pinia,然引入我们刚刚写的 路由添加函数 ,之后在挂载pinia后调用这个函数
          import { createPinia } from 'pinia'
          import type {App}  from 'vue'
          import { useLoginStore } from "@/stores/login/login"
          import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
          const pinia = createPinia()
          pinia.use(piniaPluginPersistedstate) 
          function registerStore(app:App<Element>) {
              app.use(pinia)
              // 如果页面刷新,在添加pinia之后,加载路由之前,添加所有路由信息到pinia中
              const loginStore = useLoginStore()
              loginStore.loadAllStaticInfo()
          }
          export default registerStore
      
    • 最后一步就是在main.ts中引入这个函数
          import './assets/css/index.less'
          import 'normalize.css'
          import Store from './stores'  // 这个文件就是,上面我们写的那个文件 Store 就是上面默认导出的 registerStore
      
      
          import { createApp } from 'vue'
      
          // 引入element-plus 图标库
          import * as ElementPlusIconsVue from '@element-plus/icons-vue'
      
      
      
          import App from './App.vue'
          import router from './router'
      
          const app = createApp(App)
      
          // 全局注册element-plus 图标库
          for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
              app.component(key, component)
          }
      
          app.use(Store) // 这里就是刷新页面后执行的store
          app.use(router)
          app.mount('#app')
      
    • 至此动态路由结束
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容