詳解利用 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)前訪問(wèn)地址 to.meta.alias 是否在用戶可用權(quán)限列表中,如果不存在,則提示無(wú)權(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"> <!--無(wú)三級(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)限不足,無(wú)法訪問(wè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 = [];
通過(guò) 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" ] } }
存在的問(wèn)題
- 新增 修改 刪除 按鈕還無(wú)法實(shí)現(xiàn)根據(jù)用戶權(quán)限控制其顯示
- 代碼上還存在著不足,期待大神能夠有更優(yōu)的解決方案。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
解決vue中菜單再次點(diǎn)擊內(nèi)容不刷新問(wèn)題
當(dāng)elementUI中菜單打開后,再次點(diǎn)擊不會(huì)刷新的問(wèn)題,導(dǎo)致菜單再次點(diǎn)擊不刷新的根本原因是頁(yè)面打開后,再次打開相同的頁(yè)面是不會(huì)刷新的,這應(yīng)該是框架的機(jī)制就是如此,小編整理了兩個(gè)比較不錯(cuò)的解決方法,需要的朋友可以參考下2023-08-08vue 百度地圖(vue-baidu-map)繪制方向箭頭折線實(shí)例代碼詳解
這篇文章主要介紹了vue 百度地圖(vue-baidu-map)繪制方向箭頭折線,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04vue+elementUI-el-table實(shí)現(xiàn)動(dòng)態(tài)顯示隱藏列方式
這篇文章主要介紹了vue+elementUI-el-table實(shí)現(xiàn)動(dòng)態(tài)顯示隱藏列方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01vue element實(shí)現(xiàn)表格合并行數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了vue element實(shí)現(xiàn)表格合并行數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11圖文詳解Element-UI中自定義修改el-table樣式
elementUI提供的組件間距、樣式都比較大,如果直接套用,在頁(yè)面顯示可能就會(huì)顯得很大,就比如表格,表頭、行寬如果不修改的話,遇到列較多的時(shí)候,會(huì)顯得整個(gè)頁(yè)面就不好看,下面這篇文章主要給大家介紹了關(guān)于Element-UI中自定義修改el-table樣式的相關(guān)資料,需要的朋友可以參考下2022-08-08解決Vue+Electron下Vuex的Dispatch沒有效果問(wèn)題
這篇文章主要介紹了Vue+Electron下Vuex的Dispatch沒有效果的解決方案 ,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05Vue如何獲取元素高度總是不準(zhǔn)確的問(wèn)題
這篇文章主要介紹了Vue如何獲取元素高度總是不準(zhǔn)確的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10利用Vue-cli搭建Vue項(xiàng)目框架的教程詳解
這篇文章主要為大家詳細(xì)介紹了利用Vue-cli搭建Vue項(xiàng)目框架的相關(guān)資料,對(duì)大家深入了解Vue有一定的幫助,感興趣的小伙伴可以了解一下2023-02-02Vue.js展示AJAX數(shù)據(jù)簡(jiǎn)單示例講解
當(dāng)通過(guò)AJAX方式取回?cái)?shù)據(jù)后,使用vue.js可以完美地按一定邏輯在頁(yè)面上的展示數(shù)據(jù),代碼簡(jiǎn)單、優(yōu)美、自然,而且便于與在用的頁(yè)面框架集成,本文給大家介紹Vue.js展示AJAX數(shù)據(jù)簡(jiǎn)單示例2017-03-03