table表格动态合并

table表格动态合并

最终效果图:

table表格动态合并

一、JQ插件

参考链接:http://www.jq22.com/jquery-info9377

插件封装:

(function ($) {
  $.fn.tablesMergeCell = function (options) {
    let defaults = {
      data: {}, // 后台数据
      automatic: true, // 是否自动合并
      cols: null, // 用数组表示列的索引,从0开始,然后根据指定列来处理(合并)相同内容单元格
      rows: null, // [[3, 4, 5], [6, 7]]自定义合并的行范围
    };
    let opts = $.extend(defaults, options);

    // 数据初始化
    let init = () => {
      let title = document.createElement('tr');
      for (let item of opts.data.title) {
        let th = document.createElement('th');
        th.innerHTML = item;
        title.append(th);
      }
      $(this).append(title);

      for (let rows of opts.data.content) {
        let tr = document.createElement('tr');
        for (let item of rows) {
          let td = document.createElement('td');
          td.innerHTML = item;
          tr.append(td);
        }
        $(this).append(tr);
      }
    };

    // 如果对javascript的closure和scope概念比较清楚, 这是个插件内部使用的private方法
    let tablesMergeRows = ($table, colIndex, rowIndex) => {
      $table.data('col-content', ''); // 存放单元格内容
      $table.data('col-rowspan', 1); // 存放计算的rowspan值 默认为1
      $table.data('col-td', $()); // 存放发现的第一个与前一行比较结果不同td(jQuery封装过的), 默认一个"空"的jquery对象
      $table.data('trNum', $('tbody tr', $table).length); // 要处理表格的总行数, 用于最后一行做特殊处理时进行判断之用

      // 我们对每一行数据进行"扫描"处理 关键是定位col-td, 和其对应的rowspan
      $('tbody tr', $table).each(function (index) {
        let $tr = $(this);
        // td:eq中的colIndex即列索引
        let $td = $('td:eq(' + colIndex + ')', $tr);
        let currentContent = $td.html();
        if (opts.automatic) {
          // 内容
          // 第一次时走此分支
          // console.log($table.data('col-content'));
          if ($table.data('col-content') === '') {
            $table.data('col-content', currentContent);
            $table.data('col-td', $td);
          } else {
            // 上一行与当前行内容相同
            if ($table.data('col-content') == currentContent) {
              addRowspan(); // 上一行与当前行内容相同则col-rowspan累加, 保存新值
            } else {
              newRowspan(); // 上一行与当前行内容不同
            }
          }
        } else {
          // 指定
          if (opts.rows.length > 0) {
            if (opts.rows[0].length == undefined) {
              for (let i = 0; i < opts.rows.length; i++) {
                customRowspan(opts.rows[i], opts.rows.length);
              }
            } else {
              for (let i = 0; i < opts.rows[rowIndex].length; i++) {
                customRowspan(opts.rows[rowIndex][i], opts.rows[rowIndex].length);
              }
            }
          }
        }
        function customRowspan(val, len) {
          if (index == val) {
            if ($table.data('col-content') == '') {
              if (currentContent == '') {
                currentContent = true;
              }
              $table.data('col-content', currentContent);
              $td.attr('rowspan', len);
            } else {
              $td.hide();
            }
          }
        }
        function addRowspan() {
          let rowspan = $table.data('col-rowspan') + 1;
          $table.data('col-rowspan', rowspan);
          // 值得注意的是 如果用了$td.remove()就会对其他列的处理造成影响
          $td.hide();
          // 最后一行的情况比较特殊一点
          // 比如最后2行 td中的内容是一样的, 那么到最后一行就应该把此时的col-td里保存的td设置rowspan
          if (++index == $table.data('trNum')) {
            $table.data('col-td').attr('rowspan', $table.data('col-rowspan'));
          }
        }
        function newRowspan() {
          // col-rowspan默认为1, 如果统计出的col-rowspan没有变化, 不处理
          if ($table.data('col-rowspan') !== 1) {
            $table.data('col-td').attr('rowspan', $table.data('col-rowspan'));
          }
          // 保存第一次出现不同内容的td, 和其内容, 重置col-rowspan
          $table.data('col-td', $td);
          $table.data('col-content', $td.html());
          $table.data('col-rowspan', 1);
        }
      });
    };

    // 合并列
    let tablesMergeCols = ($table, cols) => {
      $table.data('col-content', ''); // 存放单元格内容
      $table.data('col-colspan', 1); // 存放计算的cols值 默认为1
      $table.data('col-td', $()); // 存放发现的第一个与前一行比较结果不同td(jQuery封装过的), 默认一个"空"的jquery对象
      $table.data('trNum', $('tbody tr', $table).length); // 要处理表格的总行数, 用于最后一行做特殊处理时进行判断之用
      $('tbody tr', $table).each(function (index, item) {
        for (let j = 0; j < cols.length; j++) {
          let $td = $('td:eq(' + cols[j] + ')', item);
          let currentContent = $td.html();
          if (opts.automatic) {
            // 内容
            // 第一次时走此分支
            if ($table.data('col-content') === '') {
              $table.data('col-content', currentContent);
              $table.data('col-td', $td);
            } else {
              // 上一列与当前行内容相同
              if ($table.data('col-content') == currentContent) {
                // 上一列与当前列内容相同则col-rowspan累加, 保存新值
                addColspan();
              } else {
                // 上一列与当前列内容不同
                newColspan();
              }
            }
          }

          function addColspan() {
            let colspan = $table.data('col-colspan') + 1;
            $table.data('col-colspan', colspan);
            // 值得注意的是 如果用了$td.remove()就会对其他列的处理造成影响
            $td.hide();
            // 比如最后1行 连续多列td中的内容是一样的, 那么到最后一行就应该把此时的col-td里保存的td设置colspan
            if (index++ == $table.data('trNum') && j === cols.length - 1) {
              $table.data('col-td').attr('colspan', $table.data('col-colspan'));
            }
          }

          function newColspan() {
            if ($table.data('col-colspan') !== 1) {
              $table.data('col-td').attr('colspan', $table.data('col-colspan'));
            }
            // 保存第一次出现不同内容的td, 和其内容, 重置col-rowspan
            $table.data('col-td', $td);
            $table.data('col-content', $td.html());
            $table.data('col-colspan', 1);
          }
        }
      });
    };

    // 同样是个private函数 清理内存之用
    let dispose = $table => {
      $table.removeData();
    };

    return this.each(() => {
      let cols = opts.cols,
        rows = opts.rows;
      init();
      if (rows === null) {
        for (let i = cols.length - 1; cols[i] != undefined; i--) {
          tablesMergeRows($(this), cols[i]);
        }
      } else {
        for (let i = cols.length - 1, k = opts.rows.length - 1; cols[i] !== undefined; i--, k--) {
          tablesMergeRows($(this), cols[i], k);
        }
      }
      if (cols !== null) {
        tablesMergeCols($(this), cols);
      }
      dispose($(this));
    });
  };
})(jQuery);

页面调用:

$('#table').tablesMergeCell({
    data: data.data,
    // automatic: false,
    cols: [0, 1, 2, 3],
    // rows: [3, 4],
});

二、element-ui动态合并行列

参考链接:https://www.jianshu.com/p/cd34129cbfce

element-ui通过给table传入span-method方法可以实现合并行或列,方法的参数是一个对象,里面包含当前行row、当前列column、当前行号rowIndex、当前列号columnIndex四个属性。该函数可以返回一个包含两个元素的数组,第一个元素代表rowspan,第二个元素代表colspan。 也可以返回一个键名为rowspancolspan的对象。

// data
private tableHeader: string[] = [];
private tableData: object[] = [];
private mergeConfig: any = {
    position: 0,
    rowsArr: [],
    colsArr: [],
    colsRange: [0, 3],
};
created() {
    this.init();
}
activated() {
    //
}
mounted() {
    //
}
// 初始化函数
init() {
    this.getData();
    // 挂载函数
    this.getRowsArr(this.tableData);
    this.getColsArr(this.tableData);
}
getData() {
    let title = ['系统', '楼层', '区域', '机组编号', '1号', '2号', '3号', '合计', ];
    this.tableHeader = title;
    let data = [
        ['系统1', '楼层1', '区域1', '设备1', 1, 2, 3, 4],
        ['系统1', '楼层1', '区域2', '设备2', 1, 2, 3, 4],
        ['系统1', '楼层1', '区域2', '设备3', 1, 2, 3, 4],
        ['系统1', '楼层2', '区域3', '设备4', 1, 2, 3, 4],
        ['系统1', '楼层2', '区域3', '设备5', 1, 2, 3, 4],
        ['系统1', '楼层2', '区域3', '设备6', 1, 2, 3, 4],
        ['系统1', '合计', '合计', '合计', 1, 2, 3, 4],
        ['系统2', '楼层A', '区域4', '设备7', 1, 2, 3, 4],
        ['系统2', '楼层B', '区域5', '设备8', 1, 2, 3, 4],
        ['系统2', '楼层B', '区域5', '设备9', 1, 2, 3, 4],
        ['系统2', '合计', '合计', '合计', 1, 2, 3, 4],
        ['系统3', '楼层C', '区域6', '设备10', 1, 2, 3, 4],
        ['系统3', '楼层C', '区域7', '设备11', 1, 2, 3, 4],
        ['系统3', '楼层C', '区域8', '设备12', 1, 2, 3, 4],
        ['系统3', '合计', '合计', '合计', 1, 2, 3, 4],
    ];
    let obj = new Object();
    data.forEach((item, index) = > {
        item.forEach((_item, _index) = > {
            obj[title[_index]] = _item;
        });
        this.tableData.push(obj);
        // 清空对象
        obj = new Object();
    });
}
// 处理纵向数据
getRowsArr(data: any[]) {
    let spanArr: number[] = [];
    for (let prop in data[0]) {
        for (let i = 0; i < data.length; i++) {
            if (i === 0) {
                spanArr.push(1);
                this.mergeConfig.position = 0;
            } else {
                if (data[i - 1][prop] === data[i][prop]) {
                    spanArr.push(0);
                    spanArr[this.mergeConfig.position] += 1;
                } else {
                    spanArr.push(1);
                    this.mergeConfig.position = i;
                }
            }
        }
        this.mergeConfig.rowsArr.push(spanArr);
        spanArr = [];
    }
}
// 处理横向数据
getColsArr(data: any[]) {
    let spanArr: number[] = [];
    let keyArr = Object.keys(data[0]);
    for (let item of data) {
        for (let i = this.mergeConfig.colsRange[0]; i <= this.mergeConfig.colsRange[1]; i++) {
            if (i === 0) {
                spanArr.push(1);
                this.mergeConfig.position = 0;
            } else {
                if (item[keyArr[i - 1]] === item[keyArr[i]]) {
                    spanArr.push(0);
                    spanArr[this.mergeConfig.position] += 1;
                } else {
                    spanArr.push(1);
                    this.mergeConfig.position = i;
                }
            }
        }
        this.mergeConfig.colsArr.push(spanArr);
        spanArr = [];
    }
}
// 返回合并数组
spanMethod({
    row, column, rowIndex, columnIndex
}) {
    if (this.mergeConfig.colsRange[0] <= columnIndex && columnIndex <= this.mergeConfig.colsRange[1]) {
        const _row = this.mergeConfig.rowsArr[columnIndex][rowIndex];
        const _col = this.mergeConfig.colsArr[rowIndex][columnIndex];
        return [_row, _col];
    }
}

页面调用:

<el-table :data="tableData" :span-method="spanMethod" border style=" 1000px;">
    <el-table-column v-for="(item, i) of tableHeader" :key="i" :label="item" :prop="item" align="center"></el-table-column>
</el-table>