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 相关文章推荐
一种JavaScript的设计模式
Nov 22 Javascript
利用js跨页面保存变量做菜单的方法
Jan 17 Javascript
jquery text()要注意啦
Oct 30 Javascript
java和javascript获取word文档的书签位置对比
Jun 19 Javascript
js实现键盘上下左右键选择文字并显示在文本框的方法
May 07 Javascript
原生js实现无缝轮播图效果
Jan 11 Javascript
浅谈JavaScript正则表达式-非捕获性分组
Mar 08 Javascript
JavaScript ES6常用基础知识总结
Feb 09 Javascript
JavaScript实现省份城市的三级联动
Feb 11 Javascript
Vue事件处理原理及过程详解
Mar 11 Javascript
编写一个javascript元循环求值器的方法
Apr 14 Javascript
基于JS实现视频上传显示进度条
May 12 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+SqlServer实现分页显示
2006/10/09 PHP
PHP下载远程图片的几种方法总结
2017/04/07 PHP
PHP中mysqli_get_server_version()的实例用法
2020/02/03 PHP
MooBox 基于Mootools的对话框插件
2012/01/20 Javascript
使用JavaScript动态设置样式实现代码及演示动画
2013/01/25 Javascript
javascript表单验证使用示例(javascript验证邮箱)
2014/01/07 Javascript
javascript实现动态加载CSS
2015/01/26 Javascript
javascript DIV实现跟随鼠标移动
2020/03/19 Javascript
BootStrap实用代码片段之一
2016/03/22 Javascript
javascript小数精度丢失的完美解决方法
2016/05/31 Javascript
javascript实现的全国省市县无刷新多级关联菜单效果代码
2016/08/01 Javascript
浅谈javascript中的 “ &amp;&amp; ” 和 “ || ”
2017/02/02 Javascript
Echarts基本用法_动力节点Java学院整理
2017/08/11 Javascript
详解使用jest对vue项目进行单元测试
2018/09/07 Javascript
Vue 实例事件简单示例
2019/09/19 Javascript
java遇到微信小程序 &quot;支付验证签名失败&quot; 问题解决
2019/12/22 Javascript
vue 动态添加的路由页面刷新时失效的原因及解决方案
2021/02/26 Vue.js
用Python写的图片蜘蛛人代码
2012/08/27 Python
Python实现删除文件中含“指定内容”的行示例
2017/06/09 Python
numpy中索引和切片详解
2017/12/15 Python
Python 经典面试题 21 道【不可错过】
2018/09/21 Python
Django开发的简易留言板案例详解
2018/12/04 Python
Python编程快速上手——strip()函数的正则表达式实现方法分析
2020/02/29 Python
历史学专业大学生找工作的自我评价
2013/10/16 职场文书
村干部承诺书
2014/03/28 职场文书
职业道德模范事迹材料
2014/08/24 职场文书
审计班子对照检查材料
2014/08/27 职场文书
四风对照检查材料思想汇报
2014/09/20 职场文书
领导班子“四风问题”“整改方案
2014/10/02 职场文书
安全生产月宣传标语
2014/10/06 职场文书
学生检讨书怎么写
2014/10/09 职场文书
2015年高校图书馆工作总结
2015/04/30 职场文书
男人帮观后感
2015/06/18 职场文书
幼儿园大班教师评语
2019/06/21 职场文书
Nginx配置根据url参数重定向
2022/04/11 Servers
APP界面设计技巧和注意事项
2022/04/29 杂记