实际工作中更常见的情况是:文档已经写好并带有完整的章节结构,只是当初没有生成目录。此时无需重排内容,只要在原有的标题样式基础上补一个目录域,即可得到带页码和跳转的完整目录。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接打开和编辑 Word 文档,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
与新建文档相比,为已有文档添加目录多了两个关键步骤:一是通过 LoadFromFile 从 VFS 载入原有文档,二是通过 Paragraphs.Insert 把目录段落到文档最开头,而不是默认追加到末尾。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。
添加默认目录
为已有文档添加默认目录的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和待处理的 Word 文档载入 WASM 虚拟文件系统;然后实例化 Document 并通过 LoadFromFile 载入文档,新建一个段落并用 AppendTOC 插入目录域,再通过 Paragraphs.Insert(0, tocPara) 将其移动到文档最开头;最后调用 UpdateTableOfContents 填充条目与页码,保存文档后从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
示例使用的输入文档 AddTocToExisting.docx 是一份带有三章十二个多级标题、但尚未生成目录的技术报告。
function App() {
const AddTableOfContentsToExistingDocument = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 确保 WASM 模块完全加载完成
if (!docModule) {
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 = 'AddTocToExisting.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 创建文档实例并载入已有文档
const doc = new docModule.Document();
doc.LoadFromFile({ fileName: inputFileName });
// 获取文档的第一节
let section = doc.Sections.get_Item(0);
// 新建段落并插入目录域,收集 Heading 1 至 Heading 3 的条目
let tocPara = section.AddParagraph();
tocPara.AppendTOC(1, 3);
// 将目录段落移动到文档最开头
section.Paragraphs.Insert(0, tocPara);
// 更新目录,填充条目与页码
doc.UpdateTableOfContents();
// 定义输出文件名并保存
const outputFileName = "为已有文档添加默认目录.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>点击以下按钮为已有文档添加默认目录</h1>
<button onClick={AddTableOfContentsToExistingDocument}>
生成
</button>
</div>
);
}
export default App;
通过 LoadFromFile 载入已有文档并插入目录域后,目录被放置在文档最开头,原有章节内容与版式保持不变。

添加自定义目录
AppendTOC 生成的目录使用 Word 默认的域开关,当需要控制目录的具体行为时,可以改为直接构造 TableOfContent 对象并指定开关串。与上一个功能点的区别在于插入方式:需要手动将目录对象加入段落,并补齐域分隔符与域结束标记,同时将该对象赋给 document.TOC。常用的域开关及其含义如下:
| 开关 | 说明 |
|---|---|
\o "1-3" |
按内置标题样式收集条目,此处表示收录 Heading 1 至 Heading 3 |
\h |
将目录条目设为超链接,点击即可跳转到对应章节 |
\z |
在 Web 版式视图中隐藏页码与制表符前导符 |
\u |
按段落的大纲级别收集条目 |
如果希望目录单独占一页并与正文分开,可以在插入目录段落之后,再向同一节添加一个分页符:
tocPara.AppendBreak(docModule.BreakType.PageBreak);
function App() {
const CustomizeTableOfContent = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 确保 WASM 模块完全加载完成
if (!docModule) {
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 = 'AddTocToExisting.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 创建文档实例并载入已有文档
const doc = new docModule.Document();
doc.LoadFromFile({ fileName: inputFileName });
// 获取文档的第一节
let section = doc.Sections.get_Item(0);
// 构造带自定义域开关的目录对象
let toc = new docModule.TableOfContent(doc, "{\\o \"1-3\" \\h \\z \\u}");
// 将目录对象加入段落
let tocPara = section.AddParagraph();
tocPara.Items.Add(toc);
// 补齐域分隔符与域结束标记
tocPara.AppendFieldMark(docModule.FieldMarkType.FieldSeparator);
tocPara.AppendText("TOC");
tocPara.AppendFieldMark(docModule.FieldMarkType.FieldEnd);
// 将该目录绑定到文档
doc.TOC = toc;
// 将目录段落移动到文档最开头
section.Paragraphs.Insert(0, tocPara);
// 更新目录,填充条目与页码
doc.UpdateTableOfContents();
// 定义输出文件名并保存
const outputFileName = "为已有文档添加自定义目录.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>点击以下按钮为已有文档添加自定义目录</h1>
<button onClick={CustomizeTableOfContent}>
生成
</button>
</div>
);
}
export default App;
通过 TableOfContent 对象与自定义域开关生成的目录,条目层级、超链接与页码表现均由开关串决定。

常见问题
目录出现在文档末尾而不是开头
原因:AddParagraph 默认把新段落追加到所在节的末尾,直接在其上插入目录域,目录自然也会出现在文末。已有文档的内容已经排好,因此必须显式指定插入位置。
解决:先创建目录段落,再用 Paragraphs.Insert 把它移动到文档最开头:
let tocPara = section.AddParagraph();
tocPara.AppendTOC(1, 3);
section.Paragraphs.Insert(0, tocPara);
目录为空或没有任何条目
原因:目录域按标题样式收集条目。如果原文档中的章节标题只是手动加粗、放大了字号,而没有应用 Heading1 至 Heading3 等内置标题样式,更新后目录中不会出现任何条目。
解决:先检查原文档的标题是否使用内置标题样式。若没有,可以在载入文档后重新为这些段落应用样式:
let heading = section.Paragraphs.get_Item(2);
heading.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
目录页码为空或与实际页码不一致
原因:AppendTOC 只是插入了目录域本身,域内容需要显式更新。若在保存前未调用 UpdateTableOfContents,生成的目录只有域代码,没有条目和页码。
解决:在 SaveToFile 之前调用更新方法:
doc.UpdateTableOfContents();
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。









