此封装具有字体样式、内容居中、多行表头、单元格合并、多表导出功能。
一、开发背景
为响应客户需求的字体样式,试了很多的插件
最终确定用exceljs
github: https://github.com/exceljs/exceljs/tree/v3.10.0
二、封装
安装exceljs
npm install exceljs
导入exceljs
import ExcelJS from 'exceljs'
放在全局
// exceljs
// github: https://github.com/exceljs/exceljs/tree/v3.10.0
const ExcelJS = require("exceljs");
import FileSaver from "file-saver";
const { i18n } = require("@/utils/i18n.js");
import BUSINESS_CONFIG from "@/business_config.js";
require("script-loader!xlsx/dist/xlsx.core.min");
const twoHeaderRows = 65534;
const oneHeaderRows = 65535;
var rows = [];
export default {
install(Vue) {
Vue.prototype.$exportExcelJs = async function (
columns,
dataList,
fileName,
fileType
) {
// 工作表下标
var sheetIndex = 0;
// 创建工作簿
var workbook = new ExcelJS.Workbook();
// 设置工作簿属性
workbook.creator = "Me";
workbook.lastModifiedBy = "Her";
workbook.created = new Date(1985, 8, 30);
workbook.modified = new Date();
workbook.lastPrinted = new Date(2016, 9, 27);
// 将工作簿日期设置为 1904 年日期系统
workbook.properties.date1904 = true;
// 所有工作表
var worksheetAll = [];
// 是否两行标题
let isTwo = false;
for (var index = 0; index < columns.length; index++) {
if ("children" in columns[index]) {
isTwo = true;
break;
}
}
// 递归分表
recursiveSharding(
isTwo,
dataList,
worksheetAll,
workbook,
sheetIndex,
columns
);
// 转成buffer
const buffer = await workbook.xlsx.writeBuffer();
// file-saver在保存文件时,需要把Buffer/Blob转换为Blob URL
const blob = new Blob([buffer], {
type: "application/vnd.openxmlformats",
});
var defaultTitle = fileName || i18n.t("列表");
if (fileType == ".xls") {
FileSaver.saveAs(blob, `${defaultTitle}.xls`);
} else {
FileSaver.saveAs(blob, `${defaultTitle}.xls`);
}
this.$store.commit("saveLoadShow", false);
};
},
};
之所以用递归创建表是因为.xls的表超过65536行不显示。
三、递归分表
/* 递归分表
isTwo 是否两行标题,dataList 数据源,
worksheetAll 所有工作表, workbook 工作簿,
sheetIndex 工作表下标,columns字段配置
*/
function recursiveSharding(
isTwo,
dataList,
worksheetAll,
workbook,
sheetIndex,
columns
) {
if (
(isTwo && dataList.length - twoHeaderRows * sheetIndex >= 0) ||
dataList.length - oneHeaderRows * sheetIndex >= 0
) {
// 获取起始下标、终点下标
var startIndex = oneHeaderRows * sheetIndex;
var endIndex = oneHeaderRows * (sheetIndex + 1);
if (isTwo) {
startIndex = twoHeaderRows * sheetIndex;
endIndex = twoHeaderRows * (sheetIndex + 1);
}
// 添加工作表
worksheetAll.push(workbook.addWorksheet(`Sheet${sheetIndex + 1}`));
// 按 name 提取工作表
// var worksheet = workbook.getWorksheet("My Sheet");
// 不能复制标题,要重新获取标题,不然标题会被第二行覆盖,也没有合并居中
const { formatColumns } = getColumns(columns, worksheetAll[sheetIndex]);
worksheetAll[sheetIndex].columns = formatColumns;
rows = dataList.slice(startIndex, endIndex);
worksheetAll[sheetIndex].addRows(rows);
worksheetAll[sheetIndex].columns = setWidth(worksheetAll[sheetIndex]);
setCellFontStyle(isTwo, worksheetAll[sheetIndex]);
sheetIndex += 1;
recursiveSharding(
isTwo,
dataList,
worksheetAll,
workbook,
sheetIndex,
columns
);
} else {
return;
}
}
四、获取标题行
// 获取标题行
function getColumns(columns, worksheet) {
var tHeaderChildren = [];
var isHaveChildren = false;
let formatColumns = [];
for (var index = 0; index < columns.length; index++) {
if ("children" in columns[index]) {
isHaveChildren = true;
break;
}
}
if (isHaveChildren) {
// 设置第一行标题
let startChar = "A";
let endChar = "B";
columns.forEach((item) => {
// 这里是定义Excel表格里面的表头和内容
if ("children" in item) {
endChar = startChar.charCodeAt() + item.children.length - 1;
endChar = String.fromCharCode(endChar);
// 合并,保持长度一致
worksheet.mergeCells(`${startChar}1:${endChar}1`);
// 设置合并单元格内容
worksheet.getCell(`${startChar}1`).value = item.title;
// 设置下一个合并起点
startChar = endChar.charCodeAt() + 1;
startChar = String.fromCharCode(startChar);
item.children.forEach((ele) => {
tHeaderChildren.push(ele.title);
formatColumns.push({
header: ele.title,
key: ele.dataIndex,
width: 10,
});
});
}
});
// 设置第二行副标题
worksheet.getRow(2).values = tHeaderChildren;
return { formatColumns: formatColumns, isTwo: true };
} else {
// 添加列标题并定义列键和宽度
// 注意:这些列结构仅是构建工作簿的方便之处,除了列宽之外,它们不会完全保留。
columns.forEach((item) => {
formatColumns.push({
header: item.title,
key: item.dataIndex,
width: 10,
});
});
return { formatColumns: formatColumns, isTwo: false };
}
}
五、设置worksheet每列的最大宽度
/*设置worksheet每列的最大宽度*/
function setWidth(worksheet) {
/*以第一行为初始值*/
var result = [];
worksheet.columns.forEach((item) => {
result.push(item.width);
});
worksheet._rows.forEach((row) => {
if (Array.isArray(row._cells)) {
row._cells.forEach((cell, index) => {
let cellValue = cell.value;
if (cellValue == null) {
cell.tempWidth = 10;
} else if (cellValue.toString().charCodeAt(0) > 255) {
// 中文
cell.tempWidth = cellValue.toString().length * 2 + 2;
} else {
cell.tempWidth = cellValue.toString().length + 4;
}
// 每列的最大宽度
if (result[index] < cell.tempWidth) {
result[index] = cell.tempWidth;
}
// 居中对齐
cell.alignment = { vertical: "middle", horizontal: "center" };
});
}
});
// 赋值给标题行
worksheet.columns.forEach((item, index) => {
if (item.width < result[index]) {
item.width = result[index];
}
});
return worksheet.columns;
}
六、根据环境配置字体样式
// 设置字体样式
function setCellFontStyle(isTwo, worksheet) {
// 4.0字体样式
var font = {
name: "宋体",
family: 4,
size: 12,
};
// 美格字体样式
if (BUSINESS_CONFIG.isMgBusiness()) {
font = {
name: "宋体",
family: 4,
size: 10,
};
}
// 根据环境配置字体样式
worksheet._rows.forEach((row, index) => {
row.font = {
name: font.name,
family: font.family,
size: font.size,
bold: isTwo
? [0, 1].includes(index)
? true
: false
: index == 0
? true
: false,
};
});
}
七、打开.xls报错问题
可看我另一篇文章
【已解决】xls“的文件格式和扩展名不匹配。文件可能已损坏或不安全。除非您信任其来源,否则请勿打开。是否仍要打开它?-CSDN博客