本篇文章是细谈 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,欢迎各位加入一块儿嗨
我的准备从新捡回本身的公众号了,以后每周保证一篇高质量好文,感兴趣的小伙伴能够关注一波。