最近做 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.limitVersioninnerValue:组件内部的值,这个值是比较特殊的,是一个二维数组,每一项是一个数组,比如[[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。数组的话,根据情况使用不同组件。
这里是一个简单的实现,如果有更复杂的需求,可以根据实际情况来调整。