vue-router+vuex addRoutes实现路由动态加载及菜单动态加载


Posted in Javascript onSeptember 28, 2017

此案例主要实现了一个功能是,在vue实例首次运行时,在加载了login和404两个路由规则,登录成功后,根据登录用户角色权限获取该角色相应菜单权限,生成新的路由规则添加进去。

做过后台管理系统都一定做过这个功能,在对菜单权限进行粗粒度权限控制的时候,通过角色获取菜单后,异步生成菜单,所以一开始拿到需求的时候,我也以为这和平常的没什么不同,不过做起来就发现了很多问题,

1.vue-router的实例,在new vue实例的时候,就加载了,且必须加载,这个时候,登录路由一定要加载,可是这个时候没有登录,无法确定权限
2.路由规则与菜单的同步

解决思路演化,菜单和路由同步,肯定是采用了vuex,一开始的思路的是,在一开始,就把所有的路由规则加载,然后在登录的时候,取得权限路由,对比两个路由,通过修改修改一个权限字段来隐藏菜单,如果在后台页面添加了新菜单规则,路由是按模块加载的不同的文件,这时对路由的文件进行新的读写,虽然可以解决问题,但是如果手动在浏览器地址上路由,依然可以访问,所以在路由的全局钩子上还要做拦截。

这个解决方案虽然解决,但是显的比较复杂,于是就想需找新的方法,重新浏览官方api,发现在2.2.0以后,官方新增了api,addRoutes,专门针对服务端渲染路由,那么这下问题就比较简单了,下面列出实现代码。以下代码不能直接复用,需要根据实际情况修改,只是提供思路

app.js

let permission = JSON.parse(window.sessionStorage.getItem('permission')) 
if (permission) { 
 store.commit(ADD_MENU, permission) 
 router.addRoutes(store.state.menu.items) 
} 
router.beforeEach((route, redirect, next) => { 
 if (state.app.device.isMobile && state.app.sidebar.opened) { 
  store.commit(TOGGLE_SIDEBAR, false) 
 } 
 if (route.path === '/login') { 
  window.sessionStorage.removeItem('user') 
  window.sessionStorage.removeItem('permission') 
  store.commit(ADD_MENU, []) 
 } 
 let user = JSON.parse(window.sessionStorage.getItem('user')) 
 if (!user && route.path !== '/login') { 
  next({ path: '/login' }) 
 } else { 
  if (route.name) { 
   next() 
  } else { 
   next({ path: '/nofound' }) 
  } 
 } 
})

登录的组件login.vue

<template> 
 <el-form :model="ruleForm2" :rules="rules2" ref="ruleForm2" label-position="left" label-width="0px" 
      class="demo-ruleForm login-container"> 
  <h3 class="title">系统登录</h3> 
  <el-form-item prop="account"> 
   <el-input type="text" v-model="ruleForm2.account" auto-complete="off" placeholder="账号"></el-input> 
  </el-form-item> 
  <el-form-item prop="checkPass"> 
   <el-input type="password" v-model="ruleForm2.checkPass" auto-complete="off" placeholder="密码"></el-input> 
  </el-form-item> 
  <el-checkbox v-model="checked" checked class="remember">记住密码</el-checkbox> 
  <el-form-item style="width:100%;"> 
   <el-button type="primary" style="width:100%;" @click.native.prevent="handleSubmit2" :loading="logining">登录 
   </el-button> 
   <!--<el-button @click.native.prevent="handleReset2">重置</el-button>--> 
  </el-form-item> 
 </el-form> 
</template> 
 
<script> 
 import NProgress from 'nprogress' 
 import { mapActions, mapGetters } from 'vuex' 
 export default { 
  data () { 
   return { 
    logining: false, 
    ruleForm2: { 
     account: 'admin', 
     checkPass: '123456' 
    }, 
    rules2: { 
     account: [ 
      {required: true, message: '请输入账号', trigger: 'blur'} 
      // { validator: validaePass } 
     ], 
     checkPass: [ 
      {required: true, message: '请输入密码', trigger: 'blur'} 
      // { validator: validaePass2 } 
     ] 
    }, 
    checked: true 
   } 
  }, 
  computed: { 
   ...mapGetters([ 
    'menuitems', 
    'isLoadRoutes' 
    // ... 
   ]) 
  }, 
  methods: { 
   handleReset2 () { 
    this.$refs.ruleForm2.resetFields() 
   }, 
   handleSubmit2 (ev) { 
    this.$refs.ruleForm2.validate((valid) => { 
     if (valid) { 
      this.logining = true 
      NProgress.start() 
      let loginParams = {loginName: this.ruleForm2.account, password: this.ruleForm2.checkPass} 
      this.$http.post('/api/privilege/user/login', loginParams).then(resp => { 
       this.logining = false 
       NProgress.done() 
       let {message, data} = resp.data 
 
       if (message === 'fail') { 
        this.$notify({ 
         title: '错误', 
         message: message, 
         type: 'error' 
        }) 
       } else { 
        window.sessionStorage.setItem('user', JSON.stringify(data.user)) 
        window.sessionStorage.setItem('permission', JSON.stringify(data.permission)) 
        this.addMenu(data.permission) 
        if (!this.isLoadRoutes) { 
         this.$router.addRoutes(this.menuitems) 
         this.loadRoutes() 
        } 
        this.$router.push('/system/office') 
       } 
      }) 
     } else { 
      console.log('error submit!!') 
      return false 
     } 
    }) 
   }, 
 
   ...mapActions([ 
    'addMenu', 
    'loadRoutes' 
   ]) 
  } 
 } 
 
</script> 
 
<style lang="scss" scoped> 
 .login-container { 
  /*box-shadow: 0 0px 8px 0 rgba(0, 0, 0, 0.06), 0 1px 0px 0 rgba(0, 0, 0, 0.02);*/ 
  -webkit-border-radius: 5px; 
  border-radius: 5px; 
  -moz-border-radius: 5px; 
  background-clip: padding-box; 
  margin-bottom: 20px; 
  background-color: #F9FAFC; 
  margin: 180px auto; 
  border: 2px solid #8492A6; 
  width: 350px; 
  padding: 35px 35px 15px 35px; 
 
 .title { 
  margin: 0px auto 40px auto; 
  text-align: center; 
  color: #505458; 
 } 
 
 .remember { 
  margin: 0px 0px 35px 0px; 
 } 
 
 } 
</style>

关键点解释

computed: { 
   ...mapGetters([ 
    'menuitems', 
    'isLoadRoutes' 
    // ... 
   ]) 
  },

这里是从vuex取得两个对象,menuitems是菜单对象,isLoadRoutes是用来判断是否是第一次登录,用来排除重复加载路由规则

...mapActions([ 
    'addMenu', 
    'loadRoutes' 
   ])

这里是从vuex取得两个方法,一个是添加菜单,一个更改loadRoutes的值

this.$router.addRoutes(this.menuitems)

这是关键api,动态的向router实例中添加路由规则

menu模块的state与mutations

const state = { 
 items: [ 
 ], 
 isLoadRoutes: false 
} 
 
const mutations = { 
 [types.EXPAND_MENU] (state, menuItem) { 
  if (menuItem.index > -1) { 
   if (state.items[menuItem.index] && state.items[menuItem.index].meta) { 
    state.items[menuItem.index].meta.expanded = menuItem.expanded 
   } 
  } else if (menuItem.item && 'expanded' in menuItem.item.meta) { 
   menuItem.item.meta.expanded = menuItem.expanded 
  } 
 }, 
 [types.ADD_MENU] (state, menuItems) { 
  if (menuItems.length === 0) { 
   state.items = [] 
  } else { 
   generateMenuItems(state.items, menuItems) 
  } 
 }, 
 [types.LOAD_ROUTES] (state) { 
  state.isLoadRoutes = !state.isLoadRoutes 
 } 
}

路由配置文件router.js

import Vue from 'vue' 
import Router from 'vue-router' 
import menuModule from 'vuex-store/modules/menu' 
Vue.use(Router) 
 
export default new Router({ 
 mode: 'hash', // Demo is living in GitHub.io, so required! 
 linkActiveClass: 'is-active', 
 scrollBehavior: () => ({ y: 0 }), 
 routes: [ 
  { 
   path: '/login', 
   component: require('../Login.vue'), 
   meta: { 
    expanded: false, 
    show: false 
   }, 
   name: 'Login' 
  }, 
  { 
   path: '/', 
   component: require('../views/Home.vue'), 
   meta: { 
    expanded: false, 
    show: false 
   }, 
   children: [ 
    { path: '/nofound', component: require('../404.vue'), name: 'NOFOUND', meta: {show: false} } 
   ] 
  }, 
  ...generateRoutesFromMenu(menuModule.state.items) 
 ] 
}) 
 
// Menu should have 2 levels. 
function generateRoutesFromMenu (menu = [], routes = []) { 
 for (let i = 0, l = menu.length; i < l; i++) { 
  let item = menu[i] 
  if (item.path) { 
   routes.push(item) 
  } 
 } 
 return routes 
}

vuex

import Vue from 'vue' 
import Vuex from 'vuex' 
import * as actions from './actions' 
import * as getters from './getters' 
 
import menu from './modules/menu' 
 
Vue.use(Vuex) 
 
const store = new Vuex.Store({ 
 strict: true, // process.env.NODE_ENV !== 'development', 
 actions, 
 getters, 
 modules: { 
  menu 
 }, 
 mutations: { 
 } 
}) 
 
export default store

actions

export const addMenu = ({ commit }, menuItems) => { 
 if (menuItems.length > 0) { 
  commit(types.ADD_MENU, menuItems) 
 } 
} 
 
export const loadRoutes = ({ commit }) => { 
 commit(types.LOAD_ROUTES) 
}

getters

const menuitems = state => state.menu.items 
const isLoadRoutes = state => state.menu.isLoadRoutes 
export { 
 menuitems, 
 isLoadRoutes 
}

mutations_type.js

export const ADD_MENU = 'ADD_MENU' 
 
export const LOAD_ROUTES = 'LOAD_ROUTES'

因为上面的代码不能直接运行,再次梳理一下思路,

1.创建vue实例的时候,将vuex和vue-router加载,这个时候,vue-router只有登录规则和404规则

2.vuex中state管理的状态对象有,菜单对象menuitems,是否加载过路由loadRoutes ,并提供相应的getters与actions当然还有一些其他的,这里没有列举

3.然后在登录组件中,登录成功后,将服务端传回来之后,调用actions更改state.menuitems,并且中间有格式化的过程,这个过程的代码没有贴出来,主要是由于不同的表涉和服务端返回的数据不一样,,

4.然后调用addRoutes和actions更改已经加载过路由的方法

5.然后为了防止用户直接手动按f5刷新页面,这个时候会重新构建vue实例,而又没有重新登录,所以vuex里面的东西会清空,所以将登录后的数据存放在sessionStroage中,在刷新页面,重新构建vue实例的时候,会有判断

6.之后会渲染侧边栏组件,列出菜单,数据就可以根据state.menuitems来就可以了,我这里没有贴我的,实际根据自己的需求来

后面有时间会在github上上传完整代码。

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

Javascript 相关文章推荐
JSON格式化输出
Nov 10 Javascript
简单实现兼容各大浏览器的js复制内容到剪切板
Sep 09 Javascript
window.onload绑定多个事件的两种解决方案
May 15 Javascript
gulp-uglify 与gulp.watch()配合使用时报错(重复压缩问题)
Aug 24 Javascript
简洁实用的BootStrap jQuery手风琴插件
Aug 31 Javascript
JS基于面向对象实现的多个倒计时器功能示例
Feb 28 Javascript
微信小程序 设置启动页面的两种方法
Mar 09 Javascript
Bootstrap Table使用整理(二)
Jun 09 Javascript
vue项目中用cdn优化的方法
Jan 03 Javascript
JS实现点击按钮可实现编辑功能
Jul 03 Javascript
Electron 打包问题:electron-builder 下载各种依赖出错(推荐)
Jul 09 Javascript
javascript如何使用函数random来实现课堂随机点名方法详解
Jul 28 Javascript
node.js学习之事件模块Events的使用示例
Sep 28 #Javascript
es6中的解构赋值、扩展运算符和rest参数使用详解
Sep 28 #Javascript
JS获取日期的方法实例【昨天,今天,明天,前n天,后n天的日期】
Sep 28 #Javascript
jquery实现左右轮播图效果
Sep 28 #jQuery
bootstrap table实现点击翻页功能 可记录上下页选中的行
Sep 28 #Javascript
JavaScript判断输入是否为数字类型的方法总结
Sep 28 #Javascript
详解Node全局变量global模块
Sep 28 #Javascript
You might like
php文件缓存类用法实例分析
2015/04/22 PHP
在Debian系统下配置LNMP的教程
2015/07/09 PHP
PHP微信开发之微信消息自动回复下所遇到的坑
2016/05/09 PHP
PHP html_entity_decode()函数讲解
2019/02/25 PHP
一个级联菜单代码学习及removeClass与addClass的应用
2013/01/24 Javascript
JS求平均值的小例子
2013/11/29 Javascript
JS检测输入字符是否包含非法字符的示例代码
2014/02/11 Javascript
jQuery中阻止冒泡事件的方法介绍
2014/04/12 Javascript
深入学习JavaScript中的Rest参数和参数默认值
2015/07/28 Javascript
js实现登陆遮罩效果的方法
2015/07/28 Javascript
全面解析Angular中$Apply()及$Digest()的区别
2016/08/04 Javascript
Angular路由简单学习
2016/12/26 Javascript
JS字符串统计操作示例【遍历,截取,输出,计算】
2017/03/27 Javascript
angular中使用Socket.io实例代码
2017/06/03 Javascript
three.js加载obj模型的实例代码
2017/11/10 Javascript
[49:02]KG vs Infamous 2019国际邀请赛淘汰赛 败者组BO1 8.20.mp4
2020/07/19 DOTA
python用来获得图片exif信息的库实例分析
2015/03/16 Python
python计算N天之后日期的方法
2015/03/31 Python
详解Python中__str__和__repr__方法的区别
2015/04/17 Python
Python中线程编程之threading模块的使用详解
2015/06/23 Python
tensorflow 获取模型所有参数总和数量的方法
2018/06/14 Python
python K近邻算法的kd树实现
2018/09/06 Python
Python Opencv提取图片中某种颜色组成的图形的方法
2019/09/19 Python
python opencv圆、椭圆与任意多边形的绘制实例详解
2020/02/06 Python
Python csv文件记录流程代码解析
2020/07/16 Python
奥地利汽车配件店:Pkwteile.at
2017/03/10 全球购物
伯利陶器:Burleigh Pottery
2018/01/03 全球购物
西班牙美妆电商:Perfume’s Club(有中文站)
2018/08/08 全球购物
英国领先的电动可调床制造商:Laybrook
2019/12/26 全球购物
Nasty Gal英国:美国女性服饰销售网站
2021/03/02 全球购物
linux面试题参考答案(7)
2012/10/29 面试题
竞选学委演讲稿
2014/09/13 职场文书
党员干部廉政承诺书
2015/04/28 职场文书
酒店圣诞节活动总结
2015/05/06 职场文书
生日祝酒词大全
2015/08/10 职场文书
Vue自定义铃声提示音组件的实现
2022/01/22 Vue.js