您好,登錄后才能下訂單哦!
此案例主要實現了一個功能是,在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"> <h4 class="title">系統登錄</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>
關鍵點解釋
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上上傳完整代碼。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。