一、为什么前端导出Excel这么难?
最近在项目中遇到了一个需求:将el-table中的数据导出为Excel,并且要在表格下方插入图片。
💡听起来很简单?实际开发中却遇到了这些问题:
图片处理难题:前端如何将图片插入到Excel中?
跨域限制:图片来自CDN,存在跨域问题
性能瓶颈:当数据量达到5000条时,浏览器直接卡死
格式混乱:导出的Excel列宽不一致,样式丑陋
传统的解决方案是让后端生成Excel文件,但这样会增加服务器压力,用户体验也不好。难道前端真的不能优雅地解决这个问题吗?
当然可以! 经过一周的探索和实践,我终于找到了一套完美的前端Excel导出方案!
二、方案对比
✨ 在找到最佳方案前,我们先看看常见的几种方案:
方案1:后端生成(传统方案)
// 前端请求后端接口axios.post('/api/export-excel', { data })
优点:功能强大,支持复杂格式缺点:服务器压力大,响应慢,用户体验差❌
方案2:SheetJS(社区版)
import XLSX from 'xlsx';XLSX.writeFile(workbook, '导出.xlsx');
优点:轻量级,纯数据导出速度快缺点:不支持图片插入,样式控制弱❌方案3:xlsx-populate
// 可以操作已有的Excel模板workbook.toFileAsync('输出.xlsx');
优点:支持模板,功能强大缺点:体积较大(1MB+),学习成本高❌方案4:exceljs(我们的选择)✅
import ExcelJS from 'exceljs';// 功能全面,支持图片、样式、公式等
综合评分:★★★★★推荐理由:功能全面、文档完善、社区活跃、体积适中三、核心实现:手把手教你实现el-table数据+图片导出
<el-button type="primary" @click="handleExport">导出</el-button>
3.1 安装依赖
# 安装exceljs和文件保存工具npm install exceljs file-saver# 可选:如果处理大量数据,安装lodash提高性能npm install lodash
const handleExport = async () => { const tableRef = this.$refs['tableRef']; // 表头和数据 let tableHeaders = []; let tableRows = []; const myChart = this.$refs['lineRef'].$refs['luEchartLine'].myChart; // 获取图表的 base64 URL let imgDataURL = myChart.getDataURL({ type: 'png', // 可以是 'png', 'jpeg' 等 pixelRatio: 2, // 图片分辨率比例,默认为1 backgroundColor: '#fff' // 背景颜色 }); const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('Sheet1'); if(tableRef) { tableHeaders = tableRef.columns.map(col => col.label); tableRows = tableRef.data; } else { throw new Error('请提供列配置或tableRef'); } // 添加表头 const headerRow = worksheet.addRow(tableHeaders); headerRow.font = { bold: true }; // headerRow.fill = { // type: 'pattern', // pattern: 'solid', // fgColor: { argb: '4472C4' } // }; headerRow.alignment = { horizontal: 'center' }; // 添加数据行 tableRows.forEach(rowData => { console.log('rowData', rowData); const rowValue = tableRef.columns.map(col => { const value = rowData[col.property]; return value; }); worksheet.addRow(rowValue); }); // 7. 自动调整列宽 worksheet.columns.forEach((column, index) => { let maxLength = 0; column.eachCell({ includeEmpty: true }, cell => { const cellLength = cell.value ? cell.value.toString().length : 0; if(cellLength > maxLength) { maxLength = cellLength; } }); column.width = Math.min(Math.max(maxLength, 10), 50); }); const imageId = workbook.addImage({ buffer: this.base64ToArrayBuffer(imgDataURL), extension: 'png', }); console.log(imageId, 'imageId'); // 指定图片位置(从B2单元格开始) worksheet.addImage(imageId, { tl: { col: 0, row: tableRows.length + 2, offsetX: 0, offsetY: 0 }, br: { col: 16, row: tableRows.length + 16, offsetX: 0, offsetY: 0 } }); // 生成文件 const buffer = await workbook.xlsx.writeBuffer(); // 下载 const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); // FileSaver.saveAs(blob, '导出.xlsx'); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = '导出.xlsx'; a.click();}
base64ToArrayBuffer(base64Str) { // 移除数据URI前缀(如果存在)并清理空白字符 base64Str = base64Str.replace(/^data:.+;base64,/, '').replace(/\s/g, ''); // 补充填充字符 const padding = '='.repeat((4 - base64Str.length % 4) % 4); const base64 = base64Str + padding; // 解码并转换 const binaryString = atob(base64); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes.buffer; },
- 数据获取
- 图表通过
getDataURL()获取base64格式的PNG图片。 - 表头数据和表格数据通过
tableRef.columns和tableRef.data获取
- Excel生成
- 使用ExcelJS库创建工作簿(workbook)和工作表(worksheet)
- 图表插入
- 将base64图片转为ArrayBuffer后插入
- 图片位置通过
tl(左上角)和br(右下角)坐标控制