# 前言
和vue2的解决思路基本一致,各个子表单分别管理自己的表单数据,父组件负责组装和传递初始表单数据,各个子表单的通信就相当于组件通信,这里不过多阐述。相比于vue2的混入,vue3的组合式api来源更加清晰,但父组件自动管理各个子组件的ref有点不方便,必须要声明,同时vue3的组合式api相比于vue2的配置项写法上手难度更高,一不小心就容易写出冗杂繁琐的代码。
父组件
<template>
<div>
<sub-form11 :data="formDataMap"
formKey="SubForm1"
ref="SubForm1">
</sub-form11>
<sub-form22 :data="formDataMap"
formKey="SubForm2"
ref="SubForm2">
</sub-form22>
{{submitForm}}
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary"
@click="onSubmit">
提交数据
</a-button>
<a-button style="margin-left: 10px;"
@click="resetFields">
清除数据
</a-button>
</a-form-item>
</div>
</template>
<script setup>
import { ref } from '@vue/reactivity'
import SubForm11 from './components/subForm1.vue'
import SubForm22 from './components/subForm2.vue'
import { computed } from '@vue/runtime-core'
// 进行提交的数据
const submitForm = ref({})
// 前端表单组件映射字段
const formDataMap = ref({
SubForm1: {
name1: '',
region1: undefined,
date1: undefined,
delivery1: false,
type1: [],
resource1: '',
desc1: '',
},
SubForm2: {
name2: '',
region2: undefined,
date2: undefined,
delivery2: false,
type2: [],
resource2: '',
desc2: '',
},
})
// 前后端表单数据结构进行转换方法
const getConvertdData = (refer, target) => {
const form = {}
Object.entries(refer).forEach(([key, value]) => {
if (key.startsWith('Sub')) {
// 这里相当于做了一个约定,只要以Sub开头就代表数据还需要继续转换
form[key] = getConvertdData(value, target)
} else {
form[key] = target[key]
}
})
return form
}
// 第一个子组件
const SubForm1 = ref()
// 第二个子组件
const SubForm2 = ref()
// 获取子组件方法
const getFormRefName = computed(() => {
return [SubForm1, SubForm2]
})
// 获取所有子组件表单数据并进行拼装
const getFormData = () => {
return getFormRefName.value
.map((item) => {
return item.value.getFormData()
})
.reduce((total, cur) => Object.assign(total, cur), {})
}
// 验证所有表单子组件方法
const validate = () => {
return getFormRefName.value.map(async (item) => {
return await item.value.validate()
})
}
// 重置所有表单子组件
const resetFields = () => {
getFormRefName.value.forEach((item) => {
item.value.resetFields()
})
}
// 验证成功提交数据
const onSubmit = () => {
Promise.all(validate()).then((res) => {
if (res.every((val) => val)) {
console.log(getFormData())
}
})
}
</script>
<style></style>
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
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
子组件1
<template>
<a-card title="子表单1">
<a-form :model="formData"
:label-col="labelCol"
:wrapper-col="wrapperCol"
ref="form">
<a-form-item label="Activity name"
name="name1">
<a-input v-model:value="formData.name1" />
</a-form-item>
<a-form-item label="Activity zone"
name="region1">
<a-select v-model:value="formData.region1"
placeholder="please select your zone">
<a-select-option value="shanghai">
Zone one
</a-select-option>
<a-select-option value="beijing">
Zone two
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Activity time"
name="date1">
<a-date-picker v-model:value="formData.date1"
show-time
type="date"
placeholder="Pick a date"
style="width: 100%;" />
</a-form-item>
<a-form-item label="Instant delivery"
name="delivery1">
<a-switch v-model:checked="formData.delivery1" />
</a-form-item>
<a-form-item label="Activity type"
name="type1">
<a-checkbox-group v-model:value="formData.type1">
<a-checkbox value="1"
name="type">
Online
</a-checkbox>
<a-checkbox value="2"
name="type">
Promotion
</a-checkbox>
<a-checkbox value="3"
name="type">
Offline
</a-checkbox>
</a-checkbox-group>
</a-form-item>
<a-form-item label="Resources"
name="resource1">
<a-radio-group v-model:value="formData.resource1">
<a-radio value="1">
Sponsor
</a-radio>
<a-radio value="2">
Venue
</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="Activity form"
name="desc1">
<a-input v-model:value="formData.desc1"
type="textarea" />
</a-form-item>
</a-form>
{{formData}}
</a-card>
</template>
<script setup>
import { useForm } from '../hook/useForm'
// 父组件传递的数据
const props = defineProps({
data: {
type: Object,
default() {
return {}
},
},
// 表单标识
formKey: {
type: String,
default: '',
},
})
const {
formData,
form,
getFormData,
validate,
resetFields,
labelCol,
wrapperCol,
} = useForm(props)
// 对父组件暴露子组件方法
defineExpose({
getFormData,
validate,
resetFields,
})
</script>
<style>
</style>
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
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
子组件2
<template>
<a-card title="子表单2">
<a-form :model="formData"
:label-col="labelCol"
:wrapper-col="wrapperCol"
ref="form">
<a-form-item label="Activity name"
name="name2">
<a-input v-model:value="formData.name2" />
</a-form-item>
<a-form-item label="Activity zone"
name="region2">
<a-select v-model:value="formData.region2"
placeholder="please select your zone">
<a-select-option value="shanghai">
Zone one
</a-select-option>
<a-select-option value="beijing">
Zone two
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Activity time"
name="date2">
<a-date-picker v-model:value="formData.date2"
show-time
type="date"
placeholder="Pick a date"
style="width: 100%;" />
</a-form-item>
<a-form-item label="Instant delivery"
name="delivery2">
<a-switch v-model:checked="formData.delivery2" />
</a-form-item>
<a-form-item label="Activity type"
name="type2">
<a-checkbox-group v-model:value="formData.type2">
<a-checkbox value="1"
name="type">
Online
</a-checkbox>
<a-checkbox value="2"
name="type">
Promotion
</a-checkbox>
<a-checkbox value="3"
name="type">
Offline
</a-checkbox>
</a-checkbox-group>
</a-form-item>
<a-form-item label="Resources"
name="resource2">
<a-radio-group v-model:value="formData.resource2">
<a-radio value="1">
Sponsor
</a-radio>
<a-radio value="2">
Venue
</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="Activity form"
name="desc2">
<a-input v-model:value="formData.desc2"
type="textarea" />
</a-form-item>
</a-form>
{{formData}}
</a-card>
</template>
<script setup>
import { useForm } from '../hook/useForm'
// 父组件传递的数据
const props = defineProps({
data: {
type: Object,
default() {
return {}
},
},
// 表单标识
formKey: {
type: String,
default: '',
},
})
const {
formData,
form,
getFormData,
validate,
resetFields,
labelCol,
wrapperCol,
} = useForm(props)
// 对父组件暴露子组件方法
defineExpose({
getFormData,
validate,
resetFields,
})
</script>
<style>
</style>
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
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
组合式api
import { ref, toRefs, reactive } from '@vue/reactivity'
import { computed, watch } from '@vue/runtime-core'
import { cloneDeep } from "lodash"
export const useForm = (props) => {
// 对传进来的props对象进行解构并保持响应式
const { data, formKey } = toRefs(props)
// 表单数据
const formData = ref({})
// 与表单数据对应的父组件数据
const parentFormData = computed(() => {
return data.value[formKey.value]
})
// 以父组件数据为初始化数据给表单数据赋值
watch(parentFormData, val => {
formData.value = cloneDeep(val);
}, {
immediate: true
})
// 获取表单数据
const getFormData = () => {
return formData.value
}
// 包裹表单容器的宽度
const labelCol = reactive({ span: 4 })
const wrapperCol = reactive({ span: 14 })
// 表单组件绑定的ref
const form = ref()
// 表单组件校检方法
const validate = () => {
return form.value.validate();
}
// 重置表单组件方法
const resetFields = () => {
form.value.resetFields();
}
return {
formData,
form,
getFormData,
validate,
resetFields,
labelCol,
wrapperCol,
}
}
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
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