最近做 vue2 项目,用到了 element-ui,其中有个需求是实现动态表单,点击按钮新增行/删除行,这里记录一下实现过程。
其实本质上就是操作数组,新增行就是往数组里 push 一个对象,删除行就是从数组里删除一项。
大致效果如下:
使用方式
这里外层是一个el-form
,增删行是其中一个el-form-item
,对应formData
的一个字段,代码如下所示:
html
<el-form :model="formData">
<el-form-item label="最低版本号">
<LimitVersion v-model="formData.limitVersion" />
</el-form-item>
<!-- 其他el-form-item -->
</el-form>
LimitVersion
的组件实现
LimitVersion
的组件逻辑:
value
:父组件传入的值,即formData.limitVersion
innerValue
:组件内部的值,这个值是比较特殊的,是一个二维数组,每一项是一个数组,比如[[1, '1.1.1'], [2, '2.2.2']]
,使用computed
属性get
来同步父组件传入的值,使用set
来同步组件内部的值到父组件,由此实现双向绑定deviceOptions
:是下拉选项,这里我使用的是el-cascader
,这个选项是异步获取的,所以需要在created
钩子中获取,结构是[{ value: 1, label: 'xdf-n1', children: [{ value: 1.1.1, label: '1.1.1' }] }]
,这里我使用了两个接口,一个是获取设备列表,一个是获取设备对应的版本号列表,因为设备列表不是很多,所以我使用了Promise.all
来获取所有设备对应的版本号列表,这样就不用每次选择设备时都去请求一次版本号列表。removeRow
:删除行,传入index
,删除innerValue
中对应的项addRow
:新增行,往innerValue
中 push 一个空数组
最最重点其实就是v-for
循环innerValue
,每一项是一个el-cascader
,然后每一项后面有一个删除按钮,每个el-cascader
的值是innerValue[index]
,这样就实现了每一行的双向绑定。
以下是LimitVersion
的组件实现:
vue
<template>
<div class="limit-box">
<div v-for="(item, index) in innerValue" :key="index">
<el-cascader
clearable
v-model="innerValue[index]"
:options="deviceOptions"
></el-cascader>
<el-button
icon="el-icon-close"
type="text danger"
class="del-btn"
v-if="innerValue.length > 1"
@click="removeRow($event, index)"
></el-button>
</div>
<el-button class="add-btn" plain type="primary" @click="addRow"
>添加</el-button
>
</div>
</template>
<script>
import {
getDeviceDicFormat,
getRomUpgradeVersionSelectFormat,
} from '@/service.js';
export default {
name: 'RomLimit',
props: {
value: {
type: Array,
default: () => [],
},
},
created() {
this.getVersionOptions();
},
data() {
return {
deviceOptions: [],
};
},
computed: {
innerValue: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val);
},
},
},
methods: {
async getVersionOptions() {
const deviceOptions = await getDeviceDicFormat();
const detailedOptions = await Promise.all(
deviceOptions.map(({ value }) =>
getRomUpgradeVersionSelectFormat({ platformType: value })
)
);
this.deviceOptions = deviceOptions.map((item, index) => {
return {
...item,
children: detailedOptions[index],
};
});
},
removeRow(e, index) {
// 防止触发el-form的submit事件
e.preventDefault();
this.innerValue.splice(index, 1);
},
addRow(e) {
// 防止触发el-form的submit事件
e.preventDefault();
this.innerValue.push([]);
},
},
};
</script>
<style scoped lang="less">
.limit-box {
.add-btn {
margin-top: 10px;
}
.del-btn {
color: rgb(247, 137, 137);
font-size: 18px;
}
.del-btn:focus,
.del-btn:hover {
color: rgba(247, 137, 137, 0.8);
}
}
</style>
当然上面的一行比较简单,只是数组,如果复杂些是个对象,多个数据,那么就需要在addRow
中 push 一个对象,然后在removeRow
中删除对象。
对象的增删
使用方式仍然没有发生变化,假设formData.limitVersion
,是一个对象数组,[{ id: 1, version: '1.1.1' }, { id: 2, version: '2.2.2' }]
。
效果如下:
其实实现仍然很简单,对象的话,使用el-form
好了。el-form
的model
是一个对象,是 innerValue[index],这样就实现了每一行的双向绑定,内部的每项是 innerValue[index].xxx。
html
<div class="limit-box">
<div style="display:flex;" v-for="(item, index) in innerValue" :key="index">
<el-form
style="display:flex;margin-bottom: 10px;"
:inline="true"
:model="innerValue[index]"
>
<el-form-item label="类型">
<el-input v-model="innerValue[index].id" clearable></el-input>
</el-form-item>
<el-form-item label="版本">
<el-input v-model="innerValue[index].version" clearable></el-input>
</el-form-item>
</el-form>
<!-- <el-button icon="el-icon-close" .... -->
</div>
<!-- add -->
</div>
<!--
methods的addRow需要改成这样
addRow(e) {
e.preventDefault()
this.innerValue.push({ id: null, version: null })
},
-->
注意
这边的需求是,至少留一项,所以删除按钮的显示条件是innerValue.length > 1
,如果不需要这个条件,可以直接去掉。注意默认值,如果默认留一项,数组的话就是[[]]
,对象的话就是[{ id: null, version: null }]
。
总结
其实实现动态表单,就是操作数组,新增行就是往数组里 push 一个对象,删除行就是从数组里删除一项。对象的话,使用el-form
好了,el-form
的model
是一个对象,是 innerValue[index],这样就实现了每一行的双向绑定,内部的每项是 innerValue[index].xxx。数组的话,根据情况使用不同组件。
这里是一个简单的实现,如果有更复杂的需求,可以根据实际情况来调整。