【前端】使用vue3 + el-table组件,实现合并行/列。

前言

在开发的过程中遇见一个需求,就是要求合并行。UI设计的效果图如下
在这里插入图片描述
大致需求就是, 1.将订单号、创建人、创建日期和操作列合并; 2.将父订单号合并。只是他们合并的逻辑不一样,前者这需要判断订单号一样就合并,而后者需要判断父订单号一样且订单号也一样时才合并。
这个需求倒是没有什么难度,项目用的是 vue3 + element Plus组件库开发的,所以我就去element官网逛了逛。链接如下,感兴趣的大佬可以自行跳转过去看看。
https://element-plus.org/zh-CN/component/table#%E5%90%88%E5%B9%B6%E8%A1%8C%E6%88%96%E5%88%97
在这里插入图片描述
el-table组件的 span-method 属性,接受一个函数, 用来计算合并行或列的方法。该方法有四个参数 row: any, column: any, rowIndex: number, columnIndex: number
以数组形式返回

const arraySpanMethod = ({
  row,
  column,
  rowIndex,
  columnIndex,
}: SpanMethodProps) => {
  if (rowIndex % 2 === 0) {
    if (columnIndex === 0) {
      return [1, 2]
    } else if (columnIndex === 1) {
      return [0, 0]
    }
  }
}

以对象形式返回

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

但是,官网的例子比较简单,合并逻辑还是得根据自己的需求写业务逻辑。
这里提供两种实现方式,实现逻辑有所区别,第一种是在合并方法里面写合并逻辑,第二种获取数据过后,将需要合并的数据先处理,在合并方法里面直接用处理好的数据。这里更推荐第二种,因为性能更好些。

代码实现

第一种实现方式(直接在合并方法里面些合并逻辑)

function spanMethod1({ row, column, rowIndex, columnIndex }) {
  // 合并的列,从0开始。这里是合并1、4、5、6列
  if ([1, 4, 5, 6].includes(columnIndex)) {
    console.log("进入打印")
    if (
      rowIndex === 0 || (
        rowIndex > 0 &&
        row.orderNo !== tableData.value[rowIndex - 1].orderNo
      )) {
      let rowspan = 1;
      for (let i = rowIndex + 1; i < tableData.value.length; i++) {
        console.log("循环打印")
        if (row.orderNo === tableData.value[i].orderNo) {
          rowspan++;
        } else {
          break;
        }
      }
      return [rowspan, 1];
    } else {
      return [0, 0];
    }
  }
}

这样的缺点是,只要页面大小发生变化,都会触发合并方法,对性能不友好。

Video_2025-03-02_131438

接下来是第二种(提前计算好合并逻辑,直接使用)

function spanMethod({ row, column, rowIndex, columnIndex }) {
  if ([1, 4, 5, 6].includes(columnIndex)) {
    console.log("进入打印")
    let rowSpan = mergeInfo.value.orderNos[rowIndex];
    const colSpan = rowSpan > 0 ? 1 : 0;
    return [rowSpan, colSpan];
  }
  if (columnIndex === 2) {
    let rowSpan = mergeInfo.value.children[rowIndex];
    const colSpan = rowSpan > 0 ? 1 : 0;
    return [rowSpan, colSpan];
  }
}

function handleMergeData() {
  let orderNoIndex = 0;
  let childrenIndex = 0;
  tableData.value.forEach((item, index) => {
    if (index === 0) {
      mergeInfo.value.orderNos.push(1);
      mergeInfo.value.children.push(1);
      orderNoIndex = 0;
      childrenIndex = 0;
    } else {
      if (item.orderNo === tableData.value[index - 1].orderNo) {
        mergeInfo.value.orderNos[orderNoIndex] += 1;
        mergeInfo.value.orderNos.push(0);
      } else {
        mergeInfo.value.orderNos.push(1);
        orderNoIndex = index;
      }
      if (item.orderNo === tableData.value[index - 1].orderNo &&
        item.parentOrderNo === tableData.value[index - 1].parentOrderNo
      ) {
        mergeInfo.value.children[childrenIndex] += 1;
        mergeInfo.value.children.push(0);
      } else {
        mergeInfo.value.children.push(1);
        childrenIndex = index;
      }
    }
  });
}

Video_2025-03-02_132800

从视频中可以直观的看出来,后者比前者性能更好些,每次渲染不用再次进行计算合并逻辑。

Vue3 + Element Plus(Element-UI 的 Vue3 版本)中实现**动态多级表头表格的导出功能**,需要结合 `el-table` 的多级表头能力与前端 Excel 导出库(如 [SheetJS / xlsx](https://github.com/SheetJS/sheetjs))来完成。由于 Element Plus 表格支持嵌套 `el-table-column` 实现多级表头,但原生不支持导出,因此需手动解析表头结构并生成对应的 Excel 文件。 下面是一个完整的解决方案: --- ### ✅ 功能目标: 1. 支持 **动态配置的多级表头**(任意层级) 2. 使用 `Element Plus` 的 `<el-table>` 展示数据 3. 点击按钮将表格(含多级表头)导出为 `.xlsx` 文件 --- ### ✅ 所需依赖 ```bash npm install xlsx ``` --- ### ✅ 示例代码(Vue3 + Composition API + Element Plus) ```vue <template> <div> <el-button @click="exportToExcel">导出为 Excel</el-button> <el-table :data="tableData" border style="width: 100%; margin-top: 20px" ref="tableRef" > <!-- 动态渲染多级表头 --> <template v-for="col in tableColumns" :key="col.prop"> <el-table-column v-if="!col.children" :prop="col.prop" :label="col.label" :width="col.width" /> <el-table-column v-else :label="col.label" :width="col.width" > <template v-for="child in col.children" :key="child.prop"> <el-table-column :prop="child.prop" :label="child.label" :width="child.width" /> </template> </el-table-column> </template> </el-table> </div> </template> <script setup> import { ref, reactive } from &#39;vue&#39; import * as XLSX from &#39;xlsx&#39; // 模拟数据 const tableData = ref([ { name: &#39;张三&#39;, info_age: 25, info_city: &#39;北京&#39;, score_chinese: 88, score_math: 92 }, { name: &#39;李四&#39;, info_age: 27, info_city: &#39;上海&#39;, score_chinese: 76, score_math: 98 }, { name: &#39;王五&#39;, info_age: 24, info_city: &#39;深圳&#39;, score_chinese: 90, score_math: 85 } ]) // 多级表头定义(动态可变) const tableColumns = reactive([ { prop: &#39;name&#39;, label: &#39;姓名&#39;, width: 100 }, { label: &#39;个人信息&#39;, children: [ { prop: &#39;info_age&#39;, label: &#39;年龄&#39;, width: 80 }, { prop: &#39;info_city&#39;, label: &#39;城市&#39;, width: 100 } ] }, { label: &#39;成绩信息&#39;, children: [ { prop: &#39;score_chinese&#39;, label: &#39;语文&#39;, width: 80 }, { prop: &#39;score_math&#39;, label: &#39;数学&#39;, width: 80 } ] } ]) // 将多级表头扁平化,并记录每的完整路径用于映射数据 function flattenHeaders(columns, parentPath = &#39;&#39;, parentLabel = &#39;&#39;) { let result = [] columns.forEach(col => { if (col.children && col.children.length > 0) { const groupLabel = parentLabel ? `${parentLabel} - ${col.label}` : col.label col.children.forEach(child => { result.push({ prop: child.prop, header: [groupLabel, child.label], // 用于生成两层表头 valuePath: `${parentPath}${child.prop}` }) }) } else { result.push({ prop: col.prop, header: [col.label], valuePath: col.prop }) } }) return result } // 导出为 Excel const exportToExcel = () => { const flatHeaders = flattenHeaders(tableColumns) // 构建表头(二维数组,支持多级) const headerRows = [[], []] // 第一:主分组;第二:具体字段名 const data = [] flatHeaders.forEach(item => { // 填充第一(分组名) if (item.header.length === 2) { headerRows[0].push(item.header[0]) headerRows[1].push(item.header[1]) } else { headerRows[0].push(item.header[0]) headerRows[1].push(&#39;&#39;) // 第二为空 } }) // 合并单元格逻辑(仅对第一分组合并) const merges = [] let lastGroup = null let groupStartCol = 0 headerRows[0].forEach((groupLabel, index) => { if (groupLabel !== lastGroup) { if (lastGroup !== null) { if (index - groupStartCol > 1) { merges.push({ s: { r: 0, c: groupStartCol }, // start e: { r: 0, c: index - 1 } // end }) } } lastGroup = groupLabel groupStartCol = index } }) // 添加最后一个组的合并 if (headerRows[0].length - groupStartCol > 1 && lastGroup) { merges.push({ s: { r: 0, c: groupStartCol }, e: { r: 0, c: headerRows[0].length - 1 } }) } // 填充数据 tableData.value.forEach(row => { const rowData = [] flatHeaders.forEach(item => { rowData.push(row[item.prop] || &#39;&#39;) }) data.push(rowData) }) // 使用 SheetJS 创建工作簿和工作表 const worksheet = XLSX.utils.aoa_to_sheet([]) // 写入第一(分组) XLSX.utils.sheet_add_aoa(worksheet, [headerRows[0]], { origin: &#39;A1&#39; }) // 写入第二(字段) XLSX.utils.sheet_add_aoa(worksheet, [headerRows[1]], { origin: &#39;A2&#39; }) // 写入数据(从第3开始) XLSX.utils.sheet_add_aoa(worksheet, data, { origin: &#39;A3&#39; }) // 设置宽(可选) const colWidths = flatHeaders.map(item => ({ wch: 12 })) worksheet[&#39;!cols&#39;] = colWidths // 设置合并单元格 worksheet[&#39;!merges&#39;] = merges // 创建工作簿并导出 const workbook = XLSX.utils.book_new() XLSX.utils.book_append_sheet(workbook, worksheet, &#39;Sheet1&#39;) // 触发下载 XLSX.writeFile(workbook, &#39;动态多级表头导出.xlsx&#39;) } </script> <style scoped> .el-table { margin-top: 20px; } </style> ``` --- ### 🔍 代码解释 | 部分 | 说明 | |------|------| | `tableColumns` | 定义了支持嵌套 `children` 的多级结构,是动态可配置的核心 | | `flattenHeaders()` | 将嵌套结构展平,提取每个叶子节点的路径、标签和数据属性 | | `headerRows` | 构造两表头:第一为“个人信息”、“成绩信息”等大类,第二为具体字段 | | `merges` | 记录需要合并的单元格范围(第一中相同分组的连续) | | `XLSX.utils.aoa_to_sheet` | 使用数组 of 数组方式构建表格 | | `sheet_add_aoa` | 分别写入不同的数据 | | `worksheet[&#39;!merges&#39;]` | 设置 Excel 中的合并单元格 | --- ### ✅ 输出效果(Excel) | 个人信息 | 成绩信息 | |----------------|--------------| | 年龄 | 城市 | 语文 | 数学 | | 25 | 北京 | 88 | 92 | | 27 | 上海 | 76 | 98 | 其中“个人信息”跨两合并,“成绩信息”也跨两--- ### ✅ 注意事项 - 如果表头超过两层(比如三级),需要递归处理并生成更多- 当前方案适用于中小型数据量导出(千以内)。大数据建议使用流式导出或后端服务。 - 可扩展支持样式、冻结窗格、数字格式等高级特性。 --- ### ✅ 相关优化方向 - 抽离成通用组件 `DynamicTableExporter` - 支持自定义导出字段过滤 - 支持时间格式、金额格式自动识别 - 添加 loading 和错误提示 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值