Table表格合并

460 阅读1分钟

这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

picture

vue+elementUI 中的 Table 表格的合并功能,可配置,使用方便。

1、合并前后效果图

beforeMerge.png

afterMerge.png

2、代码实现

<template>
  <div class="hello">
    {{msg}}
    <div>
      <el-table
        :data="tableData"
        :span-method="arraySpanMethod"
        border
        style="width: 1000px;margin:30px auto;"
        height="550"
      >
        <el-table-column
          v-for="(item, index) in tableTitleData"
          :key="index"
          :prop="item.prop"
          :label="item.label"
          :min-width="item.minWidth"
        >
          <template slot-scope="scope">
            <span>{{scope.row[item.prop]}}</span>
          </template>
        </el-table-column>
      </el-table>
    </div>
  </div>
</template>

<script>
  import { tableTitleData, tableData } from './js/options';
  export default {
    name: 'mergeTable',
    data() {
      return {
        msg: '表格合并',
        tableData,
        tableTitleData
      };
    },
    methods: {
      // ====================================== 合并多列单元格逻辑
      arraySpanMethod({ row, column, rowIndex, columnIndex }) {
        // 合并单元格
        /**
         * desc 该方法会触发n次,n是表格data的length*表头data的length;
         * @param  {String} row         [ 当前遍历到的行数据 (后台接口返回的) ]
         * @param  {Object} column      [ 当前遍历到的列数据 (elemen表格对象内置的) ]
         * @param  {Number} rowIndex    [ 当前遍历到的行下标 ]
         * @param  {Number} columnIndex [ 当前遍历到的列下标 ]
         * @return {Object} 包含合并的行和列信息
         */
        //  ================================================================ 分割线
        if (!(this.realTitleData || []).length) {
          this.realTitleData = []; // 这个数组是带children的表头data展开children后的数组
          this.tableTitleData.forEach(val => {
            // 展开存在children的titleData
            if (val.children && (val.children || []).length) {
              val.children.forEach(child => {
                this.realTitleData.push(child);
              });
            } else {
              this.realTitleData.push(val);
            }
          });
        }
        if (!(this.mergeTabelArr || []).length) {
          // 只运行一次,获得要合并的信息数组
          const keywords = ['areaName']; // (这个数组要根据不同表格来配置!!!) 一些prop名的集合,这些porp名代表的字段 在每个做过合并的行数据集合里 它的的值 都是唯一的(比如区域、片区、中心名称之类的)。这个集合用于确保合并信息的唯一性,确保表格不会错乱
          this.mergeTabelArr = this.getMergeTabelArr(
            this.tableData,
            this.realTitleData,
            keywords,
            [0, 1]
          );
          // console.log('完整的合并表格的信息集合', this.mergeTabelArr);
        }
        // if (columnIndex > 0) { // 除掉序号那一列(这里用于自定义过滤某列),都要合并
        const _rowArr = (
          this.mergeTabelArr.find(n => n.key === column.property) || {}
        ).connect; // connect数组
        if (_rowArr) {
          const _row = _rowArr[rowIndex];
          return {
            rowspan: _row, // 0代表删除这列,1代表展示这列,>1代表合并列
            colspan: _row > 0 ? 1 : 0 // 0代表删除这列,1代表展示这列,>1代表合并列
          };
        }
        // }
      },
      getMergeTabelArr(
        data = [],
        titleData = [],
        keywords = [],
        colIndexs = []
      ) {
        // 获取合并表格的信息集合
        /**
         * desc 该方法为最终组合‘合并表格的信息集合’的方法
         * @param  {Array}  data                   [ 表格的data ]
         * @param  {Array}  titleData              [ 表格的表头的data ]
         * @param  {Array}  keywords  ['1','a'...] [ 一些prop名的集合,这些porp名代表的字段 在每个做过合并的行数据集合里 它的的值 都是唯一的(比如区域、片区、中心名称之类的)。这个集合用于确保合并信息的唯一性,确保表格不会错乱 ]
         * @param  {Array}  colIndexs [0,1,-2,...] [ 合并规则数组,这个数组里所有的负数值代表不合并的列的下标,正数代表合并的列的下标 ]
         * @return {Array} arr  示例:
         * [
         *   {
         *     key: 'nextSite', // 对应列的prop名
         *     connect: [2, 0, 2, 0, 1]  // connect数组
         *   },
         *   ...
         * ];
         */
        //  ================================================================ 分割线

        const columnArr = colIndexs.length
          ? titleData.filter((n, i) => {
              const index = colIndexs.findIndex(realV => {
                if (realV < 0) return false;
                const v = Math.abs(realV);
                return i === v;
              });
              return index !== -1;
            })
          : []; // 按传递进来的规则过滤

        this.oldMergeTabel = []; // 给this添加oldMergeTabel属性,防止后面报错
        const arr = columnArr.map(v1 => {
          const obj = this.getConnectArr(
            data,
            v1.prop,
            keywords,
            this.oldMergeTabel
          );
          this.oldMergeTabel = obj.connect; // 保存前一条的connect数组信息
          return obj;
        });
        return arr;
      },
      getConnectArr(data, key, keywords, oldMergeTabel) {
        // 获取connect数组
        /**
         * desc 该方法会触发n次,n是过滤后的,要合并的表头data的length;
         * @param  {Array}  data          [ 表格的data数据 ]
         * @param  {String} key           [ 要合并的表头的prop名 ]
         * @param  {Array}  keywords      [ 一些prop名的集合,这些porp名代表的字段 在每个做过合并的行数据集合里 它的的值 都是唯一的(比如区域、片区、中心名称之类的)。这个集合用于确保合并信息的唯一性,确保表格不会错乱 ]
         * @param  {Object} oldMergeTabel [ 前一条组合好的connect数组信息,用于确保表格不会错乱 ]
         * @return {Object} 包含合并的行和列信息(其实就是 ‘合并表格的信息数组’ 的一个成员,看上面getMergeTabelArr方法的示例)
         */
        //  ================================================================ 分割线

        const oldArr = []; // 保存上一个数组
        let oldObj = {}; // 控制只跟上一个元素做对比,不跟整个数组对比

        // 正确的代码
        data.forEach((v, i) => {
          let newObj;
          let newKey = ''; // 保证唯一性的key
          keywords.forEach(k => {
            newKey += `${v[k]}-`;
          });
          newKey += v[key];

          const oldIndex = oldArr.findIndex(n => n.key === newKey); // 保存的数组里是否存在与当前表头数据一样的元素(注意,findIndex找到的是第一个匹配的元素的下标)
          if (oldIndex !== -1 && oldObj.key === newKey) {
            newObj = {
              key: newKey,
              value: 0
            };
            oldArr[oldIndex].value++; // 找到重复项的第一项,值加1
          } else {
            newObj = {
              key: newKey,
              value: 1
            };
          }
          oldArr.push(newObj);
          oldObj = newObj; // 保存当前数据,用作与下一条数据的对比,看是否相同,相同把下一条数据的占用列置成0,这样表格不会错乱
        });
        const connect = oldArr.map(n => n.value);

        if (oldMergeTabel.length) {
          // 控制数组合并。如果有重复项也不能跨越过上一条的合并行
          oldMergeTabel.forEach((oldVal, i) => {
            const newVal = connect[i]; // 新值
            let count = 0; // connect数组各项值的和,
            const rule1 = newVal > oldVal && oldVal > 0;
            const rule2 = newVal < oldVal && newVal === 0;
            if (rule1 || rule2) {
              connect[i] = oldVal;
              connect.forEach(v => {
                count += v;
              }); // 赋值后计算connect数组各项值的和
              if (count > data.length) {
                // connect数组各项值加起来不能超过表格数据data的length,(可能有bug,以后再解决吧,哈哈哈)
                connect[i] = count - data.length;
              }
            }
          });
        }
        return {
          key: key,
          connect: connect
        };
      },
      // 查询数据
      queryData() {
        // 在工作中,一般是要调接口去查询数据,在查询之前,需要将mergeTabelArr置为空数组,然后在执行之后的操作。
        this.mergeTabelArr = [];
        // ...
      }
    }
  };
</script>
<style>
  .tableHeader {
    background: red !important;
  }
</style>

3、options.js 数据来源文件

/**
 * 表头配置
 */

const tableTitleData = [
  {
    minWidth: '100',
    prop: 'areaName',
    label: '片区'
  },
  {
    minWidth: '100',
    prop: 'managementAreaName',
    label: '省区'
  },
  {
    minWidth: '100',
    prop: 'centerName',
    label: '市区'
  }
];
const tableData = [
  {
    areaName: '东北',
    managementAreaName: '黑龙江',
    centerName: '哈尔滨市'
  },
  {
    areaName: '东北',
    managementAreaName: '黑龙江',
    centerName: '齐齐哈尔市'
  },
  {
    areaName: '东北',
    managementAreaName: '黑龙江',
    centerName: '鹤岗市'
  },
  {
    areaName: '东北',
    managementAreaName: '黑龙江',
    centerName: '牡丹江市'
  },
  {
    areaName: '东北',
    managementAreaName: '黑龙江',
    centerName: '佳木斯市'
  },
  {
    areaName: '东北',
    managementAreaName: '黑龙江',
    centerName: '绥化市'
  },
  {
    areaName: '东北',
    managementAreaName: '吉林',
    centerName: '长春市'
  },
  {
    areaName: '东北',
    managementAreaName: '吉林',
    centerName: '磐石市'
  },
  {
    areaName: '东北',
    managementAreaName: '吉林',
    centerName: '舒兰市'
  },
  {
    areaName: '华东',
    managementAreaName: '上海',
    centerName: '青浦区'
  },
  {
    areaName: '华东',
    managementAreaName: '上海',
    centerName: '徐汇区'
  },
  {
    areaName: '华东',
    managementAreaName: '上海',
    centerName: '黄浦区'
  },
  {
    areaName: '华东',
    managementAreaName: '上海',
    centerName: '浦东新区'
  },
  {
    areaName: '华东',
    managementAreaName: '上海',
    centerName: '松江区'
  },
  {
    areaName: '华东',
    managementAreaName: '上海',
    centerName: '嘉定区'
  }
];
export { tableTitleData, tableData };

4、完整 Table 合并代码 Demo

Github Demo