vue:功能【xlsx】动态行内合并

场景:纯前端导出excel数据,涉及到列合并、行合并。

注)当前数据表头固定,行内数据不固定。以第一列WM为判断条件,相同名字的那几行数据合并单元格。合并的那几行数据,后面的列按需求进行合并。

注)本示例是用 vue3+element plus 实现的。

要求导出Excel效果图:

一、按需引入插件

javascript 复制代码
npm i -S file-saver xlsx
npm i -D script-loader

二、导出实现

1、封装方法

excel导出的数据都是二维数组格式,如果是常规的list数据,需要转成二维数组。

注)生成的excel封装的方法如下(支持表头合并、导出的 excel 支持生成多个sheet工作表、表格可自适应宽度、自适应高度、合并表格)

【步骤】

1、导出操作涉及到使用 OutExcelSheet.exportSheetExcel 函数来导出一个名为 karlaExport导出.xlsx 的 Excel 文件。【三个参数:sheetData、mergesHeader 和文件名】

2、sheetData 是一个数组,用于存储要导出的表格数据。在代码中,使用了一个名为 sheet1 的对象来定义表格的名称、数据、合并单元格和行高等信息。

3、mergesHeader 是一个数组,用于指定要合并的行和列的范围。在给定的代码中,合并了一些特定的行和列,以创建标题行和表头的合并效果。

4、最后,通过调用 OutExcelSheet.exportSheetExcel 函数,并传递以上参数,将生成的 Excel 文件导出到本地。

javascript 复制代码
import XLSX from 'xlsx-js-style'
import FileSaver from 'file-saver'

export default {
  // 三个参数:sheetData、mergesHeader 和文件名。
  exportSheetExcel(sheetData, mergerArr, fileName = 'karlaExport.xlsx') {
    const wb = XLSX.utils.book_new() // 创建一个新工作簿

    for (let i = 0; i < sheetData.length; i++) {
      const sheet = sheetData[i]

      // 检查数据项是否存在
      if (!sheet.data) {
        continue // 如果数据项不存在,则跳过当前循环
      }

      const ws = XLSX.utils.aoa_to_sheet(sheet.data) // 将数据数组转换为工作表

      // 设置合并单元格
      ws['!merges'] = sheet.merges && sheet.merges.length > 0 ? [...sheet.merges, ...(mergerArr || [])] : mergerArr;

      // 设置列宽为自适应
      if (sheet.data.length > 0) {
        ws['!cols'] = sheet.data[0].map((_, index) => ({ wch: 15 }))
      }

      // 设置行高
      if (sheet.rowHeights && sheet.rowHeights.length > 0) {
        ws['!rows'] = sheet.rowHeights.map((height) => ({ hpt: height, hpx: height }))
      }

      const borderAll = {
        top: { style: 'thin' },
        bottom: { style: 'thin' },
        left: { style: 'thin' },
        right: { style: 'thin' }
      }

      // 设置单元格样式
      for (const key in ws) {
        if (ws.hasOwnProperty(key)) {
          const cell = ws[key]
          if (typeof cell === 'object') {
            cell.s = {
              border: borderAll,
              alignment: {
                horizontal: 'center',
                vertical: 'center',
                wrapText: true
              },
              font: {
                sz: 12,
                color: {
                  rgb: '000000'
                }
              },
              numFmt: 'General',
              fill: {
                fgColor: { rgb: 'FFFFFF' }
              }
            }
          }
        }
      }

      XLSX.utils.book_append_sheet(wb, ws, sheet.name) // 将工作表添加到工作簿并指定名称
    }

    const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }) // 将工作簿转换为数组

    const file = new Blob([wbout], { type: 'application/octet-stream' }) // 创建Blob对象
    FileSaver.saveAs(file, fileName) // 下载文件
  },
  // 二维数组中空的数据设置为 0
  emptyValues(array, defaultValue) {
    for (let i = 0; i < array.length; i++) {
      for (let j = 0; j < array[i].length; j++) {
        if (array[i][j] === null || array[i][j] === undefined || array[i][j] === '') {
          array[i][j] = defaultValue
        }
      }
    }
    return array
  },
  // 生成excel列表数据
  handleExcelTable(columnHeader, list) {
    if (list.length === 0) return []

    // 表头
    const tableColumn = Object.keys([columnHeader][0])

    // 表格生成的数据
    const sheet = [tableColumn]
    list.forEach((item) => {
      const row = tableColumn.map((column) => item[column])
      sheet.push(row)
    })

    // 表头匹配对应的中文
    const firstRow = sheet[0].map((column) => columnHeader[column])
    sheet[0] = firstRow

    return sheet || []
  }
}

2、前端代码,导出,用的mock数据,是转换后的二维数组,封装的方法中有写

javascript 复制代码
<script lang="ts" setup>
// 引入导出excel 封装的方法
import OutExcelSheet from '@/hooks/web/outToExcelManySheet'

defineOptions({ name: 'export' })

/** 导出 */
const exportLoading = ref(false)
const handleExport = async () => {
  // 表格合并需要添加一行合并表头
  const header = [
    'WM',
    'Total Leave days',
    'Paid Leave Date',
    'Actual working days of previous 3 months',
    '',
    '',
    'Payout of commission 3 months',
    '',
    '',
    'Average commission / day',
    'Commission during leave',
    'Leave Payout',
    'Total Leave Payout'
  ]

  // 表头
  const columnsHeader = {
    name: 'WM',
    leaveDays: 'Total Leave days',
    paidLeaveDate: 'Paid Leave Date',
    actualDaysOneMonth: 'Actual working days of previous 3 months(近三月)',
    actualDaysTwoMonth: 'Actual working days of previous 3 months(近两月)',
    actualDaysThreeMonth: 'Actual working days of previous 3 months(近一月)',
    payoutCommissionOneMonthPrice: 'Payout of commission 3 months(近三月)',
    payoutCommissionTwoMonthPrice: 'Payout of commission 3 months(近二月)',
    payoutCommissionThreeMonthPrice: 'Payout of commission 3 months(近一月)',
    averageCommission: 'Average commission/day',
    commissionDuringLeave: 'Commission during leave',
    leavePayout: 'Leave Payout',
    totalLeavePayout: 'Total Leave Payout'
  }

  // mock 导出数据(带表头)
    const exportList = [
      [
        'WM',
        'Total Leave days',
        'Paid Leave Date',
        'Actual working days of previous 3 months\t(第一个月)',
        'Actual working days of previous 3 months\t(第二个月)',
        'Actual working days of previous 3 months\t(第三个月)',
        'Payout of commission 3 months\t第一个月)',
        'Payout of commission 3 months\t(第二个月)',
        'Payout of commission 3 months\t(第三个月)',
        'Average commission / day',
        'Commission during leave',
        'Leave Payout',
        'Total Leave Payout'
      ],
      [
        'karla',
        5,
        '2023-01-01',
        '10',
        '18',
        '18',
        '10000',
        '20000',
        '30000',
        '1400',
        '640',
        '760',
        '0.00'
      ],
      [
        'karla',
        5,
        '2023-01-04',
        '10',
        '18',
        '18',
        '10000',
        '20000',
        '30000',
        '1400',
        '1600',
        '0.00',
        '0.00'
      ],
      [
        'karla',
        5,
        '2023-01-06',
        '10',
        '18',
        '18',
        '10000',
        '20000',
        '30000',
        '1400',
        '1800',
        '0.00',
        '0.00'
      ],
      [
        'karla',
        5,
        '2023-01-24',
        '10',
        '18',
        '18',
        '10000',
        '20000',
        '30000',
        '1400',
        '0.00',
        '0.00',
        '0.00'
      ],
      [
        'karla',
        5,
        '2023-01-18',
        '10',
        '18',
        '18',
        '10000',
        '20000',
        '30000',
        '1400',
        '1600',
        '0.00',
        '0.00'
      ],
      [
        'York',
        2,
        '2023-01-18',
        '28',
        '24',
        '18',
        '10000',
        '20000',
        '30000',
        '1500',
        '1800',
        '0.00',
        '666'
      ],
      [
        'York',
        2,
        '2023-01-24',
        '28',
        '24',
        '18',
        '10000',
        '20000',
        '30000',
        '1500',
        '700',
        '800',
        '666'
      ],
      [
        'Caleb',
        1,
        '2023-01-29',
        '22',
        '15',
        '17',
        '8899.12',
        '7833',
        '1455.63',
        '1366.8',
        '734.8',
        '632',
        '0.00'
      ]
    ]

  // 生成表格
  const sheet1 = {
    name: 'LeavePay',
    // data: [header, ...OutExcelSheet.handleExcelTable(columnsHeader, list.value)],  // 常规list数据用封装的方法处理二维数据
    data: [header, ...exportList],  // 使用处理好的mock数据
    merges: [],
    rowHeights: [{ hpx: 20 }, { hpx: 20 }]
  }

  // 合并:第0列、第1列、第三列、第四列、第五列、第六列、第七列和第八列的相同值进行行合并
  const mergedRows = new Map()
  for (let i = 1; i < sheet1.data.length; i++) {
    const cellValue0 = sheet1.data[i][0]
    const cellValue1 = sheet1.data[i][1]
    const cellValue3 = sheet1.data[i][3]
    const cellValue4 = sheet1.data[i][4]
    const cellValue5 = sheet1.data[i][5]
    const cellValue6 = sheet1.data[i][6]
    const cellValue7 = sheet1.data[i][7]
    const cellValue8 = sheet1.data[i][8]
    const prevValue0 = sheet1.data[i - 1][0]
    const prevValue1 = sheet1.data[i - 1][1]
    const prevValue3 = sheet1.data[i - 1][3]
    const prevValue4 = sheet1.data[i - 1][4]
    const prevValue5 = sheet1.data[i - 1][5]
    const prevValue6 = sheet1.data[i - 1][6]
    const prevValue7 = sheet1.data[i - 1][7]
    const prevValue8 = sheet1.data[i - 1][8]

    if (
      cellValue0 === prevValue0 &&
      cellValue1 === prevValue1 &&
      cellValue3 === prevValue3 &&
      cellValue4 === prevValue4 &&
      cellValue5 === prevValue5 &&
      cellValue6 === prevValue6 &&
      cellValue7 === prevValue7 &&
      cellValue8 === prevValue8
    ) {
      if (mergedRows.has(cellValue0)) {
        // 更新合并的结束行索引
        mergedRows.get(cellValue0).end = i
      } else {
        // 添加新的合并信息
        mergedRows.set(cellValue0, { start: i - 1, end: i })
      }
    }
  }

  // 添加行合并信息到 mergesHeader
  for (const [value, { start, end }] of mergedRows.entries()) {
    sheet1.merges.push({ s: { r: start, c: 0 }, e: { r: end, c: 0 } })
    sheet1.merges.push({ s: { r: start, c: 1 }, e: { r: end, c: 1 } })
    sheet1.merges.push({ s: { r: start, c: 3 }, e: { r: end, c: 3 } })
    sheet1.merges.push({ s: { r: start, c: 4 }, e: { r: end, c: 4 } })
    sheet1.merges.push({ s: { r: start, c: 5 }, e: { r: end, c: 5 } })
    sheet1.merges.push({ s: { r: start, c: 6 }, e: { r: end, c: 6 } })
    sheet1.merges.push({ s: { r: start, c: 7 }, e: { r: end, c: 7 } })
    sheet1.merges.push({ s: { r: start, c: 8 }, e: { r: end, c: 8 } })
    sheet1.merges.push({ s: { r: start, c: 12 }, e: { r: end, c: 12 } })
  }

  // 合并表头
  const mergesHeader = [
    // 行合并
    { s: { r: 0, c: 3 }, e: { r: 0, c: 5 } },
    { s: { r: 0, c: 6 }, e: { r: 0, c: 8 } },
    // 列合并(r 表示行索引,c 表示列索引)
    { s: { r: 0, c: 0 }, e: { r: 1, c: 0 } }, // 第0列的第0行和第1行合并
    { s: { r: 0, c: 1 }, e: { r: 1, c: 1 } }, // 第1列的第0行和第1行合并
    { s: { r: 0, c: 2 }, e: { r: 1, c: 2 } }, // 第2列的第1行和第1行合并
    { s: { r: 0, c: 9 }, e: { r: 1, c: 9 } },
    { s: { r: 0, c: 10 }, e: { r: 1, c: 10 } },
    { s: { r: 0, c: 11 }, e: { r: 1, c: 11 } },
    { s: { r: 0, c: 12 }, e: { r: 1, c: 12 } }
  ]

  const sheetData = [sheet1]

  // 导出
  OutExcelSheet.exportSheetExcel(sheetData, mergesHeader, `karla导出.xlsx`)
}
</script>
相关推荐
Myli_ing23 分钟前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风25 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave32 分钟前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟34 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾1 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧1 小时前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm1 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
chusheng18401 小时前
Java项目-基于SpringBoot+vue的租房网站设计与实现
java·vue.js·spring boot·租房·租房网站
asleep7011 小时前
第8章利用CSS制作导航菜单
前端·css
hummhumm2 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架