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) 。
那么問題來了,我們怎么才能獲取到需要?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)注冊
// ...省略部分重復(fù)代碼
const openRouters = [];
const authRouters = [{
path : "order/list",
// ...
meta : {
// 是否身份驗(yàn)證(至于默認(rèn)定義false還是true由開發(fā)者自定義)
isAuth : true
}
}];
/* 動(dòng)態(tài)注冊路由 */
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);
// 注冊路由
routes.addRoutes([].concat(newAuthRoutes, openRouters));
// 設(shè)置已注冊
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; // 注冊路由
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)注冊路由
next('/404');
} else if (!isMatched && isLogin && !isAddRoutes) {
// 注冊路由
// 1.未匹配路由 && 登錄 && 未動(dòng)態(tài)注冊路由
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)注冊路由 */
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);
// 注冊路由
routes.addRoutes([].concat(newAuthRoutes, openRouters));
// 設(shè)置已注冊
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; // 注冊路由
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)注冊路由
next('/404');
} else if (!isMatched && isLogin && !isAddRoutes) {
// 注冊路由
// 1.未匹配路由 && 登錄 && 未動(dòng)態(tài)注冊路由
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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談實(shí)現(xiàn)在線預(yù)覽PDF的幾種解決辦法
這篇文章主要介紹了淺談實(shí)現(xiàn)在線預(yù)覽PDF的幾種解決辦法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Vue項(xiàng)目保存代碼之后頁面自動(dòng)更新問題
這篇文章主要介紹了Vue項(xiàng)目保存代碼之后頁面自動(dòng)更新問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
vue3-print-nb實(shí)現(xiàn)頁面打印(含分頁打印)示例代碼
大多數(shù)后臺(tái)系統(tǒng)中都存在打印的需求,在有打印需求時(shí),對(duì)前端來說當(dāng)然是直接打印頁面更容易,下面這篇文章主要給大家介紹了關(guān)于vue3-print-nb實(shí)現(xiàn)頁面打印(含分頁打印)的相關(guān)資料,需要的朋友可以參考下2024-01-01
vue-cli-service serve報(bào)錯(cuò)error:0308010C:digital enve
這篇文章主要介紹了vue-cli-service serve報(bào)錯(cuò)error:0308010C:digital envelope routines::unsupported的解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
vue 2.0項(xiàng)目中如何引入element-ui詳解
element-ui是一個(gè)比較完善的UI庫,但是使用它需要有一點(diǎn)vue的基礎(chǔ),下面這篇文章主要給大家介紹了關(guān)于在vue 2.0項(xiàng)目中如何引入element-ui的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-09-09

