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

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

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

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

要求导出Excel效果图:

一、按需引入插件

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 文件导出到本地。

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数据,是转换后的二维数组,封装的方法中有写

<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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/471827.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

github 如何关闭 2FA

一开始按照各种教程都找不到&#xff0c;新版的太小了&#xff0c; https://github.com/settings/security

HTML实现卷轴动画完整源码附注释

动画效果截图 页面的html结构代码 <!DOCTYPE html> <html> <head lang=

【Maven入门篇】(3)依赖配置,依赖传递,依赖范围,生命周期

&#x1f38a;专栏【Maven入门篇】 &#xfeff;> &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#xfeff;> &#x1f386;音乐分享【The truth that you leave】 &#xfeff;> &#x1f970;欢迎并且感谢大家指出我的问题 文章目录 &…

(四)Android布局类型(线性布局LinearLayout)

线性布局&#xff08;LinearLayout&#xff09;&#xff1a;按照一定的方向排列组件&#xff0c;方向主要分为水平方向和垂直方向。方向的设置通过属性android:orientation设置 android:orientation 其取值有两种 水平方向&#xff1a;android:orientation"horizontal&…

【精品】递归查询数据库 获取树形结构数据 通用方法

数据库表结构 实体类基类 Getter Setter ToString public class RecursionBean {/*** 编号*/private Long id;/*** 父权限ID&#xff0c;根节点的父权限为空*/JsonIgnoreprivate Long pid;private List<? extends RecursionBean> children;/*** 递归查询子节点** param…

申请双软认证需要哪些材料?软件功能测试报告怎么获取?

“双软认证”是指软件产品评估和软件企业评估&#xff0c;其中需要软件测试报告。 企业申请双软认证除了获得软件企业和软件产品的认证资质&#xff0c;同时也是对企业知识产权的一种保护方式&#xff0c;更可以让企业享受国家提供给软件行业的税收优惠政策。 那么&#xff0c;…

奇舞周刊第522期:“Vite 又开始搞事情了!!!”

奇舞推荐 ■ ■ ■ Vite 又开始搞事情了&#xff01;&#xff01;&#xff01; Vite 的最新版本将引入一种名为 Rolldown 的新型打包工具。 unocss 究竟比 tailwindcss 快多少&#xff1f; 我们知道 unocss 很快&#xff0c;也许是目前最快的原子化 CSS 引擎 (没有之一)。 巧用…

Flink:使用 Faker 和 DataGen 生成测试数据

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

Linux 发布项目到OpenEuler虚拟机

后端&#xff1a;SpringBoot 前端&#xff1a;VUE3 操作系统&#xff1a;Linux 虚拟机&#xff1a;OpenEuler 发布项目是需要先关闭虚拟机上的防火墙 systemctl stop firewalld 一、运行后端项目到虚拟机 1、安装JDK软件包 查询Jdk是否已安装 dnf list installed | grep jd…

力扣每日一题 好子数组的最大分数 单调栈 双指针

Problem: 1793. 好子数组的最大分数 &#x1f496; 单调栈 思路 &#x1f468;‍&#x1f3eb; 参考题解 以当前高度为基准&#xff0c;寻找最大的宽度组成最大的矩形面积那就是要找左边第一个小于当前高度的下标left&#xff0c;再找右边第一个小于当前高度的下标right那宽…

Linux 磁盘的一生

注意&#xff1a;实验环境都是使用VMware模拟 ​ 磁盘接口类型这里vm中是SCSI&#xff0c;扩展sata,ide(有时间可以看看或者磁盘的历史) ​ 总结&#xff1a;磁盘从有到无—类似于建房子到可以住 ————————————————————————————————————…

【PHP + 代码审计】函数详解2.0

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

【计算机网络篇】物理层(4)信道的极限容量,信道复用技术

文章目录 &#x1f354;信道的极限容量&#x1f6f8;造成信号失真的主要因素⭐码元的传输速率 &#x1f6f8;奈氏准则&#x1f6f8;香农公式&#x1f388;练习 &#x1f5d2;️小结 &#x1f354;信道复用技术⭐常见的信道复用技术&#x1f388;频分复用FDM&#x1f388;时分复…

Python之进程池、阻塞模式、非阻塞模式、进程间的通信、queue

非阻塞模式 # 当需要创建的子进程数量不多时&#xff0c;可以直接利用multiprocessing中的Process动态成生多个进程 # 但如果是上百甚至上千个目标&#xff0c;手动的去创建进程的工作量巨大&#xff0c;此时就可以用到multiprocessing模块提供的Pool方法. # 初始化Poo1时&…

分享5款专注于实用简洁的工具软件

​ 有时候一些小工具&#xff0c;能给你带来一些意想不到的效果&#xff0c;我们来看看下面这5款工具&#xff0c;你又用过其中几款呢&#xff1f; 1. 高效操作利器——Quicker ​ Quicker是一款旨在提高操作效率的强大工具。通过简单的自定义设置&#xff0c;用户能够创建个…

幼儿教育管理系统|基于jsp 技术+ Mysql+Java的幼儿教育管理系统设计与实现(可运行源码+数据库+设计文档)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;ssm&#xff0c;springboot的平台设计与实现项目系统开发资源&#xff08;可…

C++中的Union: 内存与类型转换技巧

在C中&#xff0c;union是一种特殊的数据类型&#xff0c;允许在相同的内存位置存储不同类型的数据。union提供了一种高效地利用内存的方式&#xff0c;但同时也要求开发者更加小心地处理数据以避免类型错误。 1. 基本定义 union定义了一个可以存储多种类型但任意时刻只能存储…

未来国家的希望在实体经济 民众的希望在投资理财

2024年经济的车轮已经滚滚而来&#xff0c;在阳春三月这个希望无限的季节&#xff0c;香港贵金属交易商、香港金银贸易场AA类147号行员金田金业认为&#xff0c;我们国家的希望在于实体经济发展&#xff0c;而民众要实现个人财务自由&#xff0c;仅仅靠打工还不够&#xff0c;更…

C++ Qt开发:QUdpSocket实现组播通信

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍如何运用QUdpSocket组件实现基于UDP的组播通信…

PyTorch二次反向传播报错

First StagenetF, netC = backbone_net(args, return_type=001xy)base_network = nn.Sequential(netF, netC)optimizer_f = optim.Adam(netF