Skip to content

Commit 8b097a4

Browse files
author
jianyang.li
committed
readme
1 parent b07edc9 commit 8b097a4

File tree

3 files changed

+182
-48
lines changed

3 files changed

+182
-48
lines changed

README.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,114 @@
11
复杂的表单验证:父表单嵌套不同子表单时,验证提交数据的解决方案
2+
3+
### 背景
4+
> 当我们在做后台管理系统时,经常会遇到非常复杂的表单:
5+
> + 表单项非常多
6+
> + 在各种表单类型下,显示的表单项不同
7+
> + 在某些条件下,某些表单项会关闭验证
8+
> + 在这种错综复杂的情况下,完成表单的验证和提交
9+
> + 每个表单项还会有其他自定义逻辑,比如**输入框可以插入模板变量、输入字符数量显示、图片上传并显示、富文本**。。。
10+
11+
### 普通的方式
12+
> 所有的显示隐藏逻辑、数据获取提交逻辑 放在一起
13+
> + 根据表单类型,使用`v-if/v-show`处理表单项显示隐藏
14+
> + 在表单验证逻辑中加入条件,判断是否验证
15+
> + 根据表单类型,获取不同的数据,并提交
16+
> + 其余所有的自定义逻辑
17+
18+
##### 缺点
19+
+
20+
+
21+
+ 还是乱
22+
+ 一个`vue`文件,轻轻松松上`2000`
23+
24+
25+
### 分离组件
26+
> 其实很容易想到不同的表单类型,分离出不同的子表单。但我在实践时还是遇到了很多问题,并总结出一套解决方案
27+
28+
##### 1. 子组件
29+
30+
`validate`方法:返回`elementUI`表单验证的`promise`对象。用于父组件调用,验证自己组件的表单项
31+
```js
32+
vaildate() {
33+
return this.$refs["ruleForm"].validate();
34+
},
35+
36+
```
37+
`getData`方法:用于父组件调用,提取子组件中的数据
38+
```js
39+
getData() {
40+
return this.ruleForm;
41+
},
42+
```
43+
44+
##### 2. 父组件
45+
`submit`方法:父子组件表单验证、获取整体数据、根据不同类型发送不同接口。
46+
47+
+ 使用[策略模式](https://www.cnblogs.com/xiaohuochai/p/8029651.html)获取当前类型表单的`ref`和提交函数。省略了大量的`if-else`判断。
48+
49+
```js
50+
data:{
51+
// ref名称
52+
typeMap: {
53+
1: "message",
54+
2: "mail",
55+
3: "apppush"
56+
},
57+
// 模拟的不同类型表单的提交,url会不同
58+
fakeSubmit: {
59+
1: data => alert(`短信模板创建成功${JSON.stringify(data)}`),
60+
2: data => alert(`邮件模板创建成功${JSON.stringify(data)}`),
61+
3: data => alert(`push模板创建成功${JSON.stringify(data)}`)
62+
},
63+
}
64+
```
65+
+ `elementUI`表单验证`validate`方法可以返回`promise`结果,可以利用`promise`的特性来处理父子表单的验证。比如[`then`函数可以返回另一个`promise`对象](https://juejin.im/post/5cc17448f265da0379417cfc#heading-0)[`catch`可以获取它以上所有`then``reject`](https://juejin.im/post/5cc17448f265da0379417cfc#heading-1)[`Promise.all`](https://juejin.im/post/5cc17448f265da0379417cfc#heading-6)
66+
+ 父表单验证通过才会验证子表单,存在先后顺序
67+
```js
68+
// 父表单验证通过才会验证子表单,存在先后顺序
69+
submitForm() {
70+
const templateType = this.typeMap[this.indexForm.type];
71+
this.$refs["indexForm"]
72+
.validate()
73+
.then(res => {
74+
// 父表单验证成功后,验证子表单
75+
return this.$refs[templateType].vaildate();
76+
})
77+
.then(res => {
78+
// 获取整体数据
79+
const reqData = {
80+
// 获取子组件数据
81+
...this.$refs[templateType].getData(),
82+
...this.indexForm
83+
};
84+
this.fakeSubmit[this.indexForm.type](reqData);
85+
})
86+
.catch(err => {
87+
console.log(err);
88+
});
89+
},
90+
```
91+
+ 父表单,子表单一起验证
92+
```js
93+
submitForm1() {
94+
const templateType = this.typeMap[this.indexForm.type];
95+
const validate1 = this.$refs["indexForm"].validate();
96+
const validate2 = this.$refs[templateType].vaildate();
97+
父子表单一起验证
98+
Promise.all([validate1, validate2])
99+
.then(res => {
100+
// 都通过时,发送请求
101+
const reqData = {
102+
...this.$refs[templateType].getData(),
103+
...this.indexForm
104+
};
105+
this.fakeSubmit[this.indexForm.type](reqData);
106+
})
107+
.catch(err => {
108+
console.log(err);
109+
});
110+
},
111+
```
112+
> 总结:很多项目我都遇到这种复杂的表单,也用了很多种解决方案,在此总结出了一种比较整洁简便的方案。当然还有其他很多方案,比如**可以把数据提交的方法放在每一个子组件中,公共的表单项数据通过`props`传递给子组件用于提交**。有其他更加简洁的方案,欢迎评论,或者`github`上提`issue`
113+
>
114+
> 题外话: 看了[前端架构师亲述:前端工程师成长之路的 N 问 及 回答](https://juejin.im/post/5d0ba00af265da1bc7524043#heading-3)中的几个回答后,对我有很大的启发。在对自己的技术方向、前景迷茫时,或者在埋怨自己的项目太low时,或者埋怨自己每天在做重复工作时,或者每天对层出不穷的新技术焦头烂额时,**不妨认真的审视下自己的项目,每天重复的工作,是不是可以自己造轮子了;项目太low,是不是可以平滑过渡到新技术,提高开发效率。学再多的新技术,最终也会回归并实践到项目中,所以做好当前很重要。从工作和项目的痛点出发,你会一边探索一边进步,一步步走向大神级别**

src/components/apppush.vue

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,37 @@
11

22
<template>
3-
<div>
4-
<el-form
5-
:model="ruleForm"
6-
:rules="rules"
7-
ref="ruleForm"
8-
label-width="100px"
9-
class="demo-ruleForm"
10-
size="mini"
11-
>
12-
<el-form-item label="主题" prop="title">
13-
<el-input v-model="ruleForm.title"></el-input>
14-
</el-form-item>
15-
<el-form-item label="内容" prop="content">
16-
<el-input v-model="ruleForm.content"></el-input>
17-
</el-form-item>
18-
<el-form-item label="落地页" prop="targetUrl">
19-
<el-input v-model="ruleForm.targetUrl"></el-input>
20-
</el-form-item>
21-
</el-form>
22-
</div>
3+
<div>
4+
<el-form
5+
:model="ruleForm"
6+
:rules="rules"
7+
ref="ruleForm"
8+
label-width="100px"
9+
class="demo-ruleForm"
10+
size="mini"
11+
>
12+
<el-form-item label="主题" prop="title">
13+
<el-input v-model="ruleForm.title"></el-input>
14+
</el-form-item>
15+
<el-form-item label="内容" prop="content">
16+
<el-input v-model="ruleForm.content"></el-input>
17+
</el-form-item>
18+
<el-form-item label="落地页" prop="targetUrl">
19+
<el-input v-model="ruleForm.targetUrl"></el-input>
20+
<span style="color:red;font-size:12px;">B端 落地页链接不是必填</span>
21+
</el-form-item>
22+
</el-form>
23+
</div>
2324
</template>
2425

2526
<script>
2627
export default {
28+
props: ["customerType"],
2729
data() {
2830
return {
2931
ruleForm: {
30-
content:'',
31-
title:''
32+
content: "",
33+
title: "",
34+
targetUrl:''
3235
},
3336
rules: {
3437
content: [
@@ -40,22 +43,29 @@ export default {
4043
{ min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" }
4144
],
4245
targetUrl: [
43-
{ required: true, message: "请输入落地页链接", trigger: "blur" },
44-
{ min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" }
45-
],
46+
{
47+
validator: (rule, value, callback) => {
48+
if (this.customerType === "1" && value === '') {
49+
callback(new Error("请输入落地页链接"));
50+
} else {
51+
callback();
52+
}
53+
}
54+
}
55+
]
4656
}
4757
};
4858
},
4959
methods: {
50-
vaildate(){
51-
return this.$refs['ruleForm'].validate();
52-
},
53-
getData(){
54-
return this.ruleForm
55-
},
56-
resetForm() {
57-
this.$refs['ruleForm'].resetFields();
60+
vaildate() {
61+
return this.$refs["ruleForm"].validate();
62+
},
63+
getData() {
64+
return this.ruleForm;
5865
},
66+
resetForm() {
67+
this.$refs["ruleForm"].resetFields();
68+
}
5969
}
6070
};
6171
</script>

src/components/index.vue

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,25 @@
99
style="width:800px;margin:0 auto;"
1010
size="mini"
1111
>
12+
<el-form-item label="人群类型" prop="customerType">
13+
<el-select v-model="indexForm.customerType" placeholder="请选择人群类型">
14+
<el-option label="B端" value="1"></el-option>
15+
<el-option label="C端" value="2"></el-option>
16+
</el-select>
17+
</el-form-item>
1218
<el-form-item label="类型" prop="type">
13-
<el-select v-model="indexForm.type" placeholder="请选择类型" @change="typeChange">
19+
<el-select v-model="indexForm.type" placeholder="请选择类型">
1420
<el-option label="短信" value="1"></el-option>
1521
<el-option label="邮件" value="2"></el-option>
16-
<el-option label="apppush" value="3"></el-option>
22+
<el-option label="App Push" value="3"></el-option>
1723
</el-select>
1824
</el-form-item>
1925
<el-form-item label="名称" prop="name">
2026
<el-input v-model="indexForm.name"></el-input>
2127
</el-form-item>
2228
<message ref="message" v-if="indexForm.type==='1'"/>
2329
<mail ref="mail" v-if="indexForm.type==='2'"/>
24-
<apppush ref="apppush" v-if="indexForm.type==='3'"/>
30+
<apppush :customerType="indexForm.customerType" ref="apppush" v-if="indexForm.type==='3'"/>
2531
<el-form-item>
2632
<el-button type="primary" @click="submitForm1()">立即创建</el-button>
2733
</el-form-item>
@@ -37,7 +43,8 @@ export default {
3743
return {
3844
indexForm: {
3945
type: "1",
40-
name: ""
46+
name: "",
47+
customerType:'1'
4148
},
4249
rules: {
4350
name: [
@@ -50,12 +57,12 @@ export default {
5057
2: "mail",
5158
3: "apppush"
5259
},
53-
fakeApi: {
60+
// 模拟的不同类型表单的提交
61+
fakeSubmit: {
5462
1: data => alert(`短信模板创建成功${JSON.stringify(data)}`),
5563
2: data => alert(`邮件模板创建成功${JSON.stringify(data)}`),
5664
3: data => alert(`push模板创建成功${JSON.stringify(data)}`)
5765
},
58-
templateType: "message"
5966
};
6067
},
6168
components: {
@@ -66,41 +73,45 @@ export default {
6673
methods: {
6774
// 父表单验证通过才会验证子表单,存在先后顺序
6875
submitForm() {
76+
const templateType = this.typeMap[this.indexForm.type];
6977
this.$refs["indexForm"]
7078
.validate()
7179
.then(res => {
72-
return this.$refs[this.templateType].vaildate();
80+
// 父表单验证成功后,验证子表单
81+
return this.$refs[templateType].vaildate();
7382
})
7483
.then(res => {
84+
// 获取整体数据
7585
const reqData = {
76-
...this.$refs[this.templateType].getData(),
86+
// 获取子组件数据
87+
...this.$refs[templateType].getData(),
7788
...this.indexForm
7889
};
79-
this.fakeApi[this.indexForm.type](reqData);
90+
this.fakeSubmit[this.indexForm.type](reqData);
8091
})
8192
.catch(err => {
8293
console.log(err);
8394
});
8495
},
8596
// 父表单,子表单一起验证
8697
submitForm1() {
98+
const templateType = this.typeMap[this.indexForm.type];
8799
const validate1 = this.$refs["indexForm"].validate();
88-
const validate2 = this.$refs[this.templateType].vaildate();
100+
const validate2 = this.$refs[templateType].vaildate();
101+
父子表单一起验证
89102
Promise.all([validate1, validate2])
90103
.then(res => {
104+
// 都通过时,发送请求
91105
const reqData = {
92-
...this.$refs[this.templateType].getData(),
106+
...this.$refs[templateType].getData(),
93107
...this.indexForm
94108
};
95-
this.fakeApi[this.indexForm.type](reqData);
109+
this.fakeSubmit[this.indexForm.type](reqData);
96110
})
97111
.catch(err => {
98112
console.log(err);
99113
});
100114
},
101-
typeChange(type) {
102-
this.templateType = this.typeMap[type];
103-
}
104115
}
105116
};
106117
</script>

0 commit comments

Comments
 (0)