动态表单的原理就是,需要每个表单都需要唯一的标志。不可直接使用index作为唯一标志。因为在增删表单的时候,index重新赋值,会造成错乱。
效果预览
示例1
示例2
1 方法一
每个表单项的name值中使用 【guid唯一标志 + 表单name】结合,进行增删,好处就是代码量少,赋值删除与校验都方便。不好的地方就是,它不像组件式表单一样,方便管理。所有的逻辑都混合在一个页面。而且拿表单值的时候,拿到的key不是单纯的name形式,而是name + guid形式。
<template>
<div>
<a-form
:form="form"
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
@submit="handleSubmit"
>
<a-row v-for="(item, index) in data" :key="item.guid">
<a-col :span="9">
<a-form-item label="名称">
<a-input
v-decorator="[`name_${item.guid}`, { rules: [{ required: true, message: '名称不能为空'}] }]"
@change="(e)=> {item.name = e.target.value}"
/>
</a-form-item>
</a-col>
<a-col :span="9">
<a-form-item label="年龄">
<a-input
v-decorator="[`age_${item.guid}`, {initialValue: 'initialValue默认值'}, { rules: [{ required: true, message: '年龄不能为空'}] }]"
@change="(e)=> item.age = e.target.value"
/>
</a-form-item>
</a-col>
<!-- 以下为操作按钮 -->
<a-col :span="2">
<a-form-item >
<a-button @click="setChildFormData(item)">默认赋值</a-button>
</a-form-item>
</a-col>
<a-col :span="2">
<a-form-item >
<a-icon type="plus" title="添加" style="font-size: 20px" @click="add(index)"/>
</a-form-item>
</a-col>
<a-col :span="2">
<a-form-item>
<a-icon v-if="index >= 1" type="minus" title="删除" style="font-size: 20px" @click="del(index)"/>
</a-form-item>
</a-col>
</a-row>
<a-form-item :wrapper-col="{ span: 12, offset: 5 }">
<a-button type="primary" html-type="submit">
Submit
</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script>
export default {
name: 'formAddDel',
data () {
return {
formLayout: 'horizontal',
form: this.$form.createForm(this),
data: [] // 表单数据
}
},
created () {
this.add()
},
computed: {
formItemLayout () {
const { formLayout } = this
return formLayout === 'horizontal'
? {
labelCol: { span: 4 },
wrapperCol: { span: 20 }
}
: {}
}
},
methods: {
/**
* 表单新增条目
* @method add
*/
add (index = 0) {
this.data.splice(index + 1, 0, {
guid: this.guid(),
name: undefined,
age: undefined
})
},
/**
* 表单删除条目
* @method add
*/
del (index) {
this.data.splice(index, 1)
},
/**
* 设置表单值
* @method setChildFormData
*/
setChildFormData (item) {
this.form.setFieldsValue({
[`age_${item.guid}`]: '设置的age~~',
[`name_${item.guid}`]: '设置的name~~'
})
},
/**
* 唯一值guid计算
* @method guid
*/
guid () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
var r = Math.random() * 16 || 0
let v = c === 'x' ? r : (r & 0x3 || 0x8)
return v.toString(16)
})
},
/**
* 表单submit
* @method handleSubmit
*/
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err) => {
console.log('示例1', this.data) // 打印值
if (!err) {
// 执行操作
}
})
}
}
}
</script>
2方法2
使用组件形式引用,结合guid + index,好处就是便于管理,不好的地方就是,拿值与赋值和表单校验都要稍微麻烦一点。但是好处就是,便于管理与复用。子表单项的逻辑与父表单的逻辑分开,拿取的子表单的值都是单纯的name。复杂逻辑的表单更加适用于这个方法。
2.1
子组件 formAddDel2_Child.vue
<template>
<div>
<a-form
:form="form"
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
>
<a-row>
<a-col :span="12">
<a-form-item label="名称">
<a-input
v-decorator="[`name`, { rules: [{ required: true, message: '名称不能为空'}] }]"
@change="(e)=> {data.name = e.target.value}"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="年龄">
<a-input
v-decorator="[`age`, { rules: [{ required: true, message: '年龄不能为空'}] }]"
@change="(e)=> data.age = e.target.value"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</div>
</template>
<script>
export default {
name: 'formAddDel',
props: {
data: {
type: Object,
default: () => {}
},
index: {
type: [Number, String],
default: () => '1'
}
},
data () {
return {
formLayout: 'horizontal',
form: this.$form.createForm(this)
}
},
watch: {
// // 监听数据并直接赋值
// data (newV, oldV) {
// this.form.setFieldsValue({
// ...newV
// })
// }
},
computed: {
formItemLayout () {
const { formLayout } = this
return formLayout === 'horizontal'
? {
labelCol: { span: 4 },
wrapperCol: { span: 20 }
}
: {}
}
},
methods: {
/**
* 唯一值guid计算
* @method guid
*/
guid () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
var r = Math.random() * 16 || 0
let v = c === 'x' ? r : (r & 0x3 || 0x8)
return v.toString(16)
})
},
/**
* 提供给父组件校验表单
* @method validate
* @returns {Boolean}
*/
validate (e) {
this.form.validateFields((err, values) => {
if (err) {
return false
} else {
return true
}
})
},
/**
* 提供给父组件拿取表单值
* @method getData
*/
getData () {
return this.form.getFieldsValue()
// 如果你需要基于这个index来标记哪一个数据
// const result = {}
// const formData = this.form.getFieldsValue() || []
// Object.keys(formData).forEach(item => {
// result[`${item}${this.index}`] = formData[item]
// })
// return result
// return this.form.getFieldsValue()
},
/**
* 提供给父组件设置表单值
* @method setFormData
*/
setFormData (data) {
if (data && typeof data === 'object' && data instanceof Object) {
this.form.setFieldsValue(data)
// 如果你想更谨慎些,你可以这样
// const fields = Object.keys(this.form.getFieldsValue())
// const formData = {}
// Object.keys(data).forEach(it => {
// if (fields.includes(it)) {
// formData[it] = data[it]
// }
// })
// this.form.setFieldsValue(formData)
}
}
}
}
</script>
父组件
<template>
<div>
<a-form
:form="form"
:label-col="formItemLayout.labelCol"
:wrapper-col="formItemLayout.wrapperCol"
@submit="handleSubmit"
>
<a-row v-for="(item, index) in data" :key="item.guid">
<a-col :span="18">
<formAddDelChild :data="item" :ref="item.guid" :index="index"></formAddDelChild>
</a-col>
<a-col :span="2">
<a-form-item >
<a-button @click="setChildFormData(item)">默认赋值</a-button>
</a-form-item>
</a-col>
<a-col :span="2">
<a-form-item >
<a-icon type="plus" title="添加" style="font-size: 20px" @click="add(index)"/>
</a-form-item>
</a-col>
<a-col :span="2">
<a-form-item>
<a-icon v-if="index >= 1" type="minus" title="删除" style="font-size: 20px" @click="del(index)"/>
</a-form-item>
</a-col>
</a-row>
<a-form-item :wrapper-col="{ span: 12, offset: 5 }">
<a-button type="primary" html-type="submit">
Submit
</a-button>
</a-form-item>
</a-form>
</div>
</template>
<script>
import formAddDelChild from './formAddDel2_Child'
export default {
name: 'formAddDel',
components: {formAddDelChild},
data () {
return {
formLayout: 'horizontal',
form: this.$form.createForm(this),
data: []
}
},
created () {
this.add()
},
computed: {
formItemLayout () {
const { formLayout } = this
return formLayout === 'horizontal'
? {
labelCol: { span: 4 },
wrapperCol: { span: 20 }
}
: {}
}
},
methods: {
/**
* 表单新增条目
* @method add
*/
add (index = 0) {
this.data.splice(index + 1, 0, {
guid: this.guid(),
name: undefined,
age: undefined
})
},
/**
* 表单删除条目
* @method add
*/
del (index) {
this.data.splice(index, 1)
},
/**
* 唯一值guid计算
* @method guid
*/
guid () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
var r = Math.random() * 16 || 0
let v = c === 'x' ? r : (r & 0x3 || 0x8)
return v.toString(16)
})
},
/**
* 设置组件的表单值
* @method setChildFormData
*/
setChildFormData (item) {
// this?.$refs?.[item.guid]?.[0]?.setFormData 这种写法我的破浏览器不支持,哎~
if (this.$refs[item.guid] && this.$refs[item.guid][0] && this.$refs[item.guid][0].setFormData) {
const forData = {
name: '默认名称999',
age: '默认age~~~'
}
this.$refs[item.guid][0].setFormData(forData)
}
// 你也可以直接拿取子组件的form进行赋值操作,但是不建议,如下
// if (this.$refs[item.guid] && this.$refs[item.guid][0] && this.$refs[item.guid][0].form) {
// const forData = {
// name: '默认名称999',
// age: '默认age~~~'
// }
// this.$refs[item.guid][0].form.setFieldsValues(forData)
// }
},
/**
* 表单submit
* @method handleSubmit
*
*/
handleSubmit (e) {
e.preventDefault()
let flag = false
// 通过子组件提供的getData,可以直接循环表单项拿值
const result = []
this.data.forEach(item => {
if (item.guid && this.$refs[item.guid] && this.$refs[item.guid][0]) {
if (this.$refs[item.guid][0].validate()) {
flag = true
}
result.push(this.$refs[item.guid][0].getData())
}
})
// 校验父组件的表单
this.form.validateFields((err, values) => {
if (!err && !flag) {
flag = true
}
})
console.log('示例2', result)
if (flag) {
// 执行操作
}
}
}
}
</script>