前端实现 excel 数据导出,封装方法支持一次导出多个Sheet

一、前言

后台管理项目有时会有需要前端导出excel表格的功能,有时还需要导出多个sheet,并给每个sheet重新命名,下面我们就来实现一下。

二、实现效果图

在这里插入图片描述

三、实现步骤

1、 安装

命令行安装 xlsxfile-saver

npm install xlsx -S
npm install file-saver

注意:vue2和vue3中引入xlsx写法不同

vue2:import xlsx from ‘xlsx’
vue3:import * as XLSX from ‘xlsx’

2、封装工具类

utils文件夹中新建exportToExcel.js文件封装公用导出excel方法。

exportToExcel.js

/* eslint-disable */
import { saveAs } from 'file-saver'
import XLSX from 'xlsx'function generateArray (table) {const out = []const rows = table.querySelectorAll('tr')const ranges = []for (let R = 0; R < rows.length; ++R) {const outRow = []const row = rows[R]const columns = row.querySelectorAll('td')for (let C = 0; C < columns.length; ++C) {const cell = columns[C]let colspan = cell.getAttribute('colspan')let rowspan = cell.getAttribute('rowspan')let cellValue = cell.innerTextif (cellValue !== '' && cellValue === +cellValue) cellValue = +cellValue// Skip rangesranges.forEach(function (range) {if (R >= range.s.r &&R <= range.e.r &&outRow.length >= range.s.c &&outRow.length <= range.e.c) {for (let i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null)}})// Handle Row Spanif (rowspan || colspan) {rowspan = rowspan || 1colspan = colspan || 1ranges.push({s: {r: R,c: outRow.length},e: {r: R + rowspan - 1,c: outRow.length + colspan - 1}})}// Handle ValueoutRow.push(cellValue !== '' ? cellValue : null)// Handle Colspanif (colspan) for (let k = 0; k < colspan - 1; ++k) outRow.push(null)}out.push(outRow)}return [out, ranges]
}function datenum (v, date1904) {if (date1904) v += 1462const epoch = Date.parse(v)return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000)
}function sheet_from_array_of_arrays (data) {const ws = {}const range = {s: {c: 10000000,r: 10000000},e: {c: 0,r: 0}}for (let R = 0; R !== data.length; ++R) {for (let C = 0; C !== data[R].length; ++C) {if (range.s.r > R) range.s.r = Rif (range.s.c > C) range.s.c = Cif (range.e.r < R) range.e.r = Rif (range.e.c < C) range.e.c = Cconst cell = {v: data[R][C]}if (cell.v === null) continueconst cellRef = XLSX.utils.encode_cell({c: C,r: R})if (typeof cell.v === 'number') cell.t = 'n'else if (typeof cell.v === 'boolean') cell.t = 'b'else if (cell.v instanceof Date) {cell.t = 'n'cell.z = XLSX.SSF._table[14]cell.v = datenum(cell.v)} else cell.t = 's'ws[cellRef] = cell}}if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range)return ws
}function Workbook () {if (!(this instanceof Workbook)) return new Workbook()this.SheetNames = []this.Sheets = {}
}function s2ab (s) {const buf = new ArrayBuffer(s.length)const view = new Uint8Array(buf)for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xffreturn buf
}//导出单个excel的方法
export function export_table_to_excel (id) {//获取table的dom节点const theTable = document.getElementById(id)//获取table的所有数据const oo = generateArray(theTable)const ranges = oo[1]const data = oo[0]//设置导出的文件名称const ws_name = 'SheetJS'//设置工作文件const wb = new Workbook()//设置sheet内容const ws = sheet_from_array_of_arrays(data)//设置多级表头ws['!merges'] = ranges//设置sheet的名称  可push多个wb.SheetNames.push(ws_name)//设置sheet的内容wb.Sheets[ws_name] = ws//将wb写入到xlsxconst wbout = XLSX.write(wb, {bookType: 'xlsx',bookSST: false,type: 'binary'})//通过s2absaveAs(new Blob([s2ab(wbout)], {type: 'application/octet-stream'}),'test.xlsx')
}export function export_json_to_excel ({multiHeader = [],header,data,filename,merges = [],autoWidth = true,bookType = 'xlsx'
} = {}) {//文件名称filename = filename || 'excel-list'//文件数据data = [...data]//将表头添加到数据的顶部data.unshift(header)for (let i = multiHeader.length - 1; i > -1; i--) {data.unshift(multiHeader[i])}//设置工作文本const wb = new Workbook()
//设置sheet名称const ws_name = 'SheetJS'// 设置sheet数据const ws = sheet_from_array_of_arrays (data)//设置多级表头if (merges.length > 0) {if (!ws['!merges']) ws['!merges'] = []merges.forEach((item) => {ws['!merges'].push(XLSX.utils.decode_range(item))})}//设置自适应行宽if (autoWidth) {/* 设置worksheet每列的最大宽度 */const colWidth = data.map((row) =>row.map((val) => {/* 先判断是否为null/undefined */if (val === null) {return {'wch': 10}/* 再判断是否为中文 */} else if (val.toString().charCodeAt(0) > 255) {return {'wch': val.toString().length * 2}} else {return {'wch': val.toString().length}}}))/* 以第一行为初始值 */const result = colWidth[0]for (let i = 1; i < colWidth.length; i++) {for (let j = 0; j < colWidth[i].length; j++) {if (result[j]['wch'] < colWidth[i][j]['wch']) {result[j]['wch'] = colWidth[i][j]['wch']}}}ws['!cols'] = result}//将数据添加到工作文本wb.SheetNames.push(ws_name)wb.Sheets[ws_name] = ws//生成xlsx bookType生成的文件类型const wbout = XLSX.write(wb, {bookType: bookType,bookSST: false,type: 'binary'})//导出xlsxsaveAs(new Blob([s2ab(wbout)], {type: 'application/octet-stream'}), `${filename}.${bookType}`)}/*** 导出多个sheet的excel表格* @param sheetsData [{sheetName:'sheet1', header:['名称1','名称2'], data: [value1, value2]}, {sheetName:'sheet2', header:['名称1','名称2'], data: [value1, value2]}]* |    名称1    |    名称2    |* |    value1   |    value2   |*/
export function exportMultiSheetToExcel({ sheetsData, filename = 'excel-file' }) {const wb = new Workbook()sheetsData.forEach((sheet, index) => {const {data,sheetName = `Sheet${index + 1}`, // 默认值multiHeader = [],header,merges = [],autoWidth = true,} = sheet// 处理数据,添加多级表头const sheetData = [...data]if (header) sheetData.unshift(header)// 过滤非法字符const safeSheetName = sanitizeSheetName(sheetName)for (let i = multiHeader.length - 1; i > -1; i--) {sheetData.unshift(multiHeader[i])}// 创建工作表const ws = sheet_from_array_of_arrays(sheetData)// 处理合并单元格if (merges.length > 0) {if (!ws['!merges']) ws['!merges'] = []merges.forEach((item) => {ws['!merges'].push(XLSX.utils.decode_range(item))})}// 处理自适应宽度if (autoWidth) {/* 设置worksheet每列的最大宽度 */const colWidth = data.map((row) =>row.map((val) => {/* 先判断是否为null/undefined */if (val === null) {return {wch: 10,}/* 再判断是否为中文 */} else if (val.toString().charCodeAt(0) > 255) {return {wch: val.toString().length * 2,}} else {return {wch: val.toString().length,}}}))/* 以第一行为初始值 */const result = colWidth[0]for (let i = 1; i < colWidth.length; i++) {for (let j = 0; j < colWidth[i].length; j++) {if (result[j]['wch'] < colWidth[i][j]['wch']) {result[j]['wch'] = colWidth[i][j]['wch']}}}ws['!cols'] = result}// 添加工作表到工作簿wb.SheetNames.push(safeSheetName)wb.Sheets[safeSheetName] = ws})// 生成并保存Excel文件const wbout = XLSX.write(wb, {bookType: 'xlsx',bookSST: false,type: 'binary',})saveAs(new Blob([s2ab(wbout)], {type: 'application/octet-stream',}),`${filename}.xlsx`)
}// 过滤Excel工作表名称中的非法字符
function sanitizeSheetName(name) {if (!name) return 'Sheet'return name.replace(/[\\/:*?"[\]]/g, '_') // 将非法字符串替换为下划线.substring(0, 31) // Excel工作表名称最大长度为31个字符
}

3. 在vue2中使用excel单个sheet导出

<template><div class="app-container"><el-button :loading="exportLoading" type="primary" plain icon="el-icon-download" @click="handleExport">数据导出</el-button></div>
</template><script>
export default {name:'',data(){return {exportLoading: false,// 模拟接口返回数据tableProp: ['2025-01', '2025-02', '2025-03', '2025-04', '2025-05', '2025-06', '2025-07'],resTableData: [{ // 模拟接口返回数据'2025-01':'47.21','2025-02':'40.49','2025-03':'43.87','2025-04':'40.65','2025-05':'40.30','2025-06':'37.95','2025-07':'38.51', '2025-08':'47.21',odr: 1,tableThName: '(厂商)手续费/服务费总计',tableThProp: 'amortizeProvisionChange',合计: '336.69'},{'2025-01':'4528,826.01','2025-02':'4090,552.52','2025-03':'4528,826.01','2025-04':'4382,734.85','2025-05':'44528,826.01','2025-06':'4382,734.85','2025-07':'4529,483.94', '2025-08':'47.21',odr: 2,tableThName: '(其它)手续费/服务费总计',tableThProp: 'amortizeOtherChange',合计: '30,971,984.19'}]}},menthods: {async handleExport () {try{await this.$comfirm('确认导出数据吗?')this.exportLoading = trueconst tHeader = ['项目名称', ...this.tableProp]const filterVal = ['tableThName', ...this.tableProp]const data = await this.formatAmoJson(filterVal)if(!data) {return (this.exportLoading = false)}const excel = await import('@/utils/excel')excel.export_json_to_excel({header: tHeader,data,filename: '摊销计提'})} catch (error) {console.log(error)} finally{this.exportLoading = false}},async formatAmoJson(filterVal){return this.resTableData.map(v => filterVal.map(j => {return v[j]}))}}
}

效果图如下

在这里插入图片描述

在这里插入图片描述

4. 在vue2中实现excel表格中多个sheet导出

<template><div class="app-container"><el-button type="primary" plain icon="el-icon-download" @click="handleExport">多个sheet导出</el-button></div>
</template><script>
export default {name:'',data(){return {// 模拟接口返回数据resTableData: [{"detailName": '测试-查询停息放款明细',"reportDetailUid": '357896657400820001',"headerEn" : "GRANT_NUM, PLN_REPYMT, ODUE_DYS, MTNR","headerCn" : "放款编号, 计划还款日, 逾期天数, 维护人员","odr" : 1,"result":{"curPage": 1,"pageSize": 20,"total": 130,"data": [["0000000000153","2021-03-20","285","1170000001"],["0000000000159","2021-01-25","768","1170000001"],["0000000000198","2021-02-08","476","1170000001"],["0000000000205","2021-03-15","285","1170000001"],["0000000000219","2021-01-15","285","1170000001"],]}},{"detailName": '测试2-查询停息放款明细22',"reportDetailUid": '357896657400820001',"headerEn" : "ETL_DT, PRJ_NUM, CTR_NUM, MTNR","headerCn" : "ETL,项目编号, 合同编号, 放款编号, 维护人员","odr" : 4,"result":{"curPage": 1,"pageSize": 20,"total": 4908,"data": [["0000000000153","2021-03-20","285","1170000001"],["0000000000159","2021-01-25","768","1170000001"],["0000000000198","2021-02-08","476","1170000001"],["0000000000205","2021-03-15","285","1170000001"],["0000000000219","2021-01-15","285","1170000001"],]}}]}},menthods: {async handleExport () {const sheetsData = this.resTableData.map((item) => {const sheetItem = {sheetName: item.detailName,header: item.headerCn.split(','),data: item.result}return sheetItem})const excel = await import('@/utils/Export2Excel')excel.exportMultiSheetToExcel({sheetsData,filename: '摊销计提详情'})}

效果图如下

在这里插入图片描述

参考:前端开发之xlsx的使用和实例,并导出多个sheet

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

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

相关文章

【Lambda 表达式】返回值为什么是auto

一个例子&#xff1a; int x 10; auto add_x [x](int y) -> int {return x y; }; int result add_x(5); // 结果是 15lambda 是匿名类型&#xff0c;必须用 auto 来接收。&#xff08;必须写auto&#xff0c;不可省略&#xff09;内层 -> auto 是函数的返回类型自动推…

【小董谈前端】【样式】 CSS与样式库:从实现工具到设计思维的跨越

CSS与样式库&#xff1a;从实现工具到设计思维的跨越 一、CSS的本质&#xff1a;样式实现的「施工队」 CSS作为网页样式的描述语言&#xff0c;其核心能力在于&#xff1a; 精确控制元素的尺寸、位置、颜色实现响应式布局和动画效果与HTML/JavaScript协同完成交互体验 但CS…

MTSC2025参会感悟:大模型 + CV 重构全终端 UI 检测技术体系

目录 一、传统 UI 自动化的困局:高成本与低效率的双重枷锁 1.1 根深蒂固的技术痛点 1.2 多维度质量挑战的叠加 二、Page eyes 1.0:纯视觉方案破解 UI 检测困局 2.1 纯视觉检测的核心理念 2.2 页面加载完成的智能判断 2.3 视觉模型驱动的异常检测 2.4 大模型赋能未知异…

使用Claude Code从零到一打造一个现代化的GitHub Star项目管理器

在日常的开发工作中&#xff0c;我们经常会在GitHub上star一些有用的项目库。随着时间的推移&#xff0c;star的项目越来越多&#xff0c;如何有效管理这些项目成为了一个痛点。 今天&#xff0c;分享我使用Claude Code从零构建的一个GitHub Star管理插件。项目背景与需求分析 …

为什么 Linux 启动后还能升级内核?

✅ 为什么 Linux 启动后还能升级内核&#xff1f; 简单结论&#xff1a; 因为 “安装/升级内核 ≠ 当前就使用该内核”&#xff0c;Linux允许你安装多个内核版本&#xff0c;并在下次启动时选择其中一个来加载运行。 &#x1f9e0; 举个现实生活类比 你在穿一件衣服&#xff08…

Go语言实战案例-统计文件中每个字母出现频率

以下是《Go语言100个实战案例》中的 文件与IO操作篇 - 案例19&#xff1a;统计文件中每个字母出现频率 的完整内容。本案例适合用来练习文件读取、字符处理、map统计等基础技能。&#x1f3af; 案例目标读取一个本地文本文件&#xff0c;统计并打印出其中每个英文字母&#xff…

Notepad++工具操作技巧

1、notepad -> ctrlf -> 替换(正则表达式) -> $-a ->每行的行尾加a&#xff1b; 2、notepad -> ctrlf -> 替换(正则表达式) -> ^-a ->每行的行首加a &#xff1b; 3、按住alt切换为列模式 4、删除空行-不包括有空格符号的空行 查找替代 查找目标…

领码课堂 | Java与AI的“硬核“交响曲:当企业级工程思维遇上智能时代

摘要 &#x1f680; 在AI工业化落地的深水区&#xff0c;Java正以其独特的工程化优势成为中流砥柱。本文系统解构Java在AI项目全生命周期中的技术矩阵&#xff0c;通过"三阶性能优化模型"、"微服务化AI部署架构"等原创方法论&#xff0c;结合大模型部署、M…

面经 - 基于Linux的高性能在线OJ平台

真实面试环境中&#xff0c;被问到的相关问题&#xff0c;感兴趣的可以看下1. 这个项目是你独立完成的吗&#xff1f;团队中你的职责是什么&#xff1f;是的&#xff0c;这个项目是我独立完成的&#xff0c;从需求分析、系统设计到项目部署都我做的。重点工作包括&#xff1a;使…

Ubuntu 20.04 上安装 SPDK

以下是在 Ubuntu 20.04 上安装 SPDK (Storage Performance Development Kit) 的完整步骤&#xff1a;1. 系统准备# 更新系统 sudo apt update sudo apt upgrade -y# 安装基础依赖 sudo apt install -y git make gcc g libssl-dev libaio-dev libnuma-dev \pkg-config python3 p…

解决WPS图片在Excel表格中无法打开

若出现无法打开的情况&#xff0c;还请回到WPS中&#xff0c;点击图片&#xff0c;右键&#xff1a;转化为浮动图片保存&#xff0c;然后便能正常打开&#xff01;

【Ollama】open-webui部署模型

目录 一、本地部署Ollama 1.1 进入官网复安装命令 1.2 执行安装命令 1.3 验证是否安装成功 二、启动Ollama服务 三、运行模型 方法一&#xff1a;拉取模型镜像 方法二&#xff1a;拉取本地模型 四、使用Open WebUI 部署模型 4.1 创建虚拟环境 4.2 安装依赖 4.3 运行…

C#文件操作(创建、读取、修改)

判断文件是否存在 不存在则创建默认文件 并写入默认值/// <summary>/// 判断文件是否存在 不存在则创建默认文件 并写入默认值/// </summary>public void IsConfigFileExist(){try{// 获取应用程序的当前工作目录。string fileName System.IO.Directory.GetCurr…

基于阿里云平台的文章评价模型训练与应用全流程指南

基于阿里云平台的文章评价模型训练与应用全流程指南 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff0c;觉得好请收藏。点击跳转到网站。 1. 项目概述 1.1 项目背景 在当今信息爆炸的时代&…

AI 及开发领域动态与资源汇总(2025年7月24日)

AI 项目、工具及动态汇总 项目/产品名称核心功能/简介主要特点/亮点相关链接Supervision一个流行的计算机视觉工具库&#xff0c;用于加速计算机视觉应用的构建。模型无关&#xff0c;可与多种主流库集成&#xff1b;提供丰富的可定制标注工具&#xff1b;支持多种数据集操作和…

C专题8:文件操作1

1.C语言中的文件是什么?所谓文件&#xff08;file&#xff09;一般指存储在外部介质上数据的集合&#xff0c;比如我们经常使用的txt、bmp、jpg、exe、rmvb等等。这些文件各有各的用途&#xff0c;我们通常将它们存放在磁盘或者可移动盘等介质中。文件无非就是一段数据的集合&…

Opencv C# 重叠 粘连 Overlap 轮廓分割 (不知道不知道)

先上效果图一种基于凹陷检测重叠轮廓分割的方法这两个星期压力大的一批&#xff0c;心脏都给干得乱跳了&#xff0c;现在高血压心率不齐贫血。兄弟们保重身体啊。简单说下逻辑&#xff1a;前处理&#xff1a;的噼里啪啦我就不说了&#xff0c;根据样品来(灰度&#xff0c;滤波&…

CentOS7 安装 rust 1.82.0

CentOS7 安装 rust 1.82.0 我在CentOS7.9中安装rust遇到报错版本低&#xff0c;再升级版本的过程中遇到诸多问题&#xff0c;简单记录。 遇到的问题 提示版本低 centos7 安装 ERROR: Rust 1.75.0 or newer required.Rust version 1.72.1 was found.原因是 CentOS7 的默认的软件…

Compose 适配 - 键鼠模式

一、概念不止触摸交互&#xff0c;在 ChromeOS 或外接键鼠的设备上&#xff0c;需要考虑焦点、悬停、右键等操作逻辑。二、使用2.1 焦点使用 Tab 键来导航&#xff0c;改变边框以提供清晰的焦点指示器。Composable fun Demo() {val interactionSource remember { MutableInter…

征服 Linux 网络:核心服务与实战解析

在当今的IT基础设施中&#xff0c;Linux作为服务器操作系统的基石&#xff0c;其强大的网络功能是其不可或缺的优势。对于任何志在成为高级系统管理员或运维工程师的人来说&#xff0c;精通Linux网络配置与服务管理是核心竞争力。 与日常应用不同&#xff0c;Linux网络管理往往…