Spire.OfficeJS 11.6.5 现已发布。该版本升级了产品底层结构,并调整了发布包结构。详情如下。
优化:
Spire.Office for JavaScript 11.7.0 现已正式发布。该版本支持将 Word 直接保存为 XLSX 格式,以及操作格式修订信息。同时,还支持设置图表数据标签位置,并支持追加、修改或删除 SmartArt 图形。更多详情如下。
新功能:
let doc = new spiredoc.Document();
doc.LoadFromFile(inputFileName);
doc.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.XLSX});
spire.copyFileFromFSToLocalStorage(outFileName, outputFile);
doc.Close();
let doc = new spiredoc.Document();
doc.LoadFromFile(inputFileName);
let revisionInfoCollection = doc.GetRevisionInfos();
for (let i = 0; i < revisionInfoCollection.Count; i++) {
let revisionInfo = revisionInfoCollection.get_Item(i);
if (revisionInfo.RevisionType === spiredoc.RevisionType.FormatChange)
{
revisionInfo.Reject(); // 或 Accept()
i--;
}
}
doc.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.Docx});
spire.copyFileFromFSToLocalStorage(outFileName, outputFile);
doc.Close();
let doc = new spiredoc.Document();
doc.LoadFromFile(inputFileName);
let section = doc.Sections.get_Item(0);
let para1 = section.Paragraphs.get_Item(2);
para1.Format.AdjustRightIndent = true;
section.AddParagraph();
let para3 = section.Paragraphs.get_Item(7);
para3.AppendText("News Line Sample");
para3.Format.AdjustRightIndent = true;
doc.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.Docx});
spire.copyFileFromFSToLocalStorage(outFileName, outputFile);
doc.Close();
let document = new spiredoc.Document();
let section = document.AddSection();
let paragraph = section.AddParagraph();
paragraph.Format.HorizontalAlignment = spiredoc.HorizontalAlignment.Center;
let textRange = paragraph.AppendText("RepeatingBendingProcess");
textRange.CharacterFormat.FontSize = 28;
textRange.CharacterFormat.FontName = "Times New Roman";
paragraph = section.AddParagraph();
paragraph = section.AddParagraph();
paragraph.Format.HorizontalAlignment = spiredoc.HorizontalAlignment.Center;
let shape = paragraph.AppendSmartArt(spiredoc.SmartArtType.RepeatingBendingProcess, 432, 252);
let repeatingBendingSmartArt = shape.SmartArt;
let process1 = repeatingBendingSmartArt.Nodes.get_Item(0);
process1.Text = "1";
if (process1.Paragraphs.get_Item(0).ChildObjects.get_Item(0) instanceof spiredoc.TextRange) {
process1.Paragraphs.get_Item(0).ChildObjects.get_Item(0).CharacterFormat.FontName = "Calibri";
process1.Paragraphs.get_Item(0).ChildObjects.get_Item(0).CharacterFormat.FontSize = 20;
process1.Paragraphs.get_Item(0).ChildObjects.get_Item(0).CharacterFormat.TextColor = spiredoc.Color.FromArgb(220, 20, 60);
}
repeatingBendingSmartArt.UpdateSmartArt();
document.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.Docx});
spire.copyFileFromFSToLocalStorage(outFileName, outputFile);
document.Close();
let doc = new spiredoc.Document();
doc.LoadFromFile(inputFileName);
let table = doc.FirstSection.Body.Tables.get_Item(0);
let firstRow = table.FirstRow;
firstRow.Hidden = true;
// 验证行内所有元素的 CharacterFormat.Hidden
for (let c = 0; c < row.Cells.Count; c++) {
let cell = row.Cells.get_Item(c);
for (let p = 0; p < cell.Paragraphs.Count; p++) {
let para = cell.Paragraphs.get_Item(p);
for (let r = 0; r < para.ChildObjects.Count; r++) {
let run = para.ChildObjects.get_Item(r);
if (run instanceof spiredoc.TextRange) { ... }
}
}
}
doc.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.Docx});
spire.copyFileFromFSToLocalStorage(outFileName, outputFile);
doc.Close();
let doc = new spiredoc.Document();
doc.CompatibilityOptions.UlTrailSpace = true; // 或 false
let sec = doc.AddSection();
let para = sec.AddParagraph();
let blanks = "(6) ";
let tr = para.AppendText(blanks);
tr.CharacterFormat.UnderlineStyle = spiredoc.UnderlineStyle.Single;
doc.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.Docx2013});
spire.copyFileFromFSToLocalStorage(outFileName, outputFile);
let doc = new spiredoc.Document();
doc.LoadFromFile(inputFileName);
for (let s = 0; s < doc.Sections.Count; s++) {
let sec = doc.Sections.get_Item(s);
for (let p = 0; p < sec.Paragraphs.Count; p++) {
let paragraph = sec.Paragraphs.get_Item(p);
for (let i = 0; i < paragraph.ChildObjects.Count; i++) {
let obj = paragraph.ChildObjects.get_Item(i);
if (obj instanceof spiredoc.ShapeObject) {
let shape = obj;
let chart = shape.Chart;
// 读取X轴数据
for (let x = 0; x < chart.XValues.Count; x++) {
let xVal = chart.XValues.get_Item(x);
}
// 读取Y轴数据
let series = chart.Series.get_Item(0);
for (let y = 0; y < series.YValues.Count; y++) {
let yVal = series.YValues.get_Item(y);
}
}
}
}
}
doc.Dispose();
let doc = new spiredoc.Document();
doc.LoadFromFile(inputFileName);
let revisionInfoCollection = doc.GetRevisionInfos();
for (let i = 0; i < revisionInfoCollection.Count; i++) {
let revisionInfo = revisionInfoCollection.get_Item(i);
// 获取修订信息
let author = revisionInfo.Author;
let revisionType = revisionInfo.RevisionType;
let dateTime = revisionInfo.DateTime;
let ownerObjType = revisionInfo.OwnerObject.DocumentObjectType;
// 判断作用对象类型
if (revisionInfo.OwnerObject instanceof spiredoc.TextRange) {
let range = revisionInfo.OwnerObject;
}
}
doc.Dispose();
let doc = new spiredoc.Document();
// 单独设置兼容选项
doc.CompatibilityOptions.UlTrailSpace = false;
doc.CompatibilityOptions.AdjustLineHeightInTable = true;
doc.CompatibilityOptions.SpaceForUL = true;
doc.CompatibilityOptions.ApplyBreakingRules = true;
doc.CompatibilityOptions.DoNotExpandShiftReturn = false;
doc.CompatibilityOptions.OverrideTableStyleFontSizeAndJustification = false;
doc.CompatibilityOptions.DoNotAutofitConstrainedTables = true;
// 按Word版本优化
doc.CompatibilityOptions.OptimizeForWordVersion(spiredoc.WordVersion.Word2016);
doc.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.Docx2016});
spire.copyFileFromFSToLocalStorage(outFileName, outputFile);
doc.Close();
let doc = new spiredoc.Document();
let sec = doc.AddSection();
for (let item of testData) {
let pa = sec.AddParagraph();
let textRange = pa.AppendText(item.text);
textRange.CharacterFormat.Kerning = item.kerning;
}
doc.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.Docx});
spire.copyFileFromFSToLocalStorage(outFileName, outputFile);
doc.Close();
let doc = new spiredoc.Document();
let section = doc.AddSection();
section.TextDirection = spiredoc.TextDirection.RightToLeft;
let paragraph = section.AddParagraph();
let range = paragraph.AppendText("一、選擇題:每題");
let farEastLayout2 = paragraph.AppendText("34");
let style = new spiredoc.FarEastLayout();
style.Vertical = true;
farEastLayout2.CharacterFormat.FarEastLayout = style;
doc.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.Doc});
spire.copyFileFromFSToLocalStorage(outFileName, outputFile);
doc.Close();
let document = new spiredoc.Document();
document.LoadFromFile(inputFileName);
for (let s = 0; s < document.Sections.Count; s++) {
let section = document.Sections.get_Item(s);
for (let p = 0; p < section.Paragraphs.Count; p++) {
let paragraph = section.Paragraphs.get_Item(p);
for (let c = 0; c < paragraph.ChildObjects.Count; c++) {
let childObj = paragraph.ChildObjects.get_Item(c);
if (childObj instanceof spiredoc.Shape && childObj.HasSmartArt) {
let smartArt = childObj.SmartArt;
// 获取SmartArt类型
let type = smartArt.SmartArtType;
// 获取背景填充
let bgFillType = smartArt.BackgroundFill.FillType;
// 遍历节点
for (let n = 0; n < smartArt.Nodes.Count; n++) {
let node = smartArt.Nodes.get_Item(n);
// 节点文本、字体、形状属性
node.Text;
node.ShapeProperties.get(0).Fill.FillType;
node.ShapeProperties.get(0).Fill.Color;
node.ShapeProperties.get(0).LineFormat.Fill.FillType;
}
}
}
}
}
document.Dispose();
let doc = new spiredoc.Document();
doc.LoadFromFile(inputFileName);
let revisionInfoCollection = doc.GetRevisionInfos();
for (let i = 0; i < revisionInfoCollection.Count; i++) {
let revisionInfo = revisionInfoCollection.get_Item(i);
if (revisionInfo.RevisionType === spiredoc.RevisionType.FormatChange) {
if (revisionInfo.OwnerObject instanceof spiredoc.TextRange) {
let range = revisionInfo.OwnerObject;
doc.RevisionsView = spiredoc.RevisionsView.Original;
// 读取Original样式
let bold = range.CharacterFormat.Bold;
doc.RevisionsView = spiredoc.RevisionsView.Final;
// 读取Final样式
let color = range.CharacterFormat.TextColor;
}
}
}
doc.Close();
let doc = new spiredoc.Document();
for (let pos of positions) {
let section = doc.AddSection();
let newPara = section.AddParagraph();
let shape = newPara.AppendChart(spiredoc.ChartType.Pie, 500, 300);
let chart = shape.Chart;
chart.Series.get_Item(0).HasDataLabels = true;
chart.Series.get_Item(0).DataLabels.ShowCategoryName = true;
chart.Series.get_Item(0).DataLabels.ShowValue = true;
chart.Series.get_Item(0).DataLabels.Position = pos;
}
doc.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.Docx});
spire.copyFileFromFSToLocalStorage(outFileName, outputFile);
doc.Dispose();
let document = new spiredoc.Document();
document.LoadFromFile(inputFileName);
let paragraph = document.LastParagraph;
let shape1 = paragraph.ChildObjects.get_Item(0);
let smartArt = shape1.SmartArt;
// 修改背景填充
smartArt.BackgroundFill.FillType = spiredoc.FillType.Solid;
smartArt.BackgroundFill.Color = spiredoc.Color.FromArgb(255, 242, 169, 132);
// 修改节点文本
let node = smartArt.Nodes.get_Item(0);
node.Text = "Goals";
// 修改节点填充
let shape = node.ShapeProperties.get(0);
shape.Fill.FillType = spiredoc.FillType.Solid;
shape.Fill.Color = spiredoc.Color.FromArgb(255, 160, 43, 147);
shape.LineFormat.Fill.FillType = spiredoc.FillType.Solid;
shape.LineFormat.Fill.Color = spiredoc.Color.FromArgb(255, 160, 43, 147);
// 添加新节点
let newNode = smartArt.Nodes.Add();
newNode.Text = "地图";
smartArt.UpdateSmartArt();
document.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.Docx});
spire.copyFileFromFSToLocalStorage(outFileName, fullOutputPath);
document.Close();
let document = new spiredoc.Document();
document.LoadFromFile(inputFileName);
for (let j = 0; j < document.Sections.get_Item(0).Paragraphs.Count; j++) {
let paragraph = document.Sections.get_Item(0).Paragraphs.get_Item(j);
for (let i = 0; i < paragraph.ChildObjects.Count; i++) {
let childObj = paragraph.ChildObjects.get_Item(i);
try {
let smartArt = childObj.SmartArt;
if (smartArt != null) {
paragraph.Items.RemoveAt(i);
i--;
}
} catch (e) { }
}
}
document.SaveToFile({fileName: outFileName, fileFormat: spiredoc.FileFormat.Docx});
spire.copyFileFromFSToLocalStorage(outFileName, fullOutputPath);
document.Close();
https://www.e-iceblue.cn/Downloads/Spire-Office-Javascript.html
将文档中的占位符替换为 HTML 内容或另一个文档的段落,是文档自动化中非常实用的需求——例如将富文本编辑器中编排好的 HTML 内容填入 Word 模板的占位符位置;或从标准条款库文档中提取指定段落,替换到合同模板中的对应位置。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成此类替换操作,通过虚拟文件系统(VFS)管理字体及文档文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。
将占位符替换为 HTML 的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件、HTML 文件和目标 Word 文档载入 WASM 虚拟文件系统;然后创建临时 Section,通过 AppendHTML 将 HTML 字符串渲染为文档对象并收集到替换列表中,再调用 FindAllString 查找所有 [#placeholder] 占位符,对匹配位置排序后逐一用 ChildObjects.Insert 插入替换内容并移除原文本;最后移除临时 Section,保存文档,从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
import React, { useState } from 'react';
function App() {
// 定义占位符替换逻辑
function ReplacedWithHTML(location, replacement) {
let textRange = location.Text;
let index = location.Index;
let paragraph = location.Owner;
let sectionBody = paragraph.OwnerTextBody;
let paragraphIndex = sectionBody.ChildObjects.IndexOf(paragraph);
let replacementIndex = -1;
if (index === 0) {
paragraph.ChildObjects.RemoveAt(0);
replacementIndex = sectionBody.ChildObjects.IndexOf(paragraph);
} else if (index === paragraph.ChildObjects.Count - 1) {
paragraph.ChildObjects.RemoveAt(index);
replacementIndex = paragraphIndex + 1;
} else {
let paragraph1 = paragraph.Clone();
while (paragraph.ChildObjects.Count > index) {
paragraph.ChildObjects.RemoveAt(index);
}
let i = 0;
let count = index + 1;
while (i < count) {
paragraph1.ChildObjects.RemoveAt(0);
i += 1;
}
sectionBody.ChildObjects.Insert(paragraphIndex + 1, paragraph1);
replacementIndex = paragraphIndex + 1;
}
for (let i = 0; i <= replacement.length - 1; i++) {
sectionBody.ChildObjects.Insert(replacementIndex + i, replacement[i].Clone());
}
}
function TextRangeLocation(TextRange) {
this.Text = TextRange;
this.Owner = this.Text.OwnerParagraph;
this.Index = this.Owner.ChildObjects.IndexOf(this.Text);
this.CompareTo = function (other) {
return -(this.Index - other.Index);
};
}
const ReplaceWithHTML = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// Load the ARIALUNI.TTF font file into the virtual file system (VFS)
await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 将 HTML 文件和 Word 文档载入 VFS
let HTMLName = 'InputHtml1.txt';
await window.spire.FetchFileToVFS(HTMLName, '', `${process.env.PUBLIC_URL}/data/`);
const HTML = window.dotnetRuntime.Module.FS.readFile(HTMLName);
let inputFileName = 'ReplaceWithHtml.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 加载文档
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// 创建临时 Section 并渲染 HTML
let replacement = [];
let tempSection = doc.AddSection();
let par = tempSection.AddParagraph();
const decoder = new TextDecoder('utf-8');
const HTMLString = decoder.decode(HTML);
par.AppendHTML(HTMLString);
// 收集渲染后的文档对象
for (let i = 0; i < tempSection.Body.ChildObjects.Count; i++) {
let docObj = tempSection.Body.ChildObjects.get_Item(i);
replacement.push(docObj);
}
// 查找所有占位符并排序
let selections = doc.FindAllString('[#placeholder]', false, true);
let locations = [];
for (let selection of selections) {
locations.push(new TextRangeLocation(selection.GetAsOneRange()));
}
locations.sort();
// 逐个替换
for (let location of locations) {
ReplacedWithHTML(location, replacement);
}
// 移除临时 Section
doc.Sections.Remove(tempSection);
// 定义输出文件名并保存
const outputFileName = 'ReplaceWithHtml_output.docx';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>将 Word 文档中的占位符替换为 HTML</h1>
<button onClick={ReplaceWithHTML}>
生成
</button>
</div>
);
}
export default App;
文档中的 [#placeholder] 占位符被 HTML 渲染后的富文本内容替换
![文档中的 [#placeholder] 占位符被 HTML 渲染后的富文本内容替换](https://cdn.e-iceblue.cn/images/tutorials-images/replace-placeholder-with-html-or-paragraph-zh-1.webp)
将占位符替换为另一个文档的段落的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和两个 Word 文档载入 WASM 虚拟文件系统;然后分别加载主文档和来源文档,调用 FindAllPattern 通过正则表达式查找占位符(如 [MY_DOCUMENT]),遍历来源文档的所有 Section 及 Paragraphs,通过 ChildObjects.Insert 将每个段落逐条插入到主文档的对应位置,最后移除原占位符文本;最后保存文档,从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
import React, { useState } from 'react';
function App() {
const ReplaceContentWithDoc = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将两个 Word 文档载入 VFS
let inputFileName1 = 'ReplaceContentWithDoc.docx';
await window.spire.FetchFileToVFS(inputFileName1, '', `${process.env.PUBLIC_URL}/data/`);
let inputFileName2 = 'Insert.docx';
await window.spire.FetchFileToVFS(inputFileName2, '', `${process.env.PUBLIC_URL}/data/`);
// 加载主文档
let document1 = new docModule.Document();
document1.LoadFromFile(inputFileName1);
// 加载来源文档(包含待插入的段落)
let document2 = new docModule.Document();
document2.LoadFromFile(inputFileName2);
// 获取主文档的第一个 Section
let section1 = document1.Sections.get_Item(0);
// 创建正则表达式查找占位符
let regex = new docModule.Regex('\\[MY_DOCUMENT\\]', docModule.RegexOptions.None);
// 查找所有匹配的占位符
let textSections = document1.FindAllPattern({ pattern: regex });
// 遍历每个匹配位置
for (let i = 0; i < textSections.length; i++) {
let selection = textSections[i];
let para = selection.GetAsOneRange().OwnerParagraph;
let textRange = selection.GetAsOneRange();
let index = section1.Body.ChildObjects.IndexOf(para);
// 将来源文档的所有段落插入到占位符位置
for (let i = 0; i < document2.Sections.Count; i++) {
let section2 = document2.Sections.get_Item(i);
for (let j = 0; j < section2.Paragraphs.Count; j++) {
let paragraph = section2.Paragraphs.get_Item(j);
section1.Body.ChildObjects.Insert(index++, paragraph.Clone());
}
}
// 移除原占位符文本
para.ChildObjects.Remove(textRange);
}
// 定义输出文件名并保存
const outputFileName = 'ReplaceContentWithDoc_output.docx';
document1.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
document1.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>用另一个文档中的段落替换占位符</h1>
<button onClick={ReplaceContentWithDoc}>
生成
</button>
</div>
);
}
export default App;
主文档中的 [MY_DOCUMENT] 占位符被来源文档的全部段落替换
![主文档中的 [MY_DOCUMENT] 占位符被来源文档的全部段落替换](https://cdn.e-iceblue.cn/images/tutorials-images/replace-placeholder-with-html-or-paragraph-zh-2.webp)
原因:AppendHTML 方法支持的 HTML 标签范围有限,仅能识别基础的块级和行内标签(如 <p>、<b>、<i>、<table> 等),复杂的 CSS 样式、JavaScript 代码或 HTML5 新标签会被忽略。
解决:确保输入的 HTML 仅使用基础标签,并通过内联样式(如 style="color:red")而非 CSS 类名来定义格式:
<p style="font-size:14pt; color:#2E75B6;">这是蓝色标题文本</p>
<ul><li>项目一</li><li>项目二</li></ul>
原因:多个占位符替换时,未对匹配位置进行排序处理,从前往后依次替换导致后续位置的索引发生偏移,使段落插入到了错误的位置。
解决:先对所有匹配位置按索引降序排序(从文档尾部向前替换),或记录每个位置的原始索引偏移量:
let locations = [];
for (let selection of selections) {
locations.push(new TextRangeLocation(selection.GetAsOneRange()));
}
locations.sort(); // 降序排列,从后往前替换
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
将文档中的特定占位文本替换为图片或表格,是实际开发中频率极高的需求——例如将合同尾部「(签章处)」替换为公司印章图片、将数据汇总占位符替换为统计表格等。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成此类替换操作,通过虚拟文件系统(VFS)管理字体、图片及文档文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。
将文本替换为图片的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件、目标图片和 Word 文档载入 WASM 虚拟文件系统;然后调用 FindAllString 查找所有匹配的目标文本,遍历每个匹配位置,通过 DocPicture 加载图片并用 ChildObjects.Insert 插入图片,再用 ChildObjects.Remove 移除原文本;最后保存文档,从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
import React, { useState } from 'react';
function App() {
const ReplaceWithImage = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将图片和 Word 文档载入 VFS
let pngName = 'E-iceblue.png';
await window.spire.FetchFileToVFS(pngName, '', `${process.env.PUBLIC_URL}/data/`);
let inputFileName = 'Template.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 加载文档
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// 查找所有匹配的文本
let selections = doc.FindAllString('E-iceblue', true, true);
// 遍历每个匹配位置,将文本替换为图片
for (let i = 0; i < selections.length; i++) {
// 创建 DocPicture 对象并加载图片
let pic = new docModule.DocPicture(doc);
pic.LoadImage(pngName);
let selection = selections[i];
// 获取当前文本范围
let range = selection.GetAsOneRange();
// 获取 TextRange 在其所属段落 ChildObjects 中的索引
let index = range.OwnerParagraph.ChildObjects.IndexOf(range);
// 在 TextRange 位置插入图片
range.OwnerParagraph.ChildObjects.Insert(index, pic);
// 移除原 TextRange
range.OwnerParagraph.ChildObjects.Remove(range);
}
// 定义输出文件名并保存
const outputFileName = 'ReplaceWithImage_output.docx';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>在Word文档中用图片替换文本</h1>
<button onClick={ReplaceWithImage}>
生成
</button>
</div>
);
}
export default App;
文档中的目标文本被查找并替换为指定图片后的效果

将文本替换为表格的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 Word 文档载入 WASM 虚拟文件系统;然后调用 FindString 查找指定文本,通过 GetAsOneRange 获取文本范围,再通过 OwnerTextBody.ChildObjects 获取段落索引,创建新表格后用 ChildObjects.Remove 移除原段落,并用 ChildObjects.Insert 在相同位置插入表格;最后保存文档,从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
import React, { useState } from 'react';
function App() {
const ReplaceTextWithTable = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将字体文件载入 VFS
await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 将 Word 文档载入 VFS
let inputFileName = 'Template_Docx_1.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 加载文档
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// 获取第一个章节
let section = doc.Sections.get_Item(0);
// 查找目标文本
let selection = doc.FindString('Christmas Day, December 25', true, true);
// 获取文本范围及所属段落
let range = selection.GetAsOneRange();
let paragraph = range.OwnerParagraph;
// 获取段落所属的文本体,计算段落索引
let body = paragraph.OwnerTextBody;
let index = body.ChildObjects.IndexOf(paragraph);
// 创建 3×3 表格
let table = section.AddTable(true);
table.ResetCells(3, 3);
// 移除原段落,在相同位置插入表格
body.ChildObjects.Remove(paragraph);
body.ChildObjects.Insert(index, table);
// 定义输出文件名并保存
const outputFileName = 'ReplaceTextWithTable_output.docx';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>在Word文档中将文本替换为表格</h1>
<button onClick={ReplaceTextWithTable}>
生成
</button>
</div>
);
}
export default App;
文档中的目标段落被移除后,在相同位置插入指定规格的表格

原因:图片文件未加载到 WASM 虚拟文件系统,或 FetchFileToVFS 加载路径不正确,导致 DocPicture.LoadImage 无法从 VFS 中读取图片数据。
解决:确保在调用 LoadImage 之前,已通过 FetchFileToVFS 将图片文件正确载入 VFS,并检查文件名称与路径是否一致:
await window.spire.FetchFileToVFS(
'E-iceblue.png', '', `${process.env.PUBLIC_URL}/data/`
);
原因:通过 OwnerTextBody.ChildObjects.IndexOf 获取的段落索引不准确,或选中的文本跨多个段落导致索引偏移,使表格插入到了错误的位置。
解决:先确认 FindString 查找的文本位于单个段落内,然后通过 GetAsOneRange 获取准确的 OwnerParagraph,再获取其在 OwnerTextBody.ChildObjects 中的索引:
let range = selection.GetAsOneRange();
let paragraph = range.OwnerParagraph;
let body = paragraph.OwnerTextBody;
let index = body.ChildObjects.IndexOf(paragraph);
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
文本替换是 Word 文档处理中最常见的需求之一——批量替换合同中的占位符、统一修正术语、或者对特定格式的文本进行规范化处理,都可以通过查找替换功能高效完成。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接处理 Word 文档,通过虚拟文件系统(VFS)管理字体和文件资源,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript 的方法。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。
替换指定文本是最基本的文本操作——将文档中的某个词或短语统一替换为另一个字符串,支持大小写敏感和全词匹配选项。核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 Word 文档载入 WASM 虚拟文件系统;然后实例化 Document 加载文件,调用 Replace 方法匹配指定字符串并替换为新文本,可通过 caseSensitive 和 wholeWord 参数控制匹配规则;最后保存文档,从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
import React, { useState } from 'react';
function App() {
const ReplaceWithText = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将示例文件载入 VFS
let inputFileName = 'Sample.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 创建文档实例
const doc = new docModule.Document();
// 从虚拟文件系统加载文档
doc.LoadFromFile(inputFileName);
// 替换文本:"word" → "ReplacedText",不区分大小写,全词匹配
doc.Replace({ matchString: 'word', newValue: 'ReplacedText', caseSensitive: false, wholeWord: true });
// 定义输出文件名并保存
const outputFileName = 'ReplaceWithText_output.docx';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>点击以下按钮替换 Word 文档中的文本。</h1>
<button onClick={ReplaceWithText}>
Generate
</button>
</div>
);
};
export default App;
通过 Replace 方法替换指定文本后,文档中的目标字符串将被统一替换为新内容

当需要匹配不固定格式的文本时,正则表达式是最强大的工具——例如将所有以 # 开头的标签文本统一替换为固定内容。核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 Word 文档载入 WASM 虚拟文件系统;然后创建 Regex 对象定义匹配模式,调用 Replace 方法将匹配到的文本统一替换为指定内容;最后保存文档,从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
import React, { useState } from 'react';
function App() {
const ReplaceWithText = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将示例文件载入 VFS
let inputFileName = 'Sample.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}static/data/`);
// 创建文档实例
const doc = new docModule.Document();
// 从虚拟文件系统加载文档
doc.LoadFromFile(inputFileName);
// 创建正则表达式,匹配以 # 开头的单词
let regex = new docModule.Regex('\\bword\\b', docModule.RegexOptions.None);
// 使用正则表达式替换文本
doc.Replace(regex, 'Spire.Doc');
// 定义输出文件名并保存
const outputFileName = 'ReplaceTextByRegex_output.docx';
doc.SaveToFile({fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>点击以下按钮替换 Word 文档中的文本。</h1>
<button onClick={ReplaceWithText}>
Generate
</button>
</div>
);
};
export default App;
通过正则表达式匹配并替换文本后,所有符合模式的字符串将被统一替换

原因:caseSensitive 或 wholeWord 参数设置不正确导致匹配失败,实际文本与匹配条件不符。
解决:检查大小写是否匹配,或将 caseSensitive 设为 false 以忽略大小写,将 wholeWord 设为 false 以匹配部分单词:
doc.Replace({ matchString: 'word', newValue: 'ReplacedText', caseSensitive: false, wholeWord: false });
原因:正则表达式模式中特殊字符(如 #、\)未正确转义,导致匹配失败。
解决:确保正则表达式中的特殊字符已正确转义。例如匹配 #tag 格式的文本时,# 和 \b 需要按字符串转义规则处理:
let regex = new wasmModule.Regex('#\\S+', wasmModule.RegexOptions.None);
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
将文档中的特定文本替换为域代码或另一个文档的内容,是文档自动化中非常实用的需求——例如将模板中的「当前日期」占位符替换为日期域(DATE Field),使其在每次打开文档时自动更新为系统日期;或将合同模板中的「条款内容」占位符替换为另一个 Word 文档中的详细条款内容,实现文档的模块化组装。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成此类替换操作,通过虚拟文件系统(VFS)管理字体及文档文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。
将文本替换为域的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 Word 文档载入 WASM 虚拟文件系统;然后调用 FindString 查找目标文本,获取其所在段落及索引位置,创建 Field 对象并指定域类型(如 FieldDate),依次在段落中插入 Field、FieldMark(FieldSeparator)、FieldMark(FieldEnd) 以构建完整的域结构,最后移除原文本;最后保存文档,从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
import React, { useState } from 'react';
function App() {
const ReplaceTextWithField = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将 Word 文档载入 VFS
let inputFileName = 'ReplaceTextWithField.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 加载文档
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// 查找目标文本
let selection = doc.FindString({
stringValue: 'summary',
caseSensitive: false,
wholeWord: true,
});
// 获取文本范围及其所在段落
let textRange = selection.GetAsOneRange();
let ownParagraph = textRange.OwnerParagraph;
let rangeIndex = ownParagraph.ChildObjects.IndexOf(textRange);
// 创建日期域(DATE Field)
let eqField = new docModule.Field(doc);
eqField.Type = docModule.FieldType.FieldDate;
eqField.Code = 'DATE \\@ "yyyyMMdd "';
// 依次插入 Field、FieldSeparator、FieldEnd
ownParagraph.ChildObjects.Insert(rangeIndex, eqField);
let mark = new docModule.FieldMark(doc, docModule.FieldMarkType.FieldSeparator);
ownParagraph.ChildObjects.Insert(rangeIndex + 1, mark);
let end = new docModule.FieldMark(doc, docModule.FieldMarkType.FieldEnd);
ownParagraph.ChildObjects.Insert(rangeIndex + 2, end);
// 移除原文本
ownParagraph.ChildObjects.Remove(textRange);
// 定义输出文件名并保存
const outputFileName = 'ReplaceTextWithField_output.docx';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>在 Word 文档中将文本替换为域</h1>
<button onClick={ReplaceTextWithField}>
生成
</button>
</div>
);
}
export default App;
文档中的目标文本被替换为日期域后,打开文档时会自动显示当前系统日期

将文本替换为另一个文档的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和两个 Word 文档(主文档和待替换内容文档)载入 WASM 虚拟文件系统;然后分别实例化两个 Document 对象加载两个文档,在主文档上调用 Replace 方法并传入 matchDoc 参数(指向另一个文档对象),将匹配的文本替换为该文档的全部内容;最后保存文档,从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
import React, { useState } from 'react';
function App() {
const ReplaceWithDocument = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将两个 Word 文档载入 VFS
let inputFileName1 = 'Text2.docx';
await window.spire.FetchFileToVFS(inputFileName1, '', `${process.env.PUBLIC_URL}/data/`);
let inputFileName2 = 'Text1.docx';
await window.spire.FetchFileToVFS(inputFileName2, '', `${process.env.PUBLIC_URL}/data/`);
// 加载主文档
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName1);
// 加载另一个文档(替换内容)
let replaceDoc = new docModule.Document();
replaceDoc.LoadFromFile(inputFileName2);
// 用另一个文档的内容替换指定文本
doc.Replace({
matchString: 'Document1',
matchDoc: replaceDoc,
caseSensitive: false,
wholeWord: true,
});
// 定义输出文件名并保存
const outputFileName = 'ReplaceWithDocument_output.docx';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>在 Word 文档中将文本替换为另一个文档</h1>
<button onClick={ReplaceWithDocument}>
生成
</button>
</div>
);
}
export default App;
主文档中的占位文本被另一个文档的全部内容替换后的效果

原因:Field、FieldMark(FieldSeparator)、FieldMark(FieldEnd) 三者没有按照正确的顺序插入,破坏了域结构的完整性,导致 Word 无法正确识别和计算域值。
解决:确保按顺序依次插入 Field、FieldSeparator、FieldEnd,且插入索引连续递增:
ownParagraph.ChildObjects.Insert(rangeIndex, eqField);
ownParagraph.ChildObjects.Insert(rangeIndex + 1, mark);
ownParagraph.ChildObjects.Insert(rangeIndex + 2, end);
原因:Replace 方法中 matchDoc 参数传入的文档对象未正确加载到 VFS 中,或文件路径/名称与 FetchFileToVFS 载入时不一致,导致 LoadFromFile 无法找到目标文件。
解决:确认两个文档均已通过 FetchFileToVFS 载入 VFS,并在调用 Replace 之前分别创建 Document 对象并成功加载:
let doc = new docModule.Document();
doc.LoadFromFile('Text2.docx');
let replaceDoc = new docModule.Document();
replaceDoc.LoadFromFile('Text1.docx');
doc.Replace({
matchString: 'Document1',
matchDoc: replaceDoc,
caseSensitive: false,
wholeWord: true,
});
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。

JSON 广泛应用于 REST API、Web 服务和企业应用中的数据交换。然而,业务人员更习惯使用 Excel 进行报表展示、数据筛选和分析。因此,开发者在导出 API 响应、生成报表或向非技术人员共享结构化数据时,经常需要在 Java 中将 JSON 转换为 Excel。
Java 生态中有多种 JSON 处理库,但要将数据转换为结构良好的 Excel 文件,还需要处理列头、单元格类型、行遍历和输出格式等问题——如果没有合适的工具,这些工作会非常繁琐。Spire.XLS for Java 提供了简洁的 API,无需依赖 Microsoft Office 即可创建 Excel 工作簿,大幅简化了这一过程。
本文将介绍如何使用 Spire.XLS for Java 和 Jackson 在 Java 中将 JSON 转换为 Excel,涵盖 JSON 数组转换、嵌套 JSON 处理、JSON 文件读取、XLSX 和 XLS 导出、自适应列宽、格式化以及大数据集处理的最佳实践。
快速导航
JSON 因其轻量且易于机器解析的特点,成为 REST API、Web 服务和企业应用中数据交换的主流格式。然而,业务人员通常需要 Excel 文件来进行报表展示、数据筛选、可视化和进一步分析。
在 Java 中将 JSON 转换为 Excel,能够有效打通后端系统与业务流程之间的壁垒。典型应用场景包括:
许多 REST API 返回 JSON 格式的响应。将这些响应转换为 Excel,用户无需手动处理原始 JSON,即可直接查看、筛选和分析数据。
Java 应用可以将来自 API、数据库或其他数据源的 JSON 数据转换为结构化的 Excel 报表,包含表头、格式化和规范的表格布局。
Excel 文件便于分发,且支持图表、公式和数据透视表等分析工具。将 JSON 数据导出为 Excel,非技术用户也能直接使用这些功能。
在开始转换之前,需要在项目中配置以下依赖。
Spire.XLS for Java 已通过 e-iceblue Maven 仓库发布。在 pom.xml 中添加仓库和依赖:
<repositories>
<repository>
<id>com.e-iceblue</id>
<name>e-iceblue</name>
<url>https://repo.e-iceblue.cn/repository/maven-public/</url>
</repository>
</repositories>
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.xls</artifactId>
<version>16.6.5</version>
</dependency>
也可以下载 Spire.XLS for Java,手动将 JAR 文件添加到项目中。
Java 没有内置的 JSON 支持。本指南使用 Jackson,这是 Java 生态中最广泛采用的 JSON 处理库:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version>
</dependency>
在 Java 源文件中添加以下导入语句:
import com.spire.xls.*;
import com.spire.xls.core.spreadsheet.collections.AutoFitType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
为了演示转换过程,我们使用一个简单的 JSON 数组,其中每个对象代表一行数据,每个属性代表一列。这是 REST API 响应和数据导出中最常见的 JSON 结构。
[
{
"ID": 1,
"Name": "陈明辉",
"Department": "销售部",
"Salary": 75000,
"HireDate": "2022-03-15"
},
{
"ID": 2,
"Name": "林雨婷",
"Department": "市场部",
"Salary": 68000,
"HireDate": "2021-07-01"
},
{
"ID": 3,
"Name": "赵文博",
"Department": "技术部",
"Salary": 92000,
"HireDate": "2023-01-10"
}
]
JSON 与 Excel 之间的映射关系非常直观:
理解这一映射关系,有助于理解后续章节中的代码示例。
转换过程分为五个步骤:创建工作簿、获取工作表、解析 JSON 数据、写入列头和填充单元格值。本节逐一讲解每个步骤,最后给出完整代码。
Workbook 类代表一个 Excel 文件。实例化该类即可创建一个新的空工作簿:
Workbook workbook = new Workbook();
一个工作簿包含一个或多个工作表。获取默认创建的第一个工作表,并可选择为其重命名:
Worksheet sheet = workbook.getWorksheets().get(0);
sheet.setName("员工数据");
使用 Jackson 的 ObjectMapper 将 JSON 字符串解析为 JsonNode 树。如果根元素是 JSON 数组,将其转换为 ArrayNode 以便遍历:
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(jsonString);
if (!rootNode.isArray()) {
throw new IllegalArgumentException("Expected a JSON array at the root level");
}
ArrayNode jsonArray = (ArrayNode) rootNode;
从第一个 JSON 对象中提取字段名,写入工作表的第一行。注意 Spire.XLS 的行和列索引从 1 开始:
JsonNode firstObject = jsonArray.get(0);
int col = 1;
for (Iterator<Map.Entry<String, JsonNode>> it = firstObject.fields(); it.hasNext(); ) {
Map.Entry<String, JsonNode> entry = it.next();
sheet.get(1, col).setValue(entry.getKey());
col++;
}
遍历数组中的每个 JSON 对象,将其值写入对应的行。由于第 1 行已是列头,数据从第 2 行开始写入:
for (int i = 0; i < jsonArray.size(); i++) {
JsonNode record = jsonArray.get(i);
int dataRow = i + 2;
int dataCol = 1;
for (Iterator<Map.Entry<String, JsonNode>> it = record.fields(); it.hasNext(); ) {
Map.Entry<String, JsonNode> entry = it.next();
JsonNode value = entry.getValue();
if (value.isNumber()) {
sheet.get(dataRow, dataCol).setNumberValue(value.doubleValue());
} else if (value.isBoolean()) {
sheet.get(dataRow, dataCol).setBooleanValue(value.booleanValue());
} else {
sheet.get(dataRow, dataCol).setValue(value.asText());
}
dataCol++;
}
}
这种方式保留了数据类型——数值和布尔值以对应的类型写入单元格,而非作为字符串存储,从而确保生成的 Excel 文件中数值排序、筛选和公式计算能够正常工作。
以下是完整的可运行程序,读取 JSON 字符串并将其转换为 Excel 文件,展示了在 Java 中将 JSON 转换为 Excel 的完整流程:
import com.spire.xls.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import java.io.File;
import java.util.Iterator;
import java.util.Map;
public class JsonToExcelConverter {
public static void main(String[] args) {
// 示例 JSON 数据——员工记录数组
String jsonString = "["
+ "{\"ID\":1,\"Name\":\"陈明辉\",\"Department\":\"销售部\",\"Salary\":75000,\"HireDate\":\"2022-03-15\"},"
+ "{\"ID\":2,\"Name\":\"林雨婷\",\"Department\":\"市场部\",\"Salary\":68000,\"HireDate\":\"2021-07-01\"},"
+ "{\"ID\":3,\"Name\":\"赵文博\",\"Department\":\"技术部\",\"Salary\":92000,\"HireDate\":\"2023-01-10\"}"
+ "]";
try {
// 将 JSON 字符串解析为 JsonNode 树结构
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(jsonString);
if (!rootNode.isArray()) {
throw new IllegalArgumentException("Expected a JSON array at the root level");
}
ArrayNode jsonArray = (ArrayNode) rootNode;
// 创建新工作簿并获取第一个工作表
Workbook workbook = new Workbook();
Worksheet sheet = workbook.getWorksheets().get(0);
sheet.setName("员工数据");
// 从第一个 JSON 对象的键名提取列头
JsonNode firstObject = jsonArray.get(0);
int col = 1;
for (Iterator<Map.Entry<String, JsonNode>> it = firstObject.fields(); it.hasNext(); ) {
Map.Entry<String, JsonNode> entry = it.next();
sheet.get(1, col).setValue(entry.getKey());
col++;
}
// 将 JSON 值逐行写入 Excel
for (int i = 0; i < jsonArray.size(); i++) {
JsonNode record = jsonArray.get(i);
int dataRow = i + 2;
int dataCol = 1;
for (Iterator<Map.Entry<String, JsonNode>> it = record.fields(); it.hasNext(); ) {
Map.Entry<String, JsonNode> entry = it.next();
JsonNode value = entry.getValue();
// 保留数据类型:数值和布尔值以对应类型写入单元格
if (value.isNumber()) {
sheet.get(dataRow, dataCol).setNumberValue(value.doubleValue());
} else if (value.isBoolean()) {
sheet.get(dataRow, dataCol).setBooleanValue(value.booleanValue());
} else {
sheet.get(dataRow, dataCol).setValue(value.asText());
}
dataCol++;
}
}
// 自适应列宽,提升可读性
sheet.getAllocatedRange().autoFitColumns();
// 将工作簿保存为 XLSX 文件
workbook.saveToFile("员工数据.xlsx", ExcelVersion.Version2016);
System.out.println("JSON 已成功转换为 Excel 文件。");
// 释放资源
workbook.dispose();
} catch (Exception e) {
System.err.println("JSON 转 Excel 过程中发生错误:" + e.getMessage());
e.printStackTrace();
}
}
}
运行程序后,JSON 数据将被转换为 Excel 工作表。生成的 员工数据.xlsx 文件包含员工记录,数据类型得以保留,列宽已自动调整:

get(int row, int column) — 返回指定单元格的 CellRange 对象。行和列索引从 1 开始。setValue(String) — 将单元格值设置为字符串,用于文本和表头。setNumberValue(double) — 将单元格值设置为数值,保留数值类型以支持计算。setBooleanValue(boolean) — 将单元格值设置为布尔值(TRUE/FALSE)。saveToFile(String, ExcelVersion) — 将工作簿按指定 Excel 格式保存到文件。dispose() — 释放工作簿占用的非托管资源。如果还需要将 Excel 文件转换回 JSON 格式,请参阅我们关于如何在 Java 中将 Excel 转换为 JSON的指南。
Spire.XLS for Java 同时支持现代的 XLSX 格式(Excel 2007 及以上版本)和传统的 XLS 格式(Excel 97–2003)。通过向 saveToFile() 传递不同的 ExcelVersion 枚举值,即可控制输出格式。
// 导出为现代 Excel 格式(.xlsx)
workbook.saveToFile("员工数据.xlsx", ExcelVersion.Version2016);
// 导出为传统 Excel 格式(.xls)
workbook.saveToFile("员工数据.xls", ExcelVersion.Version97to2003);
| 格式 | 说明 | 适用场景 |
|---|---|---|
| XLSX | 现代 Excel 格式(Excel 2007+) | 默认选择;文件更小,功能更完整 |
| XLS | 传统 Excel 格式(Excel 97–2003) | 需要兼容旧版系统时使用 |
同一个工作簿对象可以保存为上述任意格式,只需修改文件扩展名和版本参数即可,无需其他代码改动。这在应用需要同时支持新旧环境时尤为实用。
如需了解格式迁移或旧版升级的场景,还可以参考如何在 Java 中转换 XLS 和 XLSX 格式。
实际项目中的 JSON 数据通常包含嵌套的对象和数组。要将嵌套 JSON 写入 Excel,需要将层级结构扁平化为表格格式,每个嵌套字段作为独立的列。
以下 JSON 包含带有嵌套联系方式的员工记录:
[
{
"ID": 1,
"Name": "陈明辉",
"Department": "销售部",
"Contact": {
"Email": "chenminghui@ company.com",
"Phone": "555-0101"
}
},
{
"ID": 2,
"Name": "林雨婷",
"Department": "市场部",
"Contact": {
"Email": "linyuting@ company.com",
"Phone": "555-0102"
}
}
]
目标是将 Contact 对象扁平化,使 Email 和 Phone 成为独立的列:
| ID | Name | Department | Contact.Email | Contact.Phone |
|---|---|---|---|---|
| 1 | 陈明辉 | 销售部 | 该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。 | 555-0101 |
| 2 | 林雨婷 | 市场部 | 该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。 | 555-0102 |
以下代码使用递归扁平化方法,可处理任意深度的嵌套:
import com.spire.xls.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
public class NestedJsonToExcel {
public static void main(String[] args) {
String jsonString = "["
+ "{\"ID\":1,\"Name\":\"陈明辉\",\"Department\":\"销售部\","
+ "\"Contact\":{\"Email\":\"chenminghui@ company.com\",\"Phone\":\"555-0101\"}},"
+ "{\"ID\":2,\"Name\":\"林雨婷\",\"Department\":\"市场部\","
+ "\"Contact\":{\"Email\":\"linyuting@ company.com\",\"Phone\":\"555-0102\"}}"
+ "]";
try {
ObjectMapper mapper = new ObjectMapper();
ArrayNode jsonArray = (ArrayNode) mapper.readTree(jsonString);
Workbook workbook = new Workbook();
Worksheet sheet = workbook.getWorksheets().get(0);
sheet.setName("员工信息");
// 扁平化第一个对象,提取所有列头(包括嵌套键)
LinkedHashMap<String, String> firstFlat = flattenJson(jsonArray.get(0), "");
int col = 1;
for (String key : firstFlat.keySet()) {
sheet.get(1, col).setValue(key);
col++;
}
// 写入数据行
for (int i = 0; i < jsonArray.size(); i++) {
LinkedHashMap<String, String> flat = flattenJson(jsonArray.get(i), "");
int dataRow = i + 2;
int dataCol = 1;
for (String key : firstFlat.keySet()) {
String value = flat.getOrDefault(key, "");
sheet.get(dataRow, dataCol).setValue(value);
dataCol++;
}
}
sheet.getAllocatedRange().autoFitColumns();
workbook.saveToFile("NestedEmployees.xlsx", ExcelVersion.Version2016);
System.out.println("嵌套 JSON 已成功转换为 Excel 文件。");
workbook.dispose();
} catch (Exception e) {
System.err.println("错误:" + e.getMessage());
}
}
/**
* 递归扁平化 JSON 对象为键值对。
* 嵌套键以点号连接(例如 "Contact.Email")。
*/
private static LinkedHashMap<String, String> flattenJson(JsonNode node, String prefix) {
LinkedHashMap<String, String> flat = new LinkedHashMap<>();
if (node.isObject()) {
for (Iterator<Map.Entry<String, JsonNode>> it = node.fields(); it.hasNext(); ) {
Map.Entry<String, JsonNode> entry = it.next();
String newPrefix = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey();
flat.putAll(flattenJson(entry.getValue(), newPrefix));
}
} else {
flat.put(prefix, node.asText());
}
return flat;
}
}
flattenJson 方法递归遍历每个 JSON 对象。遇到嵌套对象时,以点号作为分隔符拼接父键名(如 Contact.Email);到达叶子节点时,将完整的点分隔键及其值存入 Map。这确保了任意嵌套深度的字段都能在 Excel 中表示为独立的列。

在实际应用中,JSON 数据通常来自磁盘文件,而非内联字符串。转换步骤保持不变,只是 JSON 的数据源不同。Jackson 的 ObjectMapper 可以直接从 File 对象读取:
import com.spire.xls.*;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import java.io.File;
import java.util.Iterator;
import java.util.Map;
public class JsonFileToExcel {
public static void main(String[] args) {
try {
// 第 1 步:读取并解析 JSON 文件
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(new File("employees.json"));
if (!rootNode.isArray()) {
throw new IllegalArgumentException("Expected a JSON array at the root level");
}
ArrayNode jsonArray = (ArrayNode) rootNode;
// 第 2 步:创建工作簿
Workbook workbook = new Workbook();
Worksheet sheet = workbook.getWorksheets().get(0);
sheet.setName("员工信息");
// 第 3 步:从第一个对象提取列头
JsonNode firstObject = jsonArray.get(0);
int col = 1;
for (Iterator<Map.Entry<String, JsonNode>> it = firstObject.fields(); it.hasNext(); ) {
Map.Entry<String, JsonNode> entry = it.next();
sheet.get(1, col).setValue(entry.getKey());
col++;
}
// 第 4 步:写入数据行
for (int i = 0; i < jsonArray.size(); i++) {
JsonNode record = jsonArray.get(i);
int dataRow = i + 2;
int dataCol = 1;
for (Iterator<Map.Entry<String, JsonNode>> it = record.fields(); it.hasNext(); ) {
Map.Entry<String, JsonNode> entry = it.next();
JsonNode value = entry.getValue();
if (value.isNumber()) {
sheet.get(dataRow, dataCol).setNumberValue(value.doubleValue());
} else if (value.isBoolean()) {
sheet.get(dataRow, dataCol).setBooleanValue(value.booleanValue());
} else {
sheet.get(dataRow, dataCol).setValue(value.asText());
}
dataCol++;
}
}
// 第 5 步:导出为 Excel
sheet.getAllocatedRange().autoFitColumns();
workbook.saveToFile("EmployeesFromJson.xlsx", ExcelVersion.Version2016);
System.out.println("JSON 文件已成功转换为 Excel。");
workbook.dispose();
} catch (Exception e) {
System.err.println("读取 JSON 文件时出错:" + e.getMessage());
e.printStackTrace();
}
}
}
这种方式能够高效处理大型 JSON 文件,因为 Jackson 以流式树模型处理文件。对于超大 JSON 文件(数百 MB),建议使用 Jackson 的 JsonParser 流式模式逐条读取记录,避免将整个文件一次性加载到内存中。
将 JSON 数据写入 Excel 单元格后,默认列宽可能不足以显示所有内容,文本值(如邮箱地址、URL 或较长的描述)会被截断。Spire.XLS 提供了自适应方法,可根据内容自动调整列宽和行高:
// 自适应已用区域的所有列宽和行高
sheet.getAllocatedRange().autoFitColumns();
sheet.getAllocatedRange().autoFitRows();
在写入所有数据之后、保存工作簿之前添加以上代码。getAllocatedRange() 方法返回包含数据的单元格区域,因此只有已填充的单元格会受到影响。
如需更精细的控制,可以对单独列执行自适应:
// 自适应特定列(例如第 3 列)
sheet.getAllocatedRange().getColumns()[2].autoFitColumns();
自适应处理能让电子表格更加专业和易读,尤其在 JSON 数据包含长度不一的文本字段时效果明显。下图对比了原始导出与应用自适应后的效果差异。
原始数据导出往往需要进一步格式化,才能满足企业报表的要求。Spire.XLS for Java 提供了丰富的单元格格式化 API,支持设置表头样式、数字格式和日期格式——全部通过代码完成。
为第一行应用加粗字体和背景色,使表头与数据区分开来:
import com.spire.xls.core.spreadsheet.styles.CellStyle;
import java.awt.Color;
// 为表头行应用格式
CellRange headerRange = sheet.getAllocatedRange().getRows()[0];
headerRange.getStyle().setFont(new ExcelFont(true));
headerRange.getStyle().setColor(Color.decode("#4472C4"));
headerRange.getStyle().getFont().setColor(Color.WHITE);
headerRange.setStyle(headerRange.getStyle());
为数值列应用货币或百分比格式:
// 将 Salary 列(第 4 列)格式化为货币格式
CellRange salaryColumn = sheet.getAllocatedRange().getColumns()[3];
salaryColumn.setNumberFormat("$#,##0.00");
如果 JSON 中包含日期字符串,可以对相应列设置统一的日期显示格式:
// 将 HireDate 列(第 5 列)格式化为日期格式
CellRange dateColumn = sheet.getAllocatedRange().getColumns()[4];
dateColumn.setNumberFormat("yyyy-mm-dd");
上述格式化方法可以组合使用,生成专业的 Excel 报表。如需了解更全面的 Excel 格式化功能,请参考如何使用 Spire.XLS 在 Java 中创建和格式化 Excel 文件。
实际项目中的 JSON 数据往往不如教程示例那样理想。以下是开发者在转换过程中最常遇到的问题及对应的解决方案。
同一数组中的不同 JSON 对象可能包含不同的字段。某条记录可能包含 Phone 字段,而另一条则完全没有。如果代码假设所有对象具有相同的键,缺失的字段会导致 Excel 输出中的列错位。
解决方案: 先收集所有对象中的全部唯一键,再按统一的键列表写入每条记录的值:
// 收集所有 JSON 对象中的唯一键
LinkedHashSet<String> allKeys = new LinkedHashSet<>();
for (JsonNode record : jsonArray) {
record.fieldNames().forEachRemaining(allKeys::add);
}
// 按完整的键集合写入列头
int col = 1;
for (String key : allKeys) {
sheet.get(1, col).setValue(key);
col++;
}
// 写入值,缺失字段以空字符串填充
for (int i = 0; i < jsonArray.size(); i++) {
JsonNode record = jsonArray.get(i);
int dataRow = i + 2;
int dataCol = 1;
for (String key : allKeys) {
JsonNode value = record.get(key);
String cellValue = (value != null && !value.isNull()) ? value.asText() : "";
sheet.get(dataRow, dataCol).setValue(cellValue);
dataCol++;
}
}
JSON 对象可以包含任意深度的嵌套。如果直接将嵌套对象写入单元格,输出结果将是不可读的 [object Object] 或序列化后的 JSON 字符串。
解决方案: 使用第 7 节中演示的递归扁平化方法。flattenJson 方法遍历整个对象树,生成扁平的键值对,嵌套键以点号分隔。
将超大 JSON 文件(数百 MB 或更大)解析为单一的内存树结构可能导致 Java 的 OutOfMemoryError。此外,逐单元格写入数万行数据也会很慢。
解决方案: 使用 Jackson 的流式 API(JsonParser)逐条读取 JSON 记录,每读取一条立即写入 Excel。这样无论文件多大,内存占用都保持恒定:
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
JsonFactory factory = new JsonFactory();
try (JsonParser parser = factory.createParser(new File("large_data.json"))) {
int dataRow = 2;
while (parser.nextToken() != JsonToken.END_ARRAY) {
// 逐条解析对象
JsonNode record = mapper.readTree(parser);
// 写入 Excel...
dataRow++;
}
}
JSON 支持字符串、数值、布尔值、null、数组和对象。Excel 单元格支持文本、数值、布尔值、日期和错误值。类型不匹配——例如将数值存储为字符串——会导致 Excel 的排序和公式功能无法正常工作。
解决方案: 在写入单元格前检查每个 JSON 值的类型。数值使用 setNumberValue(),布尔值使用 setBooleanValue(),文本使用 setValue()。对于 null 值,写入空字符串或占位符。对于日期字符串,将其解析为 Date 对象后使用 setDateTimeValue() 写入为 Excel 日期单元格:
if (value == null || value.isNull()) {
sheet.get(dataRow, dataCol).setValue("");
} else if (value.isNumber()) {
sheet.get(dataRow, dataCol).setNumberValue(value.doubleValue());
} else if (value.isBoolean()) {
sheet.get(dataRow, dataCol).setBooleanValue(value.booleanValue());
} else {
sheet.get(dataRow, dataCol).setValue(value.asText());
}
以下特性使 Spire.XLS for Java 在企业 Java 应用的 JSON 转 Excel 场景中表现出色。
Spire.XLS for Java 是一个独立的库,不依赖 Microsoft Office 或任何外部软件。它可以在任何安装了 Java 运行环境的系统上运行,包括 Linux 服务器、Docker 容器和未安装 Office 的云平台。
该库同时支持传统的 XLS 格式(Excel 97–2003)和现代的 XLSX 格式(Excel 2007+)。只需修改一个参数即可导出为任意格式,轻松适配不同的下游环境。
除了基本的单元格值写入,Spire.XLS 还提供全面的格式化能力——单元格样式、数字格式、字体、颜色、边框、条件格式、图表和数据透视表。可以直接从 JSON 数据生成专业级的 Excel 文件,无需在 Excel 中进行后处理。
API 遵循直观的对象模型:Workbook 包含 Worksheets,每个 Worksheet 包含 CellRanges,每个 CellRange 支持值设置、样式和格式化。熟悉 Excel 对象模型的开发者可以快速上手。
Spire.XLS for Java 专为服务端和企业场景设计。它能高效处理大文件,支持多线程访问模式,并与 Spring Boot、Jakarta EE 等企业级 Java 框架无缝集成。
可以申请 30 天免费许可证来在项目中评估所有功能。
本文详细介绍了如何使用 Spire.XLS for Java 和 Jackson 在 Java 中将 JSON 转换为 Excel。通过解析 JSON 数据、将值写入 Excel 工作表,并将工作簿导出为 XLSX 或 XLS 文件,开发者可以高效地将结构化 JSON 数据转换为易读的电子表格。
Spire.XLS for Java 提供了一种简洁灵活的方式来从 JSON 数据生成 Excel 文件,无需依赖 Microsoft Office 或其他外部依赖。同时支持格式化、自适应列宽以及处理复杂数据结构等高级功能,满足专业 Excel 报表的生成需求。
使用 Jackson 的 ObjectMapper 解析 JSON 数据,通过 Spire.XLS for Java 创建 Workbook 和 Worksheet,将 JSON 键名写入第一行作为列头,然后遍历 JSON 数组填充每一行数据。最后使用 saveToFile() 配合指定的 ExcelVersion 保存工作簿。完整代码示例见第 5 节。
可以。Spire.XLS for Java 是一个独立的库,不需要 Microsoft Office 或其他软件。它能完全在 Java 中创建、读取和写入 XLSX 文件,适用于运行在 Linux、Docker 或云平台上的服务端应用。
使用递归扁平化函数遍历 JSON 对象树,生成扁平的键值对。嵌套键以点号分隔(如 Contact.Email),扁平化后的键作为 Excel 中的列头。完整实现见第 7 节。
setValue(String) 将字符串值写入单元格,而 setNumberValue(double) 将数值写入单元格,Excel 会将其作为数字处理。对数值类型的 JSON 字段使用 setNumberValue() 可以确保排序、筛选和公式计算正常工作。类似地,setBooleanValue(boolean) 写入的是类型化的布尔值。
对于大型 JSON 文件,使用 Jackson 的流式 API(JsonParser)逐条读取和处理 JSON 记录,而非将整个文件加载到内存中。每解析一条记录就立即写入 Excel 工作表,这样无论文件多大,内存占用都保持恒定。
Spire.XLS for Java 是商业库。同时提供 Free Spire.XLS for Java 免费版本,但在工作表数量和功能上有限制。也可以申请 30 天免费许可证来评估完整功能后再决定是否购买。
在实际文档排版中,文本框是承载独立内容的理想容器——它可以自由定位、不受页面流限制,非常适合用于侧边栏、引述框、产品示意图等场景。而往文本框中插入图片或表格,则是实现丰富布局的常见需求。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接处理 Word 文档,通过虚拟文件系统(VFS)管理字体和文件资源,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript 的方法。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。
将图片填入文本框是制作产品标签、名片或宣传物料时的常用技巧。Spire.Doc 通过 AppendTextBox 方法创建文本框,然后利用 FillEfects.SetPicture 用图片填充文本框背景。核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和图片文件载入 WASM 虚拟文件系统;然后创建文档,添加文本框并设置位置属性,通过填充效果将图片应用到文本框中;最后保存文档并从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
function App() {
const InsertImageIntoTextBox = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将字体和图片文件载入 VFS
const imageFileName = 'Spire.Doc.png';
await window.spire.FetchFileToVFS(imageFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 创建文档
const doc = new docModule.Document();
// 添加一个节
let section = doc.AddSection();
// 添加一个段落
let paragraph = section.AddParagraph();
// 在段落中追加一个 220x220 的文本框
let tb = paragraph.AppendTextBox(220, 220);
// 设置文本框位置
tb.Format.HorizontalOrigin = docModule.HorizontalOrigin.Page;
tb.Format.HorizontalPosition = 50;
tb.Format.VerticalOrigin = docModule.VerticalOrigin.Page;
tb.Format.VerticalPosition = 50;
// 将文本框的填充类型设为图片
tb.Format.FillEfects.Type = docModule.BackgroundType.Picture;
// 用图片填充文本框
tb.Format.FillEfects.SetPicture(imageFileName);
// 定义输出文件名并保存
const outputFileName = "InsertImageIntoTextBox_output.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Close();
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>向 Word 文本框插入图片</h1>
<button onClick={InsertImageIntoTextBox}>
Generate
</button>
</div>
);
}
export default App;
用图片填充文本框后,图片将按文本框尺寸自动适配显示。

在侧边栏或引用区域中嵌入结构化表格数据,是技术文档和报表中常见的需求。Spire.Doc 支持在文本框内创建表格,并通过 textbox.Body.AddTable 方法直接将表格添加到文本框主体中。相比在主文档中创建表格,文本框内的表格可以独立定位,不受页面布局影响。核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件载入 WASM 虚拟文件系统;然后创建文档和文本框,通过 textbox.Body.AddTable 添加表格并指定行列数,逐行填充数据并应用样式;最后保存文档并从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
function App() {
const InsertTableIntoTextBox = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将字体文件载入 VFS
await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 创建文档
const doc = new docModule.Document();
// 添加一个节
let section = doc.AddSection();
// 添加一个段落
let paragraph = section.AddParagraph();
// 添加一个 300x100 的文本框
let textbox = paragraph.AppendTextBox(300, 150);
// 设置文本框位置
textbox.Format.HorizontalOrigin = docModule.HorizontalOrigin.Page;
textbox.Format.HorizontalPosition = 140;
textbox.Format.VerticalOrigin = docModule.VerticalOrigin.Page;
textbox.Format.VerticalPosition = 50;
// 在文本框中添加标题文本
let textboxParagraph = textbox.Body.AddParagraph();
let textboxRange = textboxParagraph.AppendText("表格 1");
textboxRange.CharacterFormat.FontName = "微软雅黑";
// 在文本框中插入表格
let table = textbox.Body.AddTable({ showBorder: true });
// 指定表格的行列数
table.ResetCells(4, 4);
let data = [
["姓名", "年龄", "性别", "编号"],
["张三", "28", "男", "0023"],
["李四", "30", "男", "0024"],
["王五", "26", "女", "0025"]
];
// 向表格填充数据
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
let tableRange = table.Rows.get_Item(i).Cells.get_Item(j).AddParagraph().AppendText(data[i][j]);
tableRange.CharacterFormat.FontName = "微软雅黑";
}
}
// 应用表格样式
table.ApplyStyle({ builtinTableStyle: docModule.DefaultTableStyle.TableColorful2 });
// 定义输出文件名并保存
const outputFileName = "InsertTableIntoTextBox_output.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Close();
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>向 Word 文本框插入表格</h1>
<button onClick={InsertTableIntoTextBox}>
Generate
</button>
</div>
);
}
export default App;
在文本框中创建表格后,表格将包含在文本框内部,可随文本框自由定位。

原因:文本框尺寸与图片宽高比不匹配,创建文本框时指定的尺寸无法完整容纳图片内容。
解决:调整文本框尺寸以匹配图片比例,或选择适合文本框尺寸的图片:
let tb = paragraph.AppendTextBox(400, 300);
原因:创建表格时 showBorder 参数未设置或设为 false,导致表格无边框。
解决:创建表格时确认 showBorder 参数为 true:
let table = textbox.Body.AddTable({ showBorder: true });
原因:HorizontalOrigin 或 VerticalOrigin 设置不正确,导致定位基准点与预期不同。
解决:根据需求选择合适的原点类型,并结合 HorizontalPosition / VerticalPosition 微调位置:
tb.Format.HorizontalOrigin = docModule.HorizontalOrigin.Page;
tb.Format.HorizontalPosition = 50;
tb.Format.VerticalOrigin = docModule.VerticalOrigin.Page;
tb.Format.VerticalPosition = 50;
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
在实际文档流转中,水印是标识文档状态(如"机密""草稿")或保护版权的常用手段。无论是给内部文档打上"保密"字样,还是将公司 Logo 以水印形式嵌入合同,亦或是清理他人文档中的现有水印,都是高频操作。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接处理 Word 文档,通过虚拟文件系统(VFS)管理字体和文件资源,无需后端服务支持。
本文介绍三个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript 的方法。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。
为文档添加文字水印是最常见的需求之一。例如,在内部文档上标注"机密",或在审阅版本中标明"草稿"。Spire.Doc 通过 TextWatermark 对象,可设置水印文字、字号、颜色和布局方向(水平/对角线)。核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 Word 文件载入 WASM 虚拟文件系统;然后实例化 Document 加载文件,创建 TextWatermark 对象并设置属性,将其赋值给文档的 Watermark 属性;最后保存文档并从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
function App() {
const AddTextWatermark = async () => {
// 获取 Spire.Doc WASM 模块
const wasmModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!wasmModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将字体和 Word 文件载入 VFS
await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'Template.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 加载文档
const doc = new wasmModule.Document();
doc.LoadFromFile(inputFileName);
// 创建文字水印对象
let txtWatermark = new wasmModule.TextWatermark();
txtWatermark.Text = "机密";
txtWatermark.FontSize = 95;
txtWatermark.Color = wasmModule.Color.get_Red();
txtWatermark.Layout = wasmModule.WatermarkLayout.Diagonal;
// 将水印应用到文档
doc.Watermark = txtWatermark;
// 定义输出文件名并保存
const outputFileName = "TextWatermark_output.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
// 释放资源
doc.Close();
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>为 Word 文档添加文字水印</h1>
<button onClick={AddTextWatermark}>
Generate
</button>
</div>
);
}
export default App;
添加文字水印后,文档背景中会显示指定的文字内容。

除了文字水印,许多企业也偏好使用图片水印——将公司 Logo 或品牌图形以半透明方式铺满文档背景,既提升文档专业度,又防止内容被随意复制传播。Spire.Doc 通过 PictureWatermark 对象实现图片水印的添加,支持设置缩放比例和冲刷效果(IsWashout)。相比文字水印,图片水印需要额外将图片文件载入 VFS。
function App() {
const AddImageWatermark = async () => {
// 获取 Spire.Doc WASM 模块
const wasmModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!wasmModule) {
alert('Spire.Doc is not ready yet');
return;
}
//图片和 Word 文件载入 VFS
const inputFileName = 'Template.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
const imageFileName = 'Logo.png';
await window.spire.FetchFileToVFS(imageFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 加载文档
const doc = new wasmModule.Document();
doc.LoadFromFile(inputFileName);
// 创建图片水印对象
let picture = new wasmModule.PictureWatermark();
picture.SetPicture(imageFileName);
picture.Scaling = 250;
picture.IsWashout = false;
// 将水印应用到文档
doc.Watermark = picture;
// 定义输出文件名并保存
const outputFileName = "ImageWatermark_output.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
// 释放资源
doc.Close();
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>为 Word 文档添加图片水印</h1>
<button onClick={AddImageWatermark}>
Generate
</button>
</div>
);
}
export default App;
添加图片水印后,文档背景会显示指定的图片内容。

接手他人文档时,常需先清除原有水印再应用企业模板。无论是文字水印还是图片水印,Spire.Doc 均通过将文档的 Watermark 属性设为 null 一键移除,无需区分水印类型。核心流程:载入文档 → 将 Watermark 置空 → 保存输出。
function App() {
const RemoveWatermark = async () => {
// 获取 Spire.Doc WASM 模块
const wasmModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!wasmModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将Word 文件载入 VFS
const inputFileName = 'Watermarked.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 加载文档
const doc = new wasmModule.Document();
doc.LoadFromFile(inputFileName);
// 将水印设为 null,移除文字或图片水印
doc.Watermark = null;
// 定义输出文件名并保存
const outputFileName = "RemoveWatermark_output.docx";
doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
// 释放资源
doc.Close();
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>移除 Word 文档中的水印</h1>
<button onClick={RemoveWatermark}>
Generate
</button>
</div>
);
}
export default App;
水印移除后,文档恢复为无干扰的纯内容状态。

原因:水印对象已创建但未赋值给 document.Watermark 属性,或水印颜色与文档背景色相同导致不可见。
解决:确保创建后执行赋值操作,并选择与背景有对比的颜色:
document.Watermark = txtWatermark;
原因:图片文件未正确载入 VFS,或 SetPicture 传入的文件名与 VFS 中的路径不一致。
解决:确认图片已通过 FetchFileToVFS 载入,且文件名完全匹配:
await window.spire.FetchFileToVFS('Logo.png', '', `${process.env.PUBLIC_URL}/data/`);
picture.SetPicture('Logo.png');
原因:错误地创建了空文档而非加载原始文档,导致水印移除后保存的是空文档。
解决:确认使用 LoadFromFile 加载了正确的输入文档:
document.LoadFromFile(inputFileName);
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
将 Excel 文件按工作表、按行或按列拆分为多个独立文件,是数据分发与管理的常见需求。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成拆分操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍三个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
按工作表拆分是将多工作表工作簿中的每个工作表导出为独立的 Excel 文件。当一个工作簿中包含多个工作表,且每个表代表不同的数据,如不同部门或月份时,我们可以将每个工作表拆分并保存为独立的 Excel 文件。Spire.XLS 通过遍历源文件中的所有工作表,创建新的工作簿,并拷贝当前工作表来完成这个任务。下方为具体的操作步骤:
Workbook 对象并使用 LoadFromFile() 加载 Excel 源文档。Workbook 对象。CopyFrom 方法将源工作表复制到新工作簿的默认工作表中。sheet.Name 获取工作表的名称,作为输出文件名。SaveToFile() 方法保存新的工作簿为 Excel 文件。下面是一个完整的代码示例,展示了将工作表拆分为多个独立的 Excel 文件:
function App() {
const splitByWorksheet = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'Sample.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 遍历每个工作表,将其导出为独立文件
for (let i = 0; i < workbook.Worksheets.Count; i++) {
let sheet = workbook.Worksheets.get(i);
// 创建新工作簿并复制当前工作表
let newWorkbook = new xlsModule.Workbook();
let newSheet = newWorkbook.Worksheets.get(0);
newSheet.CopyFrom(sheet);
// 使用工作表名称作为输出文件名
const outputFileName = `${sheet.Name}.xlsx`;
newWorkbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });
newWorkbook.Dispose();
// 从 VFS 读取拆分后的文件,触发浏览器下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
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 = outputFileName;
a.click();
URL.revokeObjectURL(url);
}
// 释放资源
workbook.Dispose();
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Split Excel By Worksheet</h1>
<button onClick={splitByWorksheet}>
Generate
</button>
</div>
);
}
export default App;
按工作表拆分后,每个独立文件包含原工作簿中的一个工作表

按行拆分适用于将大型表格按固定行数分割为多个小文件,便于分页查看或分发。当一个工作表中包含大量数据行,需要拆分为多个文件时,Spire.XLS 通过逐行复制源行到新工作簿来完成这个任务。下方为具体的操作步骤:
Workbook 对象并使用 LoadFromFile() 加载 Excel 源文档,获取第一个工作表。Workbook 对象。Copy 方法,将源工作表的指定行复制到新工作表。SaveToFile() 方法保存新的工作簿为 Excel 文件。下面是一个完整的代码示例,展示了按行将工作表拆分为多个独立的 Excel 文件:
function App() {
const splitByRow = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
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);
// 创建新的工作簿(默认自带一个工作表)
let newWorkbook1 = new xlsModule.Workbook();
let newSheet1 = newWorkbook1.Worksheets.get(0);
// 将第 1-5 行复制到目标文件
let destRow = 1;
for (let i = 0; i < 5; i++) {
sheet.Copy({
sourceRange: sheet.Rows[i],
worksheet: newSheet1,
destRow: destRow,
destColumn: 1,
copyStyle: true
});
destRow++;
}
// 复制列宽
for (let c = 0; c < sheet.Columns.length; c++) {
newSheet1.SetColumnWidth(c + 1, sheet.GetColumnWidth(c + 1));
}
// 保存第一个拆分文件
const outputFileName1 = "Rows1-5.xlsx";
newWorkbook1.SaveToFile({ fileName: outputFileName1, version: xlsModule.ExcelVersion.Version2010 });
newWorkbook1.Dispose();
// 从 VFS 读取文件数据
const fileData1 = window.dotnetRuntime.Module.FS.readFile(outputFileName1);
// 创建第二个新工作簿
let newWorkbook2 = new xlsModule.Workbook();
let newSheet2 = newWorkbook2.Worksheets.get(0);
destRow = 1;
// 复制表头
sheet.Copy({
sourceRange: sheet.Rows[0],
worksheet: newSheet2,
destRow: destRow,
destColumn: 1,
copyStyle: true
});
destRow++;
// 将第 6-10 行复制到第二个目标文件
for (let i = 5; i < 10; i++) {
sheet.Copy({
sourceRange: sheet.Rows[i],
worksheet: newSheet2,
destRow: destRow,
destColumn: 1,
copyStyle: true
});
destRow++;
}
// 复制列宽
for (let c = 0; c < sheet.Columns.length; c++) {
newSheet2.SetColumnWidth(c + 1, sheet.GetColumnWidth(c + 1));
}
// 保存第二个拆分文件
const outputFileName2 = "Rows6-10.xlsx";
newWorkbook2.SaveToFile({ fileName: outputFileName2, version: xlsModule.ExcelVersion.Version2010 });
newWorkbook2.Dispose();
// 从 VFS 读取文件数据
const fileData2 = window.dotnetRuntime.Module.FS.readFile(outputFileName2);
// 将拆分后的文件打包为 ZIP 下载
const zip = new JSZip();
zip.file(outputFileName1, fileData1);
zip.file(outputFileName2, fileData2);
const zipBlob = await zip.generateAsync({ type: 'blob' });
const zipUrl = URL.createObjectURL(zipBlob);
const a = document.createElement('a');
a.href = zipUrl;
a.download = "SplitByRows.zip";
a.click();
URL.revokeObjectURL(zipUrl);
// 释放资源
workbook.Dispose();
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Split Excel By Row</h1>
<button onClick={splitByRow}>
Generate
</button>
</div>
);
}
export default App;
按行拆分后,每个文件包含表头行和指定数量的数据行

按列拆分适用于将宽表格按列分组拆分为多个文件,使数据结构更清晰。当一个工作表包含大量列,需要将不同列组拆分为独立文件时,Spire.XLS 通过逐列复制源列到新工作簿来完成这个任务。下方为具体的操作步骤:
Workbook 对象并使用 LoadFromFile() 加载 Excel 源文档,获取第一个工作表。Workbook 对象。Copy 方法,将源工作表的指定列复制到新工作表。SaveToFile() 方法保存新的工作簿为 Excel 文件。下面是一个完整的代码示例,展示了按列将工作表拆分为多个独立的 Excel 文件:
function App() {
const splitByColumn = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
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);
// 创建新的工作簿,将第 1-2 列(AB 列)复制到新文件
let newWorkbook1 = new xlsModule.Workbook();
let newSheet1 = newWorkbook1.Worksheets.get(0);
for (let i = 1; i <= 2; i++) {
sheet.Copy({
sourceRange: sheet.Columns[i - 1],
worksheet: newSheet1,
destRow: 1,
destColumn: i,
copyStyle: true
});
}
// 复制列宽
for (let i = 1; i <= 2; i++) {
newSheet1.SetColumnWidth(i, sheet.GetColumnWidth(i));
}
// 保存第一个拆分文件
const outputFileName1 = "ColumnsAB.xlsx";
newWorkbook1.SaveToFile({ fileName: outputFileName1, version: xlsModule.ExcelVersion.Version2010 });
newWorkbook1.Dispose();
// 从 VFS 读取文件数据
const fileData1 = window.dotnetRuntime.Module.FS.readFile(outputFileName1);
// 创建第二个新工作簿,将第 3-4 列(CD 列)复制到新文件
let newWorkbook2 = new xlsModule.Workbook();
let newSheet2 = newWorkbook2.Worksheets.get(0);
for (let i = 3; i <= 4; i++) {
sheet.Copy({
sourceRange: sheet.Columns[i - 1],
worksheet: newSheet2,
destRow: 1,
destColumn: i - 2,
copyStyle: true
});
}
// 复制列宽
for (let i = 3; i <= 4; i++) {
newSheet2.SetColumnWidth(i - 2, sheet.GetColumnWidth(i));
}
// 保存第二个拆分文件
const outputFileName2 = "ColumnsCD.xlsx";
newWorkbook2.SaveToFile({ fileName: outputFileName2, version: xlsModule.ExcelVersion.Version2010 });
newWorkbook2.Dispose();
// 从 VFS 读取文件数据
const fileData2 = window.dotnetRuntime.Module.FS.readFile(outputFileName2);
// 将拆分后的文件打包为 ZIP 下载
const zip = new JSZip();
zip.file(outputFileName1, fileData1);
zip.file(outputFileName2, fileData2);
const zipBlob = await zip.generateAsync({ type: 'blob' });
const zipUrl = URL.createObjectURL(zipBlob);
const a = document.createElement('a');
a.href = zipUrl;
a.download = "SplitByColumns.zip";
a.click();
URL.revokeObjectURL(zipUrl);
// 释放资源
workbook.Dispose();
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Split Excel By Column</h1>
<button onClick={splitByColumn}>
Generate
</button>
</div>
);
}
export default App;
按列拆分后,每个文件包含原工作表中的部分列数据

原因:CopyFrom 方法仅复制工作表内容,不会保留原工作表名称。新工作簿的默认工作表保持默认名称不变。
解决:复制后通过 newSheet.Name = sheet.Name 手动设置工作表名称:
let newSheet = newWorkbook.Worksheets.get(0);
newSheet.CopyFrom(sheet);
newSheet.Name = sheet.Name;
原因:文件路径或 VFS 文件名不正确,或必需的字体文件未载入 VFS,导致工作簿加载失败。
解决:确认 FetchFileToVFS 的参数路径正确。字体文件路径应为 /Library/Fonts/,并确保字体文件名精确匹配(如 arial.ttf):
await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', fontSourcePath);
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。