vue-router 控制路由許可權的實現
注意:vue-router是無法完全控制前端路由許可權。
1、實現思路
使用vue-router例項函式addRoutes動態新增路由規則,不多廢話直接上思維導圖:
2、實現步驟
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",// 定義預設路由比如登入、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前置守衛beforeEach中引數to來簡單的實現匹配結果
2.2、登入訪問控制
在實際開發中路由常常存在是否登入訪問和是否需要登入訪問的情況,於是可以通過token和路由配置meta資訊中定義isAuth欄位來區分。
// ...省略部分重複程式碼 const openRouters = []; const authRouters = [{ path : "order/list",// ... meta : { // 是否身份驗證(至於預設定義false還是true由開發者自定義) isAuth : true } }]; routes.beforeEach((to,path } = to; let isMatched = matched && matched.length > 0; // 是否匹配路由 let isLogin = Cookie.get("token") || null; let { isAuth } = (meta || {}); if(isMatched){ // 匹配到路由 if(isAuth){ // 需要登入訪問 if(isLogin){ // 已登入訪問 next(); // 呼叫鉤子函式 }else{ // 未登入訪問 next("/login"); // 跳轉登入 } }else{ // 不需要登入訪問 next(); // 呼叫鉤子函式 } }else{ // 未匹配到路由 if(isLogin){ // 已登入訪問 }else{ // 未登入訪問 next("/login"); // 跳轉登入 } } })
2.3、動態新增路由規則
實現動態新增路由規則只需要使用vue-router例項方法router.addRoutes(routes: Array) 。
那麼問題來了,我們怎麼才能獲取到需要動態新增的路由規則呢?
2.4、構建路由規則匹配函式
假如後臺獲取到的路由許可權列表是這樣的:
[{ resourceUrl : "/order/list",childMenu : ... }]
為了對比使用者許可權和路由是否匹配我們需要提取出許可權路由陣列
// 簡單的通過遞迴獲取到了所有許可權url export function getAuthRouters(authMenu) { let authRouters = []; (authMenu || []).forEach((item) => { const { resourceUrl,childMenu } = item; resourceUrl && authRouters.push(resourceUrl); if (childMenu && childMenu.length > 0) { // 合併子級選單 authRouters = [...authRouters,...getAuthRouters(childMenu)]; } }); return authRouters; }
通過getAuthRouters函式獲取到了所有使用者路由許可權,接下來是要怎麼和vue-router路由匹配呢?
這要和(我這裡使用的是RBAC模型)系統配置許可權關聯上。vue-router路由規則要和許可權配置保持一致。所以通過遞迴動態拼接vue-router路由規則和使用者擁有的路由許可權做對比。如果匹配就保留該路由;然後得到一份過濾後的vue-router路由規則配置。最後通過例項方法addRoutes新增路由規則。具體實現程式碼如下:
// 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.當前路由匹配 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控制是否保留,之所以通過變數來決定是因為巢狀路由中父路由可能無法匹配,但是子路由能匹配所以父路由規則也需要子路參與是否保留。比如:
// 路由規則 const routers = new Router({ base : "/test",// 定義預設路由比如登入、404、401等 routes : [{ path : "/",... children : [{ path : "login",... },{ path : "about",{ path : "order",... children : [{ path : "id" }] }] }] }) // 使用者許可權 ["/order/id"]; // 在匹配的過程中 "/" 不等於 "/order/id" 、"/" 不等於 "/order" 但是子路由 "/order/id" == "/order/id" 所以不但要保留 path : "/",還得保留 path : "order" 巢狀層。
2.5、動態註冊
// ...省略部分重複程式碼 const openRouters = []; const authRouters = [{ path : "order/list",// ... meta : { // 是否身份驗證(至於預設定義false還是true由開發者自定義) isAuth : true } }]; /* 動態註冊路由 */ async function AddRoutes() { // 獲取使用者路由許可權 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); // 註冊路由 routes.addRoutes([].concat(newAuthRoutes,openRouters)); // 設定已註冊 Store.commit('UPDATE_IS_ADD_ROUTERS',true); // 儲存選單資訊 Store.commit('UPDATE_MENU_INFO',data); } } catch (error) { console.error('>>> AddRoutes() - error:',error); } } routes.beforeEach((to,path } = to; let isMatched = matched && matched.length > 0; // 是否匹配路由 let isLogin = Cookie.get("token") || null; let { isAuth } = (meta || {}); if(isMatched){ // 匹配到路由 if(isAuth){ // 需要登入訪問 if(isLogin){ // 已登入訪問 next(); // 呼叫鉤子函式 }else{ // 未登入訪問 next("/login"); // 跳轉登入 } }else{ // 不需要登入訪問 next(); // 呼叫鉤子函式 } }else{ // 未匹配到路由 if(isLogin){ // 已登入訪問 AddRoutes(); next(); }else{ // 未登入訪問 next("/login"); // 跳轉登入 } } })
2.6、歸類整理
/* 路由前置 */ let { origin } = window.location || {}; routes.beforeEach((to,path } = to; let isMatched = matched && matched.length > 0; // 是否匹配 let isAuth = (meta || {}).isAuth; // 是否授權訪問 let { isAddRoutes } = Store.state; // 註冊路由 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.未匹配路由 && 登入 && 動態註冊路由 next('/404'); } else if (!isMatched && isLogin && !isAddRoutes) { // 註冊路由 // 1.未匹配路由 && 登入 && 未動態註冊路由 AddRoutes(); next(); } });
嗯! 這下看起來舒服多了。
3、完整實現程式碼
// src/utils/index.js const { pathToRegexp } = require('path-to-regexp'); export function getAuthRouters(authMenu) { let authRouters = []; (authMenu || []).forEach((item) => { const { resourceUrl,...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,fullPath)); // 1.當前路由匹配 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",error); } } /* 路由前置 */ let { origin } = window.location || {}; routes.beforeEach((to,path } = to; let isMatched = matched && matched.length > 0; // 是否匹配 let isAuth = (meta || {}).isAuth; // 是否授權訪問 let { isAddRoutes } = Store.state; // 註冊路由 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.未匹配路由 && 登入 && 動態註冊路由 next('/404'); } else if (!isMatched && isLogin && !isAddRoutes) { // 註冊路由 // 1.未匹配路由 && 登入 && 未動態註冊路由 AddRoutes(); next(); } });
雖然前端能夠通過vue-router實現對路由許可權的控制,但是實際是偽許可權控制,無法達到完全控制;強烈建議對於需要控制路由許可權的系統採用後端控制。
到此這篇關於vue-router 控制路由許可權的實現的文章就介紹到這了,更多相關vue-router 控制路由許可權內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!