el-table多行合并

背景

        前端统计列表,数据乱序。按日期、产品、阶段、DD项(所有header名称乱写)排序,列表如下。


示例 

日期产品阶段DDEEFFGG
20240414产品1阶段1场景1A01
场景2B01
其他A01
20240410产品1阶段1场景2B01
其他A01
20240402产品2阶段1场景3B01
场景4A01
场景5B01
场景6A01
产品1阶段2场景7B01
场景9A01
场景8B01
阶段1场景10A01
场景11B01
场景12A01
阶段1B01
场景2A01
其他B01
A01
B01
A01
B01
A01
B01
A01
B01
A01
20240401产品1阶段2场景7B01
阶段1场景1A01
场景11B01
场景12A01
场景2B01
其他A01
B01
A01
B01
A01

el-table项目合并方法

 :span-method =“objectSpanMethods”

        row行数据
        column列数据
        rowIndex 行下标
        columnIndex 列下标 

      objectSpanMethods({ row, column, rowIndex, columnIndex }) {

            // 列num_ 行num

            let key = columnIndex + "_" + rowIndex;

            if (this.tableMergeIndex[key]) {

                return this.tableMergeIndex[key];

            }

        },

合并原理:

(0,0)2,1(1,0)1,1
(1,1)1,1
(0,2)5,1(1,2)3,1
(1,5)2,1

以行为例,不合并列,相当一个二维表格, (y,x)  (0,2) 第一列 第三行  5,1 代表5行,一列,
其他
比如上表中 (0,1)  为  0,1   代表行的高度0,列为1个高度。
有个初步概念,如果我们要达到示例中的效果,我们应该怎么做。

  1. featch到的数据处理后然后按 属性'dt','p1','p2','p3' 排序 当然如果 有其他这种中文,自己考虑特殊处理,
    1. dt(日期好比较大小)
    2. p1 值固定 [1,2]
    3. p2 值固定 [1,2]
    4. p3 汉字排序,特殊 其他放在最后
  2.  把特殊的位置处理后,其他做填充, 我们特殊数组A   tableMergeIndex= []

    const key= columnIndex + "_" + rowIndex;

    tableMergeIndex[key] = [ 行数,1] 

     比如 :tableMergeIndex['0_2'] = [5,1]    ableMergeIndex['1_2'] =[3,1] 

    有了这些特殊的处理, 如果是前4列有合并 后面不需要合并

    我们需要生成一个数组 B, 前四列 所有值 [0,1] 其他所有[1,1] 在把上面合并数组A 合并到

    数组B就是我们的目标数组
  •         for (let i = 0; i < this.tableData.length; i++) {
                    for (let j = 0; j < this.mergeCols.length; j++) {
                        const key = j + "_" + i;
    
                        if (j < 4) {
                            if (tableMergeIndex[key]) {
                                newList[key] = tableMergeIndex[key];
                            } else {
                                newList[key] = [0, 1];
                            }
                        } else {
                            newList[key] = [1, 1];
                        }
                    }
                }
(0,0)2-1(1,0)1-1
(1,1)1-1
(0,2)5-1(1,2)3-1
(1,5)2-1

思路

 基于第2点考虑,怎么得到(0,2)[5,1] 
                                             (1,2)  [3,1]其实从我们数据层面就能很好划分,从示例就很好看出来,日期 -》产品-》阶段-》DD

当前 20240414日中最大的行数 是3行, 产品、阶段、DD都不能超过3。所以有层级关系。
我的思路 :
        (1)获取数据按属性升序或降序排序, 如dt-》 desc       p1 -》asc
        (2)按第1点中属性排序方式,获取每种属性出现的所有的值和具体次数,得到一个数组proptyNumList,用于合并项赋值用,数组格式如下
 [
    { "20240401": 10,
       "20240402": 22,
       "20240410": 2,
       "20240414": 2,
       "20240415": 1
    },
    {
        "1": 4,  "2": 33
    },
    { "1": 4, "2": 33
    },
    {1": 18,  "2": 19
    },
    {
  “场景7”:1,  “场景2”:1,"其他": 19,
    }
]

        (3)  遍历每种属性 生成合并项的数据

这是第一项数据合并代码。我本来想写递归的,没想好。。。上线后优化吧。

          let yNum0 = 0;
            for (
                let index0 = 0;
                index0 < propertyValuesNew[0].length;
                index0++
            ) {
                let yNum1 = yNum0;
                const name0 = propertyValuesNew[0][index0];
           
                let key0 = "0_" + yNum0;
                yNum0 += propertyValues[0][name0];
                list[key0] = [propertyValues[0][name0], 1];
            }
         
            return list;


      写在一个循环里面的。不敢展示代码,嵌套了4层。我写得真丑,
        注意:     let yNum0 = 0;
                        let yNum1 = yNum0;
                        let yNum2 = yNum1;  
        的位置 和list push的时候和内层循环的顺序。

   propertyListDg(propertyValues, propertyValuesNew) {
            const list = [];

            let yNum0 = 0;
            for (
                let index0 = 0;
                index0 < propertyValuesNew[0].length;
                index0++
            ) {
                let yNum1 = yNum0;
                const name0 = propertyValuesNew[0][index0];
                // const p0 = propertyValues[0][name0];
                for (
                    let index1 = 0;
                    index1 < propertyValuesNew[1].length;
                    index1++
                ) {
                    let yNum2 = yNum1;
                    const name1 = propertyValuesNew[1][index1];
                
                    const num = this.getObjectsWithSameValues(
                        this.tableData,
                        ["dt", "productType"],
                        [name0, name1]
                    ).length;

                    if (num > 0) {
                        let key1 = "1_" + yNum1;
                        yNum1 += num;
                        list[key1] = [num, 1];
                    }
                }

                // console.log(name0);

                // console.log(data1);
                let key0 = "0_" + yNum0;
                yNum0 += propertyValues[0][name0];
                list[key0] = [propertyValues[0][name0], 1];
            }
   
            return list;
        },

最后得到 合并数组 A,  再执行  el-table项目合并方法 中第二点 就可以了。
监听这个数据的变化 刷新table就行了  this.$refs.market.refresh();

方法汇总


其中几个用到的几个方法:

按属性排序

//属性排序数组               
 sortData: [
                ["dt", "desc"],
                ["p1", "asc"],
                ["p2", "asc"],
                ["p3", "asc"],
                ["p4", "asc"],
            ],

 调用    
 this.sortByProperties(list, ...this.sortData);

       sortByProperties(arr, ...props) {
            return arr.sort((a, b) => {
                for (let i = 0; i < props.length; i++) {
                    const prop = props[i];
                    const [key, order] = Array.isArray(prop)
                        ? prop
                        : [prop, "asc"];
                    // 获取属性值
                    const valueA = a[key];
                    const valueB = b[key];
                    // 比较值,考虑升序和降序
                    if (valueA < valueB) {
                        return order === "asc" ? -1 : 1;
                    }
                    if (valueA > valueB) {
                        return order === "asc" ? 1 : -1;
                    }
                    // 如果相等,比较下一个属性
                }
                // 所有属性都相等
                return 0;
            });
        },

获取属性出现的次数

        getPropertyValues(array, propertys) {
            const list = [];
            for (let i = 0; i < propertys.length; i++) {
                const prop = propertys[i];
                const propertyValues = {};
                array.reduce((acc, item) => {
                    const value = item[prop];
                    if (value in acc) {
                        acc[value]++;
                    } else {
                        acc[value] = 1;
                    }
                    return acc;
                }, propertyValues);
                list.push(propertyValues);
            }
            return list;
        },

调用

         const propertyValues = this.getPropertyValues(
                this.tableData,
                this.sortData.map((item) => {
                    return item[0];
                })
            );
            console.log(propertyValues);

获取对象列表属性具体值出现的次数

       getObjectsWithSameValues(arr, properties, vals) {
            const list = arr.filter((item) => {
                let isRight = true;
                for (let index = 0; index < properties.length; index++) {
                    const element = properties[index];
                    if (item[element] != vals[index]) {
                        isRight = false;
                    }
                }
                return isRight;
            });

            // console.log("451", properties, vals, list.length);
            return list;
        },

调用

const name3 = propertyValuesNew4[index3];            
  const num = this.getObjectsWithSameValues(
                                this.tableData,
                                [
                                    "dt",
                                    "productType",
                                    "stageType",
                                    "scenarioNameNew",
                                ],
                                [name0, name1, name2, name3]
                            ).length;

注意:里面的name0 name1 name2 name3 都是通过获取每个属性值出现的次数的方法拿到,再通过遍历每个属性的值数组 拿到。

另外种方式

思路 判断上一个和下一个是否相同。相同就+1然后递归。取到特殊值再合并数组。

但是会出现bug ,自己试试。

<template>
  <div class="page">
    <HandleList
      v-model="pageData.versionCode"
      :inputShow="false"
      :search-show="false"
      :export-show="true"
      :add-show="false"
      searchPlaceholder="版本号"
      input-title="导出"
      :exportDisabled="!tableData.length"
    >
      <el-date-picker
        style="width: 260px"
        class="ml10"
        v-model="chatDate"
        type="daterange"
        align="right"
        size="small"
        unlink-panels
        :start-placeholder="insertStr(startDay, '4|6', '-')"
        :end-placeholder="insertStr(endDay, '4|6', '-')"
        value-format="yyyyMMdd"
        @change="changeDate"
        :picker-options="pickerOptions"
      >
      </el-date-picker>
    </HandleList>
    <div class="table">
      <el-table
        v-loading="loading"
        id="stationTable"
        class="table"
        height="100%"
        tooltip-effect="light"
        :data="tableData"
        :span-method="objectSpanMethods"
        border
        style="width: 100%"
      >
        <template v-for="cols in colConfigs">
          <!-- 无需合并的列 -->
          <el-table-column
            v-if="cols.type === 'label' && !cols.children"
            :key="cols.prop"
            :prop="cols.prop"
            :label="cols.label"
          >
          </el-table-column>
          <!-- 需要合并的列 -->
          <template v-else-if="cols.type === 'label' && cols.children">
            <el-table-column
              v-for="children in cols.children"
              :key="children.prop"
              :prop="children.prop"
              :label="children.label"
            />
          </template>
        </template>
      </el-table>
    </div>
  </div>
</template>

<script>
import HandleList from "@/components/HandleList";
import { getDateForNum } from "@/utils/formatDate.js";
export default {
  name: "Table",
  components: { HandleList },
  data() {
    return {
      maxChatQueryDay: 7,
      chatDate: null,
      startDay: null,
      endDay: null,
      pageData: {
        startDay: 20231101,
        endDay: 20231120,
        versionCode: "",
        activityType: "首贷长尾,复贷有余额,复贷无余额,授信",
        productType: 1,
      },
      pickerOptions: {
        shortcuts: [
          {
            text: "最近一周",
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
              picker.$emit("pick", [start, end]);
            },
          },
          {
            text: "最近一个月",
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
              picker.$emit("pick", [start, end]);
            },
          },
          {
            text: "最近三个月",
            onClick(picker) {
              const end = new Date();
              const start = new Date();
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
              picker.$emit("pick", [start, end]);
            },
          },
        ],
      },
      loading: false,
      tableData: [
        {
          time: "2020-08-10",
          grade: "三年二班",
          name: "小明",
          subjects: "类型一",
          score: 80,
          score1: 1120,
        },

        {
          time: "2020-08-10",
          grade: "三年二班",
          name: "小明",
          subjects: "类型二",
          score: 80,
          score1: 1120,
        },
        {
          time: "2020-08-10",
          grade: "三年二班",
          name: "总成绩",
          subjects: "总成绩",
          score: 150,
          score1: 1120,
        },
        {
          time: "2020-08-10",
          grade: "三年二4班",
          name: "小明1",
          subjects: "类型一",
          score: 80,
          score1: 1120,
        },
        {
          time: "2020-08-10",
          grade: "三年二4班",
          name: "小明12",
          subjects: "类型二",
          score: 80,
          score1: 1120,
        },
        {
          time: "2020-08-10",
          grade: "三年二4班",
          name: "总成绩",
          subjects: "总成绩",
          score: 150,
          score1: 1120,
        },
        {
          time: "2020-08-10",
          grade: "三年一班",
          name: "小雷",
          subjects: "类型二",
          score: 70,
          score1: 1120,
        },
        {
          time: "2020-08-10",
          grade: "三年一班",
          name: "小雷",
          subjects: "类型一",
          score: 80,
          score1: 1120,
        },
        {
          time: "2020-08-10",
          grade: "三年一班",
          name: "总成绩",
          subjects: "总成绩",
          score: 150,
          score1: 1120,
        },
        {
          time: "2020-08-11",
          grade: "三年三班",
          name: "小花",
          subjects: "类型二",
          score: 60,
          score1: 1120,
        },
        {
          time: "2020-08-11",
          grade: "三年三班",
          name: "小花",
          subjects: "类型一",
          score: 601,
          score1: 1120,
        },
        {
          time: "2020-08-11",
          grade: "三年三班",
          name: "小花1",
          subjects: "类型一",
          score: 190,
          score1: 11201,
        },
        {
          time: "2020-08-11",
          grade: "三年三班",
          name: "小花1",
          subjects: "类型二",
          score: 190,
          score1: 11201,
        },
        {
          time: "2020-08-11",
          grade: "三年三班",
          name: "总成绩",
          subjects: "总成绩",
          score: 120,
          score1: 11201,
        },

        {
          time: "2020-09-11",
          grade: "三年三班",
          name: "小花1",
          subjects: "类型一",
          score: 190,
          score1: 1120,
        },
        {
          time: "2020-09-11",
          grade: "三年三班",
          name: "小花1",
          subjects: "类型二",
          score: 190,
          score1: 11201,
        },
        {
          time: "2020-09-11",
          grade: "三年三班",
          name: "总成绩",
          subjects: "总成绩",
          score: 120,
          score1: 11201,
        },
      ],
      // 表格的信息 需要合并的需要放在 children 中
      colConfigs: [
        {
          type: "label",
          children: [
            { prop: "time", label: "时间" },
            { prop: "grade", label: "年级" },
            { prop: "name", label: "姓名" },
            { prop: "subjects", label: "科目" },
            { prop: "score", label: "成绩" },
            { prop: "score1", label: "成绩1" },
          ],
        },
        // { type: 'label', prop: 'age', label: '年龄' }
      ],
      // 需要合并的行列信息 index必须是table表格对应的下标 不能随意修改
      mergeCols: [
        { index: 0, name: "time" },
        { index: 1, name: "grade" },
        { index: 2, name: "name" },
        { index: 3, name: "subjects" },
        { index: 4, name: "score" },
        { index: 5, name: "score1" },
        // { index: 5, name: 'age' }
      ],
      // 用来记录每一个单元格的下标
      tableMergeIndex: [],
    };
  },
  methods: {
    // 获取当前时间
    getCurrentTime() {
      const startDay = getDateForNum(-this.maxChatQueryDay).replace(/-/g, "");
      this.startDay = startDay;
      const endDay = getDateForNum(0).replace(/-/g, "");
      this.endDay = endDay;
      this.$set(this, "chatDate", [startDay, endDay]);
      this.pageData.startDay = startDay;
      this.pageData.endDay = endDay;
    },
    // 当前时间修改
    changeDate(val) {
      if (val) {
        this.pageData.startDay = val[0];
        this.pageData.endDay = val[1];
      } else {
        this.pageData.startDay = this.startDay;
        this.pageData.endDay = this.endDay;
      }
      this.fetch();
    },
    /**
     *
     * @param {*} source  原数据
     * @param {*} start   下标位置用|隔开
     * @param {*} newStr  添加的符号
     */
    // 日期格式修改:后台返回:20240101  =>insertStr(startDay, '4|6', '-')
    insertStr(source, start, newStr) {
      if (!source) {
        return "";
      }
      const list = start.split("|");

      list.forEach((num, index) => {
        const i = Number(num) + index;
        source = source.slice(0, i) + newStr + source.slice(i);
      });
      return source;
    },
    objectSpanMethods({ row, column, rowIndex, columnIndex }) {
      let key = columnIndex + "_" + rowIndex;
      if (this.tableMergeIndex[key]) {
        return this.tableMergeIndex[key];
      }
    },
    newTableMergeData() {
      for (let i = 0; i < this.tableData.length; i++) {
        for (let j = 0; j < this.mergeCols.length; j++) {
          // 初始化行、列坐标信息
          let rowIndex = 1;
          let columnIndex = 1;
          // 比较横坐标左方的第一个元素
          if (
            j > 0 &&
            this.tableData[i][this.mergeCols[j]["name"]] ===
              this.tableData[i][this.mergeCols[j - 1]["name"]]
          ) {
            columnIndex = 0;
          }
          // 比较纵坐标上方的第一个元素
          if (
            i > 0 &&
            this.tableData[i][this.mergeCols[j]["name"]] ===
              this.tableData[i - 1][this.mergeCols[j]["name"]] &&
            ["time", "grade", "name"].includes(this.mergeCols[j]["name"])
          ) {
            rowIndex = 0;
          }
          // 比较横坐标右方元素
          if (columnIndex > 0) {
            columnIndex = this.onColIndex(
              this.tableData[i],
              j,
              j + 1,
              1,
              this.mergeCols.length
            );
          }
          // 比较纵坐标下方元素
          if (
            rowIndex > 0
            //  &&["time", "grade", "name"].includes(this.mergeCols[j]["name"])
            // &&["time", "grade", "name"].includes(this.mergeCols[j]["name"])
          ) {
            // console.log(i, i + 1, 1, this.mergeCols[j]["name"]);
            rowIndex = this.onRowIndex(
              this.tableData,
              i,
              i + 1,
              1,
              this.mergeCols[j]["name"]
            );
          }
          let key = this.mergeCols[j]["index"] + "_" + i;
          this.tableMergeIndex[key] = [rowIndex, columnIndex];
        }
      }
    },
    /**
     * 计算列坐标信息
     * data 单元格所在行数据
     * index 当前下标
     * nextIndex 下一个元素坐标
     * count 相同内容的数量
     * maxLength 当前行的列总数
     */
    onColIndex(data, index, nextIndex, count, maxLength) {
      // 比较当前单元格中的数据与同一行之后的单元格是否相同
      if (
        nextIndex < maxLength &&
        data[this.mergeCols[index]["name"]] ===
          data[this.mergeCols[nextIndex]["name"]]
      ) {
        return this.onColIndex(data, index, ++nextIndex, ++count, maxLength);
      }
      return count;
    },
    /**
     * 计算行坐标信息
     * data 表格总数据
     * index 当前下标
     * nextIndex 下一个元素坐标
     * count 相同内容的数量
     * name 数据的key
     */
    onRowIndex(data, index, nextIndex, count, name) {
      const nameIndex = this.mergeCols.findIndex((i) => {
        return i.name === name;
      });
      const tIndx = nameIndex || 1;

      const tName = this.mergeCols[tIndx].name;

      // 比较当前单元格中的数据与同一列之后的单元格是否相同
      if (
        nextIndex < data.length &&
        data[index][name] === data[nextIndex][name] &&
        // data[index][tName] === data[nextIndex][tName] &&
        ["time", "grade", "name"].includes(name)
      ) {
        return this.onRowIndex(data, index, ++nextIndex, ++count, name);
      }
      return count;
    },
  },
  mounted() {
    if (this.mergeCols.length > 0) {
      this.newTableMergeData();
    }
    this.getCurrentTime()
  },
};
</script>

<style lang="scss" scoped></style>


总结

        我使用了一种笨方法实现了需求。主要是理解思路很重要。后面所有类型应该都能理解。
        如果能解决问题,麻烦给我点个赞,非常感谢。
       文章写得有点乱,新优化的代码写好了el-table多行合并-代码(二)-CSDN博客

       请查收,记得点个赞

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
el-table是一个前端组件库element-ui中的表格组件,它提供了合并和列的功能。通过设置span-method属性,可以自定义合并的方式,具体可以通过循环数据源数据获取该列需要合并数,以实现多行相同数据变成一显示的效果。 示例代码如下: ```html <template> <div class="table"> <el-table :data="tableData" :span-method="objectSpanMethod" border style="width: 100%"> <el-table-column prop="time" label="时间"></el-table-column> <el-table-column prop="grade" label="年级"></el-table-column> <el-table-column prop="name" label="姓名"></el-table-column> <el-table-column prop="subjects" label="科目"></el-table-column> <el-table-column prop="score" label="成绩"></el-table-column> </el-table> </div> </template> <script> export default { data() { return { tableData: [ { time: '2020-01-01', grade: '一年级', name: '张三', subjects: '语文', score: 90 }, { time: '2020-01-01', grade: '一年级', name: '李四', subjects: '数学', score: 95 }, { time: '2020-01-01', grade: '一年级', name: '王五', subjects: '英语', score: 88 }, { time: '2020-01-02', grade: '二年级', name: '赵六', subjects: '语文', score: 92 }, { time: '2020-01-02', grade: '二年级', name: '钱七', subjects: '数学', score: 96 }, { time: '2020-01-02', grade: '二年级', name: '孙八', subjects: '英语', score: 85 }, ], }; }, methods: { objectSpanMethod({ row, column, rowIndex, columnIndex }) { // 根据业务逻辑判断需要合并数 if (columnIndex === 0) { // 合并第一列 if (rowIndex === 0 || rowIndex === 3) { return { rowspan: 3, colspan: 1, }; } return { rowspan: 0, colspan: 0, }; } }, }, }; </script> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值