ExcelJS 完全指南:专业级Excel导出解决方案

文章目录

  • [ExcelJS 完全指南:专业级Excel导出解决方案](#ExcelJS 完全指南:专业级Excel导出解决方案)
    • [一、ExcelJS 简介:企业级Excel处理引擎](#一、ExcelJS 简介:企业级Excel处理引擎)
      • [为什么选择 ExcelJS?](#为什么选择 ExcelJS?)
    • 二、环境配置与基础使用
    • 三、核心API深度解析
      • [1. 工作表操作](#1. 工作表操作)
      • [2. 数据操作](#2. 数据操作)
      • [3. 样式设置](#3. 样式设置)
    • 四、高级功能实现
      • [1. 公式计算](#1. 公式计算)
      • [2. 图片插入](#2. 图片插入)
      • [3. 图表生成](#3. 图表生成)
    • 五、性能优化实战
      • [1. 流式写入大数据](#1. 流式写入大数据)
      • [2. 浏览器端分块处理](#2. 浏览器端分块处理)
    • 六、企业级最佳实践
      • [1. 完整的Vue组件实现](#1. 完整的Vue组件实现)
      • [2. 错误处理策略](#2. 错误处理策略)
    • 七、扩展应用场景
      • [1. 多语言导出](#1. 多语言导出)
      • [2. 从模板生成报表](#2. 从模板生成报表)
    • 八、常见问题解答
      • [Q1: 如何设置单元格为文本格式避免科学计数法?](#Q1: 如何设置单元格为文本格式避免科学计数法?)
      • [Q2: 如何导出带超链接的单元格?](#Q2: 如何导出带超链接的单元格?)
      • [Q3: 如何实现冻结窗格?](#Q3: 如何实现冻结窗格?)
    • [结语:ExcelJS 专业级导出方案](#结语:ExcelJS 专业级导出方案)

ExcelJS 完全指南:专业级Excel导出解决方案

一、ExcelJS 简介:企业级Excel处理引擎

ExcelJS 是 Node.js 和浏览器环境中功能最全面的 Excel 处理库之一,就像一个专业的 Excel 工作站:

复制代码
[ ExcelJS 核心能力 ]
├─ 🎨 高级样式控制(字体/颜色/边框)
├─ 📊 公式计算与数据验证
├─ 🖼️ 图片/图表/图形插入
├─ 📑 多工作表复杂操作
└─ 🚀 流式处理大数据集

为什么选择 ExcelJS?

  1. 专业级功能:支持几乎所有 Excel 特性
  2. 样式控制精细:像素级样式调整能力
  3. 性能优异:流式 API 处理百万行数据
  4. 跨平台:完美支持 Node.js 和浏览器环境

二、环境配置与基础使用

安装方式

bash 复制代码
# 通过npm安装
npm install exceljs

# 浏览器直接引入
<script src="https://cdn.jsdelivr.net/npm/exceljs@4.3.0/dist/exceljs.min.js"></script>

最简示例详解

javascript 复制代码
// 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. 工作表操作

javascript 复制代码
// 获取工作表
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. 数据操作

javascript 复制代码
// 批量添加数据
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. 样式设置

javascript 复制代码
// 创建样式
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. 公式计算

javascript 复制代码
// 基本公式
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. 图片插入

javascript 复制代码
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. 图表生成

javascript 复制代码
// 注意: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. 流式写入大数据

javascript 复制代码
// 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. 浏览器端分块处理

javascript 复制代码
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组件实现

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. 多语言导出

javascript 复制代码
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. 从模板生成报表

javascript 复制代码
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: 如何设置单元格为文本格式避免科学计数法?

javascript 复制代码
worksheet.getCell('A1').value = { 
  text: '00123456', // 文本内容
  type: ExcelJS.ValueType.String // 明确指定类型
};

// 或者批量设置
worksheet.getColumn('A').eachCell(cell => {
  cell.numFmt = '@'; // @表示文本格式
});

Q2: 如何导出带超链接的单元格?

javascript 复制代码
worksheet.getCell('A1').value = {
  text: '公司官网',
  hyperlink: 'https://example.com',
  tooltip: '点击访问官网'
};

// 或者使用纯文本方式
worksheet.getCell('A2').value = '=HYPERLINK("https://example.com", "官网")';

Q3: 如何实现冻结窗格?

javascript 复制代码
worksheet.views = [
  {
    state: 'frozen',
    xSplit: 1, // 冻结第1列右侧
    ySplit: 1, // 冻结第1行下方
    topLeftCell: 'B2', // 活动单元格
    activeCell: 'B2'
  }
];

结语:ExcelJS 专业级导出方案

通过本指南,您已经掌握:

  1. ExcelJS 的核心功能和架构设计
  2. 从基础到高级的各种数据操作技巧
  3. 专业样式和高级功能实现方法
  4. 大数据量下的性能优化策略
  5. 企业级项目的最佳实践方案

专业建议

  • 对于简单需求,xlsx 可能更轻便
  • 需要精细样式控制时首选 ExcelJS
  • 超过50万行数据考虑流式写入
  • 复杂业务逻辑推荐使用模板方式

ExcelJS 就像专业的 Excel 工作站,为您提供企业级的数据导出能力。现在,您可以自信地应对任何复杂的 Excel 导出需求了!

相关推荐
UrbanJazzerati3 小时前
使用Excel制作多类别占比分析字母饼图
excel
The Future is mine6 小时前
Python实现文件夹中文件名与Excel中存在的文件名进行对比,并进行删除操作
excel
Tomorrow'sThinker13 小时前
[特殊字符] Excel 读取收件人 + Outlook 批量发送带附件邮件 —— Python 自动化实战
python·excel·outlook
bing_15819 小时前
Excel 如何进行多条件查找或求和?
excel
秀儿还能再秀19 小时前
基于Excel的数据分析思维与分析方法
数据分析·excel
bing_15819 小时前
Excel 如何处理更复杂的嵌套逻辑判断?
excel
weixin_4723394619 小时前
高效处理大体积Excel文件的Java技术方案解析
java·开发语言·excel
灵犀学长19 小时前
EasyExcel之SheetWriteHandler:解锁Excel写入的高阶玩法
spring boot·excel
全干engineer1 天前
Spring Boot 实现主表+明细表 Excel 导出(EasyPOI 实战)
java·spring boot·后端·excel·easypoi·excel导出