您好,登錄后才能下訂單哦!
這篇文章主要講解了如何實(shí)現(xiàn)vue權(quán)限管理,內(nèi)容清晰明了,對(duì)此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會(huì)有幫助。
一、整體思路
后端返回用戶(hù)權(quán)限,前端根據(jù)用戶(hù)權(quán)限處理得到左側(cè)菜單;所有路由在前端定義好,根據(jù)后端返回的用戶(hù)權(quán)限篩選出需要掛載的路由,然后使用 addRoutes 動(dòng)態(tài)掛載路由。
二、實(shí)現(xiàn)要點(diǎn)
(1)路由定義,分為初始路由和動(dòng)態(tài)路由,一般來(lái)說(shuō)初始路由只有 login,其他路由都掛載在 home 路由之下需要?jiǎng)討B(tài)掛載。
(2)用戶(hù)登錄,登錄成功之后得到 token,保存在 sessionStorage,跳轉(zhuǎn)到 home,此時(shí)會(huì)進(jìn)入路由攔截根據(jù) token 獲取用戶(hù)權(quán)限列表。
(3)全局路由攔截,根據(jù)當(dāng)前用戶(hù)有沒(méi)有 token 和 權(quán)限列表進(jìn)行相應(yīng)的判斷和跳轉(zhuǎn),當(dāng)沒(méi)有 token 時(shí)跳到 login,當(dāng)有 token 而沒(méi)有權(quán)限列表時(shí)去發(fā)請(qǐng)求獲取權(quán)限等等邏輯。
(4)處理用戶(hù)權(quán)限,在 store.js 定義一個(gè)模塊 permission.js,專(zhuān)門(mén)用于處理用戶(hù)權(quán)限相關(guān)的邏輯,用戶(hù)權(quán)限列表、菜單列表都保存在此模塊;
(5)用戶(hù)權(quán)限列表、菜單列表的處理,前端的路由要和后端返回的權(quán)限有一個(gè)唯一標(biāo)識(shí)(一般用路由名做標(biāo)識(shí)符),根據(jù)此標(biāo)識(shí)篩選出對(duì)應(yīng)的路由。
(6)左側(cè)菜單,要和用戶(hù)信息、用戶(hù)管理模塊使用的菜單信息一致,統(tǒng)一使用保存在 store 中的變量。
三、具體實(shí)現(xiàn)流程
1、準(zhǔn)備工作,路由定義
/* router/indes.js */ /* 初始路由 */ let router = new Router({ mode: 'history', routes: [ { path: '/login', name: 'login', component: () => import('@/views/login.vue'), }, ] });
/* router/indes.js */ /* 準(zhǔn)備動(dòng)態(tài)添加的路由 */ export const dynamicRoutes = [ { path: '/', name: 'home', component: () => import('@/views/home.vue'), meta: { requiresAuth: true, }, children: [ // 用戶(hù)信息 { path: '/user-info', name: 'user-info', component: () => import('@/views/user-setting/user-info.vue'), }, // 修改密碼 { path: '/user-password', name: 'user-password', component: () => import('@/views/user-setting/user-password.vue'), }, ] }, { path: '/403', component: () => import('@/views/error-page/403'), }, { path: '*', component: () => import('@/views/error-page/404'), }, ];
系統(tǒng)主要頁(yè)面的路由,后續(xù)會(huì)將這些路由經(jīng)過(guò)權(quán)限篩選,添加到 home 路由的 children 里面
/* router/router.js */ export default [ // 部署管理 { path: '/deploy-manage', name: 'deploy-manage', component: () => import('@/views/sys-admin/deploy-manage/deploy-manage.vue'), meta: { permitName: 'deploy-manage', } }, // ... ];
2、用戶(hù)登錄
用戶(hù)進(jìn)入登錄頁(yè),輸入用戶(hù)名、密碼、驗(yàn)證碼,點(diǎn)擊登錄,發(fā)送登錄請(qǐng)求,登錄成功之后,將 token 保存在 sessionStorage,然后跳轉(zhuǎn)到首頁(yè) /home ,進(jìn)入路由攔截的邏輯。
/* login.vue */ // 發(fā)送登錄請(qǐng)求 vm.$http.login(params, data => { sessionStorage.token = data.token; // ... // 跳轉(zhuǎn)到首頁(yè) home。這里會(huì)觸發(fā)全局路由攔截 router.beforeEach vm.$router.push({ name: 'home' }); }, err => { console.log(err); });
3、全局路由攔截
首先從打開(kāi)本地服務(wù) http://localhost:2001 開(kāi)始,打開(kāi)后會(huì)進(jìn)入 login 頁(yè)面,那么判斷的依據(jù)是什么?
首先是 token。沒(méi)有登錄的用戶(hù)是拿不到 token 的,而登錄后的用戶(hù)我們會(huì)將 token 存到 seesionStorage,因此,根據(jù)當(dāng)前有沒(méi)有 token 即可知道是否登錄。
/* 全局路由攔截 */ router.beforeEach((to, from, next) => { // 根據(jù)有沒(méi)有token判斷是否登錄 if (!sessionStorage.token) { // 1、當(dāng)用戶(hù)打開(kāi)localhost,to.matched === [],匹配的是空路由,此時(shí)需要重定向到login // 2、重定向到login之后,to.matched === [name: "login", path: "/login"...] 就是上一步的login頁(yè)面 // to.matched.some(item => item.meta.requiresAuth) 這句的意思是 進(jìn)入的路由頁(yè)需要登錄認(rèn)證,取反就是不用登錄,直接通過(guò) if (to.matched.length > 0 && !to.matched.some(item => item.meta.requiresAuth)) { next(); // 跳過(guò),進(jìn)入下一個(gè)導(dǎo)航鉤子。比如:在 /login 路由頁(yè)刷新頁(yè)面會(huì)走到此邏輯 } else { next({ path: '/login' }); } } else { // 現(xiàn)在有token了 if (!store.state.permission.permissionList) { // 如果沒(méi)有 permissionList,發(fā)請(qǐng)求獲取用戶(hù)權(quán)限列表 store.dispatch('permission/FETCH_PERMISSION').then(() => { next({ path: to.path, query: to.query }); }); } else { // 現(xiàn)在有 permissionList 了 if (to.path !== '/login') { if (to.matched.length === 0) { // 如果匹配到的路由形如 https://172.24.1.117/?id=xxx&name=xxx,表明是關(guān)聯(lián)跳轉(zhuǎn)時(shí)沒(méi)有權(quán)限,跳轉(zhuǎn)到403 next({ path: '/403' }); } else if (queryChange) { // 跳轉(zhuǎn)之前將路由中查詢(xún)字符串為空的過(guò)濾掉,如 xxx.com?page=&size= 這種 next({ name: to.name, params: to.params, query: to.query }); } else if (sessionStorage.isSysLock === 'true' && to.path !== '/sys-lock') { next({ path: '/sys-lock' }); } else { next(); } } else { // 1.如果用戶(hù)手動(dòng)在地址欄輸入 /login,重定向到之前的路由頁(yè) // next(from.fullPath); // 2.如果用戶(hù)手動(dòng)在地址欄輸入 /login,清除token并刷新頁(yè)面,就會(huì)去到登錄頁(yè) store.commit('goToLogin'); } } } });
(1)當(dāng)用戶(hù)打開(kāi) localhost,此時(shí)還沒(méi)有 token,匹配的是空路由,我們重定向到登錄頁(yè) next({ path: '/login' });
(2)用戶(hù)在登錄頁(yè)刷新頁(yè)面,也會(huì)進(jìn)入路由攔截,此時(shí)匹配的是 login 路由,而 login 路由是不需要登錄驗(yàn)證的(requiresAuth 為空或者 false),所以直接跳過(guò)執(zhí)行 next();
(3)用戶(hù)在登錄頁(yè)輸入了用戶(hù)名和密碼,登錄成功,保存了 token,跳轉(zhuǎn)到 /home 路由;
(4)此時(shí)進(jìn)入路由攔截,已經(jīng)有 token了,但是還沒(méi)有用戶(hù)權(quán)限 permissionList,然后發(fā)請(qǐng)求獲取用戶(hù)權(quán)限列表,得到權(quán)限后 next({ path: to.path, query: to.query }); 繼續(xù)往下走;
(5)再次進(jìn)入路由攔截,此時(shí)有 token 和 permissionList 了,就可以根據(jù)實(shí)際業(yè)務(wù)進(jìn)行跳轉(zhuǎn)了。上面的代碼是判斷當(dāng)前是不是 login 路由,如果用戶(hù)登錄后手動(dòng)在地址欄輸入 /login,則清除 token 跳轉(zhuǎn)到登錄頁(yè)。其他的邏輯就跟具體業(yè)務(wù)相關(guān)了,就不細(xì)講了。
4、處理用戶(hù)權(quán)限
處理用戶(hù)權(quán)限,在 store.js 定義一個(gè)模塊 permission.js,專(zhuān)門(mén)用于處理用戶(hù)權(quán)限相關(guān)的邏輯,用戶(hù)權(quán)限列表、菜單列表都保存在此模塊;
來(lái)看看 permission.js 主要做了什么:
/* permission.js */ /* 由于權(quán)限這塊邏輯很多,所以在vuex添加了一個(gè)permission模塊來(lái)處理權(quán)限相關(guān)的邏輯和變量 */ import httpRequest from '@/assets/js/service/http'; // http請(qǐng)求 import handleModule from '@/assets/js/common/handle-module'; // 處理路由、側(cè)邊欄的公共函數(shù) import router, { dynamicRoutes } from '@/router/index'; // 默認(rèn)路由配置,動(dòng)態(tài)路由配置 import permissionRouter from '@/router/router'; // 需要權(quán)限的路由配置 // ... export default { // ... actions: { async FETCH_PERMISSION({ commit, state }) { // 初始化路由表,注意這里必須寫(xiě),router.beforeEach 路由攔截時(shí),多次執(zhí)行 FETCH_PERMISSION commit('setPermission', []); // 發(fā)請(qǐng)求獲取后端返回的用戶(hù)權(quán)限 let data = await getUserByToken(); let userPopedoms = data.userPopedoms || []; // 保存用戶(hù)的權(quán)限模塊(去除掉用戶(hù)管理和登錄),用戶(hù)管理模塊可以使用,權(quán)限列表 let userPopeList = userPopedoms.filter(v => v.requestMapping !== 'user-manage' && v.requestMapping !== 'login'); commit('setUserPopedoms', userPopeList); // 根據(jù)權(quán)限篩選出我們?cè)O(shè)置好的路由并加入到 path='/' 的children,就是home路由的children下 let routes = handleModule.getRouter(userPopedoms, permissionRouter); let homeContainer = dynamicRoutes.find(v => v.path === '/'); // 使用concat的目的是讓 分配給用戶(hù)的權(quán)限處于 children 的第0項(xiàng) homeContainer.children = routes.concat(homeContainer.children); // 設(shè)置首頁(yè)重定向,重定向到用戶(hù)權(quán)限的第0項(xiàng) homeContainer.redirect = homeContainer.children[0].name; // 根據(jù)權(quán)限生成左側(cè)導(dǎo)航菜單 let sidebarMenu = handleModule.getSidebarMenu(userPopeList); commit('setMenu', sidebarMenu); // 初始路由 let initialRoutes = router.options.routes; // 動(dòng)態(tài)添加路由。只有刷新頁(yè)面才會(huì)清空動(dòng)態(tài)添加的路由信息 router.addRoutes(dynamicRoutes); // 完整的路由表 commit('setPermission', [...initialRoutes, ...dynamicRoutes]); } }, };
(1)首先,let data = await getUserByToken(); 發(fā)請(qǐng)求獲取用戶(hù)權(quán)限,得到 data,data.userPopedoms 格式大致如下:
[ { "moduleGroupId": 1001, "moduleGroupName": "部署管理", "requestMapping": "deploy-manage", }, { "moduleGroupId": 1100, "moduleGroupName": "系統(tǒng)管理", "requestMapping": "sys-manage", "moduleList": [ { "moduleId": 1101, "moduleName": "系統(tǒng)日志", "requestMapping": "system-log", "moduleGroupId": 1100, }, { "moduleId": 1102, "moduleName": "系統(tǒng)告警", "requestMapping": "sys-alert", "moduleGroupId": 1100, }, ], } ]
(2)然后,根據(jù)我們寫(xiě)好的路由數(shù)組,進(jìn)行對(duì)比,過(guò)濾得到我們要的路由。路由格式在上文“路由定義”的 router/router.js 已經(jīng)提到。還要根據(jù)用戶(hù)權(quán)限處理得到側(cè)邊欄菜單。
為此,我們需要兩個(gè)處理函數(shù),一個(gè)根據(jù)用戶(hù)權(quán)限列表和路由數(shù)組過(guò)濾得到最終路由,另一個(gè)根據(jù)用戶(hù)權(quán)限處理得到側(cè)邊欄菜單。所以另外專(zhuān)門(mén)創(chuàng)建了一個(gè)文件 handle-module.js 存放這兩個(gè)函數(shù)。
/* handle-module.js */ const handleModule = { /** * 根據(jù)后臺(tái)返回的權(quán)限,以及配置好的所有路由,過(guò)濾出真實(shí)路由 * @param {Array} permissionList 后臺(tái)返回的用戶(hù)權(quán)限列表 * @param {Array} allRouter 前端配置好的所有動(dòng)態(tài)路由的集合 * @return {Array} 過(guò)濾后的路由 */ getRouter(permissionList = [], allRouter = []) { // permissions 的格式為 ["deploy-manage", "system-log"] let permissions = permissionList.reduce((acc, cur) => { if (cur.moduleList && cur.moduleList.length > 0) cur = cur.moduleList; return acc.concat(cur); }, []).map(v => v.requestMapping); return allRouter.filter(item => permissions.includes(item.meta.permitName)); }, /** * 根據(jù)后臺(tái)返回的權(quán)限,生成側(cè)邊欄 * @param {Array} permissionList 后臺(tái)返回的用戶(hù)權(quán)限列表 * @return {Array} sidebarMenu 生成的側(cè)邊欄數(shù)組 */ getSidebarMenu(permissionList = []) { let sidebarMenu = []; permissionList.forEach(item => { let menuItem = { name: item.requestMapping, title: item.moduleGroupName, }; menuItem.children = (item.moduleList || []).map(child => ({ name: child.requestMapping, title: child.moduleName, })); sidebarMenu.push(menuItem); }); return sidebarMenu; } }; export default handleModule;
(3)上面得到過(guò)濾后的路由數(shù)組后,加入到 path 為 '/' 的 children 下面
{ path: '/', name: 'home', component: () => import('@/views/home.vue'), meta: { requiresAuth: true, }, children: [ /* 將上面得到的路由加入到這里 */ // 用戶(hù)信息 { path: '/user-info', name: 'user-info', component: () => import('@/views/user-setting/user-info.vue'), }, ] }
(4)上面根據(jù)權(quán)限生成側(cè)邊欄菜單之后,保存在 store 待用。
(5)上面第三步將動(dòng)態(tài)路由加入到 home 的 children 之后,就可以將 dynamicRoutes 加入到路由中了。router.addRoutes(dynamicRoutes);
(6)到了這里,路由就添加完了,也就是 FETCH_PERMISSION 操作完畢了,就可以在 action.then 里面調(diào)用 next({ path: to.path, query: to.query }); 進(jìn)去路由,也就是進(jìn)入 home。我們上面已經(jīng)將 home 路由重定向?yàn)椴藛蔚牡谝粋€(gè)路由信息,所以會(huì)進(jìn)入系統(tǒng)菜單的第一個(gè)頁(yè)面。
刷新頁(yè)面后,根據(jù) router.beforeEach 的判斷,有 token 但是沒(méi)有 permissionList ,會(huì)重新觸發(fā) action 去發(fā)請(qǐng)求獲取用戶(hù)權(quán)限,之前的邏輯會(huì)重新走一遍,所以沒(méi)有問(wèn)題。
退出登錄后,需要清除 token 并刷新頁(yè)面。因?yàn)槭峭ㄟ^(guò) addRoutes 添加路由的,而 vue-router 沒(méi)有刪除路由的 api,所以清除路由、清除 store 中存儲(chǔ)的各種信息,刷新頁(yè)面是最保險(xiǎn)的。
相關(guān)文件的目錄截圖:
四、總結(jié)
缺點(diǎn):
全局路由守衛(wèi)里,每次路由跳轉(zhuǎn)都要做判斷;
每次刷新頁(yè)面,需要重新發(fā)請(qǐng)求獲取用戶(hù)權(quán)限;
退出登錄時(shí),需要刷新一次頁(yè)面將動(dòng)態(tài)添加的路由以及權(quán)限信息清空;
優(yōu)點(diǎn):
菜單與路由分離,菜單的修改、添加、刪除由后端控制,利于后期維護(hù);
使用 addRoutes 動(dòng)態(tài)掛載路由,可控制用戶(hù)不能在 url 輸入相關(guān)地址進(jìn)行跳轉(zhuǎn);
看完上述內(nèi)容,是不是對(duì)如何實(shí)現(xiàn)vue權(quán)限管理有進(jìn)一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。