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 firefox兼容ie的dom方法脚本
May 18 Javascript
用jquery与css打造个性化的单选框和复选框
Oct 20 Javascript
基于javascript制作经典传统的拼图游戏
Mar 22 Javascript
聊一聊JavaScript作用域和作用域链
May 03 Javascript
jQuery中页面返回顶部的方法总结
Dec 30 Javascript
BootStrap Table实现server分页序号连续显示功能(当前页从上一页的结束序号开始)
Sep 12 Javascript
详解vue移动端日期选择组件
Feb 22 Javascript
vue 不使用select实现下拉框功能(推荐)
May 17 Javascript
vue+axios+mock.js环境搭建的方法步骤
Aug 28 Javascript
利用layer实现表单完美验证的方法
Sep 26 Javascript
JS获取一个字符串中指定字符串第n次出现的位置
Feb 10 Javascript
代码块高亮可复制显示js插件highlight.js+clipboard.js整合
Feb 15 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中source #N问题的解决方法
2014/01/27 PHP
laravel 获取当前url的别名方法
2019/10/11 PHP
Laravel6.2中用于用户登录的新密码确认流程详解
2019/10/16 PHP
PHP防止sql注入小技巧之sql预处理原理与实现方法分析
2019/12/13 PHP
推荐40个非常优秀的jQuery插件和教程【系列三】
2011/11/09 Javascript
js实现网页标题栏闪烁提示效果实例分析
2014/11/20 Javascript
jQuery遍历json中多个map的方法
2015/02/12 Javascript
js操作css属性实现div层展开关闭效果的方法
2015/05/11 Javascript
jQuery中 prop() attr()使用详解
2015/05/19 Javascript
JavaScript兼容浏览器FF/IE技巧
2016/08/14 Javascript
jQuery 特性操作详解及实例代码
2016/09/29 Javascript
jQuery代码实现实时获取时间
2017/01/29 Javascript
jQuery插件zTree实现更新根节点中第i个节点名称的方法示例
2017/03/08 Javascript
Django与Vue语法的冲突问题完美解决方法
2017/12/14 Javascript
vue的安装及element组件的安装方法
2018/03/09 Javascript
在Vue中获取组件声明时的name属性方法
2018/09/12 Javascript
面试题:react和vue的区别分析
2019/04/08 Javascript
Vue SSR 即时编译技术的实现
2020/05/06 Javascript
Javascript文本框脚本实现方法解析
2020/10/30 Javascript
python k-近邻算法实例分享
2014/06/11 Python
python多线程操作实例
2014/11/21 Python
python制作一个桌面便签软件
2015/08/09 Python
Atom的python插件和常用插件说明
2018/07/08 Python
用python生成(动态彩色)二维码的方法(使用myqr库实现)
2019/06/24 Python
python全栈知识点总结
2019/07/01 Python
django之自定义软删除Model的方法
2019/08/14 Python
如何使用python实现模拟鼠标点击
2020/01/06 Python
Python文件读写w+和r+区别解析
2020/03/26 Python
html5 worker 实例(一) 为什么测试不到效果
2013/06/24 HTML / CSS
Html5原创俄罗斯方块(基于canvas)
2019/01/07 HTML / CSS
企业宣传方案
2014/03/04 职场文书
幼儿园中班评语大全
2014/04/17 职场文书
2014法院干警廉洁警示教育思想汇报
2014/09/13 职场文书
领导班子整改方案和个人整改措施
2014/10/25 职场文书
党政领导班子群众路线对照检查材料
2014/10/26 职场文书
行政撤诉申请书
2015/05/18 职场文书