从开源框架el-admin中学习状态模式

最近在写项目时,想起之前看到开源框架eladmin里面表单有一个特性,就是全局只有一份代码,一个表单组件(类似表单的外框把,只有title和button等,输入控件不在其内),但是有很多不同的展示效果,例如新建,编辑,提交中等。不管哪个模块都共用的是同一个表单。这样无疑有助于后期的维护,了解后发现是用到了状态模式。这次简单记录下eladmin是如何使用状态模式的,因为本次项目也涉及到了类似的场景,看看能不能用上。

状态模式的定义与特点

状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

State抽象状态角色

  • 接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换。

ConcreteState具体状态角色

  • 具体状态主要有两个职责:一是处理本状态下的事情,二是从本状态如何过渡到其他状态。

Context环境角色

  • 定义客户端需要的接口,并且负责具体状态的切换。

eladmin框架混入及封装用的比较多,导致我也不能很具体的判断出来具体的角色指的是谁,如果有知道的还请留言告诉我,谢谢。

在eladmin中,框架对数据的新增表单的编辑、删除都通过状态模式去处理了部分逻辑判断。框架并不是针对某个具体的组件进行的状态处理,而是针对的整个过程进行的状态处理。不同组件通过该过程的不同状态,也会有不同的表现形式,例如按钮的loading显示、表单的标题与显示、方法处理的逻辑等

下面主要以新增数据为例,具体介绍下eladmin中状态模式的实现。

数据新增

在eladmin中的状态定义

1
2
3
4
5
6
7
8
9
// src/compents/Crud/crud.js
/**
* CRUD状态
*/
CRUD.STATUS = {
NORMAL: 0,
PREPARED: 1,
PROCESSING: 2
}

关键组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/compents/Crud/CRUD.operation.vue
<template>
...
<el-button
v-if="crud.optShow.add"
v-permission="permission.add"
class="filter-item"
size="mini"
type="primary"
icon="el-icon-plus"
@click="crud.toAdd"
>
新增
</el-button>
...
</template>
<script>
// 该处通过混入的方式将crud的方法引进来
import CRUD, { crud } from '@/compents/Crud//crud.js'
export default {
mixins: [crud()],
}
</script>

这里主要混入的是一些逻辑处理的方法CRUD状态定义和一些关键的data数据
混入后的关键属性有下面这些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// src/compents/Crud/CRUD.operation.vue
<template>
...
<el-button
v-if="crud.optShow.add"
v-permission="permission.add"
class="filter-item"
size="mini"
type="primary"
icon="el-icon-plus"
@click="crud.toAdd"
>
新增
</el-button>
...
</template>
<script>
export default {
data() {
return {
// 通过混入进来的
// ... 省略多余的属性
status: {
add: CRUD.STATUS.NORMAL,
edit: CRUD.STATUS.NORMAL,
// 添加或编辑状态
// 表单、按钮loading等组件的状态都是根据这里判断的
get cu() {
if (this.add === CRUD.STATUS.NORMAL && this.edit === CRUD.STATUS.NORMAL) {
return CRUD.STATUS.NORMAL
} else if (this.add === CRUD.STATUS.PREPARED || this.edit === CRUD.STATUS.PREPARED) {
return CRUD.STATUS.PREPARED
} else if (this.add === CRUD.STATUS.PROCESSING || this.edit === CRUD.STATUS.PROCESSING) {
return CRUD.STATUS.PROCESSING
}
throw new Error('wrong crud\'s cu status')
},
// 标题,控制表单Dialog组件标题在不同状态下的显示
get title() {
return this.add > CRUD.STATUS.NORMAL ? `新增${crud.title}` : this.edit > CRUD.STATUS.NORMAL ? `编辑${crud.title}` : crud.title
}
}
}
},
methods:{
// ... 省略多余的方法
/**
* 启动添加
*/
toAdd() {
// 重置表单数据
crud.resetForm()
// 对自定义钩子进行处理,如果设置了就执行
if (!(callVmHook(crud, CRUD.HOOK.beforeToAdd, crud.form) && callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) {
return
}
// 更改add的状态,从NORMAL变为PREPARED
crud.status.add = CRUD.STATUS.PREPARED
// 此时会弹出来el-dialog。该弹框通过:visible.sync="crud.status.cu > 0"控制显示

// 对自定义钩子进行处理,如果设置了就执行
callVmHook(crud, CRUD.HOOK.afterToAdd, crud.form)
callVmHook(crud, CRUD.HOOK.afterToCU, crud.form)
},
/**
* 提交新增/编辑
*/
submitCU() {
if (!callVmHook(crud, CRUD.HOOK.beforeValidateCU)) {
return
}
// 找到当前根据status显示的form组件,并进行校验
crud.findVM('form').$refs['form'].validate(valid => {
if (!valid) {
return
}
// 是否执行钩子
if (!callVmHook(crud, CRUD.HOOK.afterValidateCU)) {
return
}
// 根据状态去判断具体的执行方法
if (crud.status.add === CRUD.STATUS.PREPARED) {
crud.doAdd()
} else if (crud.status.edit === CRUD.STATUS.PREPARED) {
crud.doEdit()
}
})
},
/**
* 执行添加
*/
doAdd() {
// 自定义钩子
if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) {
return
}
// 更改整个添加过程的状态为PROCESSING
crud.status.add = CRUD.STATUS.PROCESSING
// 调取添加接口(此处eladmin做了一定处理,通过crudMethod和其属性即可调取自己定义的接口地址)
crud.crudMethod.add(crud.form).then(() => {
// 成功后将状态更改为初始状态 = 关闭表单,停止loading
crud.status.add = CRUD.STATUS.NORMAL
crud.resetForm()
crud.addSuccessNotify()
callVmHook(crud, CRUD.HOOK.afterSubmit)
crud.toQuery()
}).catch(() => {
// 出错后就更改为PREPARED状态(新增的第二个状态,此时表单仍然存在)
crud.status.add = CRUD.STATUS.PREPARED
callVmHook(crud, CRUD.HOOK.afterAddError)
})
},
}
}
</script>

简单来说,eladmin通过状态模式针对整个添加的流程做了如下规定:

NORMAL阶段

  • 表单控件不显示
  • 按钮loading为false

点击新增,进入PREPARED阶段

  • 表单显示,获取该阶段表单对应的标题

点击确定,进入PROCESSING阶段

  • 显示loading
  • 发出请求
    • 如果成功,重新设置为NORMAL阶段
    • 如果失败,则回到PREPARED阶段

总结

整个过程涉及到的组件有很多,只不过表单和按钮是最显眼的,并且按钮有很多个。
框架通过状态模式将很多按钮的判断逻辑抽离了出来,减少了不少工作量。
通过状态的判断去控制了各个组件自己的表现形式,在后期维护时也更加容易。

后面有人接手的话也只需要关注关键方法的逻辑。
而不是散落在各个组件中的小按钮和表单样式的逻辑了(假如不使用该模式)。

作者

徐云飞

发布于

2023-02-05

更新于

2023-02-05

许可协议

评论