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 相关文章推荐
利用JS自动打开页面上链接的实现代码
Sep 25 Javascript
firefox下input type=&quot;file&quot;的size是多大
Oct 24 Javascript
javascript引擎长时间独占线程造成卡顿的解决方案
Dec 03 Javascript
jQuery焦点图轮播特效代码分享(3款)
Sep 05 Javascript
详解前后端分离之VueJS前端
May 24 Javascript
浅谈vue单一组件下动态修改数据时的全部重渲染
Mar 01 Javascript
使用Vue制作图片轮播组件思路详解
Mar 21 Javascript
详解Vue项目在其他电脑npm run dev运行报错的解决方法
Oct 29 Javascript
Node.js如何对SQLite的async/await封装详解
Feb 14 Javascript
Vue开发之封装分页组件与使用示例
Apr 25 Javascript
如何解决jQuery 和其他JS库的冲突
Jun 22 jQuery
js+for循环实现字符串自动转义的代码(把后面的字符替换前面的字符)
Dec 24 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
基于mysql的论坛(7)
2006/10/09 PHP
php输出全球各个时区列表的方法
2015/03/31 PHP
php动态绑定变量的用法
2015/06/16 PHP
thinkphp3.2实现在线留言提交验证码功能
2017/07/19 PHP
微信公众平台开发教程③ PHP实现微信公众号支付功能图文详解
2019/04/10 PHP
IE浏览器PNG图片透明效果代码
2008/09/02 Javascript
基于jQuery的Tab选项框效果代码(插件)
2011/03/01 Javascript
基于jquery实现的定时显示与隐藏div广告的实现代码
2013/08/22 Javascript
JavaScript对象学习经验整理
2013/10/12 Javascript
ExtJS4 动态生成的grid导出为excel示例
2014/05/02 Javascript
jQuery 隐藏和显示 input 默认值示例
2014/06/03 Javascript
JavaScript的原型继承详解
2015/02/15 Javascript
javascript中数组方法汇总
2015/07/07 Javascript
jQuery实现TAB风格的全国省份城市滑动切换效果代码
2015/08/24 Javascript
jQuery使用$.ajax进行异步刷新的方法(附demo下载)
2015/12/04 Javascript
浅谈JavaScript函数的四种存在形态
2016/06/08 Javascript
使用BootStrap进行轮播图的制作
2017/01/06 Javascript
JQuery ZTree使用方法详解
2017/01/07 Javascript
Angular2库初探
2017/03/01 Javascript
[01:45]2014DOTA2 TI预选赛预选赛 战前探营!
2014/05/21 DOTA
跟老齐学Python之传说中的函数编写条规
2014/10/11 Python
pycharm 在windows上编辑代码用linux执行配置的方法
2018/10/27 Python
浅谈python连续赋值可能引发的错误
2018/11/10 Python
用Cython加速Python到“起飞”(推荐)
2019/08/01 Python
Python如何输出整数
2020/06/07 Python
英国领先的在线礼品店:Getting Personal
2019/09/24 全球购物
护理专科毕业推荐信
2013/11/10 职场文书
保洁主管岗位职责
2013/11/20 职场文书
综合办公室主任职责
2013/12/16 职场文书
司马光教学反思
2014/02/01 职场文书
校园绿化美化方案
2014/06/08 职场文书
我为党旗添光彩演讲稿
2014/09/10 职场文书
文员转正自我鉴定怎么写
2014/09/29 职场文书
事业单位个人查摆问题及整改措施
2014/10/28 职场文书
企业愿景口号
2015/12/25 职场文书
2016入党积极分子党校培训心得体会
2016/01/06 职场文书