el-table动态合并单元格 动态新增和动态删除

最近写了一个项目 要动态合并单元格 搞得很头疼 在网上查找了资料 借鉴一部分 自己把东西改了一部分 有一块地方不会改问了同事,同事教了我 感谢他的帮助 正好我也分享一下  发布这篇文章 也是记录一下以后再遇到这个问题再回顾 好了不墨迹了 上代码!!!!

<template>
  <div>
    <!-- input输入框输入的值都是字符串类型,例如你输入了1实际上是字符串1而不是数字1,因此需要一个类型转换 -->
    <el-table
      :data="tableData"
      :span-method="objectSpanMethod"
      border
      ref="table"
    >
      <el-table-column prop="" label="">
        <!-- <template slot-scope="scope">
          <el-input
            v-model.number="scope.row.outlock"
            @input="changeOutLook(scope.$index, scope.row)"
            >{{ scope.row.id}}
          </el-input>
        </template> -->
      </el-table-column>
      <el-table-column prop="" label="">
        <!-- <template slot-scope="scope">
          <el-input
            v-model.number="scope.row.outLook"
            @input="changeOutLook(scope.$index, scope.row)"
            >{{ scope.row.id}}
          </el-input>
        </template> -->
      </el-table-column>

      <el-table-column prop="id" label="" width="180">
        <template slot-scope="scope">
          <el-input v-model="scope.row.id">{{ scope.row.id }} </el-input>
        </template>
      </el-table-column>

      <el-table-column prop="name" label="" width="180">
        <template slot-scope="scope">
          <el-input v-model="scope.row.name">{{ scope.row.name }} </el-input>
        </template>
      </el-table-column>

      <el-table-column prop="ranktime" label="" width="250">
        <template slot-scope="scope">
          <el-date-picker
            v-model="scope.row.ranktime"
            type="date"
            placeholder="选择日期"
          >
          </el-date-picker>
        </template>
      </el-table-column>

      <el-table-column label="" align="center" prop="">
        <el-table-column label="" align="center" prop="">
          <template slot-scope="scope">
            <el-input v-model="scope.row.relevantpeople"
              >{{ scope.row.relevantpeople }}
            </el-input>
          </template>
        </el-table-column>
        <el-table-column label="" align="center" prop="">
          <template slot-scope="scope">
            <el-input v-model="scope.row.peoplejob"
              >{{ scope.row.peoplejob }}
            </el-input>
          </template>
        </el-table-column>
        <el-table-column
          label=""
          align="center"
          prop="completetime"
          width="250"
        >
          <template slot-scope="scope">
            <el-date-picker
              v-model="scope.row."
              type="date"
              placeholder="选择日期"
            >
            </el-date-picker>
          </template>
        </el-table-column>
      </el-table-column>

      <!-- P2 -->

      <el-table-column label="操作" width="180" align="center">
        <el-table-column prop="" width="90" label="新增" align="center">
          <template slot-scope="scope">
            <el-button
              size="mini"
              @click="handleAdd(scope.$index, scope.row)"
              icon="el-icon-plus"
              >新增</el-button
            >
          </template>
        </el-table-column>
        <el-table-column prop="" width="90" label="删除" align="center">
          <template slot-scope="scope">
            <el-button
              size="mini"
              @click="handleDelete(scope.$index, scope.row)"
              icon="el-icon-minus"
              >删除</el-button
            >
          </template>
        </el-table-column>
      </el-table-column>
    </el-table>
  </div>
</template>
<script>
import throttle from "lodash/throttle";
export default {
  name: "TestTable",
  data() {
    return {
      tableData: [
        {
          PERSON_TYPE: "",
          outLook: "",
          id: 1,
          name: "test1",
          ranktime: "",
          completetime: "",
          relevantpeople: "",
          peoplejob: "",
          gender: 2,
        },

        {
          PERSON_TYPE: "",
          outLook: "",
          id: 2,
          completetime: "",
          peoplejob: "",
          name: "test2",
          ranktime: "",
          relevantpeople: "",
          gender: 2,
        },
        {
          PERSON_TYPE: "",
          outLook: "",
          peoplejob: "",
          id: 3,
          completetime: "",
          name: "test3",
          relevantpeople: "",
          ranktime: "",
          gender: 2,
        },
        {
          PERSON_TYPE: "",
          outLook: "",
          id: 4,
          peoplejob: "",
          completetime: "",
          name: "test3",
          relevantpeople: "",
          ranktime: "",
          gender: 2,
        },
      ],

      // 相同行数名的索引的值
      mergerRowIndex: [],
      // 第一列的的索引
      mergerFirRowIndex: [],
      //第0列的索引
      mergerFirRowIndexzero: [],
      rowspanPERSON: [],
      rowspanoutLook: [],
    };
  },
  methods: {
    changeOutLook: throttle(function (index, row) {
      if (index < this.mergerFirRowIndex[0]) {
        for (var i = 0; i < this.mergerFirRowIndex[0]; i++) {
          this.tableData[i].outLook = row.outLook;
        }
        return;
      }
      if (index < this.mergerFirRowIndexzero[0]) {
        for (var i = 0; i < this.mergerFirRowIndexzero[0]; i++) {
          this.tableData[i].PERSON_TYPE = row.PERSON_TYPE;
        }
        return;
      }
      var start_index = this.mergerFirRowIndex.indexOf(row);
      for (
        var i = this.mergerFirRowIndex[start_index];
        i < this.mergerFirRowIndex[start_index + 1];
        i++
      ) {
        this.tableData[i].outLook = row.outLook;
      }

      for (
        var i = this.mergerFirRowIndex[start_index];
        i < this.mergerFirRowIndex[start_index + 1];
        i++
      ) {
        this.tableData[i].PERSON_TYPE = row.PERSON_TYPE;
      }
    }, 1000),
    /*
    合并后的表格,有一个坑,就是,当用户修改合并的那一行的数据,只有第一行会被修改,跟他合并的其他行数据都不会修改
    因此我们需要自己定义一个函数统一将他们修改
  这里需要补充一个知识点:节流与防抖,这里有个资料讲的很详情大家可以去参考一下
  https://muyiy.cn/blog/7/7.2.html#underscore-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90
  */
    changeId: throttle(function (index, row) {
      if (index < this.mergerRowIndex[0]) {
        for (var i = 0; i < this.mergerRowIndex[0]; i++) {
          this.tableData[i].id = row.id;
        }
        return; //这个return的作用是当只改变0到this.mergerRowIndex[0]的数据后,后续的不用改那么就中断函数,提升性能
      }
      //如果输入的值是大于this.mergerRowIndex的第一项的,因此要记录具体操作的是那一行的数据,然后改变它直到下一行的数据
      var start_index = this.mergerRowIndex.indexOf(index);
      for (
        var i = this.mergerRowIndex[start_index];
        i < this.mergerRowIndex[start_index + 1];
        i++
      ) {
        this.tableData[i].id = row.id;
      }

      console.log(this.tableData);
    }, 1000),

    //  提交表格数据,判断关键字段有无重复,这里用id做判断还有排名做举例
    submintTable() {
      var idArr = [];
      // 判断相同id下其对象中是否存在rank重复情况的标志
      var uniqueRankFlag = false;
      this.mergerRowIndex.forEach((ele, index) => {
        // 作用是将相同id的对象压入到一个数组中,
        var arr = _.filter(this.tableData, {
          id: this.tableData[this.mergerRowIndex[index] - 1].id,
        });
        console.log(arr);
        // 这一步的操作是,判断拥有相同id的对象,其rank是否存在重复的情况
        var uniqueRank = arr.every((ele) => {
          return _.filter(arr, { rank: ele.rank }).length == 1;
        });
        console.log(uniqueRank);
        // 如果uniqueRank == false说明,同一id下的对象中存在rank相同的
        if (uniqueRank == false) uniqueRankFlag = true;
        /*
      有一点需要注意
      forEach 和 map
      区别
      forEach 执行后返回 undefined
      map 执行后返回新数组
      共同点
      只能遍历数组并参数都一样
      不改变原函数(引用类型除外)
      无法中断循环;return 只是结束本地循环,进入下一次循环
      break 或 continue 都将会报错
      */

        //这里我们将相同id的数组,取出每一项,放入到一个新数组中,然后用es6 set方法去重后判断
        //原来的数组和去重后的数组长度是否一致,如果一致说明,tabledata中的id没有重复反之
        idArr.push(arr[0].id);
      });

      //判断id是不是唯一,定义一个数组去重函数,利用es6 set方法去重,然后再比较与之前的长度是否相同,该方法会将数组中重复的给去除,返回不重复的数组
      function uniqueId(arr) {
        return Array.from(new Set(arr));
      }

      var newIdArr = uniqueId(idArr);
      console.log(newIdArr);
      console.log(idArr);
      if (newIdArr.length != idArr.length) {
        alert("该数据表格存在同名id");
        return;
      }

      // 同一个id下,判断rank是否存在相同
      if (uniqueRankFlag) {
        alert("同一个id下,存在rank相同的数据,请查看");
        return;
      }
    },
    //  一键勾选左边的单项框或者右边的单项框
    changePer() {
      console.log(this.radio);
      this.tableData.forEach((ele) => (ele.gender = this.radio));
      //有些人希望头部的按钮点击了不要亮,就可以让this.radio = null 来解决这个事情
      this.radio = null;
    },

    //表格增加多一个对象
    addObecjt() {
      console.log(this.tableData);
      for (let i = 0; i < this.tableData.length; i++) {
        if (this.tableData[i].id == "" || this.tableData[i].outLook == "") {
          /*
         这里的id不能为空,因为如果存在俩个及以上为空,会合并函数,所以要确保合并的项有了属性后才能让用户再增加多一个数据
        */
          alert("表格id和outLook不能为空");
          return;
        }
      }
      this.tableData.push({
        PERSON_TYPE: "",
        outLook: "",
        id: "",
        name: null,
        rank: null,
        gender: 2,
      });
      //增加完数据后,更新一次合并的数据详情
      this.getMergerRowIndex();
    },

    //表格减少一个对象,直接删除表格末尾的数据即可
    cutObject() {
      this.tableData.pop();
      this.getMergerRowIndex();
    },

    //合并格函数
    objectSpanMethod({ row, column, rowIndex, columnIndex }) {
      if (columnIndex === 0) {
        return {
          rowspan: this.rowspanPERSON[rowIndex], // tabledata数组rowIndex下标中对应rowspanPERSON数组中的值大于0,表示这个单元格要向下合并,值多少表示合并多少单元格
          colspan: this.rowspanPERSON[rowIndex] ? 1 : 0, // tabledata数组rowIndex下标中对应rowspanPERSON数组中的值为0,表示这个单元格被合并了
        };
      }
      if (columnIndex === 1) {
        return {
          rowspan: this.rowspanoutLook[rowIndex], // tabledata数组rowIndex下标中对应rowspanoutLook数组中的值大于0,表示这个单元格要向下合并,值多少表示合并多少单元格
          colspan: this.rowspanoutLook[rowIndex] ? 1 : 0, // tabledata数组rowIndex下标中对应rowspanoutLook数组中的值为0,表示这个单元格被合并了
        };
      }
    },
    // 增加一行
    handleAdd(index, row) {
      console.log(this.tableData);
      for (let i = 0; i < this.tableData.length; i++) {
        if (this.tableData[i].id == "" || this.tableData[i].outLook == "") {
          /*
         这里的id不能为空,因为如果存在俩个及以上为空,会合并函数,所以要确保合并的项有了属性后才能让用户再增加多一个数据
        */
          console.log("表格id和outLook不能为空");
          return;
        }
      }
      for (var i = 0; i < this.mergerFirRowIndex.length; i++) {
        if (index < this.mergerFirRowIndex[i]) {
          for (var x = i; x < this.mergerFirRowIndex.length; x++) {
            this.mergerFirRowIndex[x] += 1;
          }
          break;
        }
      }
      // 合并的行数往后推一;例如这里的是 2 3 5 如果index是3 那么就让其变成2 4 6,
      //并且在其后面添加一个数据,可以使用splice(xx,0,{}) 这个方法能在指定位置加数据
      for (var i = 0; i < this.mergerRowIndex.length; i++) {
        if (index < this.mergerRowIndex[i]) {
          for (var x = i; x < this.mergerRowIndex.length; x++) {
            this.mergerRowIndex[x] += 1;
          }
          // this.mergeRowindexFirst += 1;
          this.tableData.splice(index + 1, 0, {
            PERSON_TYPE: row.PERSON_TYPE,
            outLook: row.outLook,
            name: "",
            id: row.id,
            rank: null,
          });
          /* 这里为什么要加break:因为要中断循环,循环的目的是找到其用户点击的是哪一行,然后那一行之后的所有数据
          都加一,找到了之后即可以直接跳出循环了,否则其后面的所有序列都加一,放在外第439行玩什么不行?
          因为放在439行,会导致最外层的for循环只执行一次,就被中断了,不能执行后续的判断
          */
          break;
        }
      }
      this.getRowspan(this.tableData);
      console.log("新增:", this.tableData);
      //   console.log(this.mergerRowIndex);
      //   console.log(this.mergerFirRowIndex)
    },

    // 减少一行
    handleDelete(index, row) {
      console.log("sub");
      for (var i = 0; i < this.mergerFirRowIndex.length; i++) {
        if (index < this.mergerFirRowIndex[i]) {
          for (var x = i; x < this.mergerFirRowIndex.length; x++) {
            this.mergerFirRowIndex[x] -= 1;
          }
          if (
            this.mergerFirRowIndex[x] == 0 ||
            this.mergerFirRowIndex[x] == this.mergerFirRowIndex[x - 1]
          ) {
            this.mergerFirRowIndex.splice(x, 1);
          }
        }
        this.tableData.splice(index, 1);
        break;
      }

      for (var i = 0; i < this.mergerRowIndex.length; i++) {
        if (index < this.mergerRowIndex[i]) {
          for (var x = i; x < this.mergerRowIndex.length; x++) {
            this.mergerRowIndex[x] -= 1;
            /*
             这里做判断是:当点击某一个行数时,后续的索引都要减一,但是可能存在一个数组存在俩个相同的数
             这就会导致错位了,因此要把这个重复的数给去除 即this.mergerRowIndex[x] == this.mergerRowIndex[x - 1]
             而:this.mergerRowIndex[x] == 0 其主要作用是当用户重复删除index=0的那一行时,就会导致
             合并的行数都不断的减一,直到this.mergerRowIndex某一项变为0,如果变为0就应该把其从this.mergerRowIndex中去除
            */
            if (
              this.mergerRowIndex[x] == 0 ||
              this.mergerRowIndex[x] == this.mergerRowIndex[x - 1]
            ) {
              this.mergerRowIndex.splice(x, 1);
            }
          }
          this.tableData.splice(index, 1);
          break;
        }
      }
      this.getRowspan(this.tableData);
      console.log(this.tableData);
    },

    // 查验有无相同行数名的对象,如果知道不同行数名的位置,则记住其位置并且压入
    getMergerRowIndex() {
      // 每次调用这个函数,就需要把之前压入到函数内的值给清空
      this.mergerRowIndex = [];
      this.mergerFirRowIndex = [];
      console.log(this.tableData);
      for (let i = 1; i < this.tableData.length; i++) {
        if (this.tableData[i].id != this.tableData[i - 1].id) {
          this.mergerRowIndex.push(i);
        }
        if (this.tableData[i].outLook != this.tableData[i - 1].outLook) {
          this.mergerFirRowIndex.push(i);
        }
      }
      //  并且还要压入表格的的长度
      this.mergerRowIndex.push(this.tableData.length);
      this.mergerFirRowIndex.push(this.tableData.length);
      console.log(this.mergerRowIndex, this.mergerFirRowIndex);
      this.getRowspan(this.tableData);
    },
    getRowspan(tabledata) {
      let item = 0, // 在for循环中表示需要合并单元格的数组的下标。从0开始:表示从第一次循环开始。
        outLookitem = 0;
      this.rowspanPERSON = []; // 该数组存放,
      //table单元格是否合并,数组长度与tabledata数组长度相同
      //(该数组中:0代表对应的tbale那条数据单元格被合并, 0以上表示向下合并的单元格数量)
      this.rowspanoutLook = []; // 该数组存放,
      //table单元格是否合并,数组长度与tabledata数组长度相同
      //(该数组中:0代表对应的tbale那条数据单元格被合并, 0以上表示向下合并的单元格数量)
      for (let index = 0; index < tabledata.length; index++) {
        if (index === 0) {
          // 第一次循环, 单元格不允许被合并,所以赋值1,这是table中的第一行
          this.rowspanPERSON.push(1);
          this.rowspanoutLook.push(1);
        } else {
          // 从第二行开始,判断tabledata[index]数据与上一条数据中字段PERSON_TYPE值是否相同
          if (
            tabledata[index].PERSON_TYPE === tabledata[index - 1].PERSON_TYPE
          ) {
            // 如果相同,该单元格要被合并,rowspanPERSON中item下标的值加一(item表示相同字段值的第一出现)
            this.rowspanPERSON[item] += 1;
             // rowspanPERSON数组新增一个值为0的元素,表示,这个单元格被合并了。
            this.rowspanPERSON.push(0);
          } else {
             // 不相同,表示该单元格不应被合并,rowspanPERSON数组新增1,该单元格被保留
            this.rowspanPERSON.push(1);
             // item被重新赋值,表示该单元格的值在for循环中第一次出现
            item = index;
          }
          if (tabledata[index].outLook === tabledata[index - 1].outLook) {
            this.rowspanoutLook[outLookitem] += 1;
            this.rowspanoutLook.push(0);
          } else {
            this.rowspanoutLook.push(1);
            outLookitem = index;
          }
        }
      }
    },
  },

  created() {
    this.getMergerRowIndex();
  },
};
</script>

实现的效果图,我就不展现了,大概就是这样的一个逻辑,希望能帮助到大家,写这篇文章也是以后当自己不会了可以翻下再看看

加油!!!

eg:一个很笨又热爱学习的人

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值