React 前端处理 Excel 工作表复制的技术实践

在日常的企业级后台开发中,Excel 文件的在线处理是个绕不开的需求。除了常见的读取和报表导出,有时我们还需要在纯前端对已有 Excel 文件做结构调整,比如在同一工作簿内复制工作表、跨工作簿复制,或者只复制选定的单元格区域。

最近在项目中正好遇到了这个场景,我把实现过程整理了下来。本文会从环境准备、核心逻辑、几种复制场景的实现以及实际遇到的问题这几个方面展开。

为什么选择在前端处理

把文件传到后端让 Java 或 Python 处理是常规做法,但有些情况下前端直接处理也有它的好处:

  • 文件不用离开浏览器,对数据隐私更友好
  • 省去上传下载的时间,操作完直接拿到结果
  • 简单的结构调整,没必要再绕一圈服务端

随着 WebAssembly 的普及,一些原本只能在服务端跑的类库也能在浏览器里用了,这让前端处理 Excel 内部结构变成了可能。

环境准备与初始化

首先创建一个 React 项目,然后将运行所需的 Wasm 文件、JS 资源以及字体文件放到 public 目录下。这类文件体积通常不小,不适合直接打进 bundle,放在静态资源目录或走 CDN 是更合理的做法。

Spire.XLS for JavaScript 的初始化需要先加载 WASM 模块,并将文件载入虚拟文件系统(VFS)。下面是一个基础的初始化 Hook:

typescript 复制代码
import { useEffect, useState } from "react";

export function useXlsInit() {
  const [xlsModule, setXlsModule] = useState<any>(null);
  const [isReady, setIsReady] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const init = async () => {
      try {
        // 等待 WASM 模块就绪
        const module = window.wasmModule?.spirexls;
        if (!module) {
          setError("WASM 模块未加载");
          return;
        }
        setXlsModule(module);

        // 将字体文件载入 VFS(处理中文时需要)
        await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

        setIsReady(true);
      } catch (e: any) {
        setError(`初始化失败: ${e.message}`);
      }
    };

    init();
  }, []);

  return { xlsModule, isReady, error };
}

这里需要注意的是,字体文件对中文内容的渲染很重要,如果 Excel 中包含中文字符,务必提前将字体加载到 VFS 中。

场景一:同一工作簿内复制工作表

这是最常见的需求。官方 API 提供了两种思路:一是通过 Add 新建空工作表再用 CopyFrom 复制内容,二是直接用 AddCopy 在复制的同时创建工作表。

以下示例采用 CopyFrom 的方式,将第一个工作表的内容复制到新工作表中:

typescript 复制代码
const copySheetWithinWorkbook = async (xlsModule: any) => {
  if (!xlsModule) return;

  // 将 Excel 文件载入 VFS
  await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

  // 加载工作簿
  const workbook = new xlsModule.Workbook();
  workbook.LoadFromFile({ fileName: inputFileName });

  // 获取第一个工作表
  let sheet = workbook.Worksheets.get(0);

  // 添加新工作表,并将源表内容复制过来
  let sheet1 = workbook.Worksheets.Add("MySheet");
  sheet1.CopyFrom(sheet);

  // 保存文件
  const outputFileName = "CopySheetWithinWorkbook_output.xlsx";
  workbook.SaveToFile({ fileName: outputFileName });

  // 释放资源
  workbook.Dispose();

  // 触发下载
  downloadFromVFS(outputFileName);
};

整个流程很清晰:载入文件 → 定位源表 → 新建目标表并复制 → 保存并下载。CopyFrom 会复制单元格数据、样式和公式。

场景二:跨工作簿复制工作表

有时候源表在一个文件里,目标表在另一个文件里,需要把整个工作表从一个工作簿搬到另一个工作簿。这时可以用 AddCopy 方法,直接传入源工作表对象:

typescript 复制代码
const copySheetAcrossWorkbooks = async (xlsModule: any) => {
  if (!xlsModule) return;

  const sourceFileName = "ReadImages.xlsx";
  const targetFileName = "Sample.xlsx";

  // 将两个文件都载入 VFS
  await window.spire.FetchFileToVFS(sourceFileName, '', `${process.env.PUBLIC_URL}data/`);
  await window.spire.FetchFileToVFS(targetFileName, '', `${process.env.PUBLIC_URL}data/`);

  // 加载源工作簿
  const sourceWorkbook = new xlsModule.Workbook();
  sourceWorkbook.LoadFromFile({ fileName: sourceFileName });
  const srcWorksheet = sourceWorkbook.Worksheets.get(0);

  // 加载目标工作簿
  const targetWorkbook = new xlsModule.Workbook();
  targetWorkbook.LoadFromFile({ fileName: targetFileName });

  // 在目标工作簿中添加新工作表,并从源工作表复制
  targetWorkbook.Worksheets.AddCopy({ sheet: srcWorksheet });

  // 保存目标工作簿
  const outputFileName = "CopyAcrossWorkbooks_output.xlsx";
  targetWorkbook.SaveToFile({ fileName: outputFileName });

  // 释放资源
  sourceWorkbook.Dispose();
  targetWorkbook.Dispose();

  // 触发下载
  downloadFromVFS(outputFileName);
};

AddCopy 的参数是一个包含 sheet 属性的对象,传入源工作表即可。这种方式会保留原表的所有内容,包括数据、格式和图表等。

场景三:复制选定的单元格区域

如果不需要整表复制,只想拷贝某个区域的数据,可以用 Range 配合 Copy 方法精确控制:

typescript 复制代码
const copyRange = async (xlsModule: any) => {
  if (!xlsModule) return;

  const inputFileName = "Sample.xlsx";
  await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

  const workbook = new xlsModule.Workbook();
  workbook.LoadFromFile({ fileName: inputFileName });

  const sheet = workbook.Worksheets.get(0);

  // 指定源区域,例如 A1:E1
  const sourceRange = sheet.Range.get("A1:E1");

  // 新增目标工作表
  let sheet1 = workbook.Worksheets.Add("AddSheet");

  // 将源区域复制到目标工作表的指定起始位置
  sheet.Copy(sourceRange, sheet1, sheet.FirstRow, sheet.FirstColumn, true);
  sheet1.AllocatedRange.AutoFitColumns();

  const outputFileName = "CopyRange_output.xlsx";
  workbook.SaveToFile({ fileName: outputFileName });

  workbook.Dispose();
  downloadFromVFS(outputFileName);
};

Copy 方法的参数依次是:源区域、目标工作表、起始行、起始列、是否保留列宽。这种方式灵活性很高,适合只需要部分数据的场景。

文件下载的通用处理

由于处理后的文件保存在 VFS 中,需要读取并转换为 Blob 再触发浏览器下载。可以封装一个通用函数:

typescript 复制代码
const downloadFromVFS = (fileName: string) => {
  const fileArray = window.dotnetRuntime.Module.FS.readFile(fileName);
  const blob = new Blob([fileArray], {
    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);
};

React 组件整合示例

将上述逻辑整合到 React 组件中,给用户提供一个可选择复制模式的界面:

tsx 复制代码
const App: React.FC = () => {
  const { xlsModule, isReady, error } = useXlsInit();

  if (!isReady) return <div>正在加载组件资源,请稍候...</div>;
  if (error) return <div>加载失败:{error}</div>;

  return (
    <div style={{ padding: 24 }}>
      <h2>Excel 工作表复制</h2>
      <p>请选择复制模式:</p>
      <button onClick={() => copySheetWithinWorkbook(xlsModule)}>
        同一工作簿内复制
      </button>
      <button
        onClick={() => copySheetAcrossWorkbooks(xlsModule)}
        style={{ marginLeft: 12 }}
      >
        跨工作簿复制
      </button>
      <button
        onClick={() => copyRange(xlsModule)}
        style={{ marginLeft: 12 }}
      >
        复制单元格区域
      </button>
    </div>
  );
};

实际使用中可能遇到的问题

Wasm 加载时间较长

核心文件体积不小,首次加载可能要等好几秒甚至更久。建议在应用启动阶段提前加载,并给用户一个进度提示,避免在未就绪时就能操作。

大文件处理有瓶颈

纯前端处理受浏览器内存限制,如果 Excel 超过几十 MB,可能会出现卡顿甚至页面崩溃。可以在上传时做体积校验,超过一定阈值就引导用户走服务端方案。

样式和公式的兼容性

虽然复制方法能保留大部分格式,但如果文件里有特殊图表、条件格式或者宏,复制后可能会丢失部分内容。实际用之前最好拿业务文件充分测试一下。

字体文件的影响

涉及中文内容时,如果忘记加载字体文件,导出的文件可能会出现乱码或排版异常。初始化时务必确认字体加载成功。

API 版本差异

不同版本的方法名或参数细节可能存在出入,开发时务必以当前实际引入版本的文档为准。

总结

通过这次实践可以看到,在前端直接完成 Excel 工作表的复制操作是完全可行的。无论是同文件内复制、跨文件复制,还是精确到单元格区域的复制,都有对应的 API 支持。整个流程不经过服务端,既轻量又直接。

当然这种方案也不是万能的,大文件处理和复杂格式兼容性需要在实际业务中权衡。希望这篇整理能给正在做类似需求的同学提供一点参考。

相关推荐
phltxy1 小时前
LangChain_v1_Agent快速开发和更新说明
前端·javascript·langchain
wujian83111 小时前
怎么用AI做excel表格 AI导出鸭来救场
人工智能·ai·excel·豆包·deepseek·ai导出鸭
光影少年2 小时前
RN原生交互 & 桥接
前端·javascript·react native·react.js·前端框架
用户526835677902 小时前
云原生运维实践:构建 Prometheus + 自定义 Webhook 的硬件声光告警闭环
javascript
GuWenyue2 小时前
踩坑无数!吃透useState 3个核心技巧,彻底解决状态旧值、性能卡顿问题
前端·javascript·react.js
无人生还2 小时前
从 Vue3 到 React · 快速上手系列第 4 篇:条件渲染与列表渲染
前端·vue.js·react.js
咩咩啃树皮2 小时前
第46篇:Vue3 Router工程化进阶——懒加载+嵌套路由+全局守卫+权限控制
javascript·vue.js·ecmascript
为你学会写情书2 小时前
手写 Mini React 核心原理解析
react.js
触底反弹2 小时前
💡 React 父子组件通信:一个进度条教会我的 5 件事
前端·react.js·面试