手把手教學(xué)vue的路由權(quán)限問題
后臺(tái)管理類系統(tǒng)大多都涉及權(quán)限管理,菜單權(quán)限,按鈕權(quán)限。
菜單權(quán)限
菜單權(quán)限對(duì)應(yīng) - 路由。菜單權(quán)限 - 根據(jù)用戶角色不同,路由文件動(dòng)態(tài)配置。
相關(guān)知識(shí)點(diǎn)了解
vue-router是vue項(xiàng)目在進(jìn)行開發(fā)過程中必不可少缺少的插件,目前vue2依賴的是vue-router3,vue3依賴的vue-router4
在進(jìn)行權(quán)限控制之前一定要了解哪些路由需要權(quán)限哪些不需要
知識(shí)點(diǎn)集結(jié)
router.addRoutes()
動(dòng)態(tài)添加更多的路由規(guī)則。參數(shù)必須是一個(gè)符合 routes 選項(xiàng)要求的數(shù)組。
已廢棄目前版本再使用該api會(huì)被提示已經(jīng)廢棄,但是暫時(shí)依舊可以使用
router.addRoutes(routes: Array<RouteConfig>)
router.addRoute()
添加一條新路由規(guī)則。如果該路由規(guī)則有 name,并且已經(jīng)存在一個(gè)與之相同的名字,則會(huì)覆蓋它。
addRoute(route: RouteConfig)
router.getRoutes()
獲取所有活躍的路由記錄列表。注意只有文檔中記錄下來的 property 才被視為公共 API,避免使用任何其它 property,例如 regex,因?yàn)樗?Vue Router 4 中不存在。
路由導(dǎo)航守衛(wèi) - beforeEach
router.beforeEach((to, from, next) => {
/* 必須調(diào)用 `next` */
})全局前置守衛(wèi) - 跳轉(zhuǎn)一個(gè)路由之前都會(huì)執(zhí)行.
3個(gè)參數(shù):
to: 即將進(jìn)入的目標(biāo)的路由對(duì)象from:當(dāng)前導(dǎo)航正在離開的路由next: 是個(gè)函數(shù),進(jìn)入下一個(gè)鉤子
next(): 進(jìn)行管道中的下一個(gè)鉤子。如果全部鉤子執(zhí)行完了,則導(dǎo)航的狀態(tài)就是 confirmed (確認(rèn)的)。next(false): 中斷當(dāng)前的導(dǎo)航。如果瀏覽器的 URL 改變了 (可能是用戶手動(dòng)或者瀏覽器后退按鈕),那么 URL 地址會(huì)重置到 from 路由對(duì)應(yīng)的地址。next('/')或者next({ path: '/' }): 跳轉(zhuǎn)到一個(gè)不同的地址。當(dāng)前的導(dǎo)航被中斷,然后進(jìn)行一個(gè)新的導(dǎo)航。你可以向 next 傳遞任意位置對(duì)象,且允許設(shè)置諸如 replace: true、name: 'home' 之類的選項(xiàng)以及任何用在 router-link 的 to prop 或 router.push 中的選項(xiàng)。next(error): (2.4.0+) 如果傳入 next 的參數(shù)是一個(gè) Error 實(shí)例,則導(dǎo)航會(huì)被終止且該錯(cuò)誤會(huì)被傳遞給 router.onError() 注冊(cè)過的回調(diào)。
功能實(shí)現(xiàn)過程
路由權(quán)限第一種可以是后端全部返回,直接調(diào)用接口使用后端的可以使用的路由,但是這種情況一般較少。
第二種前端有一個(gè)份完整路由,根據(jù)后端接口進(jìn)行篩選,這里講解第二種情況。
(1)定義路由文件
router -> index.js
import Vue from 'vue'
import Router from 'vue-router'
import store from '../store'
Vue.use(Router)
//沒有權(quán)限的路由
export const constantRoutes = [
{ path: '/', name: 'index', redirect: '/login' },
{ path: '/login', name: 'login', component: () => import('@/views/login') },
{ path: '/register', name: 'register', component: () => import('@/views/register') },
{ path: '/forget', name: 'forget', component: () => import('@/views/forget') },
{ path: '/404', name: 'notfing', component: () => import('@/views/404')}
]
//有權(quán)限的路由
export const myAsyncRoute = [{
path: '/portal',
name: 'portal',
component: LayoutPortal,
redirect: '/portal/home',
meta: {id: 'no'},
children: [
{
path: '/portal/home',
name: 'portal-home',
component: () => import('@/views/portal/home/index.vue'),
meta: {id: 100100, title: '首頁', show: true}
},
{
path: '/portal/user',
name: 'portal-user',
component: () => import('@/views/layout/submenu'),
meta: {id: 100200, title: '統(tǒng)一身份認(rèn)證', show: true},
redirect: '/portal/user/userInfo',
children: [
{
path: '/portal/user/userInfo',
name: 'portal-user-userInfo',
component: () => import('@/views/portal/userInfo/index.vue'),
meta: {id: 100201, title: '統(tǒng)一用戶管理', show: true}
},
{
path: '/portal/user/userInfo/detial',
name: 'portal-userInfo-detial',
component: () => import('@/views/portal/userInfo/detial.vue'),
meta: { id: 100201, activeMenu: '/portal/user/userInfo', title: '統(tǒng)一用戶管理', show: false},
},
{
path: '/portal/user/userAuth',
name: 'portal-user-userAuth',
component: () => import('@/views/portal/userAuth/index.vue'),
meta: {id: 100202, title: '用戶認(rèn)證', show: true}
},
]
},
{
path: '/portal/journal',
name: 'portal-journal',
component: () => import('@/views/portal/journal/index.vue'),
meta: {id: 100303, title: '統(tǒng)一日志管理', show: true},
},
{
path: 'personal',
name: 'portal-personal',
component: () => import('@/views/portal/personal/index.vue'),
meta: {
id: 'no',
activeMenu: '/portal/home',
show: false
},
},
],
}]
export default new Router({
routes: constantRoutes,
})(2)注冊(cè)路由
main.js
import router from './router'
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})正常注冊(cè)完路由就可以開始進(jìn)行權(quán)限設(shè)置了
(3)獲取有權(quán)限路由
不同的項(xiàng)目獲取路由權(quán)限并不相同,大多數(shù) - 登錄接口返回/單獨(dú)接口進(jìn)行獲取
登錄獲取權(quán)限保存
let res = await this.$api.user.login(data);
if(res.token) {
this.$store.commit('setToken', res.token);
this.$store.commit('setUsername', res.nickName);
this.$store.commit('setUserInfo', res);
//存儲(chǔ)權(quán)限
localStorage.setItem('menuIdList', JSON.stringify(res.menuIdList))
this.$message({
message: '登錄成功',
type: 'success'
});
setTimeout(() => {
this.loading = false;
this.$router.push('/portal')
}, 1000);
}在main.js中攔截
獲取權(quán)限進(jìn)行匹配 - beforeEach一定要有一個(gè)next()的出口,不然會(huì)陷入死循環(huán)
let flag = true;
router.beforeEach((to, from, next) => {
if (['/login', '/forget', '/register'].includes(to.path)) {
next();
} else {
let token = localStorage.getItem("token");
if (token) {
if(flag) {
try {
//獲取有權(quán)限的路由進(jìn)行組裝
let route = asyncRoute() || [];
router.addRoutes(route)
router.addRoute({
path: '*',
redirect: '/404'
})
flag = false
next({...to, replace:true})
}catch(e) {
next('/login')
}
}else {
next();
}
}else {
next({ path: '/login' })
}
}
}
);注意: addRoute之后,打印router是看不見的,要獲取所有的權(quán)限,必須使用router.getRoute()進(jìn)行查看
(4)路由權(quán)限匹配
router.js-根據(jù)后端返回的權(quán)限,和自己的路由權(quán)限匹配,組織成新的路由,和自己想要的格式
export const asyncRoute = function() {
let menuIdList = localStorage.getItem('menuIdList') ? JSON.parse(localStorage.getItem('menuIdList')) : [];
let tempArr = filterRoute(myAsyncRoute, menuIdList);
store.dispatch('getRoute', tempArr)
let showRoute = [];
let nowRoute =JSON.parse(JSON.stringify(tempArr))
if(nowRoute[0].children && nowRoute[0].children.length){
nowRoute[0].children.forEach(item => {
let arr = [];
if(item.children && item.children.length){
arr = item.children.filter(obj => obj.meta.show)
}
if(item.meta.show){
item['showRouteChildren'] = arr;
showRoute.push(item)
}
})
}
store.dispatch('getShowRoute', showRoute)
return tempArr
}
function filterRoute(arr, menuIdList) {
if(!arr.length) return [];
return arr.filter(item => {
if(item.children && item.children.length) {
item.children = filterRoute(item.children, menuIdList);
}
return (item.meta && item.meta.id && menuIdList.includes(item.meta.id)) || (item.meta && item.meta.id == 'no') || (item.children && item.children.length > 0)
})
}在這個(gè)過程中,在store存儲(chǔ)了路由
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
routes: JSON.parse(localStorage.getItem('routes')) || [],
addRoutes: JSON.parse(localStorage.getItem('addRoutes')) ||[],
showRoutes: []
},
mutations: {
setRoutes(state, routes) {
state.addRoutes = routes;
state.routes = constantRoutes.concat(routes)
localStorage.setItem('routes', JSON.stringify(state.routes))
localStorage.setItem('addRoutes', JSON.stringify(state.addRoutes))
},
setShowRoutes(state, routes) {
state.showRoutes = routes;
}
},
actions: {
getRoute({commit}, list) {
return new Promise(resolve => {
commit('setRoutes', list)
resolve(list)
})
},
getShowRoute({commit}, list) {
return new Promise(resolve => {
commit('setShowRoutes', list)
resolve(list)
})
}
}
})總結(jié): 最后在理一下整個(gè)過程 - 存儲(chǔ)權(quán)限路由數(shù)據(jù),在進(jìn)行跳轉(zhuǎn)的時(shí)候進(jìn)行篩選組合路由。其實(shí)篩選路由不一定要寫在router.js,只要是你認(rèn)為合適的地方都可以,在權(quán)限控制過程中最重要的是路由攔截。
模擬一下路由攔截的過程
假設(shè)login之后進(jìn)入的/index,路由攔截的過程
/index - > token -> flag(true) ->獲取路由權(quán)限 -> next('/index') -> 重新進(jìn)入beforeEach-> token->flag(false) -> next() ->結(jié)束
按鈕權(quán)限 - 操作(自定義指令)
按鈕權(quán)限主要涉及的知識(shí)點(diǎn)就是全局自定義指令
寫在main.js?;蛘邌为?dú)js文件,main.js進(jìn)行引入
import Vue from "vue"
import store from "../store"
//自定義指令 v-has進(jìn)行權(quán)限判斷
Vue.directive("has",{
inserted : function (el,binding){
//按鈕權(quán)限
const data = store.state.buttons;
//按鈕的值 <el-button v-has>aa</el-button>
const value = binding.value; //a
const hasPermissions = data.includes(value);
if(!hasPermissions){
//隱藏按鈕
el.style.display = "none";
setTimeout(()=>{
el.parentNode.removeChild(el)
},0)
}
}
})總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Vue自定義復(fù)制指令 v-copy功能的實(shí)現(xiàn)
這篇文章主要介紹了Vue自定義復(fù)制指令 v-copy,使用自定義指令創(chuàng)建一個(gè)點(diǎn)擊復(fù)制文本功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01
vue使用axios?post發(fā)送json數(shù)據(jù)跨域請(qǐng)求403的解決方案
這篇文章主要介紹了vue使用axios?post發(fā)送json數(shù)據(jù)跨域請(qǐng)求403的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
vue.config.js中devServer.proxy配置說明及配置正確不生效問題解決
Vue項(xiàng)目devServer.proxy代理配置詳解的是一個(gè)非常常見的需求,下面這篇文章主要給大家介紹了關(guān)于vue.config.js中devServer.proxy配置說明及配置正確不生效問題解決的相關(guān)資料,需要的朋友可以參考下2023-02-02
vue-element-admin 菜單標(biāo)簽失效的解決方式
今天小編就為大家分享一篇vue-element-admin 菜單標(biāo)簽失效的解決方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-11-11
Vue Vine實(shí)現(xiàn)一個(gè)文件中寫多個(gè)組件的方法(最近很火)
Vue Vine提供了全新Vue組件書寫方式,主要的賣點(diǎn)是可以在一個(gè)文件里面寫多個(gè)vue組件,Vue Vine是一個(gè)vite插件,vite解析每個(gè)模塊時(shí)都會(huì)觸發(fā)插件的transform鉤子函數(shù),本文介紹Vue Vine是如何實(shí)現(xiàn)一個(gè)文件中寫多個(gè)組件,感興趣的朋友一起看看吧2024-07-07
Vue+axios 實(shí)現(xiàn)http攔截及路由攔截實(shí)例
這篇文章主要介紹了Vue+axios 實(shí)現(xiàn)http攔截及路由攔截 ,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04

