前兩篇關於vue權限路由文章的填坑,說了一堆理論,是時候操做一波了。html
選擇d2-admin是由於element-ui的相關開源項目裏,d2-admin的結構和代碼是讓我感到最舒服的,並且基於d2-admin實現RBAC權限管理也很方便,對d2-admin沒有大的侵入性的改動。html5
預覽地址git
Githubgithub
不瞭解RBAC,能夠看這裏企業管理系統先後端分離架構設計 系列一 權限模型篇vuex
菜單
與功能
,一個菜單下能夠有多個功能,菜單
類型的permission
字段標識訪問這個菜單須要的功能權限,功能
類型的permission
字段至關於此功能的別稱,因此菜單
類型的permission
字段爲其某個功能
類型子節點的permission
值permission
字段過濾出用戶所能訪問的路由使用d2admin
的原有登陸邏輯,全局路由守衛中判斷是否已經拉取權限信息,獲取後標識爲已獲取。數據庫
const token = util.cookies.get('token') if (token && token !== 'undefined') { //拉取權限信息 if (!isFetchPermissionInfo) { await fetchPermissionInfo(); isFetchPermissionInfo = true; next(to.path, true) } else { next() } } else { // 將當前預計打開的頁面完整地址臨時存儲 登陸後繼續跳轉 // 這個 cookie(redirect) 會在登陸後自動刪除 util.cookies.set('redirect', to.fullPath) // 沒有登陸的時候跳轉到登陸界面 next({ name: 'login' }) }
//標記是否已經拉取權限信息 let isFetchPermissionInfo = false let fetchPermissionInfo = async () => { //處理動態添加的路由 const formatRoutes = function (routes) { routes.forEach(route => { route.component = routerMapComponents[route.component] if (route.children) { formatRoutes(route.children) } }) } try { let userPermissionInfo = await userService.getUserPermissionInfo() permissionMenu = userPermissionInfo.accessMenus permissionRouter = userPermissionInfo.accessRoutes permission.functions = userPermissionInfo.userPermissions permission.roles = userPermissionInfo.userRoles permission.interfaces = util.formatInterfaces(userPermissionInfo.accessInterfaces) permission.isAdmin = userPermissionInfo.isAdmin == 1 } catch (ex) { console.log(ex) } formatRoutes(permissionRouter) let allMenuAside = [...menuAside, ...permissionMenu] let allMenuHeader = [...menuHeader, ...permissionMenu] //動態添加路由 router.addRoutes(permissionRouter); // 處理路由 獲得每一級的路由設置 store.commit('d2admin/page/init', [...frameInRoutes, ...permissionRouter]) // 設置頂欄菜單 store.commit('d2admin/menu/headerSet', allMenuHeader) // 設置側邊欄菜單 store.commit('d2admin/menu/fullAsideSet', allMenuAside) // 初始化菜單搜索功能 store.commit('d2admin/search/init', allMenuHeader) // 設置權限信息 store.commit('d2admin/permission/set', permission) // 加載上次退出時的多頁列表 store.dispatch('d2admin/page/openedLoad') await Promise.resolve() }
後端須要返回的權限信息包括權限過濾後的角色編碼集合,功能編碼集合,接口信息集合,菜單列表,路由列表,以及是否系統管理員標識。格式以下element-ui
{ "statusCode": 200, "msg": "", "data": { "userName": "MenuManager", "userRoles": [ "R_MENUADMIN" ], "userPermissions": [ "p_menu_view", "p_menu_edit", "p_menu_menu" ], "accessMenus": [ { "title": "系統", "path": "/system", "icon": "cogs", "children": [ { "title": "系統設置", "icon": "cogs", "children": [ { "title": "菜單管理", "path": "/system/menu", "icon": "th-list" } ] }, { "title": "組織架構", "icon": "pie-chart", "children": [ { "title": "部門管理", "icon": "html5" }, { "title": "職位管理", "icon": "opencart" } ] } ] } ], "accessRoutes": [ { "name": "System", "path": "/system", "component": "layoutHeaderAside", "componentPath": "layout/header-aside/layout", "meta": { "title": "系統設置", "cache": true }, "children": [ { "name": "MenuPage", "path": "/system/menu", "component": "menu", "componentPath": "pages/sys/menu/index", "meta": { "title": "菜單管理", "cache": true } }, { "name": "RoutePage", "path": "/system/route", "component": "route", "componentPath": "pages/sys/route/index", "meta": { "title": "路由管理", "cache": true } }, { "name": "RolePage", "path": "/system/role", "component": "role", "componentPath": "pages/sys/role/index", "meta": { "title": "角色管理", "cache": true } }, { "name": "UserPage", "path": "/system/user", "component": "user", "componentPath": "pages/sys/user/index", "meta": { "title": "用戶管理", "cache": true } }, { "name": "InterfacePage", "path": "/system/interface", "component": "interface", "meta": { "title": "接口管理" } } ] } ], "accessInterfaces": [ { "path": "/menu/:id", "method": "get" }, { "path": "/menu", "method": "get" }, { "path": "/menu/save", "method": "post" }, { "path": "/interface/paged", "method": "get" } ], "isAdmin": 0, "avatarUrl": "https://api.adorable.io/avatars/85/abott@adorable.png" } }
將固定菜單(/menu/header
、/menu/aside
)與後端返回的權限菜單(accessMenus
)合併後,存入相應的vuex store模塊中json
... let allMenuAside = [...menuAside, ...permissionMenu] let allMenuHeader = [...menuHeader, ...permissionMenu] ... // 設置頂欄菜單 store.commit('d2admin/menu/headerSet', allMenuHeader) // 設置側邊欄菜單 store.commit('d2admin/menu/fullAsideSet', allMenuAside) // 初始化菜單搜索功能 store.commit('d2admin/search/init', allMenuHeader)
默認使用routerMapComponents
的方式處理後端返回的權限路由
//處理動態添加的路由 const formatRoutes = function (routes) { routes.forEach(route => { route.component = routerMapComponents[route.component] if (route.children) { formatRoutes(route.children) } }) } ... formatRoutes(permissionRouter) //動態添加路由 router.addRoutes(permissionRouter); // 處理路由 獲得每一級的路由設置 store.commit('d2admin/page/init', [...frameInRoutes, ...permissionRouter])
路由處理方式及區別可看vue權限路由實現方式總結二
將角色編碼集合,功能編碼集合,接口信息集合,以及是否系統管理員標識存入相應的vuex store模塊中
... permission.functions = userPermissionInfo.userPermissions permission.roles = userPermissionInfo.userRoles permission.interfaces = util.formatInterfaces(userPermissionInfo.accessInterfaces) permission.isAdmin = userPermissionInfo.isAdmin == 1 ... // 設置權限信息 store.commit('d2admin/permission/set', permission)
支持使用角色編碼,功能編碼以及接口權限進行控制,以下
export function getMenuList() { return request({ url: '/menu', method: 'get', interfaceCheck: true, permission:["p_menu_view"], loading: { type: 'loading', options: { fullscreen: true, lock: true, text: '加載中...', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.8)' } }, success: { type: 'message', options: { message: '加載菜單成功', type: 'success' } } }) }
interfaceCheck: true
表示使用接口權限進行控制,若是vuex store中存儲的接口信息與當前要請求的接口想匹配,則可發起請求,不然請求將被攔截。
permission:["p_menu_view"]
表示使用角色編碼和功能編碼進行權限校驗,若是vuex store中存儲的角色編碼或功能編碼與當前表示的編碼相匹配,則可發起請求,不然請求將被攔截。
源碼位置在libs/permission.js
,可根據本身需求進行修改
loading
配置相關源碼在libs/loading.js
,根據本身需求進行配置,success
也是如此,源碼在libs/loading.js
。 照此思路能夠自行配置其它功能,好比請求失敗等。
使用指令v-permission
:
<el-button v-permission:function.all="['p_menu_edit']" type="primary" icon="el-icon-edit" size="mini" @click="batchEdit" >批量編輯</el-button>
參數可爲function
、role
,代表以功能編碼或角色編碼進行校驗,爲空則使用二者進行校驗。
修飾符all
,表示必須所有匹配指令值中全部的編碼。
源碼位置在plugin/permission/index.js
,根據本身實際需求進行修改。
使用v-if
+全局方法:
<el-button v-if="canAdd" type="primary" icon="el-icon-circle-plus-outline" size="mini" @click="add" >添加</el-button>
data() { return { canAdd: this.hasPermissions(["p_menu_edit"]) }; },
默認同時使用角色編碼與功能編碼進行校驗,有一項匹配便可。
相似的方法還要hasFunctions
,hasRoles
。
源碼位置在plugin/permission/index.js
,根據本身實際需求進行修改。
不要使用
v-if="hasPermissions(['p_menu_edit'])"
這種方式,會致使方法屢次執行
也能夠直接在組件中從vuex store讀取權限信息進行校驗。
頁面級別的組件放到pages/
目錄下,而且在routerMapCompnonents/index.js
中以key-value的形式導出
不須要權限控制的固定菜單放到menu/aside.js
和menu/header.js
中
不須要權限控制的路由放到router/routes.js
frameIn
內
須要權限控制的菜單與路由經過界面的管理功能進行添加,確保菜單的path
與路由的path
相對應,路由的name
與頁面組件的name
一致才能使keep-alive
生效,路由的component
在routerMapCompnonents/index.js
中能經過key匹配到。
開發階段菜單與路由的添加可由開發人員自行維護,並維護一份清單,上線後將清單交給相關的人去維護便可。
若是以爲麻煩,不想菜單與路由由後端返回,能夠在前端維護一份菜單和路由(路由中的
component
仍是使用字符串,參考mock/permissionMenuAndRouter.js
),而且在菜單和路由上面維護相應的權限編碼,通常都是使用功能編碼。後端就不須要返回菜單和路由信息了,可是其餘權限信息,好比角色編碼,功能編碼等仍是須要的。經過後端返回的功能編碼列表,在前端過濾出用戶具有權限的菜單和路由,過濾處理後後的菜單與路由格式與以前由後端返回的格式一致,而後將處理後的菜單與路由當作後端返回的同樣處理便可。
數據mock使用lazy-mock修改而來的d2-admin-server,數據真實來源於後端,相比其餘工具,支持數據持久化,存儲使用的是json文件,不須要安裝數據庫。簡單的配置便可自動生成增刪改查的接口。
後端使用中間件控制訪問權限,好比:
.get('/menu', PermissionCheck(), controllers.menu.getMenuList)
PermissionCheck
默認使用接口進行校驗,校驗用戶所能訪問的API中是否匹配當前API,支持使用功能編碼與角色編碼進行校驗PermissionCheck(["p_menu_edit"],["r_menu_admin"],true)
,第一個參數爲功能編碼,第二個爲角色編碼,第三個爲是否使用接口進行校驗。
更多詳細用法可看lazy-mock文檔
前端代碼生成還在開發中...