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 相关文章推荐
用javascript实现读取txt文档的脚本
Jul 20 Javascript
返回上一页并自动刷新的JavaScript代码
Feb 19 Javascript
JQuery为页面Dom元素绑定事件及解除绑定方法
Apr 23 Javascript
Jquery实现地铁线路指示灯提示牌效果的方法
Mar 02 Javascript
javascript验证身份证号
Mar 03 Javascript
javascript设置和获取cookie的方法实例详解
Jan 05 Javascript
详解Angular Reactive Form 表单验证
Jul 06 Javascript
JS字典Dictionary类定义与用法示例
Feb 01 Javascript
js实现打字小游戏
Dec 17 Javascript
JavaScript 实现HTML DOM增删改查操作的常见方法详解
Jan 04 Javascript
jQuery实现html可联动的百分比进度条
Mar 26 jQuery
Vue中使用JsonView来展示Json树的实例代码
Nov 16 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 开发环境配置(Zend Server安装)
2010/04/28 PHP
PHP中获取时间的下一周下个月的方法
2014/03/18 PHP
php中$_GET与$_POST过滤sql注入的方法
2014/11/03 PHP
PHP调用.NET的WebService 简单实例
2015/03/27 PHP
php中注册器模式类用法实例分析
2015/11/03 PHP
TP(thinkPHP)框架多层控制器和多级控制器的使用示例
2018/06/13 PHP
基于Laravel-admin 后台的自定义页面用法详解
2019/09/30 PHP
php使用pthreads v3多线程实现抓取新浪新闻信息操作示例
2020/02/21 PHP
jquery动态添加删除一行数据示例
2014/06/12 Javascript
使用jQuery判断IE浏览器版本的代码
2014/06/14 Javascript
原生js仿jq判断当前浏览器是否为ie,精确到ie6~8
2014/08/30 Javascript
Node.js批量给图片加水印的方法
2016/11/15 Javascript
微信小程序中的onLoad详解及简单实例
2017/04/05 Javascript
详解vue中组件参数
2018/07/09 Javascript
element ui table(表格)实现点击一行展开功能
2018/12/04 Javascript
JS JQuery获取data-*属性值方法解析
2020/09/01 jQuery
Pyramid将models.py文件的内容分布到多个文件的方法
2013/11/27 Python
python中getattr函数使用方法 getattr实现工厂模式
2014/01/20 Python
Python中多线程及程序锁浅析
2015/01/21 Python
Python多线程编程(一):threading模块综述
2015/04/05 Python
Python中用PIL库批量给图片加上序号的教程
2015/05/06 Python
Python的Django应用程序解决AJAX跨域访问问题的方法
2016/05/31 Python
python编程实现归并排序
2017/04/14 Python
利用Python实现某OA系统的自动定位功能
2020/05/27 Python
python如何变换环境
2020/07/21 Python
PIP和conda 更换国内安装源的方法步骤
2020/09/21 Python
CSS3 渐变(Gradients)之CSS3 径向渐变
2016/07/08 HTML / CSS
html5的localstorage详解
2017/05/09 HTML / CSS
HTML5中drawImage用法分析
2014/12/01 HTML / CSS
会计主管岗位职责
2014/01/03 职场文书
2015年大学迎新工作总结
2015/07/16 职场文书
《搭石》教学反思
2016/02/18 职场文书
2019年中学生的思想品德评语集锦
2019/12/19 职场文书
详解CocosCreator消息分发机制
2021/04/16 Javascript
python如何正确使用yield
2021/05/21 Python
anaconda python3.8安装后降级
2021/06/11 Python