您好,登錄后才能下訂單哦!
此案例主要實現(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í)有所幫助,也希望大家多多支持億速云。
免責(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)容。