返回项目列表

Excel 导入导出优化方案

基于 SheetJS (xlsx) + ExcelJS 的前端 Excel 导入导出解决方案,面向大数据量场景进行了深度优化,支持数据校验、类型转换、复杂表头、模板生成、图片嵌入等功能。

2026年4月22日精选
Vue.jsSheetJSExcelJS
在线演示源代码

功能特性

功能说明
数据导入支持 .xlsx / .xls / .csv,自动解析表头、字段映射
带样式导出表头样式、边框、冻结首行、自定义列宽
纯数据导出SheetJS 快速导出,无样式,文件更小
图片嵌入导出每行数据嵌入图片(如头像、商品图)
复杂表头导出多级表头、合并单元格、动态列
模板生成带下拉验证、字段说明、示例数据的导入模板
数据校验10+ 种校验规则:必填、数字、范围、邮箱、手机号、正则、枚举、自定义等
类型转换日期序列号、千分位数字、百分比、布尔值、枚举值等自动转换
错误报告标记错误单元格(红色背景 + 批注),生成错误汇总表
性能优化Web Worker 后台构建 + 分批 yieldToMain,大数据量不阻塞 UI

技术架构

┌─────────────────────────────────────────────────┐
│                   业务层 (Vue/React/...)          │
├─────────────────────────────────────────────────┤
│  ExcelImporter    │  ExcelExporter    │ Validator │
│  (SheetJS 读取)    │  (ExcelJS 写入)   │ (校验规则) │
├───────────────────┼──────────────────┼──────────┤
│  TypeConverter    │  excelWorker      │          │
│  (类型转换)        │  (Worker后台构建)  │          │
├───────────────────┴──────────────────┴──────────┤
│              SheetJS (xlsx)  │  ExcelJS          │
│              (快速读取/纯导出) │ (样式导出/复杂功能) │
└─────────────────────────────────────────────────┘

职责分工:

  • SheetJS (xlsx):负责快速读取 Excel 文件和纯数据导出(无样式),速度极快
  • ExcelJS:负责带样式导出、复杂表头、图片嵌入、数据验证等高级功能
  • Web Worker:将 ExcelJS 的 CPU 密集型操作(构建工作簿 + writeBuffer)移至后台线程

快速开始

# 克隆项目
git clone https://github.com/Adou377/excel-demo.git
cd excel-demo

# 安装依赖
npm install

# 启动开发服务器
npm run dev

# 构建生产版本
npm run build

启动后访问 http://localhost:3000,可体验全部功能。


在其他项目中引入

方式一:直接复制工具文件(推荐)

核心工具文件仅 5 个,零框架依赖,可直接复制到任何前端项目中使用。

Step 1:安装依赖

npm install exceljs xlsx

Step 2:复制工具文件

将以下文件复制到你的项目中(建议放在 src/utils/excel/ 目录下):

src/utils/excel/
├── ExcelImporter.js    # 导入器
├── ExcelExporter.js    # 导出器
├── TypeConverter.js    # 类型转换器
├── Validator.js        # 数据校验器
└── excelWorker.js      # Web Worker 导出

Step 3:调整导入路径

工具文件内部的 import 路径需要根据你的目录结构调整:

// ExcelImporter.js 中
import { Validator } from './Validator.js'
import { TypeConverter } from './TypeConverter.js'

// 确保相对路径与你的目录结构一致

Step 4:在业务代码中使用

import { ExcelImporter } from '@/utils/excel/ExcelImporter'
import { ExcelExporter } from '@/utils/excel/ExcelExporter'
import { Validator } from '@/utils/excel/Validator'
import { TypeConverter } from '@/utils/excel/TypeConverter'

方式二:在非 Vite 项目中使用

如果你使用 Webpack 或其他构建工具,Web Worker 的引入方式需要调整:

// Vite 写法(本项目)
const worker = new Worker(new URL('./utils/excelWorker.js', import.meta.url), { type: 'module' })

// Webpack 写法
const worker = new Worker(new URL('../../utils/excel/excelWorker.js', import.meta.url))

// 或者使用 worker-loader
import ExcelWorker from './excelWorker.js'
const worker = new ExcelWorker()

方式三:在 React / 原生 JS 项目中使用

工具类与框架无关,可直接在 React、Angular、原生 JS 中使用:

// React 示例
import { ExcelExporter } from './utils/excel/ExcelExporter'

function ExportButton({ data }) {
  const handleExport = async () => {
    const exporter = new ExcelExporter()
    await exporter.export(data, [
      { title: '姓名', key: 'name', width: 12 },
      { title: '年龄', key: 'age', width: 8 }
    ], '导出数据.xlsx')
  }

  return <button onClick={handleExport}>导出 Excel</button>
}

核心模块 API 文档

ExcelImporter - 导入器

基于 SheetJS 的 Excel 文件导入,支持字段映射、类型转换、数据校验。

构造函数

const importer = new ExcelImporter(config)

config 配置项:

参数类型默认值说明
sheetIndexnumber0读取的工作表索引
headerRownumber1表头所在行号(从1开始)
dataStartRownumber2数据起始行号(从1开始)
fieldTypesobject{}字段类型映射,如 { age: 'integer', date: 'date' }
fieldMappingobject{}Excel 表头到字段的映射,如 { '姓名': 'name' }
validationRulesobject{}校验规则,详见 Validator
enumOptionsobject{}枚举字段的可选值,如 { gender: ['男', '女'] }

实例方法

import(file, onProgress?)

完整导入流程:读取 → 解析 → 转换 → 校验。

const importer = new ExcelImporter({
  fieldTypes: {
    name: 'string',
    age: 'integer',
    email: 'string',
    birthday: 'date'
  },
  fieldMapping: {
    '姓名': 'name',
    '年龄': 'age',
    '邮箱': 'email',
    '生日': 'birthday'
  },
  enumOptions: {
    gender: ['男', '女']
  },
  validationRules: {
    name: [{ type: 'required', message: '姓名不能为空' }],
    age: [
      { type: 'required', message: '年龄不能为空' },
      { type: 'range', min: 0, max: 150, message: '年龄范围0-150' }
    ],
    email: [{ type: 'email', message: '邮箱格式不正确' }]
  }
})

const result = await importer.import(file, (percent, status) => {
  console.log(`${percent}% - ${status}`)
})

// result 结构:
// {
//   success: boolean,       // 是否全部校验通过
//   data: object[],         // 有效数据数组
//   errors: Error[],        // 错误列表
//   total: number,          // 总行数
//   validCount: number,     // 有效行数
//   errorCount: number      // 错误行数
// }
quickImport(file) (静态方法)

快速导入,仅读取数据,不做类型转换和校验。适合简单的数据预览场景。

const result = await ExcelImporter.quickImport(file)
// result: { headers: string[], data: object[] }

ExcelExporter - 导出器

基于 ExcelJS 的 Excel 文件导出,支持样式、复杂表头、模板、错误报告。

构造函数

const exporter = new ExcelExporter(config)

config 配置项:

参数类型默认值说明
sheetNamestring'Sheet1'工作表名称
headerStyleobject见下方表头样式
dataStyleobject见下方数据行样式
freezeRownumber1冻结行数,0 表示不冻结

默认 headerStyle:

{
  font: { bold: true, color: { argb: 'FFFFFFFF' } },
  fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF4472C4' } },
  alignment: { horizontal: 'center', vertical: 'middle' },
  border: { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } }
}

实例方法

export(data, columns, filename?, onProgress?)

标准导出,带样式和进度回调。

const exporter = new ExcelExporter()

await exporter.export(
  [
    { name: '张三', age: 28, department: '技术部' },
    { name: '李四', age: 32, department: '销售部' }
  ],
  [
    { title: '姓名', key: 'name', width: 12 },
    { title: '年龄', key: 'age', width: 8 },
    {
      title: '部门', key: 'department', width: 12,
      render: (value, row) => `${value}部`,  // 自定义渲染
      style: { font: { bold: true } }         // 自定义单元格样式
    }
  ],
  '员工数据.xlsx',
  (percent) => console.log(`导出进度: ${percent}%`)
)

columns 配置项:

参数类型说明
titlestring列标题
keystring数据字段名
widthnumber列宽,默认 15
renderfunction自定义渲染函数 (value, row) => newValue
styleobject自定义单元格样式(ExcelJS Style)
exportWithComplexHeader(data, config, filename?)

复杂表头导出,支持多级表头和合并单元格。

await exporter.exportWithComplexHeader(data, {
  headers: [
    ['姓名', '成绩', '成绩', '备注'],   // 第一行表头
    ['', '语文', '数学', '']             // 第二行表头
  ],
  merges: [
    { s: { r: 0, c: 0 }, e: { r: 1, c: 0 } },  // "姓名" 跨2行
    { s: { r: 0, c: 1 }, e: { r: 0, c: 2 } },  // "成绩" 跨2列
    { s: { r: 0, c: 3 }, e: { r: 1, c: 3 } }    // "备注" 跨2行
  ],
  columns: [
    { key: 'name', width: 12 },
    { key: 'chinese', width: 10 },
    { key: 'math', width: 10 },
    { key: 'remark', width: 20 }
  ],
  dataStartRow: 3   // 数据从第3行开始
}, '成绩表.xlsx')

merges 格式说明: { s: { r, c }, e: { r, c } },r 为行索引(从0开始),c 为列索引(从0开始)。

exportTemplate(config, filename?)

生成导入模板,带下拉验证、字段说明、示例数据。

await exporter.exportTemplate({
  fields: [
    {
      key: 'name',
      label: '姓名',
      description: '必填,不超过20字',
      width: 15,
      required: true,
      validation: { maxLength: 20 }
    },
    {
      key: 'gender',
      label: '性别',
      description: '必填,男/女',
      width: 10,
      required: true,
      options: ['男', '女']   // 生成下拉列表
    },
    {
      key: 'department',
      label: '部门',
      description: '必填,从列表选择',
      width: 15,
      required: true,
      options: ['技术部', '销售部', '人事部', '财务部']
    }
  ],
  sampleData: [
    { name: '张三', gender: '男', department: '技术部' },
    { name: '李四', gender: '女', department: '销售部' }
  ]
}, '导入模板.xlsx')

生成的模板包含:

  • 数据表:第1行为字段名(蓝色表头),第2行为灰色说明行,第3行起为示例数据
  • 下拉验证:有 options 的字段自动生成下拉列表(支持1000行)
  • 说明表:列出每个字段的名称、说明、校验规则
exportErrorReport(data, errors, columns, filename?)

生成错误报告,错误单元格标红并添加批注。

await exporter.exportErrorReport(
  rawData,     // 原始数据数组
  [            // 错误列表
    { row: 2, field: 'email', value: 'abc', message: '邮箱格式不正确' },
    { row: 3, field: 'age', value: -1, message: '年龄必须在0-150之间' }
  ],
  [            // 列定义
    { title: '姓名', key: 'name', width: 12 },
    { title: '邮箱', key: 'email', width: 25 },
    { title: '年龄', key: 'age', width: 8 }
  ],
  '错误报告.xlsx'
)

生成的错误报告包含:

  • 数据表:错误单元格红色背景 + 批注(鼠标悬停可查看错误信息)
  • 错误汇总表:行号、字段、错误值、错误信息

TypeConverter - 类型转换器

处理 Excel 与 JavaScript 之间的数据类型差异。

构造函数

const converter = new TypeConverter()

实例方法

setEnumOptions(field, options)

设置枚举字段的可选值。

converter.setEnumOptions('department', ['技术部', '销售部', '人事部', '财务部'])
convert(value, type, field?)

将值转换为指定类型。

converter.convert('1,234.56', 'number')   // 1234.56
converter.convert('3.14', 'integer')       // 3
converter.convert('3.14159', 'float')      // 3.14 (保留2位小数)
converter.convert(0.85, 'percentage')      // 85
converter.convert('是', 'boolean')          // true
converter.convert(44927, 'date')           // Date 对象
converter.convert('技术部', 'enum', 'department')  // '技术部' (校验是否在枚举内)

支持的类型:

类型说明示例
string字符串,自动 trim' hello ' → 'hello'
number数字,自动去除千分位'1,234.56' → 1234.56
integer整数'3.14' → 3
float浮点数,保留2位小数'3.14159' → 3.14
percentage百分比0.85 → 85,'85%' → 85
boolean布尔值'是'/'true'/'1' → true,'否'/'false'/'0' → false
date日期Excel 序列号 44927 → Date,字符串自动解析
datetime日期时间同 date
enum枚举值需先通过 setEnumOptions 设置可选值

布尔值识别规则:

  • true:'是'、'对'、'真'、'true'、'yes'、'y'、'1'
  • false:'否'、'错'、'假'、'false'、'no'、'n'、'0'

Excel 日期序列号说明:

Excel 内部将日期存储为数字(从1900年1月1日起的天数),TypeConverter 会自动将其转换为 JavaScript Date 对象。


Validator - 数据校验器

构造函数

const validator = new Validator(rules)

校验规则

const rules = {
  name: [
    { type: 'required', message: '姓名不能为空' },
    { type: 'maxLength', value: 20, message: '姓名不能超过20个字符' }
  ],
  age: [
    { type: 'required', message: '年龄不能为空' },
    { type: 'number', message: '年龄必须是数字' },
    { type: 'range', min: 0, max: 150, message: '年龄必须在0-150之间' }
  ],
  email: [
    { type: 'email', message: '邮箱格式不正确' }
  ],
  phone: [
    { type: 'phone', message: '手机号格式不正确' }
  ],
  department: [
    { type: 'enum', value: ['技术部', '销售部'], message: '部门不在可选范围内' }
  ],
  website: [
    { type: 'url', message: 'URL格式不正确' }
  ],
  code: [
    { type: 'pattern', value: /^[A-Z]\d{6}$/, message: '编码格式不正确' }
  ],
  customField: [
    { type: 'custom', validator: (value) => value !== 'admin', message: '不能使用admin' }
  ]
}

支持的校验类型:

类型参数说明
required-必填校验
number-数字校验
integer-整数校验
rangemin, max数值范围校验
minLengthvalue最小长度校验
maxLengthvalue最大长度校验
email-邮箱格式校验
phone-手机号校验(中国大陆11位)
url-URL 格式校验
patternvalue (RegExp)正则校验
enumvalue (string[])枚举值校验
customvalidator (function)自定义校验函数,返回 boolean

实例方法

// 校验单个字段
const result = validator.validateField('name', '')
// result: { valid: false, field: 'name', message: '姓名不能为空' }

// 校验一整行
const errors = validator.validateRow(row, rowIndex)
// errors: [{ row, field, value, message }, ...]

// 校验全部数据
const allErrors = validator.validateAll(dataArray)

// 唯一性校验
const uniqueErrors = validator.validateUniqueness(dataArray, 'email')

excelWorker - Web Worker 导出

将 ExcelJS 的构建和写入操作放到 Web Worker 中执行,避免阻塞主线程。

使用方式

function workerExport(data, columns, filename, options = {}) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(
      new URL('./utils/excelWorker.js', import.meta.url),
      { type: 'module' }
    )

    worker.onmessage = (e) => {
      const msg = e.data
      if (msg.type === 'progress') {
        console.log(`进度: ${msg.percent}%`)
      } else if (msg.type === 'complete') {
        worker.terminate()
        resolve(msg)
      } else if (msg.type === 'error') {
        worker.terminate()
        reject(new Error(msg.error))
      }
    }

    worker.onerror = (e) => {
      worker.terminate()
      reject(new Error(e.message))
    }

    const payload = { data, columns, filename, ...options }
    if (options.imageBuffer) {
      worker.postMessage(payload, [options.imageBuffer])
    } else {
      worker.postMessage(payload)
    }
  })
}

参数说明

参数类型说明
dataobject[]导出数据
columnsobject[]列定义(同 ExcelExporter 的 columns)
filenamestring文件名
withImageboolean是否嵌入图片
imageBufferArrayBuffer图片的 ArrayBuffer 数据

返回结果

{
  type: 'complete',
  buildTime: number,     // 构建耗时 (ms)
  writeTime: number,     // 写入耗时 (ms)
  bufferSize: number,    // 文件大小 (bytes)
  buffer: ArrayBuffer,   // 文件数据(Transferable)
  filename: string
}

下载文件

const result = await workerExport(data, columns, '导出.xlsx')
const blob = new Blob([result.buffer], {
  type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = result.filename
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)

性能优化详解

问题:大数据量导出时 UI 卡顿

传统方案在主线程一次性完成所有操作,数据量大时(5万行+)会导致:

  • 页面冻结,无法交互
  • 进度条不更新
  • 浏览器可能弹出"页面无响应"提示

优化策略

1. 分批生成数据 + yieldToMain

async function yieldToMain() {
  return new Promise(resolve => setTimeout(resolve, 0))
}

const BATCH_SIZE = 5000
for (let i = 0; i < totalCount; i += BATCH_SIZE) {
  const batch = generateData(Math.min(BATCH_SIZE, totalCount - i))
  allData.push(...batch)
  await yieldToMain()  // 让出主线程,UI 可以响应
}

原理: setTimeout(resolve, 0) 将后续代码放入宏任务队列,让浏览器有机会处理渲染和用户交互事件。

2. Web Worker 后台构建

// 主线程
const worker = new Worker(new URL('./excelWorker.js', import.meta.url), { type: 'module' })
worker.postMessage({ data, columns })

// Worker 线程 (excelWorker.js)
self.onmessage = async function (e) {
  const workbook = new ExcelJS.Workbook()
  // ... 构建工作簿(不阻塞主线程)
  const buffer = await workbook.xlsx.writeBuffer()
  self.postMessage({ type: 'complete', buffer }, [buffer])  // Transferable 传输
}

原理: Web Worker 在独立线程执行,不会阻塞主线程的 UI 渲染和事件处理。

3. Transferable 零拷贝传输

// Worker 中
const transferable = rawBuffer instanceof ArrayBuffer
  ? rawBuffer
  : rawBuffer.buffer.slice(rawBuffer.byteOffset, rawBuffer.byteOffset + rawBuffer.byteLength)

self.postMessage({ buffer: transferable }, [transferable])

原理: 使用 Transferable Objects 传输 ArrayBuffer,避免结构化克隆的内存拷贝开销。

4. Worker 内部分批写入

// excelWorker.js 内部
const BATCH_SIZE = 5000
for (let i = 0; i < totalRows; i += BATCH_SIZE) {
  const batch = data.slice(i, Math.min(i + BATCH_SIZE, totalRows))
  for (const item of batch) {
    worksheet.addRow(item)
  }
  self.postMessage({ type: 'progress', percent: ... })
  await new Promise(resolve => setTimeout(resolve, 0))
}

原理: 即使在 Worker 中,分批处理也能避免长时间占用 CPU,让 Worker 的进度消息能及时发送。

性能对比

数据量朴素方案优化方案提升
10,000 行~800ms~600msUI 不卡顿
50,000 行~4s~3sUI 不卡顿
100,000 行~10s~7sUI 不卡顿

关键差异不在总耗时,而在于优化方案期间 UI 保持响应,用户可以正常操作页面。


使用场景与示例

场景1:基础数据导入

import { ExcelImporter } from './utils/excel/ExcelImporter'

const importer = new ExcelImporter({
  fieldMapping: { '姓名': 'name', '年龄': 'age' },
  fieldTypes: { age: 'integer' }
})

const result = await importer.import(file)
if (result.success) {
  console.log('导入成功', result.data)
} else {
  console.log('存在错误', result.errors)
}

场景2:带校验的导入

const importer = new ExcelImporter({
  fieldTypes: {
    name: 'string',
    age: 'integer',
    email: 'string',
    phone: 'string'
  },
  enumOptions: {
    gender: ['男', '女']
  },
  validationRules: {
    name: [{ type: 'required', message: '姓名必填' }],
    age: [
      { type: 'required', message: '年龄必填' },
      { type: 'range', min: 18, max: 65, message: '年龄18-65' }
    ],
    email: [{ type: 'email', message: '邮箱格式错误' }],
    phone: [{ type: 'phone', message: '手机号格式错误' }]
  }
})

const result = await importer.import(file)

// 导出错误报告
if (!result.success) {
  const exporter = new ExcelExporter()
  await exporter.exportErrorReport(result.data, result.errors, columns, '错误报告.xlsx')
}

场景3:大数据量导出(Worker)

async function exportLargeData(data, columns, filename) {
  const worker = new Worker(
    new URL('./utils/excel/excelWorker.js', import.meta.url),
    { type: 'module' }
  )

  return new Promise((resolve, reject) => {
    worker.onmessage = (e) => {
      if (e.data.type === 'complete') {
        worker.terminate()
        const blob = new Blob([e.data.buffer], {
          type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        })
        const url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = url
        a.download = filename
        a.click()
        URL.revokeObjectURL(url)
        resolve()
      } else if (e.data.type === 'error') {
        worker.terminate()
        reject(new Error(e.data.error))
      }
    }

    worker.postMessage({ data, columns, filename })
  })
}

场景4:带图片的导出

async function exportWithImage(data, columns, filename) {
  const imageResp = await fetch('/avatar.png')
  const imageBuffer = await imageResp.arrayBuffer()

  const worker = new Worker(
    new URL('./utils/excel/excelWorker.js', import.meta.url),
    { type: 'module' }
  )

  return new Promise((resolve, reject) => {
    worker.onmessage = (e) => {
      if (e.data.type === 'complete') {
        worker.terminate()
        const blob = new Blob([e.data.buffer], {
          type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        })
        const url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = url
        a.download = filename
        a.click()
        URL.revokeObjectURL(url)
        resolve()
      } else if (e.data.type === 'error') {
        worker.terminate()
        reject(new Error(e.data.error))
      }
    }

    worker.postMessage(
      { data, columns, withImage: true, imageBuffer, filename },
      [imageBuffer]  // Transferable
    )
  })
}

场景5:动态表头导出(如考勤表)

const dates = ['2024-01-15', '2024-01-16', '2024-01-17']

const header1 = ['姓名']
const header2 = ['']
dates.forEach(date => {
  header1.push(date, date)
  header2.push('上班', '下班')
})

const merges = [{ s: { r: 0, c: 0 }, e: { r: 1, c: 0 } }]
dates.forEach((_, i) => {
  merges.push({ s: { r: 0, c: i * 2 + 1 }, e: { r: 0, c: i * 2 + 2 } })
})

const columns = [{ key: 'name', width: 12 }]
dates.forEach((_, i) => {
  columns.push({ key: `checkIn_${i}`, width: 10 })
  columns.push({ key: `checkOut_${i}`, width: 10 })
})

const exporter = new ExcelExporter()
await exporter.exportWithComplexHeader(attendanceData, {
  headers: [header1, header2],
  merges,
  columns,
  dataStartRow: 3
}, '考勤表.xlsx')

场景6:生成导入模板

const exporter = new ExcelExporter()
await exporter.exportTemplate({
  fields: [
    { key: 'name', label: '姓名', description: '必填', width: 15, required: true },
    { key: 'gender', label: '性别', description: '男/女', width: 10, options: ['男', '女'] },
    { key: 'dept', label: '部门', description: '从列表选择', width: 15, options: ['技术部', '销售部'] }
  ],
  sampleData: [
    { name: '张三', gender: '男', dept: '技术部' }
  ]
}, '导入模板.xlsx')

依赖说明

依赖版本用途
exceljs^4.4.0带样式导出、复杂表头、图片嵌入、数据验证
xlsx (SheetJS)^0.18.5快速读取 Excel、纯数据导出
vue^3.5.32演示项目 UI 框架(工具类本身不依赖 Vue)
vite^8.0.9构建工具

核心工具类(ExcelImporter / ExcelExporter / TypeConverter / Validator / excelWorker)不依赖任何前端框架,可在 Vue、React、Angular、原生 JS 等任何环境中使用。


常见问题

Q: 为什么同时使用 SheetJS 和 ExcelJS?

A: 两者各有优势:

  • SheetJS 读取速度快,纯数据导出极快,但无法设置样式
  • ExcelJS 支持样式、合并单元格、图片、数据验证等高级功能,但速度较慢

本方案根据场景选择合适的库:导入用 SheetJS(快),带样式导出用 ExcelJS(功能全),纯数据导出用 SheetJS(快)。

Q: Web Worker 中可以使用 ExcelJS 吗?

A: 可以。ExcelJS 是纯 JavaScript 实现,不依赖 DOM,可以在 Web Worker 中正常运行。但需要注意:

  • Worker 中不能访问 document、window 等浏览器 API
  • 文件下载需要在主线程完成(Worker 通过 postMessage 传回 buffer)

Q: 如何在 Webpack 项目中使用 excelWorker?

A: Webpack 需要调整 Worker 的引入方式:

// 方式1:使用 worker-loader
import ExcelWorker from 'worker-loader!./utils/excel/excelWorker.js'
const worker = new ExcelWorker()

// 方式2:使用 URL 语法(Webpack 5+)
const worker = new Worker(new URL('./utils/excel/excelWorker.js', import.meta.url))

Q: 导出大量数据时内存不足怎么办?

A: 以下策略可以缓解内存问题:

  1. 使用 Web Worker,将内存开销转移到 Worker 线程
  2. 分批生成数据,每批处理后 await yieldToMain() 让 GC 有机会回收
  3. 数据生成完毕后及时释放原始数据引用(data.length = 0)
  4. 对于超大数据(50万行+),建议改为后端生成并提供下载链接

Q: 如何自定义导出样式?

A: 通过构造函数的 config 参数或列定义的 style 属性:

const exporter = new ExcelExporter({
  headerStyle: {
    font: { bold: true, color: { argb: 'FF000000' } },
    fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFF0000' } }
  }
})

// 或者在列定义中自定义某列样式
const columns = [
  {
    title: '状态', key: 'status', width: 10,
    render: (value) => value === 1 ? '启用' : '禁用',
    style: {
      font: { bold: true },
      alignment: { horizontal: 'center' }
    }
  }
]

样式格式遵循 ExcelJS 的 Style 文档。

Q: 导入时日期解析不正确?

A: Excel 内部将日期存储为数字序列号。确保:

  1. 在 fieldTypes 中将日期字段声明为 'date' 类型
  2. TypeConverter 会自动处理 Excel 序列号 → JS Date 的转换
  3. 如果日期列在 Excel 中是文本格式,TypeConverter 也会尝试用 new Date() 解析

Q: 如何处理中文表头映射?

A: 使用 fieldMapping 配置:

const importer = new ExcelImporter({
  fieldMapping: {
    '姓名': 'name',
    '年龄': 'age',
    '部门': 'department',
    '入职日期': 'hireDate'
  }
})

未在 fieldMapping 中配置的表头将直接作为字段名使用。

技术栈

Vue.jsSheetJSExcelJS
Vue.jsSheetJSExcelJS