前言

最近在编写一个值班的排班表,然后中间涉及到了表格应用。并且还要做出类似这种效果的行合并效果:

vue3 element 合并单元格错位 element合并单元格table_数据


然后就开始找组件了。Html的table是有rowsSpancolsSpan的属性来实现行合并和列合并的。然后就在网上找资料,发现没有几篇能把这两个属性将好的,并且大多数讲的都是列合并,没有行合并。我自己用的是Vutify组件进行页面绘制,但是Vutify的table组件没有合并的api。要实现的话,就得重写body的插槽,也就等于手写table。而且,重写也只能合并列,行的合并还是没法实现。然后Vutify其实是继承ElementUI实现的,然后又看了一下ElementUI的el-table,才发现了较好的解决方式。

ElementUI实现

官方主要是在el-table上加入一个objectSpanMethod来实现的

vue3 element 合并单元格错位 element合并单元格table_数据_02


objectSpanMethod的具体实现:

objectSpanMethod({ row, column, rowIndex, columnIndex }) {
        if (columnIndex === 0) {
          if (rowIndex % 2 === 0) {
            return {
              rowspan: 2,
              colspan: 1
            };
          } else {
            return {
              rowspan: 0,
              colspan: 0
            };
          }
        }
      }

tableData的数据模型:

tableData: [{
          id: '12987122',
          name: '王小虎',
          amount1: '234',
          amount2: '3.2',
          amount3: 10
        }, {
          id: '12987123',
          name: '王小虎',
          amount1: '165',
          amount2: '4.43',
          amount3: 12
        }, {
          id: '12987124',
          name: '王小虎',
          amount1: '324',
          amount2: '1.9',
          amount3: 9
        }, {
          id: '12987125',
          name: '王小虎',
          amount1: '621',
          amount2: '2.2',
          amount3: 17
        }, {
          id: '12987126',
          name: '王小虎',
          amount1: '539',
          amount2: '4.1',
          amount3: 15
        }]
      }

然后效果图:

vue3 element 合并单元格错位 element合并单元格table_javascript_03


但看这个效果,其实还不错,但其实有一些问题:

  • 数据模型中的ID不是连续的,合并行的时候取得是该组的第一个元素的ID
  • 数据模型只能是两两配对,中途不能出现其他配对的组合,否则,数据会错乱。由于,例子中的数据长度只有5条,最后一条没有合并的选项,因此看不出问题

实际应用中,后端返回的不会只是固定的两两或者三三或者固定行合并的数据。中途可能穿插四四或者五五的都有。单靠例子的实现是行不通的。好在本人研究了十分钟后,想到了解决办法。

解决非固定行合并

解决的关键在于读懂objectSpanMethod这个方法,我们再回顾一下官方的实现模式:

objectSpanMethod({ row, column, rowIndex, columnIndex }) {
        if (columnIndex === 0) { // 表示只操作第一列
          if (rowIndex % 2 === 0) { // 表示只对奇数行进行操作(注意Index从0开始,因此不是偶数行)
            return {
              rowspan: 2, //需要合并的行数
              colspan: 1 // 合并后的列数
            };
          } else {
            return {
              rowspan: 0, // 0消掉这一行
              colspan: 0 // 0消掉这一列 二者合并起来就是消掉这一个单元格子
            };
          }
        }
      }

注意,这里的row打印值得话,实际就是每个数据条目本身

我将代码注释了一下,但是一般人估计还是看不懂,那我再上一张图:

vue3 element 合并单元格错位 element合并单元格table_elementui_04


蓝色的数据行是要合并的一组,这里是2行。黄色行是该组合并的基底行格子(奇数行),黑色行是消掉的没有绘制的格子。合并过后,黄色的格子就填满了蓝色格子区间,实现了合并。最后一行因为没有待合并的行,所以就是它自己。

读懂这个例子后,我们应该清楚要合并的话,需要知道:

  • 基底行格子所在的行的索引值
  • 从基底行开始,向下要合并的行的数量

官方实例中,这两条恰好都是写死的固定值,但是我们实际应用中,可能就不是了。
我在写这个查询表的时候,后端给我的数据长这个样子,一个2-3-2组合:

data=[
	{
		day:'2023-01-01',
		workers:[
			{
				name:'小明',
				mobile:'1334455667788'
			},
			{
				name:'小黄',
				mobile:'1334455667788'
			}
		]
	},
	{
		day:'2023-01-02',
		workers:[
			{
				name:'小郑',
				mobile:'1334455667788'
			},
			{
				name:'小朱',
				mobile:'1334455667788'
			},
			{
				name:'小刘',
				mobile:'1334455667788'
			}
		]
	},
	{
		day:'2023-01-03',
		workers:[
			{
				name:'小罗',
				mobile:'1334455667788'
			},
			{
				name:'小李',
				mobile:'1334455667788'
			}
		]
	}
]

看过官方的例子后,其实应该把数据转换为:

[
	{
		day:'2022-01-01',
		name:'小明',
		mobile:'1334455667788'
	},
	{
		day:'2022-01-01',
		name:'小黄',
		mobile:'1334455667788'
	},
	{
		day:'2022-01-02',
		name:'小郑',
		mobile:'1334455667788'
	},
	{
		day:'2022-01-02',
		name:'小朱',
		mobile:'1334455667788'
	},
	{
		day:'2022-01-02',
		name:'小刘',
		mobile:'1334455667788'
	},
	{
		day:'2022-01-03',
		name:'小罗',
		mobile:'1334455667788'
	},
	{
		day:'2022-01-03',
		name:'小李',
		mobile:'1334455667788'
	}
]

我在操作数据转换的时候,增加了isFirstsameIndex属性:

const dataList=[] // 最终转换好的数据集合
data.forEach(({ day, workers = [] }) => {
          if (Array.isArray(workers) && workers.length > 0) {
            workers.forEach(({ name, mobile }, index) => {
              dataList.push({
                date: day,
                name,
                mobile,
                isFirst: index === 0, // 合并行的时候用于标记起始合并的位置
                sameIndex: workers.length // 需要合并的行数
              })
            })
          } else {
          	// 如果没有返回worker就手动加个占位用
            dataList.push({
              date: day,
              name: '--',
              mobile: '',
              isFirst: true,
              sameIndex: 1
            })
          }
        })

然后重写objectSpanMethod方法:

objectSpanMethod({ row, column, rowIndex, columnIndex }) {
      if (columnIndex === 0) {
        if (row.isFirst === true) {
          return {
            rowspan: row.sameIndex,
            colspan: 1
          }
        } else {
          return {
            rowspan: 0,
            colspan: 0
          }
        }
      }
    }

由于row就是我们每行实际的数据,isFirst实际就是定位基底行的位置,sameIndex就是从该行开始一共要合并的行的数量,这样我们就比较巧妙地实现了行合并效果,而且是动态自动合并行数地。

最后看一下成品效果图:

vue3 element 合并单元格错位 element合并单元格table_vue.js_05