web导出excel,复制即用(支持多级表头,支持合计)

1.安装所需依赖
 

npm install xlsx-js-style

2.创建公共js库 exportExcel.js
 

import XLSX from 'xlsx-js-style'

/**
 * 递归获取所有表头配置(扁平化)
 * @param {Array} columns 多级表头配置
 * @param {Array} parentList 父级表头信息
 * @returns {Array} 扁平化表头列表
 */
function getAllColumns(columns, parentList = []) {
  let result = []
  columns.forEach(col => {
    const newParentList = [...parentList]
    if (col.children && col.children.length) {
      // 有子级,继续递归
      result = result.concat(getAllColumns(col.children, [...newParentList, col]))
    } else {
      // 叶子节点,组装完整路径
      result.push({
        ...col,
        parentList: newParentList,
        fullTitle: [...newParentList.map(p => p.title), col.title]
      })
    }
  })
  return result
}

/**
 * 计算表头合并信息(rowspan/colspan)
 * @param {Array} flatColumns 扁平化后的表头
 * @returns {Object} { headerRowCount, merges, headerTitles }
 */
function calcHeaderInfo(flatColumns) {
  const headerTitles = []
  const merges = []
  let maxLevel = 1

  // 1. 计算最大层级(表头行数)
  flatColumns.forEach(col => {
    const level = col.parentList.length + 1
    if (level > maxLevel) maxLevel = level
  })

  // 2. 初始化表头二维数组
  for (let i = 0; i < maxLevel; i++) {
    headerTitles.push([])
  }

  // 3. 填充表头数据,同时记录合并信息
  flatColumns.forEach((col, colIndex) => {
    const titlePath = col.fullTitle
    titlePath.forEach((title, level) => {
      headerTitles[level].push(title)
    })
    // 补充层级不足的空值(为了保持表头矩阵结构)
    for (let i = titlePath.length; i < maxLevel; i++) {
      headerTitles[i].push('')
    }

    // 4. 计算 rowspan(垂直合并)
    const rowspan = maxLevel - col.parentList.length
    if (rowspan > 1) {
      merges.push({
        s: { r: col.parentList.length, c: colIndex },
        e: { r: col.parentList.length + rowspan - 1, c: colIndex }
      })
    }
  })

  // 5. 计算 colspan(水平合并)
  const colSpanMap = new Map()
  flatColumns.forEach((col, colIndex) => {
    col.parentList.forEach((parent, level) => {
      const key = `${level}-${parent.title}`
      if (!colSpanMap.has(key)) {
        colSpanMap.set(key, {
          startCol: colIndex,
          count: 1
        })
      } else {
        const info = colSpanMap.get(key)
        info.count++      
      }
    })
  })

  // 处理水平合并
  colSpanMap.forEach((info, key) => {
    const [level] = key.split('-')
    const levelNum = Number(level)
    if (info.count > 1) {
      merges.push({
        s: { r: levelNum, c: info.startCol },
        e: { r: levelNum, c: info.startCol + info.count - 1 }
      })
    }
  })

  return { 
    headerRowCount: maxLevel, 
    merges, 
    headerTitles 
  }
}

/**
 * 处理表格数据,按表头顺序映射字段
 * @param {Array} flatColumns 扁平化后的表头
 * @param {Array} tableData 表格数据
 * @returns {Array} 格式化后的二维数组
 */
function formatTableData(flatColumns, tableData) {
  return tableData.map((row, rowIndex) => {
    return flatColumns.map(col => {
      // 支持 dataIndex 为字符串或数组(多级嵌套字段)
      if (Array.isArray(col.dataIndex)) {
        return col.dataIndex.reduce((acc, key) => acc?.[key], row) ?? ''
      }
      if(col.dataIndex === 'index'){
        return rowIndex + 1
      }
      return row[col.dataIndex] ?? ''
    })
  })
}

/**
 * 处理合计行数据
 * @param {Array} flatColumns 扁平化后的表头
 * @param {Array} tableData 表格数据
 * @returns {Array} 合计行数据数组
 */
function handleSumRow(flatColumns, sumColumns, tableData) {
  if (sumColumns.length === 0) return []
  return flatColumns.map((col, colIndex) => {
    if (sumColumns.includes(col.dataIndex)) {
        //计算合计值
      const sum = tableData.reduce((acc, row) => acc + Number(row[col.dataIndex] || 0), 0)
      return sum.toFixed(2)
    } else {
      return colIndex === 0 ? '合计' : ''
    }
  })
}

/**
 * 导出多级表头的 Excel 文件
 * @param {Object} options 配置项
 * @param {Array} options.columns Ant Design Vue 的 a-table 多级表头配置
 * @param {Array} options.data 表格数据
 * @param {string} [options.filename='导出数据.xlsx'] 导出文件名
 * @param {string} [options.sheetName='Sheet1'] 工作表名称
 * @param {Array} [options.sumColumns=[]] 要计算合计的列 dataIndex 列表 如['字段1', '字段2']
 */
export function exportMultiLevelTable(options) {
  const {
    columns,
    data,
    filename = '导出数据',
    sheetName = 'Sheet1',
    sumColumns = []
  } = options

  if (!columns || !columns.length) throw new Error('columns 不能为空')
  if (!data || !Array.isArray(data)) throw new Error('data 必须为数组')

  // 1. 扁平化多级表头
  const flatColumns = getAllColumns(columns)
  // 2. 计算表头合并信息和标题
  const { merges, headerTitles } = calcHeaderInfo(flatColumns)
  // 3. 格式化表格数据
  const tableBody = formatTableData(flatColumns, data)
  const sumRow = handleSumRow(flatColumns, sumColumns, data)
  
  // 4. 构建完整表格
  const fullTable = [...headerTitles, ...tableBody]
  if (sumRow.length) fullTable.push(sumRow)

  // 5. 带样式单元格(居中+边框)
  const styledData = fullTable.map(row => {
    return row.map(cell => ({
      v: cell ?? '',
      s: {
        alignment: { horizontal: 'center', vertical: 'center' },
        border: {
          top: { style: 'thin', color: { rgb: '000000' } },
          bottom: { style: 'thin', color: { rgb: '000000' } },
          left: { style: 'thin', color: { rgb: '000000' } },
          right: { style: 'thin', color: { rgb: '000000' } }
        }
      }
    }))
  })

  // 6. 生成工作表
  const worksheet = XLSX.utils.json_to_sheet([], { skipHeader: true })
  XLSX.utils.sheet_add_aoa(worksheet, styledData)
  // 7. 设置合并单元格
  worksheet['!merges'] = merges

  // 8. 自设置列宽
  worksheet['!cols'] = flatColumns.map(col => ({
    wch: Math.max(col.title.length * 2, 12)
  }))

  // 9. 导出 Excel 文件
  const workbook = XLSX.utils.book_new()
  XLSX.utils.book_append_sheet(workbook, worksheet, sheetName)
  XLSX.writeFile(workbook, `${filename}.xlsx`)
}

3.引用导出

<template>
	<div @click="fn_export "></div>
</template>

<script setup>
	import {exportMultiLevelTable} from '@/utils/exportTable'
	const fn_export = async() => {
        //调用接口拿到数据
        const tableData = [.......]
        //列
        const columns = [
            
				{
					title: '单价',
					dataIndex: 'price',
				},
				{
					title: '金额',
					dataIndex: 'totalPrice',
				},
				{
					title: '产出',
                    children: [
                        {
					        title: '数量1',
                            dataIndex: 'number1'
                        },
                        {
					        title: '数量2',
                            dataIndex: 'number2'
                        }
                    ]
				},
        ]
		exportMultiLevelTable({
			columns: columns,
			data: tableData,
			sumColumns: ['number1', 'number2'],
			filename: "导出文件名",
		})
		btnLoad.value = false
	}
</script>

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

最后导出效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值