addRoutes實現動態權限路由菜單

需求

最近接手一個後臺管理系統,須要實現導航菜單從後臺拉取的效果;根據登陸用戶的權限不一樣分別拉出來的導航菜單也不同,另外可操做的界面也存在區別。html

問題

由於後臺管理系統是準備使用vue+vue-router+element-ui+vuex的搭配來作的,但是單頁應用在進入頁面以前就已經將vue-router實例化而且注入vue實例中了,因此在進入登陸頁面的時候舊沒辦法在從新定製路由了。接下來各類百之谷之,發現vue-router在2.0版本中提供了addRoutes方法添加路由,但願的曙光出現。
通過一番折騰終於實現了功能,記錄下來便於回顧,也但願能幫助到一樣有需求的同志。vue

思路

一、首先在本地配置好固定不變的路由地址,例如登陸,404這些頁面,以下:vue-router

import Vue from 'vue'
import Router from 'vue-router'
import store from '@/vuex/store'
Vue.use(Router)

let router = new Router({
  routes: [
    {
      path: '/login',
      name: 'login',
      meta: {requireAuth: false},
      // 模塊使用異步加載
      component: (resolve) => require(['../components/login/login.vue'], resolve)
    }]
})
// 攔截登陸,token驗證
router.beforeEach((to, from, next) => {
  if (to.meta.requireAuth === undefined) {
    if (store.state.token) {
      next()
    } else {
      next({
        path: '/login'
      })
    }
  } else {
    next()
  }
})
export default router

配置好這些固定的路由後咱們纔可以到登陸頁面,否則是沒法繼續下去的。vuex

二、而後重要的一步,咱們須要跟後端老鐵約定好須要返回的權限菜單列表信息;首先這裏咱們先分析一下本身須要的路由結構,這裏以我本身的路由做爲例子。若是是我本身直接定義路由的話,會是如下結構:element-ui

let router = new Router({
  routes: [
    {
      path: '/login',
      name: 'login',
      meta: {requireAuth: false},
      component: (resolve) => require(['../components/login/login.vue'], resolve)
    },
    {
        path: '/',
        redirect: '/layout'
    },
    {
        path: '/layout',
        component: (resolve) => require(['../layout.vue'], resolve),
        children: [
            {
                path: 'index', 
                meta: {
                    type: '1',       //控制是否顯示隱藏 1顯示,2隱藏
                    code: 00010001,  // 後面須要控制路由高亮
                    title: '首頁',    // 菜單名稱
                    permissonList: [] // 權限列表
                }
                component: (resolve) => require(['@/components/index/index.vue'], resolve)
            },
            {
            ...
            }      
        ]
    }]
})

根據以上結構分析,其實真正須要動態配置的路由實際上是/layout下面的children部分,因此須要後端返回給咱們包含全部路由的一個數組就能夠了後端

clipboard.png
返回的數據中rootList中是一級導航的列表,一級導航實際是沒有路由功能,只是做爲切換二級菜單的觸發器,subList纔是咱們真正須要的路由信息。
三、拿到權限路由信息後,須要咱們在本地對數據進行處理組裝成咱們須要的數據:數組

// 登陸
      login () {
        let params = {
          account: this.loginForm.username,
          password: encrypt(this.loginForm.password)
        }
        this.loading = true
        this.$http.post(this.$bumng + '/login', this.$HP(params))
          .then((res) => {
            this.loging = false
            console.info('菜單列表:', res)
            if (res.resultCode === this.$state_ok) {
              // 合併一級菜單和二級菜單,便於顯示
              let menus = handleMenu.mergeSubInRoot(res.rootList, res.subList)
              // 本地化處理好的菜單列表
              this.saveRes({label: 'menuList', value: menus})
              // 根據subList處理路由
              let routes = handleMenu.mergeRoutes(res.subList)
              // 本地化subList,便於在刷新頁面的時候從新配置路由
              this.saveRes({label: 'subList', value: res.subList})
              // 防止重複配置相同路由
              if (this.$router.options.routes.length <= 1) {
                this.$router.addRoutes(routes)
                // this.$router不是響應式的,因此手動將路由元注入路由對象
                this.$router.options.routes.push(routes)
              }
              this.$router.replace('/layout/index')
            }
          })
          .catch((err) => {
            this.loging = false
            console.error('錯誤:', err)
          })
      },

處理菜單列表和subList的方法:mergeSubInRoot 和 mergeRoutes緩存

const routes = [
  {
    path: '/',
    redirect: '/layout'
  },
  {
    path: '/layout',
    component: (resolve) => require(['../layout.vue'], resolve),
    children: []
  }
]
export default {
  /**
   * 合併主菜單和子菜單
   * @param: rootList [Array] 主菜單列表
   * @param: subList [Array] 子菜單
   * */
  mergeSubInRoot (roots, subs) {
    if (roots && subs) {
      for (let i = 0; i < roots.length; i++) {
        let rootCode = roots[i].code
        roots[i].children = []
        for (let j = 0; j < subs.length; j++) {
          if (rootCode === subs[j].code.substring(0, 4)) {
            roots[i].children.push(subs[j])
          }
        }
      }
    }
    return roots
  },
  /**
   * 合併遠程路由到本地路由
   * @param: subList [Array] 遠程路由列表
   * @param: routes [Array] 本地路由列表
   * */
  mergeRoutes (subs) {
    if (subs) {
      for (let i = 0; i < subs.length; i++) {
        let temp = {
          path: subs[i].actUrl,
          name: subs[i].actUrl,
          component: (resolve) => require([`@/components/${subs[i].component}.vue`], resolve),
          meta: {
            type: subs[i].type,
            code: subs[i].code,
            title: subs[i].name,
            permissionList: subs[i].permissionList
          }
        }
        routes[1].children.push(temp)
      }
    }
    return routes
  }
}

至此咱們已經將權限路由成功配置進本地路由了,個人系統登陸進入以下session

clipboard.png

後續優化

一、菜單列表的顯示以及二級導航切換:app

<template>
    <div class="mainMenu">
      <el-menu
        class="menubar"
        mode="horizontal"
        :default-active="activeCode"
        background-color="#545c64"
        text-color="#fff"
        active-text-color="#ffd04b">
        <el-menu-item :index="item.code | splitCode" v-for="item in menuList" :key="item.code" @click="switchSubMenu(item)" v-if="item.code !== '0008'">
          <i :class="`iconfont icon-${item.imgUrl}`"></i>
          <span slot="title">{{item.name}}</span>
        </el-menu-item>
      </el-menu>
    </div>
</template>

<script type="text/ecmascript-6">
  import {mapState, mapMutations} from 'vuex'
  export default {
    name: 'menu',
    data () {
      return {
        msg: 'Welcome to Your Vue.js App'
      }
    },
    computed: {
      ...mapState(['menuList']),
      activeCode () {
          // 經過code保證在切換字路由的狀況下一級路由也是高亮顯示
        return this.$route.meta.code.substring(0, 4)
      }
    },
    methods: {
      ...mapMutations(['saveRes']),
      // 切換二級路由
      switchSubMenu (route) {
        console.info('路由:', route)
        if (route.actUrl !== 'index') {
          // 用currentSubMenu控制二級路由數據  
          this.saveRes({label: 'currentSubMenu', value: route.children})
          this.$router.push(`/layout/${route.children[0].actUrl}`)
        } else {
          // 不存在二級路由隱藏二級 
          this.saveRes({label: 'currentSubMenu', value: ''})
          this.$router.push(`/layout/${route.actUrl}`)
        }
      }
    },
    filters: {
      splitCode (code) {
        return code.substring(0, 4)
      }
    }
  }
</script>

二、防止刷新路由丟失;因爲在刷新的時候單頁應用會從新初始化,這時候全部配置的路由都會丟失,一朝回到解放前,只有本地配置的路由可以跳轉。這時候咱們能夠在app.vue(ps:不論在哪裏進行刷新,app.vue都會執行)中執行以下代碼:

<script>
  import {decrypt} from '@/libs/AES'
  import handleMenu from '@/router/handleMenu'
  export default {
    name: 'app',
    created () {
      // 當this.$router.options.routes的長度爲1,且本地緩存存在菜單列表的時候才從新配置路由
      if (this.$router.options.routes.length <= 1 && sessionStorage.getItem('subList')) {
        let subList = JSON.parse(decrypt(sessionStorage.getItem('subList')))
        let routes = handleMenu.mergeRoutes(subList)
        this.$router.addRoutes(routes)
        // this.$router不是響應式的,因此手動將路由元注入路由對象
        this.$router.options.routes.push(routes)
      }
    }
  }
</script>

這樣即便刷新,也會從新配置路由了。
三、關於頁面按鈕級別控制,能夠自定義一個指令,去作這件事情。由於咱們已經權限列表放入了相應路由的meta對象中,因此咱們能夠很方便的在每一個頁面回去到當前用戶在當前頁面所擁有的權限

clipboard.png

參考官方文檔自定義指令

結語

打完收工,得虧vue-router2中添加了addRoutes的方法,否則

相關文章
相關標籤/搜索