最近要对项目做一个导出excel的功能,以前的这种功能是后端提供一个数据流,前端点击直接下载即可。可是这次跟后端沟通得到的最终结果是,后端提供数据,前端导出excel。导出的excel不仅要有数据还有美观。这可是有挺大的挑战。接下来看看下面在导出excel中使用的三种不同方法和遇见的坑吧~🫠🫠🫠 最终效果:
使用xlsx库
一、安装xlsx库
首先我们需要在vue3的项目中安装xlsx库。可以使用npm 或者 pnpm来进行安装。
npm install xlsx
pnpm install xlsx
如果需要设置excel的样式,还需要安装xlsx-style库:
pnpm install xlsx-style
二、在vue组件中引入xlsx库
需要引入xlsx库才可以在代码中使用方法和函数
import * as XLSX from 'xlsx';
// 如果需要设置样式,则引入xlsx-style
// import XLSXStyle from 'xlsx-style';
获取table元素导出
这里我们需要获取导出的excel中数据的table表格。如果需要为表格增加表头或者文字说明需要手动插入html元素。
插入html元素就会造成原本页面中的表格也会被插入一个原本不存在的表头,解决方法是使用v-if控制两个相同的table并只显示其中一个。
const exportExcel = () => {
// 选择页面中ID为'my-table'的表格元素
var table = document.querySelector('#my-table');
// 尝试获取表格的thead部分,如果没有thead,则创建一个新的thead元素
var thead = table.querySelector('thead') || document.createElement('thead');
// 创建一个新的tr(表格行)元素
var tr = document.createElement('tr');
// 创建一个新的th(表头单元格)元素
var th = document.createElement('th');
// 设置th的内容为"场所管家应用情况统计表"
th.textContent = '场所管家应用情况统计表';
// 设置字体加粗
th.style.fontWeight = 'bold';
// 设置文本水平居中显示
th.style.textAlign = 'center';
// 设置文本垂直居中显示(但通常th标签内的内容默认就会垂直居中,特别是在Excel中)
th.style.verticalAlign = 'middle';
// 将th添加到新创建的tr元素中
tr.appendChild(th);
// 将包含标题的tr插入到thead的第一个位置
thead.insertBefore(tr, thead.firstChild);
// 如果表格原先没有thead,则将创建或修改后的thead添加到表格中
if (!table.querySelector('thead')) {
table.appendChild(thead);
}
// 使用XLSX库将表格转换为工作簿对象
var wb = XLSX.utils.table_to_book(table);
// 将工作簿写入为二进制数组格式的xlsx文件
var wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
try {
// 使用FileSaver.js保存文件,文件名为"应用情况说明.xlsx"
FileSaver.saveAs(new Blob([wbout], { type: 'application/octet-stream' }), '应用情况说明.xlsx');
} catch (e) {
// 捕获并打印错误信息到控制台(如果console存在的话)
if (typeof console !== 'undefined') console.error(e);
}
// 返回生成的工作簿的二进制数据
return wbout;
};
下面是导出示例:
使用表格数据导出
下面的代码中我们除了增加表头还增加了这个表格的文字说明,放在了表格的第一行。增加表格的文字
说明我们需要注意的是如果需要增加表格文字说明,就需要我们在原有的数据的第一行插入一个空对象。
const exportExcel = () => {
//设置表格文字说明
const excelHead = ref('场所管家应用情况统计表 至' + nowDate());
const data = tableData.value; // tableData.value就是需要导出的表格数据
const ws = XLSX.utils.json_to_sheet(data);
// 添加标题行(A1:R1),并准备合并它们
XLSX.utils.sheet_add_aoa(ws, [[excelHead.value]], { origin: 'A1' });
// 合并从 A1 到 R1 的单元格
if (!ws['!merges']) ws['!merges'] = [];
ws['!merges'].push({ s: { r: 0, c: 0 }, e: { r: 0, c: 17 } });
// 添加表头行(A2:R2)
XLSX.utils.sheet_add_aoa(
ws,
[
[
'表头','表头','表头','表头','表头','表头',
],
],
{ origin: 'A2' }
);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
// 这里我是将从第三行开始每三行的第一列合并成一行,如果你没有这个需求可以忽略
for (let rowIndex = 2; rowIndex < ws['!ref'].split(':')[1].replace(/[0-9]/g, '').length * tableData.value.length; rowIndex += 3) {
// 注意:这里我们假设你的数据不会超过Z列(即tableData数据的长度列),你可能需要根据你的实际数据调整这个逻辑
// 第一列是A列,对应的列索引是0(因为索引从0开始)
const startCell = XLSX.utils.encode_cell({ r: rowIndex, c: 0 }); // 起始单元格
const endCell = XLSX.utils.encode_cell({ r: rowIndex + 2, c: 0 }); // 结束单元格(每三行的最后一行)
// 合并单元格
if (!ws['!merges']) ws['!merges'] = [];
ws['!merges'].push({ s: startCell, e: endCell });
// 可选:如果你想要在合并后的单元格中显示某些数据,你可以在合并之前将数据写入起始单元格
// 例如,你可以将三行中的第一行的数据写入起始单元格(这里省略了,因为你已经有了数据)
// 但请注意,合并后的单元格中只会显示起始单元格的内容
}
// 输出版本号以确认 console.log(XLSX.version);
// 写入Excel文件
XLSX.writeFile(wb, excelHead.value + '.xlsx');
return;
};
下面是导出示例:
如果你需要修改excel样式,需要导入xlsx-style库并引用。 引入之后你会发现下面这样的错误。
import * as XLSX from "xlsx-style";
按照其他博主的解决办法:修改源代码
// 在\node_modules\xlsx-style\dist\cpexcel.js 807行
var cpt = require('./cpt' + 'able'); 改为 var cpt = cptable;
改完后重启发现了新的问题:
经过多次修改始终没有办法解决这种方法放弃了🥹🥹🥹
查找资料发现网上还有针对veu3 vite的xlsx-style-vite,我们尝试下载并引用它
pnpm i xlsx-style-vite
import * as XLSX from 'xlsx-style-vite';
重启发现还是存在问题果断方式,尝试下一种方法。
使用exceljs
一、安装exceljs库
首先我们需要在vue3的项目中安装exceljs库和file-saver库
👇👇👇👇👇👇
pnpm install xlsx
pnpm install file-saver
二、创建工作簿
// 配合exceljs使用
import FileSaver from 'file-saver';
import ExcelJS from 'exceljs';
// 创建工作簿
const workbook = new ExcelJS.Workbook();
// 添加工作表,名为sheet1
const sheet1 = workbook.addWorksheet('sheet1');
// 整理数据 (data是需要导出的excel数据)
const data = transformData(tableData.value);
三、自定义表头和表格文字说明
// 整理数据
const data = transformData(tableData.value);
// 获取表头所有键
const headers = Object.keys(data[0]);
// 初始化每列的最大宽度为标题行的宽度
let columnWidths = headers.map((header) => header.length);
// 使用 Array.from 方法生成数组
const describe = Array.from({ length: headers.length }, () => '场所管家');
// 将文字说明插入到第一行
const firstRow = sheet1.addRow(describe);
// 将标题写入第二行
const headerRow = sheet1.addRow(headers);
// 合并第一行的所有列
sheet1.mergeCells(`A1:${String.fromCharCode(64 + headers.length)}1`);
// 设置合并后的单元格样式
firstRow.eachCell((cell, index) => {
if (index === 1) {
// 只对合并后的第一个单元格设置样式
cell.font = { bold: true, size: 18 }; // 加粗字体
cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true }; // 居中对齐
}
});
这步做完导出就可以看见表头和文字说明了
四、根据需求设置样式
设置标题样式
// 设置标题行样式
headerRow.eachCell((cell, index) => {
cell.font = { bold: true };
cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true }; // 居中对齐,并启用自动换行
// 增加边框
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' },
};
});
将导出的excel每三行合并的第一列合并,并设置自动换行和边框
//将数据写入工作表
const mergeArr = []; // 记录要合并的单元格
data.forEach((row, rowIndex) => {
// 遍历数据数组,row为当前行的数据,rowIndex为当前行的索引
const rowPosition = rowIndex + 3; // 计算实际的行位置,因为前面有描述和标题两行,所以从第3行开始计算
const excelRow = sheet1.addRow(Object.values(row)); // 将当前行的数据(对象值)添加到工作表中,并获取该行的引用
// 每三行的第一列进行合并
if (rowIndex % 3 === 0) {
// 如果当前行是每组三行的第一个(即rowIndex能被3整除)
const startRow = rowPosition; // 定义合并区域的起始行号,等于当前行的实际位置
// 计算合并区域的结束行号,确保不超过数据的最大范围
const endRow = Math.min(rowPosition + 2, rowPosition + (data.length - rowIndex > 2 ? 2 : data.length - rowIndex - 1));
mergeArr.push({ startRow, endRow });
// 对合并后的单元格设置样式
// excelRow.getCell(1).alignment = { vertical: 'middle', wrapText: true }; // 设置合并后的单元格内容垂直居中对齐
}
// 为当前行的所有单元格设置自动换行和边框
excelRow.eachCell((cell) => {
cell.alignment = { vertical: 'middle' }; // 确保每个单元格都启用了自动换行
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' },
};
// 设置字体样式
cell.font = { size: 12 }; // 设置字体大小
});
});
内容居中显示
// 遍历整个工作表的所有单元格,确保第一列的单元格都启用了自动换行
sheet1.eachRow((row) => {
row.eachCell((cell, colNumber) => {
if (colNumber === 1) {
// 第一列(注意:colNumber从1开始)
cell.alignment = { ...cell.alignment, wrapText: true }; // 确保每个单元格都启用了自动换行
}
// 设置从第三列开始的所有单元格的内容居中显示
if (colNumber >= 3) {
cell.alignment = { ...cell.alignment, horizontal: 'center', vertical: 'middle' };
}
});
});
自适应宽度
// 根据计算的最大宽度设置列宽
columnWidths.forEach((width, index) => {
sheet1.columns[index].width = width < 10 ? 10 : width; // 最小宽度设为10
});
导出表格文件
// 导出表格文件
workbook.xlsx
.writeBuffer()
.then((buffer) => {
let file = new Blob([buffer], { type: 'application/octet-stream' });
FileSaver.saveAs(file, 'ExcelJS.xlsx');
})
.catch((error) => console.log('Error writing excel export', error));
完整代码演示及导出实例
// 导出excel文件
const exportExcel = () => {
// 创建工作簿
const workbook = new ExcelJS.Workbook();
// 添加工作表,名为sheet1
const sheet1 = workbook.addWorksheet('sheet1');
// 整理数据(data为导出的excel数据)
const data = transformData(tableData.value);
// 获取表头所有键
const headers = Object.keys(data[0]);
// 初始化每列的最大宽度为标题行的宽度
let columnWidths = headers.map((header) => header.length);
// 使用 Array.from 方法生成数组
const describe = Array.from({ length: headers.length }, () => '场所管家');
// 将文字说明插入到第一行
const firstRow = sheet1.addRow(describe);
// 将标题写入第二行
const headerRow = sheet1.addRow(headers);
// 合并第一行的所有列
sheet1.mergeCells(`A1:${String.fromCharCode(64 + headers.length)}1`);
// 设置合并后的单元格样式
firstRow.eachCell((cell, index) => {
if (index === 1) {
// 只对合并后的第一个单元格设置样式
cell.font = { bold: true, size: 18 }; // 加粗字体
cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true }; // 居中对齐
}
});
// 设置标题行样式
headerRow.eachCell((cell, index) => {
cell.font = { bold: true };
cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true }; // 居中对齐,并启用自动换行
// 增加边框
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' },
};
// 更新列宽
if (cell.text && cell.text.length > columnWidths[index - 1]) {
columnWidths[index - 1] = cell.text.length;
}
});
//将数据写入工作表
const mergeArr = []; // 记录要合并的单元格
data.forEach((row, rowIndex) => {
// 遍历数据数组,row为当前行的数据,rowIndex为当前行的索引
const rowPosition = rowIndex + 3; // 计算实际的行位置,因为前面有描述和标题两行,所以从第3行开始计算
const excelRow = sheet1.addRow(Object.values(row)); // 将当前行的数据(对象值)添加到工作表中,并获取该行的引用
// 每三行的第一列进行合并
if (rowIndex % 3 === 0) {
// 如果当前行是每组三行的第一个(即rowIndex能被3整除)
const startRow = rowPosition; // 定义合并区域的起始行号,等于当前行的实际位置
// 计算合并区域的结束行号,确保不超过数据的最大范围
const endRow = Math.min(rowPosition + 2, rowPosition + (data.length - rowIndex > 2 ? 2 : data.length - rowIndex - 1));
mergeArr.push({ startRow, endRow });
// 对合并后的单元格设置样式
// excelRow.getCell(1).alignment = { vertical: 'middle', wrapText: true }; // 设置合并后的单元格内容垂直居中对齐
}
// 为当前行的所有单元格设置自动换行和边框
excelRow.eachCell((cell) => {
cell.alignment = { vertical: 'middle' }; // 确保每个单元格都启用了自动换行
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' },
};
// 设置字体样式
cell.font = { size: 12 }; // 设置字体大小
});
});
// 遍历整个工作表的所有单元格,确保第一列的单元格都启用了自动换行
sheet1.eachRow((row) => {
row.eachCell((cell, colNumber) => {
if (colNumber === 1) {
// 第一列(注意:colNumber从1开始)
cell.alignment = { ...cell.alignment, wrapText: true }; // 确保每个单元格都启用了自动换行
}
// 设置从第三列开始的所有单元格的内容居中显示
if (colNumber >= 3) {
cell.alignment = { ...cell.alignment, horizontal: 'center', vertical: 'middle' };
}
});
});
// 开始遍历要合并的数组,进行遍历
mergeArr.forEach((item) => {
sheet1.mergeCells(`A${item.startRow}:A${item.endRow}`);
});
sheet1.getColumn(2).width = 15;
sheet1.getColumn(3).width = 10;
// 导出表格文件
workbook.xlsx
.writeBuffer()
.then((buffer) => {
let file = new Blob([buffer], { type: 'application/octet-stream' });
FileSaver.saveAs(file, 'ExcelJS.xlsx');
})
.catch((error) => console.log('Error writing excel export', error));
};
使用 xlsx 库和使用 exceljs 库各有优缺点:
-
使用 xlsx 库:
- 优点:如果您的项目中已经熟悉并使用了相关的库和技术,可能更容易上手。
- 缺点:在修改样式时可能会遇到一些难以解决的问题。
-
使用 exceljs 库:
- 优点:提供了更丰富和灵活的样式设置选项,对于需要复杂样式的导出需求可能更适用。
- 缺点:安装和配置相对复杂一些。
具体哪种方法更好取决于您的项目需求和个人技术偏好。如果您对样式要求不高,且项目中已经有相关依赖,xlsx 库可能是个不错的选择;如果您需要更精细的样式控制,exceljs 库可能更适合。
文章就到此结束了,大家如果感兴趣可以留言关注,我们一起学习😄😄😄
👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋