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 相关文章推荐
JsEasy简介 JsEasy是什么?与下载
Mar 07 Javascript
Javascript Object.extend
May 18 Javascript
基于jQuery的可以控制左右滚动及自动滚动效果的代码
Jul 25 Javascript
javascript textarea光标定位方法(兼容IE和FF)
Mar 12 Javascript
JavaScript中的fontsize()方法使用详解
Jun 08 Javascript
javascript实现tab响应式切换特效
Jan 29 Javascript
JS组件Bootstrap Table布局详解
May 27 Javascript
jQuery解决IE6、7、8不能使用 JSON.stringify 函数的问题
May 31 Javascript
Js获取当前日期时间及格式化代码
Sep 17 Javascript
Extjs让combobox写起来简洁又漂亮
Jan 05 Javascript
AngularJS实现根据不同条件显示不同控件
Apr 20 Javascript
vue-scroller记录滚动位置的示例代码
Jan 17 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判断远程url是否有效的几种方法小结
2011/10/08 PHP
php无限分类且支持输出树状图的详细介绍
2013/06/19 PHP
php中的路径问题与set_include_path使用介绍
2014/02/11 PHP
php实现图片以base64显示的方法
2016/10/13 PHP
PHP基于ICU扩展intl快速实现汉字转拼音及按拼音首字母分组排序的方法
2017/05/03 PHP
Yii2选项卡的简单使用
2017/05/26 PHP
让innerHTML的脚本也可以运行起来
2006/07/01 Javascript
JavaScript入门教程(2) JS基础知识
2009/01/31 Javascript
解析javascript 浏览器关闭事件
2013/07/08 Javascript
javascript的switch用法注意事项分析
2015/02/02 Javascript
javascript基本算法汇总
2016/03/09 Javascript
JavaScript编写点击查看大图的页面半透明遮罩层效果实例
2016/05/09 Javascript
学习使用Bootstrap栅格系统
2017/05/11 Javascript
NodeJS父进程与子进程资源共享原理与实现方法
2018/03/16 NodeJs
node.js连接mysql与基本用法示例
2019/01/05 Javascript
[01:32:10]NAVI vs VG Supermajor 败者组 BO3 第一场 6.5
2018/06/06 DOTA
[48:31]完美世界DOTA2联赛PWL S3 DLG vs Phoenix 第二场 12.17
2020/12/19 DOTA
Python实现备份文件实例
2014/09/16 Python
python 递归深度优先搜索与广度优先搜索算法模拟实现
2018/10/22 Python
Python中函数参数匹配模型详解
2019/06/09 Python
python3+PyQt5 自定义窗口部件--使用窗口部件样式表的方法
2019/06/26 Python
Python实现滑动平均(Moving Average)的例子
2019/08/24 Python
python 视频逐帧保存为图片的完整实例
2019/12/10 Python
解决django无法访问本地static文件(js,css,img)网页里js,cs都加载不了
2020/04/07 Python
Python中格式化字符串的四种实现
2020/05/26 Python
美国在线鞋类零售商:LifeStride
2019/06/09 全球购物
幼儿教师个人求职信范文
2013/09/21 职场文书
学校采购员岗位职责
2014/01/02 职场文书
企业道德讲堂实施方案
2014/03/19 职场文书
我的中国梦演讲稿小学篇
2014/08/19 职场文书
知识就是力量演讲稿
2014/09/13 职场文书
人事文员岗位职责
2015/02/04 职场文书
贪污检举信范文
2015/03/02 职场文书
中秋节寄语2015
2015/03/24 职场文书
浅谈MySQL 亿级数据分页的优化
2021/06/15 MySQL
CSS 一行代码实现头像与国旗的融合
2021/10/24 HTML / CSS