前端Excel導出實用方案(完整源碼,可直接應用)

目錄

前言:

技術選型:

主要功能點:

核心代碼:

完整代碼:

開發文檔


前言:

在前后端分離開發為主流的時代,很多時候,excel導出已不再由后端主導,而是把導出的操作移交到了前端。本文在全局導出組件封裝上,保持了高度的擴展性,無論大家用的是element組件庫還是antd vue的組件庫或者其他的組件庫,都容易進行更換。

技術選型:

vue + antd vue + sheetjs

前端導出excel導出,需借助第三方插件,目前兩款導出最為主流。

一款是sheetjs,優點支持多種excel格式,但是官方文檔全是英文
SheetJS Community Edition | SheetJS Community Edition

一款是exceljs,優點是中文文檔很全,缺點是導出格式受限,僅支持部分格式

https://github.com/exceljs/exceljs/blob/master/README_zh.md

因公司業務需要,用戶需支持多種excel的格式,所以本文筆者主要針對sheetjs進行封裝調用。

主要功能點:

  • 自定義dom
  • 拆分成多張表導出(默認超過1萬條數據自動拆分)
  • 自定義過濾函數
  • 各種標題自定義
  • 數據排序
  • 支持大數據量導出

核心代碼:

// 文件名稱
const filename = fileName;
//Excel第一個sheet的名稱
const ws_name = sheetName;
// 創建sheet
const ws = XLSX.utils.aoa_to_sheet([this.tableTitle]);
//添加數據
XLSX.utils.sheet_add_json(ws, apiData, {skipHeader: true,origin:origin
});
// 創建wokbook
const wb = XLSX.utils.book_new();
// 將數據添加到工作薄
XLSX.utils.book_append_sheet(wb, ws, ws_name);
// 導出文件
XLSX.writeFile(wb, filename);

完整代碼:

安裝sheetjs

npm i xlsx

全局導出組件代碼:

ExportExcelComponent.vue

<template><div id="excel-export"><slot name="custom" v-if="isCustom"></slot><a-button ghost type="primary" @click="startExport" v-else>導出excel</a-button><a-modalv-if="visible"v-model="visible":title="modelTitle":maskClosable="false":closable="false"><template #footer><a-buttontype="primary"ghostv-if="isAbnormal":loading="btnLoading"@click="startExport">重新導出</a-button><a-buttontype="primary"ghostv-if="isAbnormal":loading="btnLoading"@click="getTableData">繼續導出</a-button><a-button :loading="btnLoading" @click="handleClose"> 關閉 </a-button></template><a-progress:percent="percent":status="progressStatus"class="progress"/></a-modal></div>
</template>
<script>
import * as XLSX from "xlsx";export default {props: {//自定義過濾函數filterFunction: {type: Function,default: null,},//sheet名ws_name: {type: String,default: "Sheet",},//導出的excel的表名filename: {type: String,default: "Excel" + new Date().getTime(),},//拆分成每個表多少條數據,需要搭配isSplit屬性一起使用multiFileSize: {type: Number,default: 10e3,},//模態框標題modelTitle: {type: String,default: "導出excel",},//是否自定義dom,如果采用插槽,需要開啟該屬性,否則dom為默認buttonisCustom: {type: Boolean,default: false,},// 導出的數據表的表頭tableTitleData: {type: Array,required: true,default: () => [],},//請求數據的api函數asyncDataApi: {type: Function,default: () => {},},//請求參數listQuery: {type: Object,default: () => ({}),},},data() {return {ws: null,isAbnormal: false,btnLoading: false,progressStatus: "active",visible: false,percent: 0,tableData: [],currentPage: 1,multiFileNum: 0,};},computed: {// 導出的數據表的表頭tableTitle() {return this.tableTitleData.map((item) => {return item.title;});},//導出數據表的表頭的codetableCode() {return this.tableTitleData.map((item) => {return item.code;});},},watch: {percent: {handler(newVal) {if (newVal > 100) {this.progressStatus = "success";setTimeout(() => {this.handleClose();}, 500);}},},},methods: {//按照指定的title順序映射排序數組對象sortData(data, tit_code) {const newData = [];data.forEach((item) => {const newObj = {};tit_code.forEach((v) => {newObj[v] = item[v] || "";});newData.push(newObj);});return newData;},handleClose() {console.log("close");this.resetExport();this.visible = false;},resetExport() {this.percent = 0;this.progressStatus = "active";this.isAbnormal = false;this.tableData = [];this.currentPage = 1;this.multiFileNum = 0;this.ws = XLSX.utils.aoa_to_sheet([this.tableTitle]);},//獲取進度條百分比getPersent(res) {const persent_num =((res.paginator.currentPage * res.paginator.size) /res.paginator.total) *100;this.percent = parseInt(persent_num) - 1;},//異常處理handleAbnormal() {this.btnLoading = false;this.progressStatus = "exception";this.isAbnormal = true;},async startExport() {if (!this.asyncDataApi) {return new Promise(new Error("asyncDataApi is required"));}this.resetExport();await this.getTableData();},//請求導出的數據和標題async getTableData() {this.visible = true;this.btnLoading = true;this.isAbnormal = false;try {const res = await this.asyncDataApi({...this.listQuery,page: this.currentPage,});if (res.code !== 200) {this.handleAbnormal();this.$message.error(res.message || this.t("requestException"));return;}let apiData = res.data;apiData = this.sortData(apiData, this.tableCode);if (this.filterFunction) {apiData = this.filterFunction(apiData);}apiData = apiData.map((item) => Object.values(item));this.addSheetData(apiData, res);this.currentPage = res.paginator.currentPage + 1;console.log("res", res);this.getPersent(res);const isSplit =res.paginator.currentPage * res.paginator.size >=this.multiFileSize * (this.multiFileNum + 1);if (isSplit) {this.splitExport();}if (res.paginator.currentPage < res.paginator.page) {this.getTableData();return;}//當數據不滿足拆分數量時觸發this.hadnleOneExport(res);this.percent += 2;this.btnLoading = false;this.$message.success("導出成功");} catch (error) {console.log(error);this.$message.error("網絡錯誤,請稍后再試");this.handleAbnormal();}},//當數據不滿足拆分數量時觸發hadnleOneExport(res) {if (this.multiFileNum &&res.paginator.total > this.multiFileNum * this.multiFileSize) {this.multiFileNum += 1;this.exportExcel(this.filename + this.multiFileNum + ".xlsx",this.ws_name + this.multiFileNum);} else if (!this.multiFileNum) {this.exportExcel(this.filename + ".xlsx", this.ws_name);}},//拆分成多個excel導出splitExport() {this.multiFileNum += 1;this.exportExcel(this.filename + this.multiFileNum + ".xlsx",this.ws_name + this.multiFileNum);//重置表格this.ws = XLSX.utils.aoa_to_sheet([this.tableTitle]);},addSheetData(apiData, res) {//添加數據到表格 origin為每次添加數據從第幾行開始XLSX.utils.sheet_add_json(this.ws, apiData, {skipHeader: true,origin:(this.currentPage - 1) * res.paginator.size -this.multiFileSize * this.multiFileNum +1,});},//導出所有數據到一個excelexportExcel(fileName, sheetName) {// 文件名稱const filename = fileName;//Excel第一個sheet的名稱const ws_name = sheetName;// 創建wokbookconst wb = XLSX.utils.book_new();// 將數據添加到工作薄XLSX.utils.book_append_sheet(wb, this.ws, ws_name);// 導出文件XLSX.writeFile(wb, filename);},},
};
</script>

調用示例:

App.vue

<template><div><h1>測試表格導出</h1><div><ExportExcelComponent:tableTitleData="title":asyncDataApi="asyncDataApi":isCustom="isCustom":listQuery="listQuery"ref="export":filterFunction="handleDateFilter"><template #custom><!-- <a-button type="primary" @click="handleClick">導出excel</a-button> --><a-dropdown-button>Dropdown<a-menu slot="overlay" @click="handleMenuClick"><a-menu-item key="1"><a-icon type="user" />1st menu item</a-menu-item><a-menu-item key="2"><a-icon type="user" />2nd menu item</a-menu-item><a-menu-item key="3"><a-icon type="user" />3rd item</a-menu-item></a-menu></a-dropdown-button></template></ExportExcelComponent></div></div>
</template>
<script>
import ExportExcelComponent from "./ExportExcelComponent/ExportExcelComponent.vue";
import { asyncDataApi } from "./request";
import dayjs from "dayjs";
export default {data() {return {listQuery: {name: "yyy",age: 18,},isCustom: true,asyncDataApi: null,title: [{ code: "id", title: "序號" },{ code: "hobby", title: "愛好" },{ code: "name", title: "姓名" },{ code: "age", title: "年齡" },// { code: "hobby", title: "愛好" },{ code: "sex", title: "性別" },{ code: "address", title: "地址" },{ code: "birthday", title: "生日" },{ code: "createTime", title: "創建時間" },{ code: "updateTime", title: "更新時間" },{ code: "remark", title: "備注" },{ code: "status", title: "狀態" },],};},methods: {handleDateFilter(data) {const res = data.reduce((pre, cur) => {for (let i in cur) {if (i === "createTime") {cur[i] = dayjs(cur[i] * 1000).format("YYYY-MM-DD HH:mm:ss");}}pre.push(cur);return pre;}, []);return res;},async handleMenuClick(val) {// const titleNewData = [];// for (let i = 1; i < 500; i++) {//   this.title.forEach((item) => {//     titleNewData.push({ code: item.code + i, title: item.title + i });//   });// }// this.title = titleNewData;console.log("點擊了導出excel", val);await (this.asyncDataApi = asyncDataApi);this.$refs.export.startExport();},// async handleClick() {//   console.log("點擊了導出excel");//   await (this.asyncDataApi = asyncDataApi);//   this.$refs.export.startExport();// },},components: {ExportExcelComponent,},
};
</script>

mock數據:

request.js

const asyncDataApi = (listquery) => {console.log("params", listquery);// 模擬異步請求接口return new Promise((resolve, reject) => {setTimeout(() => {const data = [];for (let i = listquery.page * 100; i < (listquery.page + 1) * 100; i++) {const obj = {id: i - 99,name: "姓名" + i,age: 20 + i,hobby:"趙客縵胡纓,吳鉤霜雪明。銀鞍照白馬,颯沓如流星。十步殺一人,千里不留行。事了拂衣去,深藏身與名。閑過信陵飲,脫劍膝前橫。將炙啖朱亥,持觴勸侯嬴。" +i,sex: "男" + i,birthday: "2020-01-01",createTime: "1701155392",updateTime: "2020-01-01",remark: "備注" + i,status: "1" + i,};// let newObj = {};// for (var a = 1; a < 500; a++) {//   for (let k in obj) {//     newObj[k + a] = obj[k];//   }// }// data.push(newObj);data.push(obj);}resolve({data,code: 200,msg: "請求成功",paginator: {page: 1000,size: 100,total: 100000,currentPage: listquery.page,},});}, 100);});
};
export { asyncDataApi };

開發文檔

調用方式:

如果不采用自定義dom的話,直接點擊默認的按鈕可直接導出表格數據; 如果采用自定義dom的話,通過ref實例調用子組件內的startExport方法,執行導出操作

<template><ExportExcelComponent...:isCustom = "true":asyncDataApi="asyncDataApi":tableTitleData="titles"ref="export"><a-button type="primary" @click="handleClick">導出excel</a-button></ExportExcelComponent>
</template>
?
<script>import { asyncDataApi } from '@/api/member'export default{data(){return:{titles:[]asyncDataApi,}}methods:{handleClick(){this.$refs.export.startExport();}}}
</script>

API

屬性如下

參數說明類型默認值
listQuery請求參數Object{}
asyncDataApi請求數據的api函數Function必傳
tableTitleData導出的數據表的表頭Array必傳
isCustom是否自定義dom,如果采用插槽,需要開啟該屬性,否則dom為默認button;可以傳遞 v-slot:custom 來自定義 dom。Booleanfalse
modelTitle模態框標題String"導出excel"

multiFileSize

拆分成每個表多少條數據,需要搭配isSplit屬性一起使用Number10e3
filename導出的excel的表名String"Excel" + new Date().getTime()
ws_namesheet名String"Sheet"
filterFunction自定義過濾函數;可在業務層處理數據格式,如時間格式化等Function(data)null

FAQ

filterFunction怎么使用

<template><ExportExcelComponent...:isCustom = "true":asyncDataApi="asyncDataApi":tableTitleData="titles"ref="export":filterFunction="handleDateFilter"><a-button type="primary" @click="handleClick">導出excel</a-button></ExportExcelComponent>
</template>
?
<script>import { asyncDataApi } from '@/api/member'export default{data(){return:{titles:[]asyncDataApi,}}methods:{handleDateFilter(data) {const res = data.reduce((pre, cur) => {for (let i in cur) {if (i === "createTime") {cur[i] = dayjs(cur[i] * 1000).format("YYYY-MM-DD HH:mm:ss");}}pre.push(cur);return pre;}, []);return res;},handleClick(){this.$refs.export.startExport();}}}
</script>

導出表格數據為空是什么情況?

因為導出的表格數據的順序和標題的順序并不一定是一致的,所以在組件內部做了映射排序,一定要確保傳入的標題數據在調用導出接口之前執行。如果傳遞的標題有誤,在進行映射的時候,這時標題和表格數據并不匹配,那么就會出現數據映射為空的情況

Promise Error:"asyncDataApi is required"

當傳遞給組件的后端api需要在點擊dom后賦值再傳遞的時候,一定要確保在導入后端api之后再調用組件內的導出方法,否則因為后端api還沒傳遞過去就調用,然后拋錯或者導出異常

正確示例:

async handleClick() {await (this.asyncDataApi = asyncDataApi);this.$refs.export.getTableData();
},

后端導出表格api數據返回格式

因該組件為全局組件,方便以后復用,需與后端協商規定好數據導出的格式。以下為筆者的公司,與后端同事協商的數據格式。大家可根據自己公司需要,更改以上源碼中后端返回值字段。

//后端返回數據結構
{"status": true,"data": [{...},{...},],"paginator": {"currentPage": 1,"total": 200,"size": 20,"page": 10}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/207516.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/207516.shtml
英文地址,請注明出處:http://en.pswp.cn/news/207516.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

關于鎖的粒度問題——面試

鎖的粒度劃分主要有三種&#xff1a;表級鎖、頁級鎖和行鎖 1.表級鎖&#xff1a; 對整張表加鎖&#xff0c;粒度最大&#xff0c;加鎖的并發度最低&#xff0c;會導致其他事務無法訪問該表&#xff0c;只有當前事務提交或者回滾后才能釋放鎖。表級鎖適用于對表進行全表操作的場…

DeepIn,UOS統信專業版安裝運行Java,JavaFx程序

因為要適配國產統信UOS系統&#xff0c;要求JavaFx程序能簡便雙擊運行&#xff0c;由于網上UOS開發相關文章少&#xff0c;多數文章沒用&#xff0c;因此花了不少時間&#xff0c;踩了不少坑&#xff0c;下面記錄一些遇到的問題&#xff0c;我的程序環境是jdk1.8&#xff0c;為…

【K8s】Kubernetes CRD 介紹(控制器)

文章目錄 CRD 概述1. 操作CRD1.1 創建 CRD1.2 操作 CRD 2. 其他筆記2.1 Kubectl 發現機制2.2 校驗 CR2.3 簡稱和屬性 3. 架構設計3.1 控制器概覽 參考 CRD 概述 CR&#xff08;Custom Resource&#xff09;其實就是在 Kubernetes 中定義一個自己的資源類型&#xff0c;是一個具…

如何為 3D 模型制作紋理的最佳方法

在線工具推薦&#xff1a; 3D數字孿生場景編輯器 - GLTF/GLB材質紋理編輯器 - 3D模型在線轉換 - Three.js AI自動紋理開發包 - YOLO 虛幻合成數據生成器 - 三維模型預覽圖生成器 - 3D模型語義搜索引擎 您可以通過不同的方式為 3D 模型創建 3D 紋理。下面我們將介紹為 3D …

《opencv實用探索·十四》VideoCapture播放視頻和視像頭調用

1、VideoCapture播放視頻 #include <opencv2/opencv.hpp> #include <iostream>using namespace std; using namespace cv;int main() {// 定義相關VideoCapture對象VideoCapture capture;// 打開視頻文件capture.open("1.avi");// 判斷視頻流讀取是否正…

Python os模塊及用法

os 模塊代表了程序所在的操作系統&#xff0c;主要用于獲取程序運行所在操作系統的相關信息。 在 Python 的交互式解釋器中先導入 os 模塊&#xff0c;然后輸入 os.__all__ 命令&#xff08;__all__ 變量代表了該模塊開放的公開接口&#xff09;&#xff0c;即可看到該模塊所包…

Linux DataEase數據可視化分析工具本地部署與遠程訪問

文章目錄 前言1. 安裝DataEase2. 本地訪問測試3. 安裝 cpolar內網穿透軟件4. 配置DataEase公網訪問地址5. 公網遠程訪問Data Ease6. 固定Data Ease公網地址 前言 DataEase 是開源的數據可視化分析工具&#xff0c;幫助用戶快速分析數據并洞察業務趨勢&#xff0c;從而實現業務…

ExecutorService介紹

參考&#xff1a;https://blog.csdn.net/fwt336/article/details/81530581 前言 在開發中為了提高系統的響應速度和處理能力會使用到多線程&#xff0c;但線程的創建和釋放&#xff0c;需要占用不小的內存和資源。如果每次需要使用線程時&#xff0c;都new 一個Thread的話&…

【LeetCode】2723. 兩個 Promise 對象相加

兩個 Promise 對象相加 題目題解 題目 給定兩個 promise 對象 promise1 和 promise2&#xff0c;返回一個新的 promise。promise1 和 promise2 都會被解析為一個數字。返回的 Promise 應該解析為這兩個數字的和。 示例 1&#xff1a; 輸入&#xff1a; promise1 new Promise…

geoserver根據屬性字段值設置不同的顏色

<?xml version"1.0" encoding"UTF-8"?> <StyledLayerDescriptor xmlns"http://www.opengis.net/sld" xmlns:xlink"http://www.w3.org/1999/xlink" xmlns:ogc"http://www.opengis.net/ogc" xmlns:xsi"http:/…

中國聚羥基脂肪酸酯(PHA)市場評估與投資戰略報告(2024版)

內容簡介&#xff1a; PHA英文名稱為 Polyhydroxyalkanoates&#xff0c;是近20多年迅速發展起來的&#xff0c;很多天然原料合成的一種聚酯。PHA是生物基生物降解材料&#xff0c;具有良好的生物相容性和可加工性。防止水汽的穿透是保鮮包裝中的重要指標&#xff0c;PHA 有良…

os.walk()遍歷文件夾/文件

天行健&#xff0c;君子以自強不息&#xff1b;地勢坤&#xff0c;君子以厚德載物。 每個人都有惰性&#xff0c;但不斷學習是好好生活的根本&#xff0c;共勉&#xff01; 文章均為學習整理筆記&#xff0c;分享記錄為主&#xff0c;如有錯誤請指正&#xff0c;共同學習進步。…

P3 Qt 控件 —— pushButton

前言 &#x1f3ac; 個人主頁&#xff1a;ChenPi &#x1f43b;推薦專欄1: 《C_ChenPi的博客-CSDN博客》??? &#x1f525; 推薦專欄2: 《Linux C應用編程&#xff08;概念類&#xff09;_ChenPi的博客-CSDN博客》??? &#x1f33a;本篇簡介 &#xff1a;這一章我們學一…

Python evalml 庫:自動化機器學習的新前景

更多資料獲取 &#x1f4da; 個人網站&#xff1a;ipengtao.com 在機器學習領域&#xff0c;evalml 庫嶄露頭角&#xff0c;為開發者提供了一個強大而高效的自動化機器學習框架。本文將深入介紹 evalml 的核心功能、使用方法以及在實際項目中的應用。通過詳實的示例代碼&#…

前端高頻面試題大全-面試必看

內容較多&#xff0c;建議查看目錄&#xff0c;方便食用 高頻 React和Vue的區別 通常解法&#xff1a;vue是采用指令結合vue-loader實現構件用戶界面的漸進式框架&#xff0c;React是采用JSX構件用戶界面的組件化開發 詳細解法&#xff1a;在渲染界面的時候DOM操作是昂貴的&…

【Linux系統編程】項目自動化構建工具make/Makefile

介紹&#xff1a; make和Makefile是用于編譯和構建C/C程序的工具和文件。Makefile是一個文本文件&#xff0c;其中包含了編譯和構建程序所需的規則和指令。它告訴make工具如何根據源代碼文件生成可執行文件&#xff0c;里面保存的是依賴關系和依賴方法。make是一個命令行工具&a…

智匯恒星科技|控樂屋.全宅智能冠軍代言來啦, 智慧家居千億藍海

隨著5G、大數據、云計算、物聯網等技術的發展&#xff0c;智能化正覆蓋人們生活的方方面面&#xff0c;全屋智能的出現為“一鍵式”智能家居生活享受提供無限可能。近年來智能家居行業總體規模增長迅速&#xff0c;數據顯示&#xff0c;2022年中國智能家居行業市場規模約為6200…

Java內部類

文章目錄 什么是 Java 中的內部類&#xff1f;有哪些類型的內部類&#xff1f;匿名內部類局部內部類&#xff08;定義在方法中的類&#xff09;局部內部類靜態內部類 Java 類中不僅可以定義變量和方法&#xff0c;還可以定義類&#xff0c;這樣定義在類內部的類就被稱為內部類。…

Java期末復習題之封裝

點擊返回標題->23年Java期末復習-CSDN博客 第1題. 定義一個類Person,定義name和age私有屬性&#xff0c;定義有參的構造方法對name和age進行初始化。在測試類中創建該類的2個對象&#xff0c;姓名、年齡分別為lili、19和lucy、20&#xff0c;在屏幕打印出2個對象的姓名和年齡…

2024年江蘇省職業院校技能大賽信息安全管理與評估 理論題(樣卷)

2024年江蘇省職業院校技能大賽信息安全管理與評估 理論題&#xff08;樣卷&#xff09; 理論技能與職業素養&#xff08;100分&#xff09; 2024年江蘇省職業院校技能大賽&#xff08;高職學生組&#xff09; 模塊三“信息安全管理與評估”理論技能 【注意事項】 Geek極安云…