最近要对项目做一个导出excel的功能,以前的这种功能是后端提供一个数据流,前端点击直接下载即可。可是这次跟后端沟通得到的最终结果是,后端提供数据,前端导出excel。导出的excel不仅要有数据还有美观。这可是有挺大的挑战。接下来看看下面在导出excel中使用的三种不同方法和遇见的坑吧~🫠🫠🫠 最终效果:

image.png

使用xlsx库

一、安装xlsx库

首先我们需要在vue3的项目中安装xlsx库。可以使用npm 或者 pnpm来进行安装。


bash

代码解读

复制代码

npm install xlsx


bash

代码解读

复制代码

pnpm install xlsx

如果需要设置excel的样式,还需要安装xlsx-style库:


bash

代码解读

复制代码

pnpm install xlsx-style

二、在vue组件中引入xlsx库

需要引入xlsx库才可以在代码中使用方法和函数


javascript

代码解读

复制代码

import * as XLSX from 'xlsx'; // 如果需要设置样式,则引入xlsx-style // import XLSXStyle from 'xlsx-style';

获取table元素导出

这里我们需要获取导出的excel中数据的table表格。如果需要为表格增加表头或者文字说明需要手动插入html元素。插入html元素就会造成原本页面中的表格也会被插入一个原本不存在的表头,解决方法是使用v-if控制两个相同的table并只显示其中一个。

screenshots.gif


javascript

代码解读

复制代码

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; };

下面是导出示例:

image.png

使用表格数据导出

下面的代码中我们除了增加表头还增加了这个表格的文字说明,放在了表格的第一行。增加表格的文字说明我们需要注意的是如果需要增加表格文字说明,就需要我们在原有的数据的第一行插入一个空对象。


js

代码解读

复制代码

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; };

下面是导出示例:

image.png

如果你需要修改excel样式,需要导入xlsx-style库并引用。 引入之后你会发现下面这样的错误。


js

代码解读

复制代码

import * as XLSX from "xlsx-style";

image.png

按照其他博主的解决办法:修改源代码


javascript

代码解读

复制代码

// 在\node_modules\xlsx-style\dist\cpexcel.js 807行 var cpt = require('./cpt' + 'able'); 改为 var cpt = cptable;

image.png

改完后重启发现了新的问题:

image.png

经过多次修改始终没有办法解决这种方法放弃了🥹🥹🥹

查找资料发现网上还有针对veu3 vite的xlsx-style-vite,我们尝试下载并引用它


bash

代码解读

复制代码

pnpm i xlsx-style-vite


js

代码解读

复制代码

import * as XLSX from 'xlsx-style-vite';

重启发现还是存在问题果断方式,尝试下一种方法。

image.png

使用exceljs

一、安装exceljs库

首先我们需要在vue3的项目中安装exceljs库和file-saver

👇👇👇👇👇👇

exceljs 中文文档


bash

代码解读

复制代码

pnpm install xlsx pnpm install file-saver

二、创建工作簿


js

代码解读

复制代码

// 配合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);

三、自定义表头和表格文字说明


js

代码解读

复制代码

// 整理数据 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 }; // 居中对齐 } });

这步做完导出就可以看见表头和文字说明了

image.png

四、根据需求设置样式

设置标题样式


js

代码解读

复制代码

// 设置标题行样式 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每三行合并的第一列合并,并设置自动换行和边框


js

代码解读

复制代码

//将数据写入工作表 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 }; // 设置字体大小 }); });

内容居中显示


js

代码解读

复制代码

// 遍历整个工作表的所有单元格,确保第一列的单元格都启用了自动换行 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' }; } }); });

自适应宽度


js

代码解读

复制代码

// 根据计算的最大宽度设置列宽 columnWidths.forEach((width, index) => { sheet1.columns[index].width = width < 10 ? 10 : width; // 最小宽度设为10 });

导出表格文件


js

代码解读

复制代码

// 导出表格文件 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));

完整代码演示及导出实例


js

代码解读

复制代码

// 导出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)); };

image.png

文章就到此结束了,大家如果感兴趣可以留言关注,我们一起学习😄😄😄

👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋👋

原文链接:https://juejin.cn/post/7470349713024352306

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐