提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
因为项目需求要用到前端导出功能,粗略比较了几个导出插件之后,决定使用exceljs插件,至少能够满足样式、数据格式等的需求。
鉴于个人水平和时间限制,目前只是初步实现了组件化,将表头、数据按要求格式传递到组件中即可导出需要的excel文件。代码质量、实现思路存在一定的缺陷,后续有时间会继续优化,如果大家有更好的实现,感谢留言!
组件导出文件效果如下:
一级表头
二级表头
三级表头
提示:以下是本篇文章正文内容,下面案例可供参考
一、插件安装
yarn add exceljs
yarn add file-saver
二、数据准备
1、前台表格表头数据
const data = [
{
prop: "AA",
label: "地区",
minWidth: "150px"
},
{
prop: "BB",
label: "归集额",
minWidth: "100px"
},
{
label: "对下调入",
children: [
{
prop: "CC",
label: "资金",
minWidth: "90px"
},
{
prop: "DD",
label: "占比",
minWidth: "80px"
},
],
},
{
label: "本级调入",
children: [
{
prop: "EE",
label: "资金",
minWidth: "100px"
},
{
prop: "FF",
label: "占比",
minWidth: "80px"
}
]
}
]
2、传递到组件前的数据处理
// 表头数据预处理函数
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++) {
// 获取列的路径,用于后面表头数据格式处理
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, // 当前字段的prop值
path: columns[i].path, // 当前字段的路径
level: level, // 当前列所处层级
width: columns[i].width || columns[i].minWidth, // 列宽
});
delete columns[i].path;
}
}
}
columnsProcess(columns, 0);
return {
columns: columns, // 前台表格表头用到的数据
depth: maxLevel, // 树形结构表头的最大层级数--导出功能所需
baseCols: baseCols // 底层字段数据集--导出功能所需
};
};
得到的baseCols数据
//得到的baseCols数据
[
{
level: 1,
path: "地区",
prop: "AA",
width: "150px"
},
{
level: 1,
path: "资金归集额",
prop: "BB",
width: "100px"
},
{
level: 2,
path: "对下调入_资金",
prop: "CC",
width: "90px"
},
{
level: 2,
path: "对下调入_占比",
prop: "DD",
width: "80px"
},
{
level: 2,
path: "本级调入_资金",
prop: "DD",
width: "100px"
},
{
level: 2,
path: "本级调入_占比",
prop: "DD",
width: "80px"
},
]
三、插件引入
1、新建ExcelJS.js文件
2、ExcelJS.js文件中引入需要的库
import ExcelJS from "exceljs";
import FileSaver from "file-saver";
四、导出前数据处理
1、按exceljs格式创建导出函数
conf参数包含的就是上面获取到的baseCols和depth数据,及自定义的其他属性,如文件名等
/**
* @description: excel导出
* @param dataList 需要导出的数据集
* @param conf 导出函数配置数据,包括上面处理得到的底层字段信息baseCols,表头层级数据depth,文件名fileName
* @return
*/
export const downloadExcel = function (dataList = [], conf = {}) {
const workbook = new ExcelJS.Workbook();
// 设置信息
workbook.creator = "Me";
workbook.title = conf.fileName;
workbook.created = new Date();
workbook.modified = new Date();
// 创建工作表
const worksheet = workbook.addWorksheet(conf.fileName)
// 表头信息处理
let columns = columnsPreprocess(conf)
// 表头数据填充及样式设置
headerFillAndSet(columns.headers, columns.widthList, worksheet)
// 横向单元格合并处理
sheetMergesForCross(worksheet, columns.headers);
// 纵向单元格合并处理
sheetMergesForVertical(worksheet, columns.headers);
// 添加数据集
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、表头及列宽数据预处理
2.1 处理逻辑
// 获取表头及列宽数据集
function columnsPreprocess(conf = {}) {
let underlayCols = conf.baseCols;
let paths = [];
let widthArr = [];
// 将各列的路径信息及列宽分别放到数组中
for (let i = 0; i < underlayCols.length; i++) {
paths.push(underlayCols[i].path);
// 列宽放入数组前,使用excelColumnWidthProcess函数提前处理
widthArr.push(excelColumnWidthProcess(underlayCols[i].width));
}
return {
// excel使用到的表头数据没有可以直接用到的,需要用到headersProcess进行转换
headers: paths.length > 0 ? headersProcess(paths, conf.depth) : [],
widthList: widthArr,
};
}
// 表头数据处理
// 从最上面的界面表格的表头树形结构数据可以看到,有6个底层列,数据结构有2层,转为excel数据格式的 // 话,就需要转为2行6列。即表头层级有多少,这里就转为多少行,表头底层字段有多少,这里就有多少列。
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;
}
// 列宽处理
// 宽度数据需要转为数字,同时前端的宽度与excel中的列宽存在较大的区别,所以此处除以了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 处理结果
// 得到的headers处理结果
[
['地区', '资金归集额', '省对下调入', '省对下调入', '本级调入', '本级调入'],
['', '', '资金', '占比', '资金', '占比']
]
3、单元格样式处理函数
// 单元格样式处理,按需设置
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",
},
};
// // 设置第一行的对齐方式(水平垂直)
titleCell.alignment = {
vertical: "middle",
horizontal: "center",
};
return titleCell;
};
4、表头数据填充及样式设置
// 设置表头的方法还有 worksheet.columns = headers等方式,但似乎只适用于只有一层的表头,也可能是 // 自己没有找对方法。在排除直接设置的方式后,直接将表头数据当做普通行数据进行处理。同时进行行样式设 // 置,此时也就可以理解为表头样式设置。
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的列名集合,用于合并单元格时定位单元格位置
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开始,所以表示没有相同列或者此次相同列已结束
if (emptyCheck(arr[0]) && emptyCheck(arr[1])) { // arr[0]或者arr[1]为空,表示相邻元素不同,均有值表示有相同列,arr[1]便是最后一个相同的列
worksheet.mergeCells(arr[0] + ":" + arr[1]);
}
arr = [null, null]; // 相邻元素不同,arr重置,准备下一批可合并元素
}
}
}
}
6、纵向单元格合并处理
function sheetMergesForVertical(worksheet, headers) {
let col = headers[0];
// 第一层循环具体元素
for (let i = 0; i < col.length; i++) {
let sd = ""; // 开始元素
let ed = ""; // 结束元素
// 第二层循环,比较层级不同下标相同的元素
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、添加数据集
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;
}
}
五、调用导出函数
// conf = {
// baseCols: baseCols, 表头信息数组
// depth: 2, // 表头最大层级数
// fileName: 'xxxx', 导出文件名、sheet名等,自定
// .... 其他自定义属性
// }
// dataList 需要导出的数据集
downloadExcel(dataList, conf)
总结
以上代码基本可以直接使用,如果有人有更好的实现,若可以交流,十分荣幸!