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

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

环境准备与模块加载

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

bash 复制代码
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 更新组件状态,用于控制按钮的可用性:

javascript 复制代码
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 文件写入虚拟文件系统。第二个参数是虚拟文件系统中的目标目录,第三个参数是浏览器中可访问的源路径:

javascript 复制代码
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 开始):

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

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

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

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

javascript 复制代码
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 作为数据源加载,并指定分隔符与起始行列。示例的加载调用如下:

javascript 复制代码
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() 自动调整列宽:

javascript 复制代码
sheet.Range.get("A1:C4").IgnoreErrorOptions = wasmModule.IgnoreErrorType.NumberAsText;
sheet.AllocatedRange.AutoFitColumns();

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

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

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

javascript 复制代码
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 技术栈且需要在客户端直接处理表格格式转换的项目,这一方案具备可行性。

相关推荐
1-1=016 小时前
Vue 3 模板语
前端·javascript·vue.js
风卿16 小时前
看着AI写代码太孤独了,来欣赏星云吧:一个DSH 装饰插件
前端·javascript
牧艺16 小时前
弹层不用再手算坐标了:CSS Anchor Positioning 从原理到可上线
前端·javascript·css
愚坤17 小时前
前端真有意思,JS还能独立做项目?
前端·javascript·github
李纲明19 小时前
微信小程序 getSystemInfo 提示已废弃、屏幕高度算错胶囊还对不齐?getWindowInfo 和 getDeviceInfo 怎么接才稳
javascript·微信小程序·小程序·小程序开发·devdiy
FungLeo20 小时前
成为全栈·Vue 管理后台篇·用 fetch 建一层可控的请求内核
前端·javascript·vue.js·http·fetch·formdata·接口封装
用户842981424181020 小时前
ByteJS,让重要的 JavaScript 以字节码运行。
javascript
FungLeo1 天前
成为全栈·Vue 管理后台篇·Vue Router如何守住多角色后台边界
前端·javascript·vue.js·rbac·vue router·路由守卫·前端权限
FungLeo1 天前
成为全栈·Vue管理后台篇·仪表盘不是几张卡片:组合统计接口与角色视图
前端·javascript·vue.js·rbac·仪表盘·管理后台·tanstack query