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

news2025/1/16 11:35:17

场景:纯前端导出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>

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot + Vue项目(显示+删除+回显家居)

文章目录 1.显示家居信息1.com/sun/furn/controller/FurnController.java 添加方法2.postman测试3.src/views/HomeView.vue 修改el-table 并清空数据池tableData4.src/views/HomeView.vue 发送请求并取出数据1.方法池2.created阶段调用list方法3.结果展示 5.src/utils/request.…

【python】flask服务端响应与重定向处理

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

C语言经典算法-8

文章目录 其他经典例题跳转链接41.基数排序法42.循序搜寻法&#xff08;使用卫兵&#xff09;43.二分搜寻法&#xff08;搜寻原则的代表&#xff09;44.插补搜寻法45.费氏搜寻法 其他经典例题跳转链接 C语言经典算法-1 1.汉若塔 2. 费式数列 3. 巴斯卡三角形 4. 三色棋 5. 老鼠…

01 JDBC介绍

文章目录 JDBC本质版本使用核心APIDriverDriverManager驱动注册连接对象获取 Connection获取执行对象事务管理 Statement概述 ResultSet概述 JDBC本质 官方&#xff08;sun公司&#xff09;定义的一套操作所有关系型数据库的规则&#xff0c;即接口各个数据库厂商去实现这套接…

爬虫逆向实战(37)-某保险超市(AES,SHA256)

一、数据接口分析 主页地址&#xff1a;某保险超市 1、抓包 通过抓包可以发现数据接口是/tacpc/tiananapp/marketing_product_commodity/commodityList 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现&#xff0c;有一个jsonKey加密参…

Servlet使用

文章目录 简介一、快速入门二、Servlet 执行流程三、Servlet 生命周期四、Servlet 方法介绍五、Servlet 体系结构六、Servlet urlPattern配置七、XML 配置方式编写 Servlet 简介 一、快速入门 <dependencies><dependency><groupId>javax.servlet</groupId…

绝地求生:[更新周报] 3/20 不停机更新:商城无上新、23号七周年HOT TIME!

大家好&#xff0c;我是闲游盒。本周三3月20号&#xff0c;绝地求生不会有停机时间&#xff0c;大家可以随便玩~ ▲本周可选地图池 亚服/东南亚服&#xff1a;艾伦格、米拉玛、泰戈、荣都、卡拉金&#xff1b; 日服/韩服KAKAO服&#xff1a;艾伦格、泰戈、萨诺、荣都、卡拉金&a…

数据分析-Pandas序列滑动窗口配置参数

数据分析-Pandas序列滑动窗口配置参数 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&…

【保姆级】VsCode 安装GitHub Copilot实操教程

0. 前言 GitHub Copilot&#xff0c;俗称“副驾驶”&#xff0c;是GitHub携手OpenAI共同打造的一款革命性的人工智能代码辅助工具。通过将其插件化集成至编辑器&#xff08;如VS Code&#xff09;&#xff0c;Copilot能够为用户提供强大的代码自动补全功能&#xff0c;并根据用…

图神经网络实战(5)——常用图数据集

图神经网络实战&#xff08;5&#xff09;——常用图数据集 0. 前言0. 图数据集介绍2. Cora 数据集3. Facebook Page-Page 数据集小结系列链接 0. 前言 图数据集往往比单纯的连接集合更丰富&#xff0c;节点和边也可以具有表示分数、颜色、单词等的特征。在输入数据中包含这些…

get_local_ip.bat:快速获取IPv4地址

批处理脚本&#xff0c;用于在Windows命令提示符下获取本地计算机的IPv4地址。 echo off ipconfig | findstr IPv4 pause - echo off&#xff1a;这会关闭命令提示符窗口中的命令回显&#xff0c;使得在运行脚本时不会显示每条命令的执行结果。 - ipconfig&#xff1a;这是一…

IDEA 配置阿里规范检测

IDEA中安装插件 配置代码风格检查规范 使用代码风格检测 在代码类中&#xff0c;右键 然后会给出一些不符合规范的修改建议&#xff1a; 保存代码时自动格式化代码 安装插件&#xff1a; 配置插件&#xff1a;

探索未来教育:在线教育微服务的革新之路

随着互联网技术的不断发展&#xff0c;在线教育已经成为现代教育领域的重要组成部分。而在在线教育的发展过程中&#xff0c;微服务架构的应用正逐渐引起人们的关注和探讨。本文将深入探讨在线教育微服务的概念、优势以及未来发展趋势。 ## 什么是在线教育微服务&#xff1f; …

【how2j练习题】JS部分阶段练习

练习一 <!--练习&#xff0c;做一个简单的加法计算器--><html><input type"text" size "2" id "num1" ><input type"text" size "2" id "num2" ><input type"text" siz…

FSP40罗德与施瓦茨FSP40频谱分析仪

181/2461/8938产品概述&#xff1a; 频率范围:9千赫至40千兆赫 分辨率带宽:1赫兹至10兆赫 显示的平均噪音水平:-155分贝&#xff08;1赫兹&#xff09; 相位噪声:10 kHz时为-113 dB&#xff08;1Hz&#xff09; 附加滤波器:100 Hz至5 MHz的通道滤波器和RRC滤波器、1 Hz至3…

html5cssjs代码 029 CSS计数器

html5&css&js代码 029 CSS计数器 一、代码二、解释 该HTML代码定义了一个网页的结构和样式。在头部&#xff0c;通过CSS样式定义了body和h1-h2元素的样式。body元素的样式包括文本居中、计数器重置、字体颜色和背景颜色。h2元素的样式使用了CSS计数器来自动在标题前添加…

Visio 2003简体中文版软件安装教程(附软件下载地址)

软件简介&#xff1a; 软件【下载地址】获取方式见文末。注&#xff1a;推荐使用&#xff0c;更贴合此安装方法&#xff01; Visio 2003简体中文版是一款独立的专业绘图软件&#xff0c;适用于复制、可视化处理、分析和交流信息、系统和流程。不论是哪个版本的Visio&#xff…

石油炼化5G智能制造工厂数字孪生可视化平台,推进行业数字化转型

石油炼化5G智能制造工厂数字孪生可视化平台&#xff0c;推进行业数字化转型。在石油炼化行业&#xff0c;5G智能制造工厂数字孪生可视化平台的出现&#xff0c;为行业的数字化转型注入了新的活力。石油炼化行业作为传统工业的重要领域&#xff0c;面临着资源紧张、环境压力、安…

拓展商城系统的未来:微服务维度的创新之路

随着电子商务的快速发展&#xff0c;传统的单体式商城系统在应对日益复杂的业务需求和用户体验方面逐渐显露出局限性。而基于微服务架构的商城系统&#xff0c;通过多维度的拆分和组合&#xff0c;正在为商城行业带来全新的创新和发展机遇。本文将深入探讨微服务维度下的商城系…

stm32之GPIO电路介绍

文章目录 1 GPIO介绍2 GPIO的工作模式2.1 浮空输入2.2 上拉输入2.3 下拉输入2.4 模拟输入2.5 开漏输出2.6 推挽输出2.7 复用开漏输出2.8 复用推挽输出2.9 其他 3 应用方式4 常用库函数 1 GPIO介绍 保护二极管&#xff1a;保护引脚&#xff0c;让引脚的电压位于正常的范围施密特…