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

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

相关推荐
panxianren16 小时前
Bun 1.4 的 bun test --parallel 将 4 秒的测试套件缩短到约 1 秒
java·javascript
陈随易18 小时前
bm2,Node.js 与 Bun 项目部署新选择
前端·后端·程序员
可乐鸡翅yeah_19 小时前
FFmpeg 生成 HLS 独立分片 independent‑segments 参数到底有什么用
javascript·ffmpeg·音视频·safari·m3u8
福兮说19 小时前
canvas 旋转图片的六个坑:四角被裁、Math.ceil 多出 1px 黑边、JPG 角发黑、翻转方向反了
前端·javascript·图像处理·canvas
Ai-_Man20 小时前
您您这可以把Grok的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭
javascript·人工智能·ai·小程序·电脑
传人once20 小时前
多图片上传预览功能如何写
前端·css·html·jquery·html5
Eric.4621 小时前
Wan‑Animate+MiniMax-H3 本地部署 AI 漫剧流水线:8G 显存动作迁移与动态分镜工程实战
前端·人工智能·comfyui·ai漫剧
liangshanbo121521 小时前
JavaScript 异步核心主线
javascript·事件循环·异步操作
传人once1 天前
悬停旋转放大和位移效果如何写
javascript·css·动画
库拉镜像AI牛牛1 天前
短剧内容自动化生产:知漫剧工作室落地教程
大数据·服务器·前端·人工智能·语音识别