項目的基礎需求是:javascript
咱們在登陸後,將獲取到的用戶權限保存到本地緩存中,以便每次方便獲取權限,判斷權限。html
但因爲「子帳戶擁有限制權限,且可被隨時更改」這條需求,咱們的項目變得稍稍棘手一點:前端
然而,咱們的用戶特別懶,產品也特別慣着他們,他們要求——「界面刷新,就獲取最新的權限列表」。vue
看來原來的計劃得改了,獲取到的用戶權限保存到vuex的狀態管理器store中,刷新的時候,從新異步獲取一次權限,再判斷權限。java
根據項目需求,路由結構以下:vuex
routes: [
{
path: '/login',
component: Login,
name: 'login'
},
{
path: '/enterprise',
component: Enterprise,
name: 'Enterprise',
children: getRouter(routerConfig)
},
{
path: '*',
redirect: {name: 'login'}
}
]
複製代碼
項目是多頁面項目,在同一個域名下訪問,系統管理員登陸後以/admin
爲前綴,企業帳戶登陸後以/enterprise
爲前綴,企業系統作權限控制,只列出企業系統部分路由大綱。json
從輸入地址到頁面展現,權限控制流程構思以下:後端
爲了將路由與權限控制分離,我將權限管理單獨抽出來一個類。數組
所以核心邏輯大體能夠分爲:緩存
從後端獲取的權限樹格式大體長這樣:
{
"data": [
{
"title": "A模塊",
"name": "module_A",
"children": [
{
"title": "A模塊-1",
"name": "module_A_1",
"children": [
{
"title": "A模塊-1-新增",
"name": "A_1_add",
"children": []
},
{
"title": "A模塊-1-編輯",
"name": "A_1_edit",
"children": []
}
]
}
]
},
{
"title": "B模塊",
"name": "module_B",
"children": [
{
"title": "B模塊-刪除",
"name": "B_del",
"children": []
}
]
}
]
}
複製代碼
咱們須要將權限樹扁平化爲如下形式,方便判斷。
這個列表存儲爲store中的 permissions
,後期的判斷權限都以它爲依據。
{
"module_A": {
"allow": true,
"redirect": 'module_A_1'
},
"module_A_1": {
"allow": true,
"redirect": ''
},
"A_1_add": {
"allow": true,
"redirect": ''
},
"A_1_edit": {
"allow": true,
"redirect": ''
},
"module_B": {
"allow": true,
"redirect": ''
},
"B_del": {
"allow": true,
"redirect": ''
},
// ..
}
複製代碼
要提一嘴的是,redirect 用於重定向,默認是沒有的,可是對於有子層級的路由須要設置跳轉。好比A模塊下面還有4個子模塊,可是用戶只擁有A模塊下的2個模塊 A-一、A-2,redirect 設置爲2個模塊中的A-1模塊。
在權限管理以前,在router須要作額外的配置:
一個頁面可能被兩種權限獲取,如編輯頁和新增頁,所以咱們用數組包含權限。
{
path: 'module_A',
component: /***/,
name: 'module_A',
meta: {
authority: ['module_A']
},
children: [
{
path: 'A-1',
component: /***/,
name: 'module_A_1',
meta: {
authority: ['module_A']
}
},
{
path: 'A-1/:id',
component: /***/,
name: 'module_A_1-id',
meta: {
authority: ['A_1_add', 'A_1_edit'] /** 編輯頁的authority **/
}
}
]
}
複製代碼
權限管理類大體框架以下:
class AuthControl {
constructor () {
this.permissions = {} // 權限列表
this.authMenus = [] // 菜單
}
// 登陸頁
routerLogin = {
name: 'login'
}
// 計算有權限的第一個頁面
get routerHome () {
// ...
return {
name: key
}
}
// 登陸頁路由守衛
async loginGuard (to, from, next) {
// ...
}
// 企業系統路由守衛
async EnterpriseGuard (to, from, next) {
// ...
}
// 異步獲取權限
async getPermissions () {
}
_loginCheck () {
}
_permissionCheck (to) {
}
}
複製代碼
router.js 調用這個類:
const control = new AuthControl()
router.beforeEach(async (to, from, next) => {
if (to.matched[0].path === 'login') {
await control.loginGuard(to, from, next)
}
if (to.matched[0].path === '/enterprise') {
await control.EnterpriseGuard(to, from, next)
}
next()
})
複製代碼
登陸的守衛的調用是判斷登陸頁後進行的守衛。
基本邏輯就是:已登陸=>進入登陸後的第一個頁面;未登陸=>調用next()
進入。
async loginGuard (to, from, next) {
const loginStatus = this._loginCheck()
if (loginStatus) {
await this.getPermissions()
next(this.routerHome)
return
}
next()
}
複製代碼
將登陸頁守衛與企業系統守衛分開就是由於,企業系統守衛邏輯較複雜,若是全部狀況都調用同一個守衛,這邊考慮的會比較多,分開會清晰一些。
因此企業路由守衛是判斷要進入的地址是企業內容相關頁面。
邏輯大體分爲3塊:
async EnterpriseGuard (to, from, next) {
// 是否登陸
const loginStatus = this._loginCheck()
if (!loginStatus) {
next(this.routerLogin)
return
}
// 該頁面是否擁有權限
const permissionStatus = await this._permissionCheck(to)
if (!permissionStatus) {
next(this.routerHome)
return
}
// 該頁面是否須要重定向
const authority = to.meta.authority[0]
const redirect = this.permissions[authority].redirect
if (redirect) {
next({
name: redirect
})
return
}
next()
}
複製代碼
對於 _loginCheck
各家方法不太一致,不贅述。
對於 _permissionCheck
,主要根據前文在路由中的配置判斷:
_permissionCheck (to) {
const {
meta: {
authority = []
}
} = to
return this.getPermissions()
.then(permissions => {
const allow = authority.some(d => permissions[d])
return allow
})
.catch((err) => {
console.info(err)
return false
})
}
複製代碼
對於按鈕權限,會有兩種可能:
個人初步想法是,對於無權限的按鈕,設置屬性的值 —— disabled
、hidden
,這樣能夠不去修改DOM。
const permission = {
bind (el, binding) {
const { effect = 'disabled', name: authority } = binding.value
if (!Store.getters['permissions'][authority]) {
el[effect] = true
}
}
}
/** * hidden 另外需設置樣式: [hidden] { visibility: hidden; display: none; } .el-button[hidden] + .el-button { margin-left: 0; } .el-button[hidden] ~ .el-button { margin-left: 14px; } */
複製代碼
<!-- 隱藏效果 -->
<el-button v-permission="{name: 'A_1_add', effect:'hidden'}">
新建
</el-button>
<!-- 置灰效果 -->
<el-button v-permission="{name: 'A_1_edit', effect:'disabled'}">
編輯
</el-button>
複製代碼
可是仍是發現樣式會有點影響,好比按鈕外部包裹的div設置了padding值的狀況,當按鈕所有沒有的時候,margin值會撐起一段空白。
無奈,對於隱藏樣式,仍是採用移除節點的方式:
export const permission = {
bind (el, binding) {
const { effect = 'disabled', name: authority } = binding.value
if (!Store.getters['permissions'][authority]) {
if (effect === 'hidden') {
el.parentNode.removeChild(el)
}
el[effect] = true
}
}
}
複製代碼
事實上,咱們的需求並無結束。
因爲系統管理員擁有最大權限,管理全部企業,他能夠在系統管理員平臺代理登陸並跳轉任意多個企業並擁有該企業的全部權限。但因爲權限管理類單獨抽象出來,處理起來也沒有那麼難。
好比_loginCheck
方法,新增對系統管理員登陸的兼容,routerHome
的計算,進入未代理過的企業則返回系統管理員的主頁,等等。