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的方法,否则

相关文章
相关标签/搜索