本篇文章是細談 vue 系列的第五篇,這篇的內容和之前不太同樣,此次屬於實戰篇。對該系列之前的文章感興趣的能夠點擊如下連接進行傳送javascript
前兩篇咱們分別分析了 <transition>
和 <transition-group>
組件的設計思路。html
<transition>
是一個抽象組件,且只對單個元素生效。而<transition-group>
組件實現了列表的過渡,而且它會渲染一個真實的元素節點。二者都是爲元素加上過渡效果前端
今天我會對以前研究過的一些東西進行思考,並將其與實際業務的場景相結合。vue
我在公司主要負責運維基層業務的支持,好久以前有寫過一篇文章(《TypeScript + 大型項目實戰》)大體介紹過。在正常的一些項目的開發中,對於各類權限的校驗是沒法避免的。java
而我這邊的項目在服務層面,不一樣的人擁有着不一樣的操做,好比 SRE 擁有 SRE 對用的權限,能作的事情不少;普通 RD 擁有其對應的權限,能作的事情大都只是一些基本的運維能力,且這些都是在本身負責的服務下面擁有的權限。而這些權限校驗實在太多了,若是你不作統一管理,估計得瘋。vuex
或許這篇文章應該取名:《如何使用抽象組件統一管理權限操做》,若是小夥伴們不想看我對整個業務的思考過程的話,能夠直接跳過本章節直接進入下一章節。typescript
對應上述狀況,最開先的作法是直接在獲取服務具體信息時,讓後端在接口中拋給前端權限相關的字段,而後前端進行權限值的全局 set
。具體操做以下後端
vuex
interface State {
hasPermission: boolean
}
const state: State = {
hasPermission: false
}
const getters = {
hasPermisson: (state: State) => state.hasPermisson
}
const mutations = {
SET_PERMISSON (state: State, hasPermisson: boolean) {
state.hasPermisson = hasPermisson
}
}
const actions = {
async srvInfo (context: { commit: Commit }, params: { appkey: string }) {
return request.get(`xxx/srv/${params.appkey}`)
},
// 權限校驗接口(具體地址換成你本身的便可)
async checkPermisson (context: { commit: Commit }, params?: { [key: string]: string }) {
return request.get('xxx/permission', { params: params })
}
}
export default {
state,
getters,
mutations,
actions
}
複製代碼
<template>
<div class="srv-page">
<el-button @click="handleCheck('type1')">確認權限1</el-button>
<el-button @click="handleCheck('type2')">確認權限2</el-button>
</div>
</template>
<script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import { Getter, Mutation, Action } from 'vuex-class' @Component export default class SrvPage extends Vue { appkey: string = 'common-appkey' @Getter('hasPermisson') hasPermisson: boolean @Mutation('SET_PERMISSON') SET_PERMISSON: Function @Action('srvInfo') srvInfo: Function @Action('checkPermisson') checkPermisson: Function getSrvInfo () { this.srvInfo({ appkey: this.appkey }).then((res: Ajax.AjaxResponse) => { if (res.data.code === 0) { this.SET_PERMISSON(true) } else { this.SET_PERMISSON(false) } }) } handleCheck (type: string) { if (this.hasPermisson) { this.checkPermisson({ type: type }).then((res: Ajax.AjaxResponse) => { if (res.data.code !== 0) { this.notify('xxx') } }) } else { this.notify('xxx') } } notify (name?: string) { this.$notify({ title: '警告', message: `您沒有操做權限,請聯繫負責人${name}開通權限`, type: 'warning', duration: 5000 }) } } </script>
複製代碼
但因爲後端獲取服務信息的接口接了好些三方接口,致使接口響應速度有點慢,這樣會致使我有些不須要等拿到具體服務信息的操做會有個延時,致使用戶會看到默認的權限值。app
按照上面的方法管理起來,若是頁面少,操做少,可能仍是比較適用的,這也是項目初期的作法,那時候頁面上的權限操做仍是比較少的,因此也一直沒發現有什麼問題。可是,隨着權限相關的操做愈來愈多,就發現上面的作法太過雞肋。爲了讓本身後面能更好的進行項目的開發和維護,結合業務對其又進行了一次操做升級。運維
若是不少頁面中,都有不少的權限操做,那能不能將相關操做抽離作成 mixins
呢?答案是 yes。而後我又開始將上面的操做抽離出來作成了 mixins
vuex
已有部分不變,新增部分操做const state: State = {
isAppkeyFirstCheck: false
}
const getters = {
isAppkeyFirstCheck: (state: State) => state.isAppkeyFirstCheck
}
const mutations = {
SET_APPKEY_FIRST_CHECK (state: State, firstCheck: boolean) {
state.isAppkeyFirstCheck = firstCheck
}
}
複製代碼
mixins/check-permission.ts
裏面的邏輯以下:對於同一個服務咱們只作一次公共的檢查,並把服務的關鍵參數 appkey
使用 $route.query
進行保存,每次變動則將權限初始化,剩餘的操做和以前很是相似import { Vue, Component, Watch } from 'vue-property-decorator'
import { Action, Getter, Mutation } from 'vuex-class'
declare module 'vue/types/vue' {
interface Vue {
handleCheckPermission (params?: { appkey?: string, message?: string }): Promise<any>
}
}
@Component
export default class CheckPermission extends Vue {
@Getter('hasPermisson') hasPermisson: boolean
@Getter('isAppkeyFirstCheck') isAppkeyFirstCheck: boolean
@Mutation('SET_PERMISSON') SET_PERMISSON: Function
@Mutation('SET_APPKEY_FIRST_CHECK') SET_APPKEY_FIRST_CHECK: Function
@Action('checkPermisson') checkPermisson: Function
@Watch('$route.query.appkey')
onWatchAppkey (val: string) {
if (val) {
this.SET_APPKEY_FIRST_CHECK(true)
this.SET_PERMISSON(false)
}
}
handleCheckPermission (params?: { appkey?: string, message?: string }) {
return new Promise((resolve: Function, reject: Function) => {
if (!this.isAppkeyFirstCheck) {
if (!this.hasPermisson) {
this.notify('xxx')
}
resolve()
return
}
const appkey = params && params.appkey || this.$route.query.appkey
this.checkPermisson({ appkey: appkey }).then(res => {
this.SET_APPKEY_FIRST_CHECK(false)
if (res.data.code === 0) {
this.SET_PERMISSON(true)
resolve(res)
} else {
this.SET_PERMISSON(false)
this.notify('xxx')
}
}).catch(error => {
reject(error)
})
})
}
notify (name?: string) {
this.$notify({
title: '警告',
message: `您沒有操做權限,請聯繫負責人${name}開通權限`,
type: 'warning',
duration: 5000
})
}
}
複製代碼
<template>
<div class="srv-page">
<el-button @click="handleCheck('type1')">操做1</el-button>
<el-button @click="handleCheck('type2')">操做2</el-button>
</div>
</template>
<script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import CheckPermission from '@/mixins/check-permission' @Component({ mixins: [ CheckPermission ] }} export default class SrvPage extends Vue { handleCheck (type: string) { this.handleCheckPermission().then(res => { console.log(type) }) } } </script>
複製代碼
OK,到這一步,這一切看起來仍是不錯的,使用這種作法後管理起權限操做來也的確便利了不少
可是,我以爲不少頁面都要引用 mixins
很是的麻煩。而後我又進一步進行思考,還有沒有更好的方式去管理呢?答案固然是 yes
在參考了 vue
的內置組件的設計思路後,我在想,爲何我不把其思路抽過來而後與本身的業務相結合呢?
本篇文章的關鍵字是 抽象組件
,個人本意也是不渲染真實節點,使用 抽象組件
封裝一層,將權限操做全放到該組件內,然後經過校驗後執行其子節點的事件。然而,因爲我實際業務是用 TS 開發的,而 vue
貌似不支持使用 TS 寫抽象組件,由於它不能爲組件設置 abstract
屬性。(我找了一圈資料,實在沒找到如何支持,若是有小夥伴知道的話請告知下我,謝了)
場面一度十分尷尬,爲了不尷尬,我只能退而求其次,直接渲染真實節點了,即相似 <transition-group>
組件的實現方式。
思路很簡單,主要分爲幾步
render
階段渲染節點並綁定好相關事件children
子節點進行具體事件處理<permission>
和 <permission-group>
組件首先實現 <permission>
組件,它主要負責對單個元素進行權限事件綁定
<script lang="ts"> import { Vue, Component, Watch, Prop } from 'vue-property-decorator' import { Action, Getter, Mutation } from 'vuex-class' import { VNode } from 'vue' @Component({ name: 'permission' }) export default class Permission extends Vue { @Prop({ default: 'span' }) tag: string @Prop() appkey: string @Prop() message: string @Prop({ default: null }) param: { template_name: string, appkey?: string, env?: string } | null @Getter('adminsName') adminsName: string @Getter('hasPermisson') hasPermisson: boolean @Getter('isAppkeyFirstCheck') isAppkeyFirstCheck: boolean @Mutation('SET_PERMISSON') SET_PERMISSON: Function @Mutation('SET_APPKEY_FIRST_CHECK') SET_APPKEY_FIRST_CHECK: Function @Action('checkPermisson') checkPermisson: Function @Action('isSlient') isSlient: Function @Watch('$route.query.appkey') onWatchAppkey (val: string) { if (val) { this.SET_APPKEY_FIRST_CHECK(true) this.SET_PERMISSON(false) } } render (h): VNode { const tag = this.tag const children: Array<VNode> = this.$slots.default if (children.length > 1) { console.warn( '<permission> can only be used on a single element. Use ' + '<permission-group> for lists.' ) } const rawChild: VNode = children[0] this.handleOverride(rawChild) return h(tag, null, [rawChild]) } handleOverride (c: any) { if (!(c.data && (c.data.on || c.data.nativeOn))) { return console.warn('there is no permission callback') } const method = c.data.on ? c.data.on.click : c.data.nativeOn.click c.data.on && (c.data.on.click = this.handlePreCheck(method)) c.data.nativeOn && (c.data.nativeOn.click = this.handlePreCheck(method)) } handlePreCheck (cb: Function) { return () => { const { appkey = this.$route.query.appkey, message = '' } = this this.handlePermissionCheck({ appkey, message }).then(() => { cb && cb() }) } } handlePermissionCheck (params: { [key: string]: string }) { return new Promise((resolve: Function, reject: Function) => { if (!this.isAppkeyFirstCheck) { if (!this.hasPermisson) { return this.$notify({ title: '警告', message: `您沒有服務操做權限,請聯繫服務負責人開通:${this.adminsName}`, type: 'warning', duration: 5000 }) } if (this.param) { return this.isSlient(this.param).then(res => { resolve(res) }) } resolve() return } this.checkPermisson({ appkey: params.appkey || this.$route.query.appkey }).then(res => { this.SET_APPKEY_FIRST_CHECK(false) if (res.data.code === 0) { this.SET_PERMISSON(true) if (this.param) { return this.isSlient(this.param).then(slientRes => { resolve(slientRes) }) } resolve(res) } else { this.SET_PERMISSON(false) this.$notify({ title: '警告', message: params.message || res.data.message, type: 'warning', duration: 5000 }) } }).catch(error => { reject(error) }) }) } } </script>
複製代碼
而後在全局註冊
import Permission from 'components/permission.vue'
Vue.component('Permission', Permission)
複製代碼
具體使用以下,只要引用了 <permission>
組件,則其包裹的子節點進行 click
或者 native click
的時候,都會事先進行權限校驗,校驗經過才執行本身自己的方法
<template>
<div class="srv-page">
<permission>
<el-button @click.native="handleCheck('type1')">權限操做1</el-button>
</permission>
</div>
</template>
<script lang="ts"> import { Component, Vue } from 'vue-property-decorator' @Component export default class SrvPage extends Vue { handleCheck (type: string) { console.log(type) } } </script>
複製代碼
相比 <permission>
組件,<permission-group>
組件,則只需把 param
參數綁定在每一個子節點上便可。具體二者實現邏輯基本一致,只需改變權限請求的參數便可
// render 部分的不一樣
render (h): VNode {
const tag = this.tag
const rawChildren: Array<VNode> = this.$slots.default || []
const children: Array<VNode> = []
for (let i = 0; i < rawChildren.length; i++) {
const c: VNode = rawChildren[i]
if (c.tag) {
children.push(c)
}
}
children.forEach(this.handleOverride)
return h(tag, null, children)
}
// 參數部分的不一樣
const param = c.data.attrs ? c.data.attrs.param : null
複製代碼
全局進行註冊
import PermissionGroup from 'components/permission-group.vue'
Vue.component('PermissionGroup', PermissionGroup)
複製代碼
頁面使用
<template>
<div class="srv-page">
<permission-group>
<el-button @click.native="handleCheck('type1')">權限操做1</el-button>
<el-button @click.native="handleCheck('type2')">權限操做2</el-button>
</permission-group>
</div>
</template>
<script lang="ts"> import { Component, Vue } from 'vue-property-decorator' @Component export default class SrvPage extends Vue { handleCheck (type: string) { console.log(type) } } </script>
複製代碼
至此,咱們的權限攔截組件就已經實現了,雖然原本是想直接使用 抽象組件
來完成這個的,可是也木有辦法,vue
使用 TS 後是不支持 abstract
屬性。不過通過如此處理後,對於權限操做的管理就變的很是 easy,也十分便於維護。
上面咱們已經得知 vue
並不能使用 TS 編寫本身的 抽象組件
,可是 JS 能夠啊。對於 JS 實現的話,其實具體邏輯也基本是如出一轍,無非是 render
階段的不一樣而已,我就不列出全部的代碼了。相同的代碼直接省略
<script> export default { abstract: true props: { appkey: String, message: String, param: { type: Object, default: () => { return {} } } }, render (h) { const children = this.$slots.default if (children.length > 1) { console.warn( '<permission> can only be used on a single element. Use ' + '<permission-group> for lists.' ) } const rawChild = children[0] this.handleOverride(rawChild) return rawChild }, methods: { handleOverride (c) { // ... }, handlePreCheck (cb) { // ... }, handlePermissionCheck (param) { // ... } } } </script>
複製代碼
<permission-group>
則同樣,這裏我就不贅述了。
目前爲止,屬於咱們本身業務的 抽象組件
已是實現完成。而在實際業務當中,其實還有不少業務值得咱們去思考,去探索更好的方式去實現,好比咱們能夠抽離一個 防抖
或者 節流
的組件出來,這在業務中也是十分常見的。
文章末尾聊幾句雞湯:
最後,放一波我本身弄的羣
前端交流羣:731175396,歡迎各位加入一塊兒嗨
我的準備從新撿回本身的公衆號了,以後每週保證一篇高質量好文,感興趣的小夥伴能夠關注一波。