您好,登錄后才能下訂單哦!
注意: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, 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(); // 調用鉤子函數 }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, 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(); // 調用鉤子函數 }else{ // 未登錄訪問 next("/login"); // 跳轉登錄 } }else{ // 不需要登錄訪問 next(); // 調用鉤子函數 } }else{ // 未匹配到路由 if(isLogin){ // 已登錄訪問 AddRoutes(); next(); }else{ // 未登錄訪問 next("/login"); // 跳轉登錄 } } })
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; // 是否授權訪問 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, childMenu } = item; resourceUrl && authRouters.push(resourceUrl); if (childMenu && childMenu.length > 0) { // 合并子級菜單 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.當前路由匹配 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 : { // 是否身份驗證(至于默認定義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); } } /* 路由前置 */ let { origin } = window.location || {}; routes.beforeEach((to, from, next) => { const { meta, matched, 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 控制路由權限內容請搜索億速云以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持億速云!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。