React 前端实现 Excel 与 CSV 互转:基于 WebAssembly 的浏览器端方案

0 阅读4分钟

在 Web 前端处理表格数据时,Excel 与 CSV 之间的格式转换是一个常见需求。CSV 体积小、结构简单,适合数据传输与轻量存储;Excel 则支持多工作表、格式设置与公式,适合复杂业务场景。过去这类转换往往依赖后端服务或 Node.js 中间层,但随着 WebAssembly 技术的成熟,纯浏览器端的文档处理能力已经具备实用价值。本文以一个可运行的 React 示例为线索,介绍如何实现 Excel 工作表导出为 CSV,以及将 CSV 文件加载并保存为 Excel 工作簿,并对代码中的关键环节与容易出错的地方逐一说明。

环境准备与模块加载

示例所用的表格处理库以 WebAssembly 形式运行,不依赖本机安装 Microsoft Excel 或 Office。在 React 项目中使用前,需要先安装对应的 npm 包:

npm i spire.office

安装完成后,需要将运行时所需的文件复制到 React 项目的 public 目录下,包括 spire.xls.js、Spire.Xls.Wasm.zip、spire.common.js、Spire.Common.Wasm.zip 以及 _framework 文件夹。为了保证文档中的文本能够正确渲染,还需要将所需字体文件(示例中为 Arial.ttf)放置在 public/static/font 路径下,待转换的源文件放置在 public/static/data 路径下。

在 React 组件中,通过 useEffect 异步加载模块,是示例采用的初始化方式。加载时将模块挂载到 window.wasmModule,并通过 setWasmModule 更新组件状态,用于控制按钮的可用性:

useEffect(() => {
  (async () => {
    try {
      const publicUrl = process.env.PUBLIC_URL || '';
      const spireModule = await import(/* webpackIgnore: true */ `${publicUrl}/spire.xls.js`);
      const rawModule = spireModule.default || spireModule;
      window.wasmModule = typeof rawModule === 'function'
        ? await rawModule({ locateFile: p => p.endsWith('.wasm') ? `${publicUrl}/${p}` : p })
        : rawModule;
      setWasmModule(window.wasmModule);
    } catch (error) {
      console.error('Failed to load WASM module:', error);
    }
  })();
}, []);

加载成功后,Excel 处理 API 通过 window.wasmModule.spirexls 访问,虚拟文件系统相关接口通过 window.spire 与 window.dotnetRuntime.Module.FS 访问。需要留意的是,部分较新版本的包可能将 API 直接暴露在 window.spirexls 上,而非嵌套在 wasmModule 中,实际使用时可根据控制台输出确认可用的全局对象。

Excel 工作表导出为 CSV

将 Excel 工作表保存为 CSV 的核心方法是 Worksheet.SaveToFile(),该函数允许指定分隔符与编码。整体流程分为:加载字体与源文件到虚拟文件系统、创建工作簿并载入 Excel、获取目标工作表、保存为 CSV、读取生成文件并触发下载。

首先通过 FetchFileToVFS 将字体与 Excel 文件写入虚拟文件系统。第二个参数是虚拟文件系统中的目标目录,第三个参数是浏览器中可访问的源路径:

await window.spire.FetchFileToVFS('Arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
const inputFileName = 'sample.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

随后创建工作簿、加载文件并取得第一个工作表(索引从 0 开始):

const workbook = new wasmModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
let sheet = workbook.Worksheets.get(0);

保存时指定输出文件名、逗号分隔符与 UTF-8 编码:

const outputFileName = 'ExcelToCSV.csv';
sheet.SaveToFile({ fileName: outputFileName, separator: ",", encoding: wasmModule.Encoding.get_UTF8() });

生成的文件位于虚拟文件系统中,需要通过 window.dotnetRuntime.Module.FS.readFile() 读出为字节数组,再构造 Blob 对象并创建下载链接。这一步与常规的前端文件下载逻辑一致:

const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const modifiedFile = new Blob([modifiedFileArray], { type: 'text/csv' });

const url = URL.createObjectURL(modifiedFile);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);

下载触发后,调用 workbook.Dispose() 释放工作簿占用的资源,避免在单页应用中因反复操作导致内存累积。

CSV 加载并保存为 Excel 工作簿

反向转换的流程略有不同。CSV 文件没有工作表概念,因此需要先创建一个空白工作簿,再通过 LoadFromFile 将 CSV 作为数据源加载,并指定分隔符与起始行列。示例的加载调用如下:

const inputFileName = 'sample.csv';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

const workbook = new wasmModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName, separator: ",", row: 1, column: 1 });
let sheet = workbook.Worksheets.get(0);

CSV 中的数值内容在载入后可能以文本形式存储,这会影响后续在 Excel 中的排序与计算。库提供了 IgnoreErrorOptions 用于忽略“数字以文本形式保存”的警告提示,但它并不改变数据类型本身;如果业务对数据类型有严格要求,需要在加载后对特定区域进行显式转换。示例中还调用了 AllocatedRange.AutoFitColumns() 自动调整列宽:

sheet.Range.get("A1:C4").IgnoreErrorOptions = wasmModule.IgnoreErrorType.NumberAsText;
sheet.AllocatedRange.AutoFitColumns();

保存时通过 version 参数指定 Excel 版本,Version2010 对应 .xlsx 格式:

const outputFileName = 'CSVToExcel.xlsx';
workbook.SaveToFile({ fileName: outputFileName, version: wasmModule.ExcelVersion.Version2010 });

随后同样从虚拟文件系统读出字节数组,构造 Blob 对象,但 MIME 类型需设置为 Excel 对应的类型,以确保浏览器正确识别并下载为电子表格文件:

const modifiedFile = new Blob([modifiedFileArray], {
  type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});

下载逻辑与前一节相同,操作结束后同样调用 workbook.Dispose() 释放资源。

实践中的注意事项

文件名的书写错误:示例代码中 Excel 源文件变量曾写作 'sample.xlsxx',这是笔误,实际使用时需改为正确的 sample.xlsx,否则 FetchFileToVFS 无法找到对应文件。这是照搬示例代码时最容易踩的坑之一。

字体依赖:WASM 环境本身不包含系统字体。若转换过程中涉及文本渲染或列宽自动调整,缺少对应字体文件可能导致操作失败或结果异常。示例在每次转换前都将 Arial.ttf 载入虚拟文件系统,这一做法保证了字体始终可用。

资源释放:在 React 单页应用中,组件可能被多次挂载与卸载。每次转换完成后手动调用 workbook.Dispose() 是必要的,否则工作簿对象占用的内存不会自动回收。

虚拟文件系统路径:FetchFileToVFS 的第二个参数是虚拟文件系统中的目标目录,第三个参数是浏览器中的源路径,两者需要区分清楚。字体通常放入 /Library/Fonts/,业务文件可以放在根路径或自定义目录。

下载链路:window.dotnetRuntime.Module.FS.readFile() 返回的是虚拟文件系统中的字节数据,必须经由 Blob 与 URL.createObjectURL 才能触发浏览器下载。下载完成后及时调用 URL.revokeObjectURL 释放对象 URL,是完整的前端资源管理实践。

版本差异:示例通过 window.wasmModule.spirexls 访问 API,但不同版本的 spire.office 包在全局对象挂载方式上可能存在变化。若遇到 wasmModule 为 undefined 的情况,可在控制台检查 window.spirexls 是否已就绪。

结语

本文以一个 React 示例为线索,梳理了在浏览器端完成 Excel 与 CSV 双向转换的完整流程。该方案的核心特点在于转换逻辑完全运行在浏览器端,不依赖后端服务或本地 Office 环境,适合对数据隐私敏感或希望降低服务器负载的前端场景。代价是需要额外处理 WASM 文件的部署与字体资源的加载,且虚拟文件系统的操作方式与常规 Node.js 文件 API 存在差异,需要一定的适应成本。对于已有 React 技术栈且需要在客户端直接处理表格格式转换的项目,这一方案具备可行性。