背景:
笔者前端使用ant-design-vue,二次开发了a-table,但a-table组件的属性方法都可以用;
 业务需求:物资存放在不同的仓库,显示物资统计表格,以物资分组合并显示物资名称、总数量(物资A在所有库房总数量),同时显示库房名称、地址、数量(物资A在库房1的数量)。
后端返回数据结构:
[
  {
    "sumQuantity": 100,
    "id": "IMilokafkwdIAmWmrlAjLX",
    "version": 1,
    "materialId": "1",
    "quantity": 0,
    "warehouseId": "IQotvqjOcr3IknflrjDta4",
    "updateTime": "2025-02-28 13:15:00",
    "materialName": "沙袋",
    "warehouseName": "库房1",
    "warehouseAddress": "物流运输产业院1栋101"
  },
  {
    "sumQuantity": 100,
    "id": "D0TpUNP3oF6J8DGFVBDX1v",
    "version": 0,
    "materialId": "1",
    "quantity": 100,
    "warehouseId": "IBU9sfUgWhcG0RglQB8urC",
    "updateTime": "2025-02-28 10:51:51",
    "materialName": "沙袋",
    "warehouseName": "产业园3号4栋",
    "warehouseAddress": "产业园3号4栋"
  },
  {
    "sumQuantity": 100,
    "id": "BRmOAec1CbeKTnnnmwefxF",
    "version": 0,
    "materialId": "3",
    "quantity": 100,
    "warehouseId": "IBU9sfUgWhcG0RglQB8urC",
    "updateTime": "2025-02-28 10:51:51",
    "materialName": "铁锹",
    "warehouseName": "产业园3号4栋",
    "warehouseAddress": "产业园3号4栋"
  },
  {
    "sumQuantity": 101,
    "id": "JGGKndrPSaVIlvDL8tG3Ea",
    "version": 0,
    "materialId": "BtjEnxJslGMG3Ns3ZMtcVi",
    "quantity": 100,
    "warehouseId": "IBU9sfUgWhcG0RglQB8urC",
    "updateTime": "2025-02-28 10:51:51",
    "materialName": "C",
    "warehouseName": "产业园3号4栋",
    "warehouseAddress": "产业园3号4栋"
  },
  {
    "sumQuantity": 101,
    "id": "F7GtvxU2hY8HqPHF90AMpn",
    "version": 3,
    "materialId": "BtjEnxJslGMG3Ns3ZMtcVi",
    "quantity": 1,
    "warehouseId": "IQotvqjOcr3IknflrjDta4",
    "updateTime": "2025-02-28 13:15:00",
    "materialName": "C",
    "warehouseName": "库房1",
    "warehouseAddress": "物流运输产业院1栋101"
  },
  {
    "sumQuantity": 104,
    "id": "FNgj4RPi1tiGzKebbr4rar",
    "version": 2,
    "materialId": "E6Sdqv1JjR4HOzExdR2pi1",
    "quantity": 4,
    "warehouseId": "IQotvqjOcr3IknflrjDta4",
    "updateTime": "2025-02-28 13:15:00",
    "materialName": "物资B",
    "warehouseName": "库房1",
    "warehouseAddress": "物流运输产业院1栋101"
  },
  {
    "sumQuantity": 104,
    "id": "KzS27athAsOGPLGviZk4OF",
    "version": 0,
    "materialId": "E6Sdqv1JjR4HOzExdR2pi1",
    "quantity": 100,
    "warehouseId": "IBU9sfUgWhcG0RglQB8urC",
    "updateTime": "2025-02-28 10:51:51",
    "materialName": "物资B",
    "warehouseName": "产业园3号4栋",
    "warehouseAddress": "产业园3号4栋"
  },
  {
    "sumQuantity": 104,
    "id": "KQ6z4WBmWmQJPvVQE9Px2G",
    "version": 0,
    "materialId": "Ki7FlrYpS9UGpQcSyeZYap",
    "quantity": 100,
    "warehouseId": "IBU9sfUgWhcG0RglQB8urC",
    "updateTime": "2025-02-28 10:51:51",
    "materialName": "E",
    "warehouseName": "产业园3号4栋",
    "warehouseAddress": "产业园3号4栋"
  },
  {
    "sumQuantity": 104,
    "id": "JM3W9JYTNYSItMBLxDOUBf",
    "version": 1,
    "materialId": "Ki7FlrYpS9UGpQcSyeZYap",
    "quantity": 4,
    "warehouseId": "IQotvqjOcr3IknflrjDta4",
    "updateTime": "2025-02-28 08:44:18",
    "materialName": "E",
    "warehouseName": "库房1",
    "warehouseAddress": "物流运输产业院1栋101"
  }
]
执行this.convertData(xxxData)后的数据结构为
[
  {
    "sumQuantity": 100,
    "id": "IMilokafkwdIAmWmrlAjLX",
    "version": 1,
    "materialId": "1",
    "quantity": 0,
    "warehouseId": "IQotvqjOcr3IknflrjDta4",
    "updateTime": "2025-02-28 13:15:00",
    "materialName": "沙袋",
    "warehouseName": "库房1",
    "warehouseAddress": "物流运输产业院1栋101",
    
    "warehouseNameRowSpan": 1,
    "sumQuantityRowSpan": 2,
    "materialNameRowSpan": 2
  },
  {
    "sumQuantity": 100,
    "id": "D0TpUNP3oF6J8DGFVBDX1v",
    "version": 0,
    "materialId": "1",
    "quantity": 100,
    "warehouseId": "IBU9sfUgWhcG0RglQB8urC",
    "updateTime": "2025-02-28 10:51:51",
    "materialName": "沙袋",
    "warehouseName": "产业园3号4栋",
    "warehouseAddress": "产业园3号4栋",
    
    "warehouseNameRowSpan": 1,
    "sumQuantityRowSpan": 0,
    "materialNameRowSpan": 0
  },
  {
    "sumQuantity": 100,
    "id": "BRmOAec1CbeKTnnnmwefxF",
    "version": 0,
    "materialId": "3",
    "quantity": 100,
    "warehouseId": "IBU9sfUgWhcG0RglQB8urC",
    "updateTime": "2025-02-28 10:51:51",
    "materialName": "铁锹",
    "warehouseName": "产业园3号4栋",
    "warehouseAddress": "产业园3号4栋",
    
    "warehouseNameRowSpan": 1,
    "sumQuantityRowSpan": 1,
    "materialNameRowSpan": 1
  },
  {
    "sumQuantity": 101,
    "id": "JGGKndrPSaVIlvDL8tG3Ea",
    "version": 0,
    "materialId": "BtjEnxJslGMG3Ns3ZMtcVi",
    "quantity": 100,
    "warehouseId": "IBU9sfUgWhcG0RglQB8urC",
    "updateTime": "2025-02-28 10:51:51",
    "materialName": "C",
    "warehouseName": "产业园3号4栋",
    "warehouseAddress": "产业园3号4栋",
    
    "warehouseNameRowSpan": 1,
    "sumQuantityRowSpan": 2,
    "materialNameRowSpan": 2
  },
  {
    "sumQuantity": 101,
    "id": "F7GtvxU2hY8HqPHF90AMpn",
    "version": 3,
    "materialId": "BtjEnxJslGMG3Ns3ZMtcVi",
    "quantity": 1,
    "warehouseId": "IQotvqjOcr3IknflrjDta4",
    "updateTime": "2025-02-28 13:15:00",
    "materialName": "C",
    "warehouseName": "库房1",
    "warehouseAddress": "物流运输产业院1栋101",
    
    "warehouseNameRowSpan": 1,
    "sumQuantityRowSpan": 0,
    "materialNameRowSpan": 0
  },
  {
    "sumQuantity": 104,
    "id": "FNgj4RPi1tiGzKebbr4rar",
    "version": 2,
    "materialId": "E6Sdqv1JjR4HOzExdR2pi1",
    "quantity": 4,
    "warehouseId": "IQotvqjOcr3IknflrjDta4",
    "updateTime": "2025-02-28 13:15:00",
    "materialName": "物资B",
    "warehouseName": "库房1",
    "warehouseAddress": "物流运输产业院1栋101",
    
    "warehouseNameRowSpan": 1,
    "sumQuantityRowSpan": 2,
    "materialNameRowSpan": 2
  },
  {
    "sumQuantity": 104,
    "id": "KzS27athAsOGPLGviZk4OF",
    "version": 0,
    "materialId": "E6Sdqv1JjR4HOzExdR2pi1",
    "quantity": 100,
    "warehouseId": "IBU9sfUgWhcG0RglQB8urC",
    "updateTime": "2025-02-28 10:51:51",
    "materialName": "物资B",
    "warehouseName": "产业园3号4栋",
    "warehouseAddress": "产业园3号4栋",
    
    "warehouseNameRowSpan": 1,
    "sumQuantityRowSpan": 0,
    "materialNameRowSpan": 0
  },
  {
    "sumQuantity": 104,
    "id": "KQ6z4WBmWmQJPvVQE9Px2G",
    "version": 0,
    "materialId": "Ki7FlrYpS9UGpQcSyeZYap",
    "quantity": 100,
    "warehouseId": "IBU9sfUgWhcG0RglQB8urC",
    "updateTime": "2025-02-28 10:51:51",
    "materialName": "E",
    "warehouseName": "产业园3号4栋",
    "warehouseAddress": "产业园3号4栋",
    
    "warehouseNameRowSpan": 1,
    "sumQuantityRowSpan": 2,
    "materialNameRowSpan": 2
  },
  {
    "sumQuantity": 104,
    "id": "JM3W9JYTNYSItMBLxDOUBf",
    "version": 1,
    "materialId": "Ki7FlrYpS9UGpQcSyeZYap",
    "quantity": 4,
    "warehouseId": "IQotvqjOcr3IknflrjDta4",
    "updateTime": "2025-02-28 08:44:18",
    "materialName": "E",
    "warehouseName": "库房1",
    "warehouseAddress": "物流运输产业院1栋101",
    
    "warehouseNameRowSpan": 1,
    "sumQuantityRowSpan": 0,
    "materialNameRowSpan": 0
  }
]
前端部分代码
描述:1、计算每个字段(materialName)rowSpan [使用递归计算出的materialNameRowSpan存入行记录]
 2、设置组件的columns,使用customRender 参见ant-design-vue文档中Column对象 使用的 API
 注意:自定义需要合并字段数组,根据顺序合并字段值相同的行
methods部分函数如下
// 获取需要合并数据的rowSpan
    convertData(arr, levelIndex = 0) {
      const levelKey = this.sortLevel
      const key = levelKey[levelIndex]
      // 根据不同维度重新整合数据
      let groupObj = this.groupBy(arr, key) || {}
      Object.keys(groupObj).forEach((groupKey) => {
        if (levelIndex < levelKey.length - 1) {
          groupObj[groupKey] = this.convertData(groupObj[groupKey], levelIndex + 1)
        }
        // 计算rowSpan
        groupObj[groupKey].forEach((item, index, arr) => {
          item[`${key}RowSpan`] = index === 0 ? arr.length : 0
        })
      })
      return Object.values(groupObj).flat()
    },
    // 根据属性分组
    groupBy(arr = [], key) {
      let obj = {}
      arr.forEach((item) => {
        const val = item[key]
        if (!obj[val]) {
          obj[val] = []
        }
        obj[val].push(item)
      })
      return obj
    },
a-table组件columns设置如下
const materialNameRender = (value, row, index) => {
  return {
    children: value,
    attrs: {
      rowSpan: row.materialNameRowSpan,
    },
  }
}
const sumQuantityRender = (value, row, index) => {
  return {
    children: value,
    attrs: {
      rowSpan: row.sumQuantityRowSpan,
    },
  }
}
data(){
const tableColumns = [
      {
        title: "物资名称",
        align: "center",
        dataIndex: "materialName",
        key: "materialName",
        customRender: materialNameRender,
      },
      {
        title: "物资数量",
        align: "center",
        dataIndex: "sumQuantity",
        key: "sumQuantity",
        customRender: sumQuantityRender,
      },
      {
        title: "库房库位",
        children: [
          {
            title: "名称",
            dataIndex: "warehouseName",
            key: "warehouseName",
            align: "center",
          },
          {
            title: "地址",
            dataIndex: "warehouseAddress",
            key: "warehouseAddress",
            align: "center",
          },
          {
            title: "数量",
            align: "center",
            dataIndex: "quantity",
            key: "quantity",
          },
        ],
      },
      {
        title: "更新时间",
        align: "center",
        dataIndex: "updateTime",
        key: "updateTime",
      },
    ]
	return {
		tableColumns,
     	sortLevel: ["materialName", "sumQuantity", "warehouseName"],
	}
}
示例效果

总结
纯前端实现清洗数据+表格合并 ,关键点在于前端数据结构,获取合并列的rowSpan,配合customRender,设置变量存储合并列字段顺序数组(sortLevel),能自定义实现字段值相同的则被合并行



















