vue-router+vuex實現加載動態路由和菜單
動態路由加載和動態菜單渲染的應用在後端權限控制中十分常見,後端只要加載權限路由進行渲染返回到瀏覽器就可以。在前後端分離中,權限控制動態路由和動態菜單也是一個非常常見的問題。其實我們最最理想的效果是什麽呢?
我們訪問一個應用,在登錄之前有哪些路由是一定要加載的呢?你看我總結如下,你看下是不是這些:
1.登錄路由 (登錄功能路由)
2.系統路由(系統消息路由,比如歡迎界面,404,error等的路由)
但是在vue中,一旦實例化,就必須初始化路由,但這個時候你還沒有登錄,沒有獲取你的權限路由呀,如果加載全部路由,那麽在瀏覽器上輸入路由你就可以訪問(這個問題可以使用router.beforeEach鉤子進行權限鑒定解決),那麽在前後端分離的開發項目中,vue是如何實現動態路由加載實現權限控制的呢?這就是我們這篇文章要寫的內容。
我們寫過後臺渲染都知道怎麽去實現,那麽放到vue中如何去實現呢?我們先羅列幾個問題進行思考,如下
1.vue中路由是如何初始化,放入到vue實例中的?
2.vue中提供了什麽實現動態路由加載呢?
我們先順著這兩個問題進行思考,並且順著這兩個問題,我們進行對應方案解決,這個過程中會會出現很多新的問題,我們也針對新問題出對應方案,並且進行優化。
路由初始化
路由初始化發生在什麽時候呢?我們可以看主入口文件main.js,下面是我貼出的我的一個項目案例:
import Vue from ‘vue‘ import ‘normalize.css/normalize.css‘ // A modern alternative to CSS resets import Element from ‘element-ui‘ import ‘element-ui/lib/theme-chalk/index.css‘ import ‘@/styles/index.scss‘ // global css import App from ‘./App‘ import router from ‘./router‘ import store from ‘./store‘ import i18n from ‘./lang‘ // Internationalization import ‘./icons‘ // icon import ‘./errorLog‘ // error log import ‘./permission‘ // permission control import ‘./mock‘ // simulation data import * as filters from ‘./filters‘ // global filters Vue.use(Element, { size: ‘medium‘, // set element-ui default size i18n: (key, value) => i18n.t(key, value) }) // register global utility filters. Object.keys(filters).forEach(key => { Vue.filter(key, filters[key]) }) Vue.config.productionTip = false // vue實例化就已經把router初始化了 new Vue({ el: ‘#app‘, router, store, i18n, render: h => h(App) })
通過上面的主入口文件,我們就知道,這個路由初始胡就發生在vue實例化時。這個也很好理解如果你沒有初始化路由,那麽你就默認只能進入到主窗口,那麽接下來主窗口中你沒有路由你怎麽跳轉?程序也不知道你有哪些地方可以跳轉呀,路由都是需要先註冊到實例中,實例才能定位到相應的視圖。從中我們知道,路由初始化發生在vue實例化時。
那麽這個時候我們接著我們想要的權限控制目標走:程序一開始,只註冊登錄路由、系統信息路由(歡迎頁面,404路由,error路由),我們稱這些為靜態路由,登錄後我們通過接口獲取權限拿到了菜單,這個時候需要進行添加動態路由,把這些菜單信息註冊為路由,我們稱這些為動態路由。那麽vue實例化時,vue-router就已經被初始化,那麽我們是不是能夠通過類似於往router實例裏面添加路由項的方式進行註冊路由呢?我們可以查閱文檔,也可以查看vue-router源碼,有一個叫做addRoutes的方法進行動態註冊路由信息,路由對象其實就是一個路由數組,我們通過addRoutes就可以進行動態註冊路由,這個跟那個數組中extend功能類似的。
所以說道這裏我們知道可以通過addRoutes進行動態路由註冊。好,那麽我們就順著這個思路走下去。
在登錄模塊中,登錄成功後,我們通過api獲取後臺權限菜單,然後註冊路由。代碼如下:
// 登錄頁登錄方法
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid && this.isSuccess) {
this.loading = true
this.$store.dispatch(‘LoginByUsername‘, this.loginForm).then(() => {
// 在這個時候進行獲取後臺權限及菜單
this.$store.dispatch(‘getMenus‘, this.loginForm.name).then((res) => {
// 把這個菜單信息註冊為路由信息
this.$router.addRoutes(menuitems)
})
this.loading = false
// 除了登錄路由、和系統消息路由,這個跟路由是一個歡迎路由,是靜態路由
this.$router.push({ path: ‘/‘ })
}).catch(() => {
this.$message.error(‘登陸失敗,請檢查用戶名或密碼是否正確‘)
this.loading = false
})
} else {
if (!this.isSuccess) {
this.$message.error(‘請拉滑動條‘)
}
console.log(‘error submit!!‘)
return false
}
})
}
// 登錄方法計算屬性
computed: {
...mapGetters([
‘menuitems‘,
])
},
總結一下:
登錄成功以後(持久化token),調用獲取權限菜單(保存在store裏面),這個時候就完成了登錄後動態初始化權限菜單的功能。那麽這裏面所有的路由就是當前用戶可訪問的菜單,就實現了我們的目標效果。但是呢,store存儲權限菜單會有個問題,一旦刷新裏面的值就刷掉了,那麽這個時候就重新實例化的時候就會跳到404路由中,菜單信息也沒有了,那如何解決這個刷新時的問題呢?
我們先分析一下思路:
1.初始化vue實例時,初始化router,包括所有的靜態路由。
2.全局鉤子檢查token是否有效?
a.如果有效,則通過token獲取用戶信息保存到store中,根據用戶信息獲取權限菜單保存到store中,
動態註冊權限菜單的路由信息;
b.如果token無效,重新定位到靜態登錄路由進行登錄.
3.登錄模塊中,登錄成功後獲取用戶信息保存到store中,將token保存到store中並持久化到本地,
獲取權限菜單保存到store中,動態註冊權限菜單的路由信息
4.動態加載完路由後,直接跳到歡迎界面的靜態路由
5.一旦頁面刷新,那麽token就會從store中清除,token失效,那麽就會去獲得持久化在本地的token
,重新去獲取用戶信息,權限菜單,重新動態註冊路由。
6.token持久化在本地也是有時間限制的,假設token有效期為一周,一旦過了有效期,那麽會走2的b情況。
那麽上面的思路就是動態加載權限菜單路由信息的簡述,整個的環路就通了,刷新問題就解決了。
代碼如下:
import router from ‘./router‘
import store from ‘./store‘
import { Message } from ‘element-ui‘
import NProgress from ‘nprogress‘ // progress bar
import ‘nprogress/nprogress.css‘// progress bar style
import { getToken } from ‘@/utils/auth‘ // getToken from cookie
NProgress.configure({ showSpinner: false })// NProgress Configuration
// 權限判斷
function hasPermission(roles, permissionRoles) {
if (roles.indexOf(‘admin‘) >= 0) return true // admin permission passed directly
if (!permissionRoles) return true
return roles.some(role => permissionRoles.indexOf(role) >= 0)
}
const whiteList = [‘/login‘, ‘/authredirect‘]// no redirect whitelist
// 全局鉤子
router.beforeEach((to, from, next) => {
NProgress.start() // start progress bar
// 如果有token
if (getToken()) { // determine if there has token
// 登錄後進入登錄頁
if (to.path === ‘/login‘) {
next({ path: ‘/‘ })
NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
} else {
// 當進入非登錄頁時,需要進行權限校驗
if (store.getters.roles.length === 0) { // 判斷當前用戶是否已拉取完user_info信息
store.dispatch(‘GetUserInfo‘).then(res => { // 拉取user_info
const roles = res.data.data.roles // note: roles must be a array! such as: [‘editor‘,‘develop‘]
store.dispatch(‘GenerateRoutes‘, { roles }).then(() => { // 根據roles權限生成可訪問的路由表
router.addRoutes(store.getters.addRouters) // 動態添加可訪問路由表
next({ ...to, replace: true }) // hack方法 確保addRoutes已完成 ,set the replace:
})
}).catch((err) => {
store.dispatch(‘FedLogOut‘).then(() => {
Message.error(err || ‘Verification failed, please login again‘)
next({ path: ‘/‘ })
})
})
} else {
// 沒有動態改變權限的需求可直接next() 刪除下方權限判斷 ↓
if (hasPermission(store.getters.roles, to.meta.roles)) {
next()
} else {
next({ path: ‘/401‘, replace: true, query: { noGoBack: true }})
}
// 可刪 ↑
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) { // 在免登錄白名單,直接進入
next()
} else {
next(‘/login‘) // 否則全部重定向到登錄頁
NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
}
}
})
router.afterEach(() => {
NProgress.done() // finish progress bar
})
備註:根據模塊獨立性,我把登錄中獲取權限列表去掉,都放置在全局鉤子中,把上面的代碼直接引入到主入口文件main.js中。
另外這裏采用vuex進行狀態管理,所以從新捋一下思路:
1.vue實例化,初始化靜態路由
2.全局鉤子進行檢查:
a.token有效
-如果當前跳轉路由是登錄路由,直接進入根路由/
-如果跳轉路由非登錄路由,則需要進行權限校驗,如果用戶信息和權限菜單沒拉取,
則進行拉取後將權限菜單動態註冊到router中,進行權限判斷,如果有用戶信息和權限菜單信息,
則直接進行權限判斷。
b.token無效
-如果在白名單中,則直接進入
-進入到登錄頁
3.全局狀態管理采用vuex
到這裏我們就已經完成了vue-router+vuex動態註冊路由控制權限的方式就說完了,這裏我留個思考題給大家:現在根據上面的方式我再引入一個產品實體,(用戶 - 產品 - 菜單 ), 用戶可以有多個產品權限,每個產品有公用的菜單,也有各產品定制化的菜單,那麽這個時候我在前端如果做好權限校驗呢?要求:當前用戶當前產品的權限菜單才可被訪問。
vue-router+vuex實現加載動態路由和菜單