vue2項(xiàng)目使用exceljs多表頭導(dǎo)出功能詳解
前言
因?yàn)轫?xiàng)目需求要用到前端導(dǎo)出功能,粗略比較了幾個(gè)導(dǎo)出插件之后,決定使用exceljs插件,至少能夠滿足樣式、數(shù)據(jù)格式等的需求。
鑒于個(gè)人水平和時(shí)間限制,目前只是初步實(shí)現(xiàn)了組件化,將表頭、數(shù)據(jù)按要求格式傳遞到組件中即可導(dǎo)出需要的excel文件。代碼質(zhì)量、實(shí)現(xiàn)思路存在一定的缺陷,后續(xù)有時(shí)間會(huì)繼續(xù)優(yōu)化,如果大家有更好的實(shí)現(xiàn),感謝留言!
組件導(dǎo)出文件效果如下:
一級(jí)表頭
二級(jí)表頭
三級(jí)表頭
一、插件安裝
yarn add exceljs yarn add file-saver
二、數(shù)據(jù)準(zhǔn)備
1、前臺(tái)表格表頭數(shù)據(jù)
const data = [ { prop: "AA", label: "地區(qū)", minWidth: "150px" }, { prop: "BB", label: "歸集額", minWidth: "100px" }, { label: "對(duì)下調(diào)入", children: [ { prop: "CC", label: "資金", minWidth: "90px" }, { prop: "DD", label: "占比", minWidth: "80px" }, ], }, { label: "本級(jí)調(diào)入", children: [ { prop: "EE", label: "資金", minWidth: "100px" }, { prop: "FF", label: "占比", minWidth: "80px" } ] } ]
2、傳遞到組件前的數(shù)據(jù)處理
// 表頭數(shù)據(jù)預(yù)處理函數(shù) const headersPreprocess = function (businessAttr) { let columns = deepClone(businessAttr); let maxLevel = 0; let baseCols = []; function columnsProcess(columns = [], level = 0, path = "") { ++level; maxLevel = maxLevel < level ? level : maxLevel; for (let i = 0; i < columns.length; i++) { // 獲取列的路徑,用于后面表頭數(shù)據(jù)格式處理 columns[i].path = emptyCheck(path) ? path + "_" + columns[i].label : columns[i].label; columnProcess(columns[i]); if (columns[i].children) { columnsProcess(columns[i].children, level, columns[i].path); } else { baseCols.push({ prop: columns[i].prop, // 當(dāng)前字段的prop值 path: columns[i].path, // 當(dāng)前字段的路徑 level: level, // 當(dāng)前列所處層級(jí) width: columns[i].width || columns[i].minWidth, // 列寬 }); delete columns[i].path; } } } columnsProcess(columns, 0); return { columns: columns, // 前臺(tái)表格表頭用到的數(shù)據(jù) depth: maxLevel, // 樹形結(jié)構(gòu)表頭的最大層級(jí)數(shù)--導(dǎo)出功能所需 baseCols: baseCols // 底層字段數(shù)據(jù)集--導(dǎo)出功能所需 }; };
得到的baseCols數(shù)據(jù)
//得到的baseCols數(shù)據(jù) [ { level: 1, path: "地區(qū)", prop: "AA", width: "150px" }, { level: 1, path: "資金歸集額", prop: "BB", width: "100px" }, { level: 2, path: "對(duì)下調(diào)入_資金", prop: "CC", width: "90px" }, { level: 2, path: "對(duì)下調(diào)入_占比", prop: "DD", width: "80px" }, { level: 2, path: "本級(jí)調(diào)入_資金", prop: "DD", width: "100px" }, { level: 2, path: "本級(jí)調(diào)入_占比", prop: "DD", width: "80px" }, ]
三、插件引入
1、新建ExcelJS.js文件
2、ExcelJS.js文件中引入需要的庫(kù)
import ExcelJS from "exceljs"; import FileSaver from "file-saver";
四、導(dǎo)出前數(shù)據(jù)處理
1、按exceljs格式創(chuàng)建導(dǎo)出函數(shù)
conf參數(shù)包含的就是上面獲取到的baseCols和depth數(shù)據(jù),及自定義的其他屬性,如文件名等
/** * @description: excel導(dǎo)出 * @param dataList 需要導(dǎo)出的數(shù)據(jù)集 * @param conf 導(dǎo)出函數(shù)配置數(shù)據(jù),包括上面處理得到的底層字段信息baseCols,表頭層級(jí)數(shù)據(jù)depth,文件名fileName * @return */ export const downloadExcel = function (dataList = [], conf = {}) { const workbook = new ExcelJS.Workbook(); // 設(shè)置信息 workbook.creator = "Me"; workbook.title = conf.fileName; workbook.created = new Date(); workbook.modified = new Date(); // 創(chuàng)建工作表 const worksheet = workbook.addWorksheet(conf.fileName) // 表頭信息處理 let columns = columnsPreprocess(conf) // 表頭數(shù)據(jù)填充及樣式設(shè)置 headerFillAndSet(columns.headers, columns.widthList, worksheet) // 橫向單元格合并處理 sheetMergesForCross(worksheet, columns.headers); // 縱向單元格合并處理 sheetMergesForVertical(worksheet, columns.headers); // 添加數(shù)據(jù)集 worksheetDataProcess(conf.baseCols, dataList, worksheet) const _titleCell = titleStyleProcess(worksheet); // 寫入文件 workbook.xlsx.writeBuffer().then((buffer) => { let _file = new Blob([buffer], { type: "application/octet-stream", }); FileSaver.saveAs(_file, "ExcelJS.xlsx"); }); };
2、表頭及列寬數(shù)據(jù)預(yù)處理
2.1 處理邏輯
// 獲取表頭及列寬數(shù)據(jù)集 function columnsPreprocess(conf = {}) { let underlayCols = conf.baseCols; let paths = []; let widthArr = []; // 將各列的路徑信息及列寬分別放到數(shù)組中 for (let i = 0; i < underlayCols.length; i++) { paths.push(underlayCols[i].path); // 列寬放入數(shù)組前,使用excelColumnWidthProcess函數(shù)提前處理 widthArr.push(excelColumnWidthProcess(underlayCols[i].width)); } return { // excel使用到的表頭數(shù)據(jù)沒有可以直接用到的,需要用到headersProcess進(jìn)行轉(zhuǎn)換 headers: paths.length > 0 ? headersProcess(paths, conf.depth) : [], widthList: widthArr, }; } // 表頭數(shù)據(jù)處理 // 從最上面的界面表格的表頭樹形結(jié)構(gòu)數(shù)據(jù)可以看到,有6個(gè)底層列,數(shù)據(jù)結(jié)構(gòu)有2層,轉(zhuǎn)為excel數(shù)據(jù)格式的 // 話,就需要轉(zhuǎn)為2行6列。即表頭層級(jí)有多少,這里就轉(zhuǎn)為多少行,表頭底層字段有多少,這里就有多少列。 function headersProcess(pathList = [], depth = 1) { let headers = []; for (let i = 0; i < depth; i++) { headers.push([]); } let paths = []; for (let i = 0; i < pathList.length; i++) { let arr = pathList[i].split("_"); for (let j = arr.length; j < depth; j++) { arr.push(""); } paths.push(arr); } for (let i = 0; i < depth; i++) { for (let j = 0; j < paths.length; j++) { headers[i].push(paths[j][i]); } } return headers; } // 列寬處理 // 寬度數(shù)據(jù)需要轉(zhuǎn)為數(shù)字,同時(shí)前端的寬度與excel中的列寬存在較大的區(qū)別,所以此處除以了5 function excelColumnWidthProcess(width) { let result = 40; if (emptyCheck(width)) { if (getDataType(width) === "string") { if (width.includes("px")) { width = width.replace("px", ""); width = parseFloat(width); result = parseInt(width / 5); } } else if (getDataType(width) === "number") { result = parseInt(width / 5); } } return result; };
2.2 處理結(jié)果
// 得到的headers處理結(jié)果 [ ['地區(qū)', '資金歸集額', '省對(duì)下調(diào)入', '省對(duì)下調(diào)入', '本級(jí)調(diào)入', '本級(jí)調(diào)入'], ['', '', '資金', '占比', '資金', '占比'] ]
3、單元格樣式處理函數(shù)
// 單元格樣式處理,按需設(shè)置 const titleStyleProcess = function (sheet, index) { const titleCell = sheet.getRow(index); titleCell.eachCell({ includeEmpty: true }, (cell, colNumber) => { titleCell.getCell(colNumber).fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FFF5F7FA" }, }; titleCell.getCell(colNumber).border = { top: { style: "thin" }, left: { style: "thin" }, bottom: { style: "thin" }, right: { style: "thin" }, }; }); titleCell.height = 30; titleCell.font = { name: "黑體", bold: true, size: 14, color: { argb: "FF999999", }, }; // // 設(shè)置第一行的對(duì)齊方式(水平垂直) titleCell.alignment = { vertical: "middle", horizontal: "center", }; return titleCell; };
4、表頭數(shù)據(jù)填充及樣式設(shè)置
// 設(shè)置表頭的方法還有 worksheet.columns = headers等方式,但似乎只適用于只有一層的表頭,也可能是 // 自己沒有找對(duì)方法。在排除直接設(shè)置的方式后,直接將表頭數(shù)據(jù)當(dāng)做普通行數(shù)據(jù)進(jìn)行處理。同時(shí)進(jìn)行行樣式設(shè) // 置,此時(shí)也就可以理解為表頭樣式設(shè)置。 function headerFillAndSet(headers = [], widthList = [], worksheet) { for (let i = 1; i <= headers.length; i++) { worksheet.getRow(i).values = headers[i - 1]; titleStyleProcess(worksheet, i); } for (let i = 1; i <= widthList.length; i++) { worksheet.getColumn(i).width = widthList[i - 1]; } }
5、橫向單元格合并處理
// Excel的列名集合,用于合并單元格時(shí)定位單元格位置 const colNames = [ "A", "B", ..., "Y", "Z", "AA", "AB", ..., "AY", "AZ", .... ]; // function sheetMergesForCross(worksheet, headers) { for (let i = 0; i < headers.length; i++) { let arr = [null, null]; for (let j = 1; j <= headers[i].length; j++) { if (headers[i][j - 1] === headers[i][j]) { // 前后元素相同,表示可以合并 if (!emptyCheck(arr[0]) && emptyCheck(headers[i][j - 1])) { arr[0] = colNames[j - 1] + (i + 1); } arr[1] = colNames[j] + (i + 1); } else { // 前后元素不相同,j是從1開始,所以表示沒有相同列或者此次相同列已結(jié)束 if (emptyCheck(arr[0]) && emptyCheck(arr[1])) { // arr[0]或者arr[1]為空,表示相鄰元素不同,均有值表示有相同列,arr[1]便是最后一個(gè)相同的列 worksheet.mergeCells(arr[0] + ":" + arr[1]); } arr = [null, null]; // 相鄰元素不同,arr重置,準(zhǔn)備下一批可合并元素 } } } }
6、縱向單元格合并處理
function sheetMergesForVertical(worksheet, headers) { let col = headers[0]; // 第一層循環(huán)具體元素 for (let i = 0; i < col.length; i++) { let sd = ""; // 開始元素 let ed = ""; // 結(jié)束元素 // 第二層循環(huán),比較層級(jí)不同下標(biāo)相同的元素 for (let j = 1; j < headers.length; j++) { if (headers[j][i] === "") { // 元素為空,表示可與上層元素合并 sd = emptyCheck(sd) ? sd : colNames[i] + j; ed = colNames[i] + (j + 1); } } if (emptyCheck(sd) && emptyCheck(ed)) { worksheet.mergeCells(sd + ":" + ed); } } }
7、添加數(shù)據(jù)集
function worksheetDataProcess(columns = [], dataList = [], worksheet = null) { let len = 1; for (let i = 0; i < columns.length; i++) { len = len < columns[i].level ? columns[i].level : len; } for (let i = 0; i < dataList.length; i++) { let list = []; for (let j = 0; j < columns.length; j++) { list.push(dataList[i][columns[j].prop]); } worksheet.getRow(len + i + 1).values = list; } }
五、調(diào)用導(dǎo)出函數(shù)
// conf = { // baseCols: baseCols, 表頭信息數(shù)組 // depth: 2, // 表頭最大層級(jí)數(shù) // fileName: 'xxxx', 導(dǎo)出文件名、sheet名等,自定 // .... 其他自定義屬性 // } // dataList 需要導(dǎo)出的數(shù)據(jù)集 downloadExcel(dataList, conf)
總結(jié)
到此這篇關(guān)于vue2項(xiàng)目使用exceljs多表頭導(dǎo)出功能的文章就介紹到這了,更多相關(guān)vue2 exceljs多表頭導(dǎo)出內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue編寫可顯示周和月模式的日歷 Vue自定義日歷內(nèi)容的顯示
這篇文章主要為大家詳細(xì)介紹了Vue編寫可顯示周和月模式的日歷,Vue自定義日歷內(nèi)容的顯示,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06vue3組合式API中setup()概念和reactive()函數(shù)的用法
這篇文章主要介紹了vue3組合式API中setup()概念和reactive()函數(shù)的用法,接下來(lái)的事件,我將帶著你從淺到深分析為什么我們需要學(xué)習(xí)組合式API以及我們的setup()函數(shù)作為入口函數(shù)的一個(gè)基本的使用方式,需要的朋友可以參考下2023-03-03vue3父子傳值實(shí)現(xiàn)彈框功能的示例詳解
這篇文章主要為大家詳細(xì)介紹了vue3如何利用父子傳值實(shí)現(xiàn)彈框功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12vue實(shí)現(xiàn)購(gòu)物車結(jié)算功能
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)購(gòu)物車結(jié)算功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06解決vue單頁(yè)使用keep-alive頁(yè)面返回不刷新的問(wèn)題
下面小編就為大家分享一篇解決vue單頁(yè)使用keep-alive頁(yè)面返回不刷新的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03vue axios重復(fù)點(diǎn)擊取消上一次請(qǐng)求封裝的方法
這篇文章主要介紹了vue axios重復(fù)點(diǎn)擊取消上一次請(qǐng)求封裝的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06Vue?Router?返回后記住滾動(dòng)條位置的實(shí)現(xiàn)方法
使用?Vue?router?創(chuàng)建?SPA(Single?Page?App),往往有這種需求:首頁(yè)是列表頁(yè),點(diǎn)擊列表項(xiàng)進(jìn)入詳情頁(yè),在詳情頁(yè)點(diǎn)擊返回首頁(yè)后,希望看到的是,首頁(yè)不刷新,并且滾動(dòng)條停留在之前的位置,這篇文章主要介紹了Vue?Router?返回后記住滾動(dòng)條位置的實(shí)現(xiàn)方法,需要的朋友可以參考下2023-09-09