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')
      
    • 至此动态路由结束
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 226,130评论 6 524
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,210评论 3 410
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 173,639评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,755评论 1 304
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,694评论 6 404
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,160评论 1 317
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,359评论 3 433
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,436评论 0 282
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,990评论 1 328
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,981评论 3 351
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,089评论 1 359
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,669评论 5 353
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,363评论 3 342
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,762评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,950评论 1 278
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,689评论 3 384
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,124评论 2 368

推荐阅读更多精彩内容