vue-router 控制路由權(quán)限的實(shí)現(xiàn)
注意:vue-router是無法完全控制前端路由權(quán)限。
1、實(shí)現(xiàn)思路
使用vue-router實(shí)例函數(shù)addRoutes動(dòng)態(tài)添加路由規(guī)則,不多廢話直接上思維導(dǎo)圖:
2、實(shí)現(xiàn)步驟
2.1、路由匹配判斷
// src/router.js import Vue from 'vue'; import Store from '@/store'; import Router from 'vue-router'; import Cookie from 'js-cookie'; const routers = new Router({ base : "/test", // 定義默認(rèn)路由比如登錄、404、401等 routes : [{ path : "/404", // ... },{ path : "/401", // ... }] }) // ...省略部分代碼 routes.beforeEach((to, from, next) => { const { meta, matched, path } = to; let isMatched = matched && matched.length > 0; // 是否匹配路由 if(isMatched){ }else{ } })
通過vue-router前置守衛(wèi)beforeEach中參數(shù)to來簡單的實(shí)現(xiàn)匹配結(jié)果
2.2、登錄訪問控制
在實(shí)際開發(fā)中路由常常存在是否登錄訪問和是否需要登錄訪問的情況,于是可以通過token和路由配置meta信息中定義isAuth字段來區(qū)分。
// ...省略部分重復(fù)代碼 const openRouters = []; const authRouters = [{ path : "order/list", // ... meta : { // 是否身份驗(yàn)證(至于默認(rèn)定義false還是true由開發(fā)者自定義) isAuth : true } }]; routes.beforeEach((to, from, next) => { const { meta, matched, path } = to; let isMatched = matched && matched.length > 0; // 是否匹配路由 let isLogin = Cookie.get("token") || null; let { isAuth } = (meta || {}); if(isMatched){ // 匹配到路由 if(isAuth){ // 需要登錄訪問 if(isLogin){ // 已登錄訪問 next(); // 調(diào)用鉤子函數(shù) }else{ // 未登錄訪問 next("/login"); // 跳轉(zhuǎn)登錄 } }else{ // 不需要登錄訪問 next(); // 調(diào)用鉤子函數(shù) } }else{ // 未匹配到路由 if(isLogin){ // 已登錄訪問 }else{ // 未登錄訪問 next("/login"); // 跳轉(zhuǎn)登錄 } } })
2.3、動(dòng)態(tài)添加路由規(guī)則
實(shí)現(xiàn)動(dòng)態(tài)添加路由規(guī)則只需要使用vue-router實(shí)例方法router.addRoutes(routes: Array) 。
那么問題來了,我們?cè)趺床拍塬@取到需要?jiǎng)討B(tài)添加的路由規(guī)則呢?
2.4、構(gòu)建路由規(guī)則匹配函數(shù)
假如后臺(tái)獲取到的路由權(quán)限列表是這樣的:
[{ resourceUrl : "/order/list", childMenu : ... }]
為了對(duì)比用戶權(quán)限和路由是否匹配我們需要提取出權(quán)限路由數(shù)組
// 簡單的通過遞歸獲取到了所有權(quán)限url export function getAuthRouters(authMenu) { let authRouters = []; (authMenu || []).forEach((item) => { const { resourceUrl, childMenu } = item; resourceUrl && authRouters.push(resourceUrl); if (childMenu && childMenu.length > 0) { // 合并子級(jí)菜單 authRouters = [...authRouters, ...getAuthRouters(childMenu)]; } }); return authRouters; }
通過getAuthRouters函數(shù)獲取到了所有用戶路由權(quán)限,接下來是要怎么和vue-router路由匹配呢?
這要和(我這里使用的是RBAC模型)系統(tǒng)配置權(quán)限關(guān)聯(lián)上。vue-router路由規(guī)則要和權(quán)限配置保持一致。所以通過遞歸動(dòng)態(tài)拼接vue-router路由規(guī)則和用戶擁有的路由權(quán)限做對(duì)比。如果匹配就保留該路由;然后得到一份過濾后的vue-router路由規(guī)則配置。最后通過實(shí)例方法addRoutes添加路由規(guī)則。具體實(shí)現(xiàn)代碼如下:
// src/utils/index.js const { pathToRegexp } = require('path-to-regexp'); export function createAuthRouters(authRouters) { const isAuthUrl = (url) => { return (authRouters || []).some((cUrl) => { return pathToRegexp(url).toString() === pathToRegexp(cUrl).toString(); }); }; return function createRouters(routers, upperPath) { let nRouters = []; (routers || []).forEach((item) => { const { children, path, name } = item; let isMatched = false, nItem = { ...item }, fullPath = `${upperPath || ''}/${path}`.replace(/\/{2,}/, '/'), nChildren = null; children && (nChildren = createRouters(children, fullPath)); // 1.當(dāng)前路由匹配 if (isAuthUrl(fullPath)) { isMatched = true; } // 2.存在子路由匹配 if (nChildren && nChildren.length > 0) { nItem.children = nChildren; isMatched = true; } // 特殊處理(不需要可以刪除) if(name === "home"){ isMatched = true; } // nItem isMatched && nRouters.push(nItem); }); return nRouters; }; }
值得注意的是createAuthRouters方法通過變量isMatched控制是否保留,之所以通過變量來決定是因?yàn)榍短茁酚芍懈嘎酚煽赡軣o法匹配,但是子路由能匹配所以父路由規(guī)則也需要子路參與是否保留。比如:
// 路由規(guī)則 const routers = new Router({ base : "/test", // 定義默認(rèn)路由比如登錄、404、401等 routes : [{ path : "/", ... children : [{ path : "login", ... },{ path : "about", ... },{ path : "order", ... children : [{ path : "id" }] }] }] }) // 用戶權(quán)限 ["/order/id"]; // 在匹配的過程中 "/" 不等于 "/order/id" 、"/" 不等于 "/order" 但是子路由 "/order/id" == "/order/id" 所以不但要保留 path : "/",還得保留 path : "order" 嵌套層。
2.5、動(dòng)態(tài)注冊(cè)
// ...省略部分重復(fù)代碼 const openRouters = []; const authRouters = [{ path : "order/list", // ... meta : { // 是否身份驗(yàn)證(至于默認(rèn)定義false還是true由開發(fā)者自定義) isAuth : true } }]; /* 動(dòng)態(tài)注冊(cè)路由 */ async function AddRoutes() { // 獲取用戶路由權(quán)限 let res = await POST(API.AUTH_RESOURCE_LISTSIDEMENU); try { const { code, data } = res || {}; if (code === '000') { let newAuthRoutes = createAuthRouters(getAuthRouters(data))(authRouters, routes.options.base); // 注冊(cè)路由 routes.addRoutes([].concat(newAuthRoutes, openRouters)); // 設(shè)置已注冊(cè) Store.commit('UPDATE_IS_ADD_ROUTERS', true); // 保存菜單信息 Store.commit('UPDATE_MENU_INFO', data); } } catch (error) { console.error('>>> AddRoutes() - error:', error); } } routes.beforeEach((to, from, next) => { const { meta, matched, path } = to; let isMatched = matched && matched.length > 0; // 是否匹配路由 let isLogin = Cookie.get("token") || null; let { isAuth } = (meta || {}); if(isMatched){ // 匹配到路由 if(isAuth){ // 需要登錄訪問 if(isLogin){ // 已登錄訪問 next(); // 調(diào)用鉤子函數(shù) }else{ // 未登錄訪問 next("/login"); // 跳轉(zhuǎn)登錄 } }else{ // 不需要登錄訪問 next(); // 調(diào)用鉤子函數(shù) } }else{ // 未匹配到路由 if(isLogin){ // 已登錄訪問 AddRoutes(); next(); }else{ // 未登錄訪問 next("/login"); // 跳轉(zhuǎn)登錄 } } })
2.6、歸類整理
/* 路由前置 */ let { origin } = window.location || {}; routes.beforeEach((to, from, next) => { const { meta, matched, path } = to; let isMatched = matched && matched.length > 0; // 是否匹配 let isAuth = (meta || {}).isAuth; // 是否授權(quán)訪問 let { isAddRoutes } = Store.state; // 注冊(cè)路由 let isLogin = Cookie.get('token') || null; // 是否登錄 if ((isMatched && !isAuth) || (isMatched && isAuth && isLogin)) { // next() // 1.匹配路由 && 未登錄訪問 // 2.匹配路由 && 登錄訪問 && 登錄 next(); } else if ((isMatched && isAuth && !isLogin) || (!isMatched && !isLogin)) { // 登錄 // 1.匹配路由 && 登錄訪問 && 未登錄 // 2.未匹配路由 && 未登錄 next(`/login?r=${origin}/e-lottery${path}`); } else if (!isMatched && isLogin && isAddRoutes) { // 404 // 1.未匹配路由 && 登錄 && 動(dòng)態(tài)注冊(cè)路由 next('/404'); } else if (!isMatched && isLogin && !isAddRoutes) { // 注冊(cè)路由 // 1.未匹配路由 && 登錄 && 未動(dòng)態(tài)注冊(cè)路由 AddRoutes(); next(); } });
嗯! 這下看起來舒服多了。
3、完整實(shí)現(xiàn)代碼
// src/utils/index.js const { pathToRegexp } = require('path-to-regexp'); export function getAuthRouters(authMenu) { let authRouters = []; (authMenu || []).forEach((item) => { const { resourceUrl, childMenu } = item; resourceUrl && authRouters.push(resourceUrl); if (childMenu && childMenu.length > 0) { // 合并子級(jí)菜單 authRouters = [...authRouters, ...getAuthRouters(childMenu)]; } }); return authRouters; } /** * * @param { Array } authRouters */ export function createAuthRouters(authRouters) { const isAuthUrl = (url) => { return (authRouters || []).some((cUrl) => { return pathToRegexp(url).toString() === pathToRegexp(cUrl).toString(); }); }; return function createRouters(routers, upperPath) { let nRouters = []; (routers || []).forEach((item) => { const { children, path, name } = item; let isMatched = false, nItem = { ...item }, fullPath = `${upperPath || ''}/${path}`.replace(/\/{2,}/, '/'), nChildren = null; children && (nChildren = createRouters(children, fullPath)); // 1.當(dāng)前路由匹配 if (isAuthUrl(fullPath)) { isMatched = true; } // 2.存在子路由匹配 if (nChildren && nChildren.length > 0) { nItem.children = nChildren; isMatched = true; } // 特殊處理 if(name === "home"){ isMatched = true; } // nItem isMatched && nRouters.push(nItem); }); return nRouters; }; } // src/router.js import Vue from 'vue'; import Store from '@/store'; import Router from 'vue-router'; import Cookie from 'js-cookie'; const openRouters = []; const authRouters = [{ path : "order/list", // ... meta : { // 是否身份驗(yàn)證(至于默認(rèn)定義false還是true由開發(fā)者自定義) isAuth : true } }]; /* 動(dòng)態(tài)注冊(cè)路由 */ async function AddRoutes() { // 獲取用戶路由權(quán)限 let res = await POST(API.AUTH_RESOURCE_LISTSIDEMENU); try { const { code, data } = res || {}; if (code === '000') { let newAuthRoutes = createAuthRouters(getAuthRouters(data))(authRouters, routes.options.base); // 注冊(cè)路由 routes.addRoutes([].concat(newAuthRoutes, openRouters)); // 設(shè)置已注冊(cè) Store.commit('UPDATE_IS_ADD_ROUTERS', true); // 保存菜單信息 Store.commit('UPDATE_MENU_INFO', data); } } catch (error) { console.error('>>> AddRoutes() - error:', error); } } /* 路由前置 */ let { origin } = window.location || {}; routes.beforeEach((to, from, next) => { const { meta, matched, path } = to; let isMatched = matched && matched.length > 0; // 是否匹配 let isAuth = (meta || {}).isAuth; // 是否授權(quán)訪問 let { isAddRoutes } = Store.state; // 注冊(cè)路由 let isLogin = Cookie.get('token') || null; // 是否登錄 if ((isMatched && !isAuth) || (isMatched && isAuth && isLogin)) { // next() // 1.匹配路由 && 未登錄訪問 // 2.匹配路由 && 登錄訪問 && 登錄 next(); } else if ((isMatched && isAuth && !isLogin) || (!isMatched && !isLogin)) { // 登錄 // 1.匹配路由 && 登錄訪問 && 未登錄 // 2.未匹配路由 && 未登錄 next(`/login?r=${origin}/e-lottery${path}`); } else if (!isMatched && isLogin && isAddRoutes) { // 404 // 1.未匹配路由 && 登錄 && 動(dòng)態(tài)注冊(cè)路由 next('/404'); } else if (!isMatched && isLogin && !isAddRoutes) { // 注冊(cè)路由 // 1.未匹配路由 && 登錄 && 未動(dòng)態(tài)注冊(cè)路由 AddRoutes(); next(); } });
雖然前端能夠通過vue-router實(shí)現(xiàn)對(duì)路由權(quán)限的控制,但是實(shí)際是偽權(quán)限控制,無法達(dá)到完全控制;強(qiáng)烈建議對(duì)于需要控制路由權(quán)限的系統(tǒng)采用后端控制。
到此這篇關(guān)于vue-router 控制路由權(quán)限的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)vue-router 控制路由權(quán)限內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解如何在vue項(xiàng)目中使用eslint+prettier格式化代碼
在開發(fā)中我們需要一種能夠統(tǒng)一團(tuán)隊(duì)代碼風(fēng)格的工具,作為強(qiáng)制性的規(guī)范,統(tǒng)一整個(gè)項(xiàng)目的代碼風(fēng)格,這篇文章主要介紹了詳解如何在vue項(xiàng)目中使用eslint+prettier格式化代碼,需要的朋友可以參考下2018-11-11vue computed計(jì)算屬性顯示undefined的解決
這篇文章主要介紹了vue computed計(jì)算屬性顯示undefined的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11vue學(xué)習(xí)筆記之作用域插槽實(shí)例分析
這篇文章主要介紹了vue學(xué)習(xí)筆記之作用域插槽,結(jié)合實(shí)例形式分析了vue.js作用域插槽基本使用方法及操作注意事項(xiàng),需要的朋友可以參考下2020-02-02vue 數(shù)組和對(duì)象不能直接賦值情況和解決方法(推薦)
這篇文章主要介紹了vue 數(shù)組和對(duì)象不能直接賦值情況和解決方法,需要的朋友可以參考下2017-10-10詳解Vue.js使用Swiper.js在iOS<11時(shí)出現(xiàn)錯(cuò)誤
這篇文章主要介紹了詳解Vue.js使用Swiper.js在iOS<11時(shí)出現(xiàn)錯(cuò)誤,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-09-09vue實(shí)現(xiàn)集成騰訊TIM即時(shí)通訊
最近在做商城類的項(xiàng)目,需要使用到客服系統(tǒng),用戶選擇的騰訊IM即時(shí)通信,所以本文主要介紹了vue實(shí)現(xiàn)集成騰訊TIM即時(shí)通訊,感興趣的可以了解一下2021-06-06一篇文章,教你學(xué)會(huì)Vue CLI 插件開發(fā)
這篇文章主要介紹了Vue CLI插件開發(fā),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04基于iview-admin實(shí)現(xiàn)動(dòng)態(tài)路由的示例代碼
這篇文章主要介紹了基于iview-admin實(shí)現(xiàn)動(dòng)態(tài)路由的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10