溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

vue-router+vuex addRoutes實現(xiàn)路由動態(tài)加載及菜單動態(tài)加載

發(fā)布時間:2020-09-24 00:16:29 來源:腳本之家 閱讀:305 作者:BeArchitect 欄目:web開發(fā)

此案例主要實現(xiàn)了一個功能是,在vue實例首次運行時,在加載了login和404兩個路由規(guī)則,登錄成功后,根據(jù)登錄用戶角色權(quán)限獲取該角色相應(yīng)菜單權(quán)限,生成新的路由規(guī)則添加進(jìn)去。

做過后臺管理系統(tǒng)都一定做過這個功能,在對菜單權(quán)限進(jìn)行粗粒度權(quán)限控制的時候,通過角色獲取菜單后,異步生成菜單,所以一開始拿到需求的時候,我也以為這和平常的沒什么不同,不過做起來就發(fā)現(xiàn)了很多問題,

1.vue-router的實例,在new vue實例的時候,就加載了,且必須加載,這個時候,登錄路由一定要加載,可是這個時候沒有登錄,無法確定權(quán)限
2.路由規(guī)則與菜單的同步

解決思路演化,菜單和路由同步,肯定是采用了vuex,一開始的思路的是,在一開始,就把所有的路由規(guī)則加載,然后在登錄的時候,取得權(quán)限路由,對比兩個路由,通過修改修改一個權(quán)限字段來隱藏菜單,如果在后臺頁面添加了新菜單規(guī)則,路由是按模塊加載的不同的文件,這時對路由的文件進(jìn)行新的讀寫,雖然可以解決問題,但是如果手動在瀏覽器地址上路由,依然可以訪問,所以在路由的全局鉤子上還要做攔截。

這個解決方案雖然解決,但是顯的比較復(fù)雜,于是就想需找新的方法,重新瀏覽官方api,發(fā)現(xiàn)在2.2.0以后,官方新增了api,addRoutes,專門針對服務(wù)端渲染路由,那么這下問題就比較簡單了,下面列出實現(xiàn)代碼。以下代碼不能直接復(fù)用,需要根據(jù)實際情況修改,只是提供思路

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"> 
  <h4 class="title">系統(tǒng)登錄</h4> 
  <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 > 
   <el-button type="primary"  @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> 

關(guān)鍵點解釋

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

這里是從vuex取得兩個對象,menuitems是菜單對象,isLoadRoutes是用來判斷是否是第一次登錄,用來排除重復(fù)加載路由規(guī)則

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

這里是從vuex取得兩個方法,一個是添加菜單,一個更改loadRoutes的值

this.$router.addRoutes(this.menuitems) 

這是關(guān)鍵api,動態(tài)的向router實例中添加路由規(guī)則

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.創(chuàng)建vue實例的時候,將vuex和vue-router加載,這個時候,vue-router只有登錄規(guī)則和404規(guī)則

2.vuex中state管理的狀態(tài)對象有,菜單對象menuitems,是否加載過路由loadRoutes ,并提供相應(yīng)的getters與actions當(dāng)然還有一些其他的,這里沒有列舉

3.然后在登錄組件中,登錄成功后,將服務(wù)端傳回來之后,調(diào)用actions更改state.menuitems,并且中間有格式化的過程,這個過程的代碼沒有貼出來,主要是由于不同的表涉和服務(wù)端返回的數(shù)據(jù)不一樣,,

4.然后調(diào)用addRoutes和actions更改已經(jīng)加載過路由的方法

5.然后為了防止用戶直接手動按f5刷新頁面,這個時候會重新構(gòu)建vue實例,而又沒有重新登錄,所以vuex里面的東西會清空,所以將登錄后的數(shù)據(jù)存放在sessionStroage中,在刷新頁面,重新構(gòu)建vue實例的時候,會有判斷

6.之后會渲染側(cè)邊欄組件,列出菜單,數(shù)據(jù)就可以根據(jù)state.menuitems來就可以了,我這里沒有貼我的,實際根據(jù)自己的需求來

后面有時間會在github上上傳完整代碼。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI