詳解利用 Vue.js 實(shí)現(xiàn)前后端分離的RBAC角色權(quán)限管理
項(xiàng)目背景:物業(yè)管理后臺(tái),不同角色擁有不同權(quán)限
采用技術(shù):Vue.js + Vuex + Element UI
實(shí)現(xiàn) RBAC 權(quán)限管理需要后端接口支持,這里僅提供前端解決方案。
因代碼篇幅較大,對(duì)代碼進(jìn)行了刪減,文中 “...” 即為省略的一部分代碼。
大致思路:
首先登錄成功后,從后臺(tái)拉取用戶當(dāng)前可顯示的菜單和可用權(quán)限列表,分別將其存入 store 的 nav(菜單導(dǎo)航) 和 auth(用戶可用權(quán)限) 中,在用戶切換路由時(shí),判斷是否存在 auth ,如果不存在,則重新獲取,判斷當(dāng)前訪問地址 to.meta.alias 是否在用戶可用權(quán)限列表中,如果不存在,則提示無權(quán)限,否則進(jìn)入路由。
1. 路由與側(cè)邊菜單分離
側(cè)邊菜單相關(guān)代碼 Main.vue
<template>
<!-- ... -->
<aside :class="collapsed?'menu-collapsed':'menu-expanded'">
<!--導(dǎo)航菜單-->
<el-menu :default-active="$route.path"
class="el-menu-vertical-aliyun"
@open="handleopen"
@close="handleclose"
@select="handleselect"
:collapse="collapsed"
unique-opened router>
<template v-for="(item,index) in nav">
<!-- 二級(jí)菜單 -->
<el-submenu :index="index+''"
v-if="item.children && item.children.length > 0">
<!-- 二級(jí)菜單頂級(jí) -->
<template slot="title">
<i :class="['icon',item.iconCls]"></i>
<span slot="title">{{item.name}}</span>
</template>
<!-- 二級(jí)菜單下級(jí) -->
<el-menu-item-group>
<!--<span slot="title">{{item.name}}</span>-->
<!-- && child.url-->
<template v-for="child in item.children">
<!--無三級(jí)菜單-->
<el-menu-item
:index="child.url"
:key="child.url"
v-if="!child.children">
{{child.name}}
</el-menu-item>
<!--有三級(jí)菜單-->
<el-submenu
:index="child.url"
:key="child.url"
v-if="child.children">
<span slot="title">{{child.name}}</span>
<el-menu-item v-for="subChild in child.children"
:index="subChild.url"
:key="subChild.url">
{{subChild.name}}
</el-menu-item>
</el-submenu>
</template>
</el-menu-item-group>
</el-submenu>
<!-- 一級(jí)菜單 -->
<el-menu-item v-if="!item.children"
:index="item.url">
<i :class="['icon',item.iconCls]"></i>
<span slot="title">{{item.name}}</span>
</el-menu-item>
</template>
</el-menu>
</aside>
<!-- ... -->
</template>
<script>
export default {
// ...
computed: {
// 從 Vuex 中獲取導(dǎo)航菜單
nav() {
return this.$store.state.nav;
}
}
// ...
}
</script>
2. 路由切換前進(jìn)行鑒權(quán)
路由定義的部分代碼,對(duì)每個(gè)路由添加了 meta 屬性,用于鑒權(quán)。
這里 component 采用了異步引入的方式。
定義路由
// ...
// 系統(tǒng)管理
{
path: '/system',
component: Main,
name: '系統(tǒng)管理',
redirect: '/system/organization',
children: [{
path: '/system/organization',
component: () => import ('@/views/System/Organization.vue'),
name: '組織結(jié)構(gòu)',
// requiresAuth 用于確認(rèn)此地址是否需要驗(yàn)證
// alias 用于獲取后端返回rbac權(quán)限對(duì)應(yīng)的前端路由地址和導(dǎo)航菜單圖標(biāo)
meta: {requiresAuth: true, alias: 'Pmsadmin/Oragnize/list'}
},
{
path: '/system/user',
component: () => import ('@/views/System/User.vue'),
name: '人員管理',
redirect: '/system/user/index',
children: [
{
path: '/system/user/index',
component: () => import ('@/views/System/UserList.vue'),
name: '職員列表',
meta: {requiresAuth: true, alias: 'Pmsadmin/Admin/list'}
}
]
},
{
path: '/system/auth',
component: () => import ('@/views/System/Auth.vue'),
name: '角色管理',
meta: {requiresAuth: true, alias: 'Pmsadmin/Role/list'}
}
]
}
// ...
路由鉤子 beforeEach
router.beforeEach((to, from, next) => {
document.title = `${configs.title} - ${to.name}`;
const {hasAuth, auth} = store.state.user;
// 未拿到權(quán)限,則獲取
if (!hasAuth) {
store.dispatch('getUserAuth');
console.log('重新獲取用戶權(quán)限');
// next();
}
// 如果未登錄,跳轉(zhuǎn)
if (window.localStorage.getItem('IS_LOGIN') === null && to.path !== '/login') {
console.log('未登錄狀態(tài)');
next({
path: '/login',
query: {redirect: to.fullPath}
// 將跳轉(zhuǎn)的路由path作為參數(shù),登錄成功后跳轉(zhuǎn)到該路由
})
} else {
// 需要鑒權(quán)的路由地址
console.log(to, auth.indexOf(to.meta.alias), auth);
if (to.meta.requiresAuth) {
if (auth.indexOf(to.meta.alias) > -1) {
console.log('有權(quán)限進(jìn)入');
next();
} else {
if(auth.length > 0) {
Message.error({
message: '當(dāng)前用戶權(quán)限不足,無法訪問',
showClose: true,
});
} else {
next();
}
}
} else {
next();
}
}
});
在 Vuex 的 state 中,定義好 nav 對(duì)象
// 登錄用戶信息
const user = {
name: '', // 用戶名
avatar: '', // 用戶頭像
auth: [], // 用戶權(quán)限
hasAuth: false // 是否已經(jīng)加載用戶權(quán)限
};
// 導(dǎo)航菜單
const nav = [];
通過 action 異步獲取數(shù)據(jù)
// 獲取用戶權(quán)限
const getUserAuth = async ({commit}) => {
const res = await http.post('YOUR_URL', {});
if (res === null) return;
console.log('getUserAuth', res.param);
commit('SET_USER_AUTH', res.param.auth);
commit('SET_SIDE_NAV', res.param.nav);
};
Vuex 中的 mutation 的相關(guān)代碼
// 設(shè)置用戶權(quán)限
const SET_USER_AUTH = (state, auth) => {
state.user.auth = auth.concat('歡迎使用');
state.user.hasAuth = true;
};
// 設(shè)置導(dǎo)航菜單
const SET_SIDE_NAV = (state, nav) => {
// 導(dǎo)航菜單
let _nav = [{
name: '歡迎使用',
url: "/main",
iconCls: 'fa fa-bookmark'
}];
// 權(quán)限菜單對(duì)應(yīng)的路由地址
const route = {
"系統(tǒng)管理": {iconCls: 'fa fa-archive', url: ''},
"Pmsadmin/Oragnize/list": {iconCls: '', url: '/system/organization'},
"Pmsadmin/Admin/list": {iconCls: '', url: '/system/user/index'},
"Pmsadmin/Role/list": {iconCls: '', url: '/system/auth'},
"Pmsadmin/Log/record": {iconCls: '', url: '/system/logs'},
"項(xiàng)目管理": {iconCls: 'fa fa-unlock-alt', url: ''},
"Pmsadmin/Project/list": {iconCls: '', url: '/project/list/index'},
"Pmsadmin/House/list": {iconCls: '', url: '/project/house'},
"Pmsadmin/Pack/list": {iconCls: '', url: '/project/pack'},
"廣告位": {iconCls: 'fa fa-edit', url: ''},
"Pmsadmin/Place/list": {iconCls: '', url: '/adsplace/list'},
"投訴建議": {iconCls: 'fa fa-tasks', url: ''},
"Pmsadmin/Scategory/list": {iconCls: '', url: '/complain/type'},
"Pmsadmin/Complain/list": {iconCls: '', url: '/complain/list'},
"Pmsadmin/Suggest/list": {iconCls: '', url: '/complain/suggestion'},
"報(bào)事報(bào)修": {iconCls: 'fa fa-user', url: ''},
"Pmsadmin/Rcategory/list": {iconCls: '', url: '/rcategory/type'},
"Pmsadmin/Rcategory/info": {iconCls: '', url: '/rcategory/public'},
"Pmsadmin/Repair/list": {iconCls: '', url: '/rcategory/personal'},
"便民服務(wù)": {iconCls: 'fa fa-external-link', url: ''},
"Pmsadmin/Bcategory/list": {iconCls: '', url: '/bcategory/type'},
"Pmsadmin/Service/list": {iconCls: '', url: '/bcategory/list'},
"首座推薦": {iconCls: 'fa fa-file-text', url: ''},
"Pmsadmin/stcategory/list": {iconCls: '', url: '/stcategory/type'},
"Pmsadmin/Store/list": {iconCls: '', url: '/stcategory/list'},
"招商租賃": {iconCls: 'fa fa-leaf', url: ''},
"Pmsadmin/Bussiness/list": {iconCls: '', url: '/bussiness/list'},
"Pmsadmin/Company/list": {iconCls: '', url: '/bussiness/company'},
"Pmsadmin/Question/list": {iconCls: '', url: '/bussiness/question'},
"停車找車": {iconCls: 'fa fa-ra', url: ''},
"Pmsadmin/Cplace/list": {iconCls: '', url: '/cplace/cmanage'},
"Pmsadmin/Clist/list": {iconCls: '', url: '/cplace/clist'},
"Pmsadmin/Cquestion/list": {iconCls: '', url: '/cplace/cquestion'},
};
for (let key in nav) {
let item = nav[key];
let _temp = {};
let subItems = []; // 二級(jí)菜單臨時(shí)數(shù)組
if (item.children && item.children.length > 0) {
// 二級(jí)菜單
item.children.forEach(subItem => {
subItems.push(Object.assign({}, {
name: subItem.name || '',
url: route[subItem.url].url || '',
iconCls: route[subItem.url].iconCls || '',
}))
});
// 一級(jí)菜單
_temp = Object.assign({}, {
name: item.name || '',
url: item.url || '',
iconCls: route[item.name].iconCls || '',
children: subItems.slice(0)
});
_nav.push(_temp);
}
}
state.nav = _nav;
};
3. 后端接口返回內(nèi)容
{
"status": 200,
"info": "數(shù)據(jù)查詢成功!",
"param": {
"nav": {
"1": {
"name": "系統(tǒng)管理",
"url": "",
"children": [
{
"name": "組織結(jié)構(gòu)",
"url": "Pmsadmin/Oragnize/list"
},
{
"name": "人員管理",
"url": "Pmsadmin/Admin/list"
},
{
"name": "角色管理",
"url": "Pmsadmin/Role/list"
},
{
"name": "日志管理",
"url": "Pmsadmin/Log/record"
}
]
},
"61": {
"name": "廣告位",
"url": "",
"children": [
{
"name": "廣告位列表",
"url": "Pmsadmin/Place/list"
}
]
}
},
"auth": [
"系統(tǒng)管理",
"Pmsadmin/Oragnize/list",
"Pmsadmin/Admin/list",
"Pmsadmin/Role/list",
"Pmsadmin/Log/record",
"廣告位",
"Pmsadmin/Place/list"
]
}
}
存在的問題
- 新增 修改 刪除 按鈕還無法實(shí)現(xiàn)根據(jù)用戶權(quán)限控制其顯示
- 代碼上還存在著不足,期待大神能夠有更優(yōu)的解決方案。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vue3文件下載方法實(shí)現(xiàn)的簡(jiǎn)單代碼
在Web開發(fā)中,文件下載可通過多種方式實(shí)現(xiàn),下面這篇文章主要介紹了Vue3文件下載方法實(shí)現(xiàn)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10
vue學(xué)習(xí)之mintui picker選擇器實(shí)現(xiàn)省市二級(jí)聯(lián)動(dòng)示例
本篇文章主要介紹了vue學(xué)習(xí)之mintui picker選擇器實(shí)現(xiàn)省市二級(jí)聯(lián)動(dòng)示例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-10-10
vue項(xiàng)目頁面嵌入代碼塊vue-prism-editor的實(shí)現(xiàn)
這篇文章主要介紹了vue項(xiàng)目頁面嵌入代碼塊vue-prism-editor的實(shí)現(xiàn),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
vue項(xiàng)目如何實(shí)現(xiàn)ip和localhost同時(shí)訪問
這篇文章主要介紹了vue項(xiàng)目如何實(shí)現(xiàn)ip和localhost同時(shí)訪問,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
在Flask項(xiàng)目中集成并訪問Vue前端項(xiàng)目的流程步驟
在現(xiàn)代?Web?開發(fā)中,前后端分離的架構(gòu)模式越來越流行,前端通常使用?Vue、React?等框架開發(fā),而后端則使用?Flask、Django?等框架提供?API?服務(wù),本文將詳細(xì)介紹如何在一個(gè)?Flask?項(xiàng)目中集成?Vue?前端項(xiàng)目,并確保能夠正確訪問和運(yùn)行,需要的朋友可以參考下2025-03-03
VUE2.0+ElementUI2.0表格el-table循環(huán)動(dòng)態(tài)列渲染的寫法詳解
這篇文章主要介紹了VUE2.0+ElementUI2.0表格el-table循環(huán)動(dòng)態(tài)列渲染的寫法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
vue中調(diào)用百度地圖獲取經(jīng)緯度的實(shí)現(xiàn)
最近做個(gè)項(xiàng)目,需要實(shí)現(xiàn)獲取當(dāng)前位置的經(jīng)緯度,所以本文主要介紹了vue中調(diào)用百度地圖獲取經(jīng)緯度的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08

