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í)間會繼續(xù)優(yōu)化,如果大家有更好的實(shí)現(xiàn),感謝留言!
組件導(dǎo)出文件效果如下:
- 一級表頭

- 二級表頭

- 三級表頭

提示:以下是本篇文章正文內(nèi)容,下面案例可供參考
一、插件安裝
yarn add exceljs yarn add file-saver
二、數(shù)據(jù)準(zhǔn)備
1、前臺表格表頭數(shù)據(jù)
const data = [
{
prop: "AA",
label: "地區(qū)",
minWidth: "150px"
},
{
prop: "BB",
label: "歸集額",
minWidth: "100px"
},
{
label: "對下調(diào)入",
children: [
{
prop: "CC",
label: "資金",
minWidth: "90px"
},
{
prop: "DD",
label: "占比",
minWidth: "80px"
},
],
},
{
label: "本級調(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)前列所處層級
width: columns[i].width || columns[i].minWidth, // 列寬
});
delete columns[i].path;
}
}
}
columnsProcess(columns, 0);
return {
columns: columns, // 前臺表格表頭用到的數(shù)據(jù)
depth: maxLevel, // 樹形結(jié)構(gòu)表頭的最大層級數(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: "對下調(diào)入_資金",
prop: "CC",
width: "90px"
},
{
level: 2,
path: "對下調(diào)入_占比",
prop: "DD",
width: "80px"
},
{
level: 2,
path: "本級調(diào)入_資金",
prop: "DD",
width: "100px"
},
{
level: 2,
path: "本級調(diào)入_占比",
prop: "DD",
width: "80px"
},
]三、插件引入
1、新建ExcelJS.js文件
2、ExcelJS.js文件中引入需要的庫
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,表頭層級數(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列。即表頭層級有多少,這里就轉(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ū)', '資金歸集額', '省對下調(diào)入', '省對下調(diào)入', '本級調(diào)入', '本級調(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è)置第一行的對齊方式(水平垂直)
titleCell.alignment = {
vertical: "middle",
horizontal: "center",
};
return titleCell;
};4、表頭數(shù)據(jù)填充及樣式設(shè)置
// 設(shè)置表頭的方法還有 worksheet.columns = headers等方式,但似乎只適用于只有一層的表頭,也可能是 // 自己沒有找對方法。在排除直接設(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),比較層級不同下標(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;
}
}8、公共函數(shù)補(bǔ)充
// 非空判斷
export const emptyCheck = function (val) {
if (val === null || val === "" || val === undefined) {
return false;
}
let dataType = getDataType(val);
switch (dataType) {
case "string":
return val.trim().length !== 0;
case "array":
return val.length !== 0;
case "object":
return Object.keys(val).length !== 0;
}
return true;
};
// 獲取數(shù)據(jù)類型
export const getDataType = function (val) {
return Object.prototype.toString
.call(val)
.replace(/^\[object (\S+)\]$/, "$1")
.toLowerCase();
};五、調(diào)用導(dǎo)出函數(shù)
// conf = {
// baseCols: baseCols, 表頭信息數(shù)組
// depth: 2, // 表頭最大層級數(shù)
// fileName: 'xxxx', 導(dǎo)出文件名、sheet名等,自定
// .... 其他自定義屬性
// }
// dataList 需要導(dǎo)出的數(shù)據(jù)集
downloadExcel(dataList, conf)總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Vue 解決在element中使用$notify在提示信息中換行問題
這篇文章主要介紹了Vue 解決在element中使用$notify在提示信息中換行問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
vue項(xiàng)目打包之后在本地運(yùn)行的實(shí)現(xiàn)方法
這篇文章主要介紹了vue項(xiàng)目打包之后在本地運(yùn)行的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
vue3中的數(shù)據(jù)劫持的最新實(shí)現(xiàn)方案的proxy示例詳解
Vue3中使用Proxy實(shí)現(xiàn)數(shù)據(jù)劫持,解決了Vue2中數(shù)組和對象劫持的遺留問題,Proxy可以修改某些操作的默認(rèn)行為,通過get和set方法實(shí)現(xiàn)數(shù)據(jù)的劫持和保護(hù)機(jī)制,感興趣的朋友跟隨小編一起看看吧2024-11-11
Vue中使用Echarts可視化圖表寬度自適應(yīng)的完美解決方案
這篇文章主要介紹了Vue中使用Echarts可視化圖表,寬度自適應(yīng)解決方案,我的解決方案是,在放置Echarts的容器(div)外層再套一層容器(div),外層容器寬度固定設(shè)置手機(jī)屏幕寬,感興趣的朋友跟隨小編一起看看吧2022-09-09
VUE簡單的定時(shí)器實(shí)時(shí)刷新的實(shí)現(xiàn)方法
這篇文章主要介紹了VUE簡單的定時(shí)器實(shí)時(shí)刷新的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01

