element-ui实现动态表单点击按钮新增行/删除行

最近做 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-formmodel是一个对象,是 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-formmodel是一个对象,是 innerValue[index],这样就实现了每一行的双向绑定,内部的每项是 innerValue[index].xxx。数组的话,根据情况使用不同组件。

这里是一个简单的实现,如果有更复杂的需求,可以根据实际情况来调整。

相关推荐
三翼鸟数字化技术团队9 分钟前
Vue自定义指令最佳实践教程
前端·vue.js
Jasmin Tin Wei38 分钟前
蓝桥杯 web 学海无涯(axios、ecahrts)版本二
前端·蓝桥杯
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
猿榜1 小时前
js逆向-喜某拉雅Xm-Sign参数解密
javascript
转转技术团队1 小时前
代码变更暗藏危机?代码影响范围分析为你保驾护航
前端·javascript·node.js
Spark2381 小时前
关于vue3整合tiptap的slash菜单的ts支持
vue.js
Mintopia1 小时前
Node.js高级实战:自定义流与Pipeline的高效数据处理 ——从字母生成器到文件管道的深度解析
前端·javascript·node.js
Mintopia1 小时前
Three.js深度解析:InstancedBufferGeometry实现动态星空特效 ——高效渲染十万粒子的底层奥秘
前端·javascript·three.js
北凉温华1 小时前
强大的 Vue 标签输入组件:基于 Element Plus 的 ElTagInput 详解
前端
随笔记1 小时前
Flex布局下,label标签设置宽度依旧对不齐,完美解决(flex-shrink属性)
javascript·css·vue.js