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 导出需求了!

相关推荐
葡萄城技术团队1 天前
从100秒到10秒的性能优化,你真的掌握 Excel 的使用技巧了吗?
excel
QQ3596773452 天前
ArcGIS Pro实现基于 Excel 表格批量创建标准地理数据库(GDB)——高效数据库建库解决方案
数据库·arcgis·excel
星空的资源小屋4 天前
Digital Clock 4,一款免费的个性化桌面数字时钟
stm32·单片机·嵌入式硬件·电脑·excel
揭老师高效办公4 天前
在Excel和WPS表格中批量删除数据区域的批注
excel·wps表格
我是zxb4 天前
EasyExcel:快速读写Excel的工具类
数据库·oracle·excel
辣香牛肉面4 天前
[Windows] 搜索文本2.6.2(从word、wps、excel、pdf和txt文件中查找文本的工具)
word·excel·wps·搜索文本
ljf88384 天前
Java导出复杂excel,自定义excel导出
java·开发语言·excel
tebukaopu1484 天前
json文件转excel
json·excel
shizidushu4 天前
How to work with merged cells in Excel with `openpyxl` in Python?
python·microsoft·excel·openpyxl
Eiceblue5 天前
使用 C# 设置 Excel 单元格格式
开发语言·后端·c#·.net·excel