Vue使用ExcelJS实现专业级Excel导出解决方案
作者:盛夏绽放
ExcelJS 是 Node.js 和浏览器环境中功能最全面的 Excel 处理库之一,就像一个专业的 Excel 工作站,下面小编就为大家详细介绍一下如何使用ExcelJS实现专业级Excel导出吧
一、ExcelJS 简介:企业级Excel处理引擎
ExcelJS 是 Node.js 和浏览器环境中功能最全面的 Excel 处理库之一,就像一个专业的 Excel 工作站:
[ ExcelJS 核心能力 ]
├─ 高级样式控制(字体/颜色/边框)
├─ 公式计算与数据验证
├─ 图片/图表/图形插入
├─ 多工作表复杂操作
└─ 流式处理大数据集
为什么选择 ExcelJS?
- 专业级功能:支持几乎所有 Excel 特性
- 样式控制精细:像素级样式调整能力
- 性能优异:流式 API 处理百万行数据
- 跨平台:完美支持 Node.js 和浏览器环境
二、环境配置与基础使用
安装方式
# 通过npm安装 npm install exceljs # 浏览器直接引入 <script src="https://cdn.jsdelivr.net/npm/exceljs@4.3.0/dist/exceljs.min.js"></script>
最简示例详解
// 1. 创建工作簿 - 好比新建一个Excel文件 const workbook = new ExcelJS.Workbook(); /* Workbook 对象结构: - creator: 创建者信息 - lastModifiedBy: 最后修改者 - created: 创建时间 - modified: 修改时间 - worksheets: [] 工作表集合 */ // 2. 添加工作表 - 添加一个新的工作表页 const worksheet = workbook.addWorksheet('销售数据'); /* addWorksheet 参数选项: - name: 工作表名称 - properties: {tabColor, pageSetup等} - views: 视图设置 */ // 3. 设置列定义 - 类似数据库表结构 worksheet.columns = [ { header: '订单号', key: 'id', width: 20 }, { header: '金额', key: 'amount', width: 15, style: { numFmt: '¥#,##0.00' } }, { header: '日期', key: 'date', width: 15, style: { numFmt: 'yyyy-mm-dd' } } ]; /* column 配置项: - header: 列标题 - key: 数据键名 - width: 列宽(字符数) - style: 列默认样式 */ // 4. 添加数据行 worksheet.addRow({ id: 'ORD-20230001', amount: 1999.9, date: new Date() }); /* addRow 方法特性: - 接受对象/数组格式数据 - 自动匹配column定义的key - 返回Row对象可继续操作 */ // 5. 导出Excel文件 workbook.xlsx.writeBuffer().then(buffer => { const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); saveAs(blob, '销售报表.xlsx'); }); /* writeBuffer 选项: - 返回Promise<Buffer> - 支持配置压缩等参数 */
三、核心API深度解析
1. 工作表操作
// 获取工作表 const firstSheet = workbook.getWorksheet(1); // 通过索引 const targetSheet = workbook.getWorksheet('Sheet1'); // 通过名称 // 遍历工作表 workbook.eachSheet((worksheet, sheetId) => { console.log(`工作表 ${sheetId}: ${worksheet.name}`); }); // 删除工作表 workbook.removeWorksheet('Sheet2'); // 工作表属性设置 worksheet.properties = { tabColor: { argb: 'FFFF0000' }, // 标签页颜色 defaultRowHeight: 20, // 默认行高 defaultColWidth: 10, // 默认列宽 pageSetup: { // 页面设置 orientation: 'landscape', // 横向 margins: { left: 0.7, right: 0.7, top: 0.75, bottom: 0.75 } } };
2. 数据操作
// 批量添加数据 const data = [ { id: 'ORD-001', amount: 1000 }, { id: 'ORD-002', amount: 2000 } ]; worksheet.addRows(data); // 获取行数据 const row = worksheet.getRow(5); // 获取第5行 console.log(row.getCell(1).value); // 获取第1列单元格值 // 遍历数据 worksheet.eachRow((row, rowNumber) => { console.log(`Row ${rowNumber}:`, row.values); }); // 合并单元格 worksheet.mergeCells('A1:C1'); // 合并A1到C1 worksheet.mergeCells(2, 1, 2, 3); // 行2列1到行2列3 // 数据验证 worksheet.getCell('B2').dataValidation = { type: 'list', formulae: ['"选项1,选项2,选项3"'], allowBlank: true };
3. 样式设置
// 创建样式 const headerStyle = { font: { name: '微软雅黑', size: 12, bold: true, color: { argb: 'FFFFFFFF' } // 白色 }, fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF4472C4' } // 蓝色背景 }, alignment: { vertical: 'middle', horizontal: 'center' }, border: { top: { style: 'thin', color: { argb: 'FF000000' } }, left: { style: 'thin', color: { argb: 'FF000000' } }, bottom: { style: 'thin', color: { argb: 'FF000000' } }, right: { style: 'thin', color: { argb: 'FF000000' } } } }; // 应用样式 worksheet.getRow(1).eachCell(cell => { cell.style = headerStyle; }); // 条件格式 worksheet.eachRow((row, rowNumber) => { if(rowNumber > 1) { // 跳过表头 const amountCell = row.getCell('B'); if(amountCell.value > 1500) { amountCell.font = { color: { argb: 'FFFF0000' }, bold: true }; } } });
四、高级功能实现
1. 公式计算
// 基本公式 worksheet.getCell('D2').value = { formula: 'B2*C2', result: 0 // 初始值 }; // 数组公式 worksheet.getCell('E2').value = { formula: 'SUM(B2:D2)', result: 0 }; // 公式计算(需在Excel中重新计算) workbook.calcProperties.fullCalcOnLoad = true; // 获取公式值 console.log(worksheet.getCell('D2').result); // 计算结果
2. 图片插入
async function addImageToSheet() { // 1. 读取图片文件 const imageFile = await fetch('logo.png'); const imageBuffer = await imageFile.arrayBuffer(); // 2. 添加图片到工作表 const imageId = workbook.addImage({ buffer: imageBuffer, extension: 'png' }); // 3. 定位图片位置 worksheet.addImage(imageId, { tl: { col: 1, row: 1 }, // 左上角位置(列1行1) br: { col: 3, row: 5 }, // 右下角位置(列3行5) editAs: 'oneCell' // 定位方式 }); }
3. 图表生成
// 注意:ExcelJS图表功能需要Pro版本或配合其他库实现 // 以下是概念性示例: // 1. 准备图表数据 worksheet.addRows([ ['产品', '销量'], ['手机', 1200], ['电脑', 800], ['平板', 600] ]); // 2. 创建图表(伪代码) const chart = { type: 'bar', // 柱状图 data: { categories: 'A2:A4', // X轴数据 values: 'B2:B4' // Y轴数据 }, title: '产品销量统计' }; // 3. 添加图表到工作表 worksheet.addChart(chart);
五、性能优化实战
1. 流式写入大数据
// Node.js 环境流式写入 const fs = require('fs'); const { Workbook } = require('exceljs'); async function streamWrite() { // 1. 创建可写流 const stream = fs.createWriteStream('large-data.xlsx'); // 2. 初始化流式工作簿 const workbook = new Workbook.stream.xlsx.WorkbookWriter({ stream: stream, useStyles: false, // 禁用样式提升性能 useSharedStrings: false }); // 3. 添加工作表 const worksheet = workbook.addWorksheet('大数据'); // 4. 模拟大数据源 for(let i = 1; i <= 100000; i++) { worksheet.addRow({ id: `ID-${i}`, value: Math.random() * 1000, date: new Date() }).commit(); // 必须调用commit // 每1000行输出进度 if(i % 1000 === 0) { console.log(`已处理 ${i} 行`); await new Promise(resolve => setImmediate(resolve)); } } // 5. 完成写入 worksheet.commit(); await workbook.commit(); console.log('导出完成'); }
2. 浏览器端分块处理
async function chunkedExport(data, fileName = 'export.xlsx') { // 1. 初始化工作簿 const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('分块数据'); // 2. 添加表头 if(data.length > 0) { worksheet.columns = Object.keys(data[0]).map(key => ({ header: key, key, width: 15 })); } // 3. 分块处理 const CHUNK_SIZE = 5000; let processed = 0; while(processed < data.length) { const chunk = data.slice(processed, processed + CHUNK_SIZE); worksheet.addRows(chunk); processed += CHUNK_SIZE; // 更新UI updateProgress(processed / data.length * 100); // 释放主线程 await new Promise(resolve => setTimeout(resolve, 0)); } // 4. 导出文件 const buffer = await workbook.xlsx.writeBuffer(); saveAs(new Blob([buffer]), fileName); }
六、企业级最佳实践
1. 完整的Vue组件实现
<template> <div class="excel-export"> <button @click="handleExport" :disabled="isExporting" :class="{ 'exporting': isExporting }" > <span v-if="!isExporting">导出Excel</span> <span v-else> <span class="spinner"></span> 导出中 ({{ progress }}%) </span> </button> <div v-if="error" class="error-message"> {{ error }} <button v-if="showRetry" @click="handleExport">重试</button> </div> </div> </template> <script> import ExcelJS from 'exceljs'; import { saveAs } from 'file-saver'; export default { props: { data: { type: Array, required: true, validator: value => Array.isArray(value) && (value.length === 0 || typeof value[0] === 'object') }, columns: { type: Array, default: null }, fileName: { type: String, default: `export_${new Date().toISOString().slice(0, 10)}.xlsx` }, chunkSize: { type: Number, default: 5000 }, styles: { type: Object, default: () => ({ header: { font: { bold: true, color: { argb: 'FFFFFFFF' } }, fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF4472C4' } } }, zebra: { even: { fill: { fgColor: { argb: 'FFF2F2F2' } } }, odd: { fill: { fgColor: { argb: 'FFFFFFFF' } } } } }) } }, data() { return { isExporting: false, progress: 0, error: null, showRetry: false }; }, methods: { async handleExport() { this.isExporting = true; this.error = null; this.showRetry = false; try { // 1. 创建工作簿 const workbook = new ExcelJS.Workbook(); workbook.creator = this.$store.state.user.name || '系统'; workbook.created = new Date(); // 2. 添加工作表 const worksheet = workbook.addWorksheet('导出数据'); // 3. 设置列 if(this.columns) { worksheet.columns = this.columns; } else if(this.data.length > 0) { worksheet.columns = Object.keys(this.data[0]).map(key => ({ header: key, key, width: 15 })); } // 4. 添加表头样式 if(this.styles.header) { const headerRow = worksheet.getRow(1); headerRow.eachCell(cell => { cell.style = this.styles.header; }); } // 5. 分块添加数据 for(let i = 0; i < this.data.length; i += this.chunkSize) { const chunk = this.data.slice(i, i + this.chunkSize); const addedRows = worksheet.addRows(chunk); // 应用斑马纹样式 if(this.styles.zebra) { addedRows.forEach((row, rowIndex) => { const isEven = (i + rowIndex) % 2 === 0; row.eachCell(cell => { cell.style = { ...cell.style, ...(isEven ? this.styles.zebra.even : this.styles.zebra.odd) }; }); }); } // 更新进度 this.progress = Math.min(100, (i / this.data.length * 100)); await new Promise(resolve => setTimeout(resolve, 0)); } // 6. 自动调整列宽 worksheet.columns.forEach(column => { if(!column.width) { column.width = column.header.length + 5; } }); // 7. 导出文件 const buffer = await workbook.xlsx.writeBuffer(); saveAs(new Blob([buffer]), this.fileName); // 8. 触发完成事件 this.$emit('export-complete', { fileName: this.fileName, rowCount: this.data.length }); } catch (err) { console.error('导出失败:', err); this.error = this.getErrorMessage(err); this.showRetry = true; this.$emit('export-error', err); } finally { this.isExporting = false; } }, getErrorMessage(err) { if(err.message.includes('memory')) { return '数据量过大导致内存不足,建议分批导出或联系管理员'; } return `导出失败: ${err.message}`; } } }; </script> <style scoped> .excel-export button { padding: 8px 16px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } .excel-export button.exporting { background: #FFC107; } .excel-export button:disabled { background: #cccccc; cursor: not-allowed; } .error-message { color: #f44336; margin-top: 8px; } .spinner { display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: white; animation: spin 1s ease-in-out infinite; margin-right: 8px; } @keyframes spin { to { transform: rotate(360deg); } } </style>
2. 错误处理策略
错误类型 | 检测方法 | 处理方案 |
---|---|---|
内存不足 | error.message.includes('memory') | 建议分批导出或使用流式API |
数据格式错误 | error.message.includes('Invalid') | 验证数据格式并提示用户修正 |
浏览器兼容性问题 | error.message.includes('support') | 提示用户更换浏览器 |
文件保存被阻止 | error.message.includes('denied') | 检查浏览器下载设置 |
七、扩展应用场景
1. 多语言导出
function createMultiLangExport(data, lang = 'zh-CN') { const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('数据'); // 根据语言选择表头 const headers = { 'zh-CN': { id: '订单号', amount: '金额', date: '日期' }, 'en-US': { id: 'Order ID', amount: 'Amount', date: 'Date' } }; worksheet.columns = [ { header: headers[lang].id, key: 'id', width: 20 }, { header: headers[lang].amount, key: 'amount', width: 15 }, { header: headers[lang].date, key: 'date', width: 15 } ]; worksheet.addRows(data); return workbook; } // 使用示例 const workbook = createMultiLangExport(data, 'en-US'); workbook.xlsx.writeBuffer().then(/* ... */);
2. 从模板生成报表
async function generateFromTemplate(templatePath, data) { // 1. 读取模板 const templateWorkbook = new ExcelJS.Workbook(); await templateWorkbook.xlsx.readFile(templatePath); // 2. 获取模板工作表 const templateSheet = templateWorkbook.getWorksheet('Template'); // 3. 创建新工作簿 const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('Report'); // 4. 复制模板样式和结构 worksheet.model = JSON.parse(JSON.stringify(templateSheet.model)); // 5. 填充数据 data.forEach((item, index) => { worksheet.getCell(`A${index + 2}`).value = item.name; worksheet.getCell(`B${index + 2}`).value = item.value; }); // 6. 导出 return workbook.xlsx.writeBuffer(); }
八、常见问题解答
Q1: 如何设置单元格为文本格式避免科学计数法?
worksheet.getCell('A1').value = { text: '00123456', // 文本内容 type: ExcelJS.ValueType.String // 明确指定类型 }; // 或者批量设置 worksheet.getColumn('A').eachCell(cell => { cell.numFmt = '@'; // @表示文本格式 });
Q2: 如何导出带超链接的单元格?
worksheet.getCell('A1').value = { text: '公司官网', hyperlink: 'https://example.com', tooltip: '点击访问官网' }; // 或者使用纯文本方式 worksheet.getCell('A2').value = '=HYPERLINK("https://example.com", "官网")';
Q3: 如何实现冻结窗格?
worksheet.views = [ { state: 'frozen', xSplit: 1, // 冻结第1列右侧 ySplit: 1, // 冻结第1行下方 topLeftCell: 'B2', // 活动单元格 activeCell: 'B2' } ];
结语:ExcelJS 专业级导出方案
通过本指南,您已经掌握:
- ExcelJS 的核心功能和架构设计
- 从基础到高级的各种数据操作技巧
- 专业样式和高级功能实现方法
- 大数据量下的性能优化策略
- 企业级项目的最佳实践方案
专业建议:
- 对于简单需求,xlsx 可能更轻便
- 需要精细样式控制时首选 ExcelJS
- 超过50万行数据考虑流式写入
- 复杂业务逻辑推荐使用模板方式
ExcelJS 就像专业的 Excel 工作站,为您提供企业级的数据导出能力。现在,您可以自信地应对任何复杂的 Excel 导出需求了!
到此这篇关于Vue使用ExcelJS实现专业级Excel导出解决方案的文章就介绍到这了,更多相关ExcelJS导出Excel内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!