前端實現 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
英文地址,請注明出處:http://en.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網絡管理往往…