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 相关文章推荐
提高代码性能技巧谈—以创建千行表格为例
Jul 01 Javascript
在一个form用一个SUBMIT(或button)分别提交到两个处理表单页面的代码
Feb 15 Javascript
JavaScript与Div对层定位和移动获得坐标的实现代码
Sep 08 Javascript
jquery中子元素和后代元素的区别示例介绍
Apr 02 Javascript
用jQuery toggleClass 实现鼠标移上变色
May 14 Javascript
JS模拟Dialog弹出浮动框效果代码
Oct 16 Javascript
JavaScript模块化开发之SeaJS
Dec 13 Javascript
Three.js学习之文字形状及自定义形状
Aug 01 Javascript
vue2.0中goods选购栏滚动算法的实现代码
May 17 Javascript
webpack 2的react开发配置实例代码
Jul 28 Javascript
vue2.0 + element UI 中 el-table 数据导出Excel的方法
Mar 02 Javascript
vue 动态组件用法示例小结
Mar 06 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
成本8450万,票房仅2亿,口碑两极分化,又一部DC电影扑街了
2020/04/09 欧美动漫
php安装xdebug/php安装pear/phpunit详解步骤(图)
2013/12/22 PHP
在PHP中使用X-SendFile头让文件下载更快
2014/06/01 PHP
Yii2增加验证码步骤详解
2016/04/25 PHP
PHP实现的限制IP投票程序IP来源分析
2016/05/04 PHP
Thinkphp通过一个入口文件如何区分移动端和PC端
2017/04/18 PHP
JavaScript面向对象编程
2008/03/02 Javascript
Jquery 基础学习笔记
2009/05/29 Javascript
JavaScript 学习笔记(十六) js事件
2010/02/01 Javascript
js结合正则实现国内手机号段校验
2015/06/19 Javascript
JavaScript仿网易选项卡制作代码
2016/10/06 Javascript
vue 数组和对象不能直接赋值情况和解决方法(推荐)
2017/10/25 Javascript
JavaScript自执行函数和jQuery扩展方法详解
2017/10/27 jQuery
Vue源码学习之初始化模块init.js解析
2017/11/02 Javascript
Vue+webpack项目基础配置教程
2018/02/12 Javascript
vue.js在标签属性中插入变量参数的方法
2018/03/06 Javascript
vue里的data要用return返回的原因浅析
2019/05/28 Javascript
python简单实现基于SSL的IRC bot实例
2015/06/15 Python
python Django框架实现自定义表单提交
2016/03/25 Python
利用Python自动监控网站并发送邮件告警的方法
2016/08/24 Python
利用python获取某年中每个月的第一天和最后一天
2016/12/15 Python
利用python爬取软考试题之ip自动代理
2017/03/28 Python
Django数据库连接丢失问题的解决方法
2018/12/29 Python
python通过SSH登陆linux并操作的实现
2019/10/10 Python
大学生村官心得体会范文
2014/01/04 职场文书
幼儿园区域活动总结
2014/05/08 职场文书
副校长竞聘演讲稿
2014/09/01 职场文书
2015医院个人工作总结范文
2015/05/21 职场文书
钱学森观后感
2015/06/04 职场文书
2015年办税服务厅工作总结
2015/07/23 职场文书
小学六年级班主任工作经验交流材料
2015/11/02 职场文书
我的暑假生活作文(五年级)范文
2019/08/07 职场文书
Vue实现动态查询规则生成组件
2021/05/27 Vue.js
解析Redis Cluster原理
2021/06/21 Redis
VUE使用draggable实现组件拖拽
2022/04/06 Vue.js
如何利用python创作字符画
2022/06/25 Python