在日常的企业级后台开发中,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 支持。整个流程不经过服务端,既轻量又直接。
当然这种方案也不是万能的,大文件处理和复杂格式兼容性需要在实际业务中权衡。希望这篇整理能给正在做类似需求的同学提供一点参考。