vue addRoutes实现动态权限路由菜单的示例


Posted in Javascript onMay 15, 2018

需求

最近接手一个后台管理系统,需要实现导航菜单从后台拉取的效果;根据登录用户的权限不同分别拉出来的导航菜单也不一样,另外可操作的界面也存在区别。

问题

因为后台管理系统是准备使用vue+vue-router+element-ui+vuex的搭配来做的,可是单页应用在进入页面之前就已经将vue-router实例化并且注入vue实例中了,所以在进入登录页面的时候旧没办法在重新定制路由了。接下来各种百之谷之,发现vue-router在2.0版本中提供了addRoutes方法添加路由,希望的曙光出现。

经过一番折腾终于实现了功能,记录下来便于回顾,也希望能帮助到同样有需求的同志。

思路

1、首先在本地配置好固定不变的路由地址,例如登录,404这些页面,如下:

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

配置好这些固定的路由后我们才能够到登录页面,不然是无法继续下去的。

2、然后重要的一步,我们需要跟后端老铁约定好需要返回的权限菜单列表信息;首先这里我们先分析一下自己需要的路由结构,这里以我自己的路由作为例子。如果是我自己直接定义路由的话,会是以下结构:

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部分,所以需要后端返回给我们包含所有路由的一个数组就可以了

vue addRoutes实现动态权限路由菜单的示例

返回的数据中rootList中是一级导航的列表,一级导航实际是没有路由功能,只是作为切换二级菜单的触发器,subList才是我们真正需要的路由信息。

3、拿到权限路由信息后,需要我们在本地对数据进行处理组装成我们需要的数据:

// 登录
   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
 }
}

至此我们已经将权限路由成功配置进本地路由了,我的系统登录进入如下

vue addRoutes实现动态权限路由菜单的示例

后续优化

1、菜单列表的显示以及二级导航切换:

<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>

2、防止刷新路由丢失;由于在刷新的时候单页应用会重新初始化,这时候所有配置的路由都会丢失,一朝回到解放前,只有本地配置的路由能够跳转。这时候我们可以在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>

这样即使刷新,也会重新配置路由了。

3、关于页面按钮级别控制,可以自定义一个指令,去做这件事情。因为我们已经权限列表放入了相应路由的meta对象中,所以我们可以很方便的在每个页面回去到当前用户在当前页面所拥有的权限

vue addRoutes实现动态权限路由菜单的示例

参考官方文档自定义指令

结语

打完收工,得亏vue-router2中添加了addRoutes的方法,不然

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
最新优化收藏到网摘代码(digg,diigo)
Feb 07 Javascript
基于jquery的弹出提示框始终处于窗口的居中位置(类似于alert弹出框的效果)
Sep 28 Javascript
JavaScript中__proto__与prototype的关系深入理解
Dec 04 Javascript
jquery插件tooltipv顶部淡入淡出效果使用示例
Dec 05 Javascript
jQuery判断指定id的对象是否存在的方法
May 22 Javascript
模拟javascript中的sort排序(简单实例)
Aug 17 Javascript
详解在React里使用&quot;Vuex&quot;
Apr 02 Javascript
详解nuxt sass全局变量(公共scss解决方案)
Jun 27 Javascript
angular 服务的单例模式(依赖注入模式下)详解
Oct 22 Javascript
微信小程序在其他页面监听globalData中值的变化
Jul 15 Javascript
layer弹出层扩展主题的方法
Sep 11 Javascript
解决Vue中使用keepAlive不缓存问题
Aug 04 Javascript
浅谈AngularJS中$http服务的简单用法
May 15 #Javascript
Vue项目webpack打包部署到Tomcat刷新报404错误问题的解决方案
May 15 #Javascript
解决linux下node.js全局模块找不到的问题
May 15 #Javascript
vue :src 文件路径错误问题的解决方法
May 15 #Javascript
利用npm 安装删除模块的方法
May 15 #Javascript
vux uploader 图片上传组件的安装使用方法
May 15 #Javascript
使用webpack搭建react开发环境的方法
May 15 #Javascript
You might like
php 取得瑞年与平年的天数的代码
2009/08/10 PHP
php批量更改数据库表前缀实现方法
2013/10/26 PHP
ThinkPHP3.1查询语言详解
2014/06/19 PHP
Yii2分页的使用及其扩展方法详解
2016/05/23 PHP
JS 自动完成 AutoComplete(Ajax 查询)
2009/07/07 Javascript
Ext.FormPanel 提交和 Ext.Ajax.request 异步提交函数的区别
2009/11/12 Javascript
JavaScript经典效果集锦
2010/07/06 Javascript
关于javascript中的typeof和instanceof介绍
2012/12/04 Javascript
Javascript和HTML5利用canvas构建Web五子棋游戏实现算法
2013/07/17 Javascript
js 遍历json返回的map内容示例代码
2013/10/29 Javascript
JS中getYear()和getFullYear()区别分析
2014/07/04 Javascript
使用AngularJS制作一个简单的RSS阅读器的教程
2015/06/18 Javascript
js实现创建删除html元素小结
2015/09/30 Javascript
jQuery检测滚动条是否到达底部
2015/12/15 Javascript
Bootstrap树形组件jqTree的简单封装
2016/01/25 Javascript
使用RequireJS库加载JavaScript模块的实例教程
2016/06/06 Javascript
使用AngularJS 跨站请求如何解决jsonp请求问题
2017/01/16 Javascript
js原生Ajax的封装和原理详解
2017/03/11 Javascript
Vue.js实战之通过监听滚动事件实现动态锚点
2017/04/04 Javascript
Angular 4 依赖注入学习教程之FactoryProvider的使用(四)
2017/06/04 Javascript
解决在Vue中使用axios用form表单出现的问题
2019/10/30 Javascript
微信浏览器左上角返回按钮监听的实现
2020/03/04 Javascript
jquery实现上传文件进度条
2020/03/26 jQuery
[55:03]LGD vs EG 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/18 DOTA
Pandas读取MySQL数据到DataFrame的方法
2018/07/25 Python
python_opencv用线段画封闭矩形的实例
2018/12/05 Python
Python 20行简单实现有道在线翻译的详解
2019/05/15 Python
Python实现EXCEL表格的排序功能示例
2019/06/25 Python
浅谈Python访问MySQL的正确姿势
2020/01/07 Python
给Python学习者的文件读写指南(含基础与进阶)
2020/01/29 Python
python中前缀运算符 *和 **的用法示例详解
2020/05/28 Python
八项规定整改措施
2014/02/12 职场文书
店面销售职位的职责
2014/03/09 职场文书
社区服务活动小结
2014/07/08 职场文书
个人工作违纪检讨书
2015/05/05 职场文书
MySQL中优化SQL语句的方法(show status、explain分析服务器状态信息)
2022/04/09 MySQL