内容目录是长文档的组成部分。目录条目按标题层级排列,并带有页码,读者可以据此快速定位内容;正文增删之后,条目与页码也可以在保存前统一更新,避免逐页核对。
本文以一份《2026 年度产品技术报告》为例,从零创建包含封面、目录页、四章正文与数据表的 Word 文档,再为其添加内容目录。示例基于 Spire.Doc for JavaScript,该库以 WebAssembly 形式在浏览器端运行,字体与文档文件通过虚拟文件系统(VFS)读写,整个过程无需后端服务,文件也不会离开用户的设备。
本文包含以下内容:
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。为便于直接运行,后文每个功能点的代码都是完整的单文件代码:示例文档的构建代码与调用代码合并在同一段中,整段复制替换 App.js 的内容即可执行。
构建示例文档
构建示例文档的代码集中在 report.js 中:封面、目录页标题、四章正文与页脚由该模块统一生成,并在目录页预留一个 insertToc 回调,由后续四个功能点决定目录域的写法。后续各节的代码会把这段构建代码一并内联,因此不需要再单独创建 report.js。
示例文档共四章、十六个标题,涵盖 Heading 1 至 Heading 3 三个层级,并包含三张数据表与一个安全提示区块。标题层级完整,便于观察目录对不同级别标题的收录效果;版式上采用统一的字体、配色与行距,目录页与正文页之间以分页符分隔。
具体操作步骤如下:
- 创建
Document与Section,设置 A4 页面边距、页脚距离与页脚内容。 - 写入封面:品牌行、文档类别色块、标题、副标题与关键数据条。
- 写入目录页标题与说明文字,并调用
insertToc回调,把目录域的插入方式交给各功能点。 - 写入正文四章,章、节、小节标题分别应用 Heading 1、Heading 2 与 Heading 3 内置样式。
- 正文写完后调用
UpdateTableOfContents更新目录,返回构建完成的Document对象。
示例文档的构建代码如下:
// 示例文档构建器:封面 → 目录 → 四章正文
// 目录的写法由调用方通过 insertToc 回调决定,正文内容与版式在四个功能点之间共用
const FONT = '微软雅黑';
const NAVY = [31, 40, 70];
const TEAL = [28, 122, 94];
const GREY = [95, 105, 120];
const LIGHT = [240, 243, 247];
const ALT = [248, 250, 252];
const RULE = [210, 216, 224];
const WHITE = [255, 255, 255];
const CONTENT = 439; // A4 页宽 595 磅,左右页边距各 78 磅
// 版式工具:统一字体、颜色、行距与段前段后
function createLayout(docModule, doc, section) {
const color = (rgb) => docModule.Color.FromArgb(rgb[0], rgb[1], rgb[2]);
const lines = (n) => n * 12; // 行距以磅为单位,12 磅即单倍行距
const format = (size, rgb, bold = false, spacing = 0) => {
const cf = new docModule.CharacterFormat(doc);
cf.FontName = FONT;
cf.FontSize = size;
cf.TextColor = color(rgb);
cf.Bold = bold;
cf.CharacterSpacing = spacing;
return cf;
};
const paragraph = (content, o = {}) => {
const p = section.AddParagraph();
if (content) p.AppendText(content).ApplyCharacterFormat(format(o.size || 10.5, o.color || GREY, o.bold, o.spacing));
p.Format.LineSpacingRule = docModule.LineSpacingRule.Multiple;
p.Format.LineSpacing = lines(o.ls || 1.55);
p.Format.AfterSpacing = o.after === undefined ? 9 : o.after;
p.Format.BeforeSpacing = o.before || 0;
if (o.indent) p.Format.LeftIndent = o.indent;
if (o.align) p.Format.HorizontalAlignment = o.align;
if (o.rule) {
p.Format.Borders.Bottom.BorderType = docModule.BorderStyle.Single;
p.Format.Borders.Bottom.LineWidth = o.ruleWidth || 0.75;
p.Format.Borders.Bottom.Color = color(o.rule);
p.Format.Borders.Bottom.Space = o.ruleSpace === undefined ? 4 : o.ruleSpace;
}
return p;
};
// 精确高度的空行,用于封面留白
const spacer = (height) => {
const p = section.AddParagraph();
p.Format.LineSpacingRule = docModule.LineSpacingRule.Exactly;
p.Format.LineSpacing = height;
p.Format.BeforeSpacing = 0;
p.Format.AfterSpacing = 0;
return p;
};
// 标题段落应用内置样式,目录按样式收集条目
const heading = (content, level, o = {}) => {
const p = section.AddParagraph();
const size = level === 1 ? 16 : level === 2 ? 12.5 : 11;
p.AppendText(content).ApplyCharacterFormat(format(size, level === 2 ? TEAL : NAVY, true, level === 1 ? 0.6 : 0));
p.ApplyStyle({ builtinStyle: [null, docModule.BuiltinStyle.Heading1, docModule.BuiltinStyle.Heading2, docModule.BuiltinStyle.Heading3][level] });
p.Format.LineSpacingRule = docModule.LineSpacingRule.Multiple;
p.Format.LineSpacing = lines(1.25);
p.Format.BeforeSpacing = o.before === undefined ? (level === 1 ? 21 : 13) : o.before;
p.Format.AfterSpacing = level === 1 ? 9 : 4;
p.Format.KeepFollow = true;
if (level === 1) {
p.Format.Borders.Bottom.BorderType = docModule.BorderStyle.Single;
p.Format.Borders.Bottom.LineWidth = 1.5;
p.Format.Borders.Bottom.Color = color(TEAL);
p.Format.Borders.Bottom.Space = 5;
}
return p;
};
const table = (header, rows, widths) => {
const t = section.AddTable(true);
t.ResetCells(rows.length + 1, header.length);
t.SetBorders(docModule.BorderStyle.Hairline, 0.5, color(RULE));
t.Format.HorizontalAlignment = docModule.HorizontalAlignment.Center;
widths.forEach((w, i) => t.SetColumnWidth(i, CONTENT * w / 100, docModule.WidthType.Twip));
const fill = (row, i, value, bg, textColor, bold, size) => {
const c = row.Cells.get_Item(i);
const p = c.AddParagraph();
p.AppendText(value).ApplyCharacterFormat(format(size, textColor, bold));
p.Format.LineSpacingRule = docModule.LineSpacingRule.Multiple;
p.Format.LineSpacing = lines(1.2);
p.Format.AfterSpacing = 0;
c.CellFormat.BackColor = color(bg);
c.CellFormat.Paddings.Left = 10;
c.CellFormat.Paddings.Right = 10;
c.CellFormat.Paddings.Top = 7;
c.CellFormat.Paddings.Bottom = 7;
c.CellFormat.VerticalAlignment = docModule.VerticalAlignment.Middle;
};
header.forEach((h, i) => fill(t.Rows.get_Item(0), i, h, NAVY, WHITE, true, 9));
rows.forEach((r, ri) => r.forEach((v, i) => fill(t.Rows.get_Item(ri + 1), i, v, ri % 2 ? ALT : WHITE, i === 0 ? NAVY : GREY, i === 0, 9.5)));
for (let i = 0; i < t.Rows.Count; i++) {
const row = t.Rows.get_Item(i);
row.RowFormat.IsBreakAcrossPages = false;
if (i === 0) row.IsHeader = true;
}
return t;
};
// 提示区块:浅底单元格 + 左侧色条,不使用标题样式,因此不进目录
const callout = (title, lines_) => {
const t = section.AddTable(false);
t.ResetCells(1, 1);
t.Format.HorizontalAlignment = docModule.HorizontalAlignment.Center;
t.SetColumnWidth(0, CONTENT, docModule.WidthType.Twip);
const c = t.Rows.get_Item(0).Cells.get_Item(0);
c.CellFormat.BackColor = color(LIGHT);
c.CellFormat.Paddings.Left = 15;
c.CellFormat.Paddings.Right = 15;
c.CellFormat.Paddings.Top = 12;
c.CellFormat.Paddings.Bottom = 12;
c.CellFormat.Borders.Left.BorderType = docModule.BorderStyle.Single;
c.CellFormat.Borders.Left.LineWidth = 2.5;
c.CellFormat.Borders.Left.Color = color(TEAL);
const head = c.AddParagraph();
head.AppendText(title).ApplyCharacterFormat(format(9.5, NAVY, true));
head.Format.LineSpacingRule = docModule.LineSpacingRule.Multiple;
head.Format.LineSpacing = lines(1.4);
head.Format.AfterSpacing = 3;
lines_.forEach((line) => {
const p = c.AddParagraph();
p.AppendText(line).ApplyCharacterFormat(format(9, GREY));
p.Format.LineSpacingRule = docModule.LineSpacingRule.Multiple;
p.Format.LineSpacing = lines(1.5);
p.Format.AfterSpacing = 2;
});
// 单格提示不允许跨页拆分,避免色块被切断
t.Rows.get_Item(0).RowFormat.IsBreakAcrossPages = false;
};
return { format, paragraph, spacer, heading, table, callout, color, lines };
}
// 页脚:左侧说明文字,右侧页码
function addFooter(docModule, section, L) {
const footer = section.HeadersFooters.Footer.AddParagraph();
footer.Format.LineSpacingRule = docModule.LineSpacingRule.Multiple;
footer.Format.LineSpacing = 12;
footer.Format.AfterSpacing = 0;
footer.Format.Borders.Top.BorderType = docModule.BorderStyle.Single;
footer.Format.Borders.Top.LineWidth = 0.5;
footer.Format.Borders.Top.Color = L.color(RULE);
footer.Format.Borders.Top.Space = 6;
footer.Format.Tabs.AddTab(CONTENT, docModule.TabJustification.Right, docModule.TabLeader.NoLeader);
footer.AppendText('NORTHSTAR TECHNOLOGY · 产品与研发中心 · 内部资料').ApplyCharacterFormat(L.format(8, GREY));
footer.AppendText('\t').ApplyCharacterFormat(L.format(8, GREY));
footer.AppendText('第 ').ApplyCharacterFormat(L.format(8, GREY));
footer.AppendField('PAGE', docModule.FieldType.FieldPage);
footer.AppendText(' 页').ApplyCharacterFormat(L.format(8, GREY));
}
// 封面
function addCover(docModule, section, L) {
const { paragraph, spacer, format, color } = L;
paragraph('NORTHSTAR TECHNOLOGY · 产品与研发中心', { size: 8.5, color: TEAL, bold: true, spacing: 1.8, after: 6, rule: NAVY, ruleWidth: 1.5, ruleSpace: 8 });
// 色块 + 文档类别,作为封面的视觉锚点
const badge = section.AddTable(false);
badge.ResetCells(1, 1);
badge.Format.HorizontalAlignment = docModule.HorizontalAlignment.Left;
badge.SetColumnWidth(0, 120, docModule.WidthType.Twip);
const badgeCell = badge.Rows.get_Item(0).Cells.get_Item(0);
badgeCell.CellFormat.BackColor = color(TEAL);
badgeCell.CellFormat.Paddings.Left = 8;
badgeCell.CellFormat.Paddings.Right = 8;
badgeCell.CellFormat.Paddings.Top = 3;
badgeCell.CellFormat.Paddings.Bottom = 3;
const badgePara = badgeCell.AddParagraph();
badgePara.AppendText('年度技术文档').ApplyCharacterFormat(format(8, WHITE, true, 1.2));
badgePara.Format.LineSpacingRule = docModule.LineSpacingRule.Multiple;
badgePara.Format.LineSpacing = 13;
badgePara.Format.AfterSpacing = 0;
badgePara.Format.HorizontalAlignment = docModule.HorizontalAlignment.Center;
spacer(34);
paragraph('2026 年度产品技术报告', { size: 30, color: NAVY, bold: true, after: 8, ls: 1.1 });
paragraph('在线文档处理平台的能力建设、实施进展与验收规划', { size: 12.5, color: GREY, after: 22, ls: 1.4 });
// 关键数据条
const stat = section.AddTable(false);
stat.ResetCells(1, 3);
[['5 个', '核心能力域'], ['12 项', '关键功能点'], ['98%', '验收通过率']].forEach(([value, label], i) => {
const c = stat.Rows.get_Item(0).Cells.get_Item(i);
const p1 = c.AddParagraph();
p1.AppendText(value).ApplyCharacterFormat(format(18, TEAL, true));
p1.Format.LineSpacingRule = docModule.LineSpacingRule.Multiple;
p1.Format.LineSpacing = 22;
p1.Format.AfterSpacing = 3;
const p2 = c.AddParagraph();
p2.AppendText(label).ApplyCharacterFormat(format(9, GREY));
p2.Format.LineSpacingRule = docModule.LineSpacingRule.Multiple;
p2.Format.LineSpacing = 14;
p2.Format.AfterSpacing = 0;
c.CellFormat.BackColor = color(LIGHT);
c.CellFormat.Paddings.Left = 15;
c.CellFormat.Paddings.Right = 12;
c.CellFormat.Paddings.Top = 14;
c.CellFormat.Paddings.Bottom = 14;
c.CellFormat.VerticalAlignment = docModule.VerticalAlignment.Middle;
});
spacer(96);
paragraph('文件编号 NS-TR-2026-018 版 本 V2.1(评审稿)', { size: 9, color: GREY, after: 5, rule: RULE });
paragraph('编制部门 产品与研发中心 发布日期 2026 年 10 月', { size: 9, color: GREY, after: 0 });
}
// 正文纲目:第一项为标题层级,标题层级决定目录收录的级别
const OUTLINE = [
[1, '第一章 项目概况', [
'在线文档处理平台自 2025 年立项,目标是让业务部门在不安装桌面软件的前提下完成合同、报告与报表的生成与流转。2026 年度的工作集中在能力补齐、交付节奏与质量保障三条主线。',
]],
[2, '1.1 立项背景', [
'业务部门每月需要生成约 4.2 万份格式固定的文档,此前依赖分散的桌面工具与人工核对,平均每份耗时 6 分钟。平台将这部分工作迁移到浏览器端之后,单份文档的生成时间缩短至 9 秒以内。',
'与此同时,文档不再经过服务端中转,原始文件与结果文件都留在用户的设备上,合同、薪酬这类敏感内容的处理流程因此简化了一个环节。',
]],
[2, '1.2 本期交付范围', [
'本年度的交付范围覆盖文档创建、内容编辑、格式转换与批量处理四个环节,具体模块与状态如下表。',
], ['模块', '交付内容', '状态'], [
['文档创建', '段落、表格、图片、页眉页脚', '已交付'],
['内容编辑', '样式、目录、书签、查找替换', '已交付'],
['格式转换', 'PDF、图片、HTML、Markdown', '已交付'],
['批量处理', '多文件队列与模板填充', '联调中'],
], [26, 50, 24], '本期交付以浏览器端完成为前提,服务端只保留模板与权限两类数据。'],
[2, '1.3 文档结构约定', [
'报告统一采用三章一附录的结构:章为一级标题,节为二级标题,需要进一步拆分的条目为三级标题。章节编号与标题文字之间用全角空格分隔,便于目录在各级缩进下保持对齐。',
'正文中的数据表一律带表头行,表头在跨页时自动重复;表格宽度按内容比例分配,不使用固定列宽,以便同一份文档在不同页面尺寸下都能保持可读。',
]],
[1, '第二章 技术架构', [
'平台采用前后端分离的结构,浏览器端承担文档的解析与渲染,服务端负责存储、权限与审计。两端的接口以文档对象模型为边界,避免在浏览器与服务端之间反复传输文件。',
]],
[2, '2.1 整体架构', [
'文档处理核心以 WebAssembly 模块形式加载,字体、模板等静态资源通过虚拟文件系统按需载入;上层应用只需调用文档对象模型的接口,不必关心底层的内存管理与线程调度。',
]],
[2, '2.2 关键模块', [
'关键模块按职责划分为解析、渲染与安全三组,其中解析与渲染是两个独立的处理阶段,可以分别替换。',
]],
[3, '2.2.1 文档解析引擎', [
'解析引擎负责读取文档结构,将段落、表格、图片与域代码还原为内存中的对象树。解析过程不修改原始文件,同一份文档可以被多个流程重复读取。',
]],
[3, '2.2.2 渲染与导出', [
'渲染模块按页面尺寸与边距计算排版结果,导出模块将结果写入目标格式,两者共用同一套分页规则,因此导出结果与屏幕预览的页数保持一致。',
]],
[2, '2.3 数据与安全', [
'文档在浏览器端完成处理,原始文件不经过服务端;需要留存的结果文件由用户主动上传,并按部门与项目两个维度设置访问权限。',
], null, null, null, null, ['安全提示', [
'文档的解析与渲染均在用户的浏览器内完成,平台不会在服务端保留原始文件。',
'结果文件如需归档,应由用户主动上传,并按项目设置访问期限。',
]]],
[1, '第三章 实施计划', [
'实施按四个阶段推进,每个阶段结束时进行阶段评审,评审通过后进入下一阶段。',
]],
[2, '3.1 里程碑', [
'各阶段的起止时间与交付物如下表,时间以自然月为单位。',
], ['阶段', '时间', '交付物'], [
['方案设计', '2026 年 2 月', '架构说明书、接口清单'],
['核心开发', '2026 年 3—6 月', '解析与渲染模块'],
['集成联调', '2026 年 7—8 月', '端到端演示环境'],
['验收上线', '2026 年 9—10 月', '验收报告、运维手册'],
], [24, 32, 44]],
[2, '3.2 风险与应对', [
'当前识别出的主要风险是字体资源缺失导致的分页偏差,应对方式是随应用发布字体文件,并在启动时校验字体清单。',
], ['风险', '影响', '应对措施'], [
['字体缺失', '分页位置偏移', '随包发布字体并校验清单'],
['超长段落', '生成耗时上升', '按章节拆分段落后分段处理'],
['浏览器差异', '预览与导出不一致', '以导出结果作为验收基准'],
], [24, 32, 44]],
[1, '第四章 验收标准', [
'验收以功能完整性与性能指标两项为准,任一项不满足即进入整改流程。',
]],
[2, '4.1 功能验收', [
'功能验收覆盖 12 项关键功能点,按验收脚本逐条执行,当前通过率为 98%。未通过的两项集中在批量处理的进度反馈上。',
]],
[2, '4.2 性能指标', [
'性能指标按生成耗时、批量处理与内存占用三项统计,目标值与实测值如下表。',
], ['指标', '目标值', '实测值'], [
['单份 30 页文档生成', '不超过 15 秒', '9.4 秒'],
['批量处理 20 份文档', '不超过 5 分钟', '3 分 12 秒'],
['内存占用峰值', '不超过 600 MB', '468 MB'],
], [40, 32, 28]],
];
function addBody(section, L) {
const { paragraph, heading, table, callout } = L;
OUTLINE.forEach(([level, title, texts, header, rows, widths, tail, calloutData]) => {
heading(title, level);
texts.forEach((content) => paragraph(content, { after: header ? 10 : 9 }));
if (header) table(header, rows, widths);
if (tail) paragraph(tail, { size: 9, after: 0, before: 10 });
if (calloutData) callout(calloutData[0], calloutData[1]);
});
paragraph('以上指标按季度汇总,第四季度的复测结果将在下一期报告中更新。', { size: 9.5, after: 0, before: 10 });
}
// 目录页的标题栏与说明文字
function addTocHeading(L) {
L.paragraph('CONTENTS', { size: 8, color: TEAL, bold: true, spacing: 2.4, after: 4, ls: 1.1 });
L.paragraph('目 录', { size: 19, color: NAVY, bold: true, spacing: 1.6, after: 8, ls: 1.1, rule: TEAL, ruleWidth: 1, ruleSpace: 8 });
L.paragraph('下列条目由文档中应用的标题样式自动生成,页码随正文变动同步刷新。', { size: 9, after: 14 });
}
function addPageBreak(docModule, section) {
const p = section.AddParagraph();
p.AppendBreak(docModule.BreakType.PageBreak);
p.Format.LineSpacingRule = docModule.LineSpacingRule.Exactly;
p.Format.LineSpacing = 1;
p.Format.AfterSpacing = 0;
}
// 目录下方的说明区块:不使用标题样式,因此不会进入目录
const TOC_NOTES = [
'· 目录条目由应用了标题样式的段落生成,收录范围取决于插入目录时指定的标题级别。',
'· 页码取自保存时计算出的排版结果,正文增删内容后需要再次更新目录。',
'· 封面与目录页同样计入页码,正文第一章从第 3 页开始。',
];
// 构建整份文档;insertToc(doc, section) 负责在目录页插入目录域
export function buildReport(docModule, insertToc) {
const doc = new docModule.Document();
const section = doc.AddSection();
section.PageSetup.Margins.Left = 78;
section.PageSetup.Margins.Right = 78;
section.PageSetup.Margins.Top = 72;
section.PageSetup.Margins.Bottom = 70;
section.PageSetup.FooterDistance = 36;
const L = createLayout(docModule, doc, section);
addFooter(docModule, section, L);
addCover(docModule, section, L);
// 封面之后分页,目录独占一页
addPageBreak(docModule, section);
addTocHeading(L);
// 由各功能点决定目录域的写法
insertToc(doc, section);
// 目录下方的说明区块
L.spacer(16);
L.callout('阅读提示', TOC_NOTES);
// 目录之后分页,正文从新的一页开始
addPageBreak(docModule, section);
addBody(section, L);
// 更新目录,填充条目与页码
doc.UpdateTableOfContents();
return doc;
}
// 从 VFS 读取生成的文件,触发浏览器下载
export function download(fileName) {
const fileArray = window.dotnetRuntime.Module.FS.readFile(fileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
}
添加默认目录
添加默认目录时,使用 AppendTOC 方法在目录页插入目录域。该方法接收起始级别与结束级别两个参数,目录只收录落在该范围内的标题:
| 写法 | 收录范围 |
|---|---|
AppendTOC(1, 3) |
Heading 1 至 Heading 3 |
AppendTOC(1, 2) |
Heading 1 至 Heading 2 |
AppendTOC(2, 3) |
Heading 2 至 Heading 3 |
示例传入 (1, 3),目录收录第一章、1.1 与 2.2.1 三个层级的标题,条目之间以制表符前导符连接页码。
具体操作步骤如下:
- 获取 Spire.Doc 的 WASM 模块,确认已就绪。
- 将正文字体
msyh.ttc与粗体字体msyhbd.ttc载入虚拟文件系统(VFS)。 - 调用
buildReport创建示例文档,并在目录页使用AppendTOC(1, 3)插入默认目录域。 - 使用
SaveToFile方法保存文档,文件名为「在 Word 文档中创建默认目录.docx」。 - 调用
download方法从 VFS 读取结果文件,触发浏览器下载。
添加默认目录的代码如下:
function App() {
const addDefaultToc = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 确保 WASM 模块完全加载完成
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将字体载入 VFS
await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
await window.spire.FetchFileToVFS('msyhbd.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
// 创建示例文档,并在目录页插入默认目录域
const report = buildReport(docModule, (doc, section) => {
const tocPara = section.AddParagraph();
tocPara.AppendTOC(1, 3);
});
// 保存结果文件
const outputFileName = '在 Word 文档中创建默认目录.docx';
report.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
report.Dispose();
// 从 VFS 读取生成的文件,触发下载
download(outputFileName);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>为新建文档添加默认目录</h1>
<button onClick={addDefaultToc}>
生成
</button>
</div>
);
}
export default App;
目录更新完成后,目录页生成了收录三级标题、带页码与制表符前导符的条目:

设置目录收录级别
设置目录收录级别时,调整 AppendTOC 的两个参数即可。结束级别由 3 改为 2 后,目录只保留一级与二级标题,2.2.1、2.2.2 这类三级标题不再出现;起始级别改为 2 时,一级标题同样不进入目录。
具体操作步骤如下:
- 获取 Spire.Doc 的 WASM 模块,确认已就绪。
- 将正文字体
msyh.ttc与粗体字体msyhbd.ttc载入虚拟文件系统(VFS)。 - 调用
buildReport创建示例文档,并在目录页使用AppendTOC(1, 2)插入只收录两级标题的目录域。 - 使用
SaveToFile方法保存文档,文件名为「在 Word 文档中创建两级目录.docx」。 - 调用
download方法从 VFS 读取结果文件,触发浏览器下载。
设置目录收录级别的代码如下:
function App() {
const addTwoLevelToc = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 确保 WASM 模块完全加载完成
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将字体载入 VFS
await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
await window.spire.FetchFileToVFS('msyhbd.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
// 起始级别 1、结束级别 2:只收录一级与二级标题
const report = buildReport(docModule, (doc, section) => {
const tocPara = section.AddParagraph();
tocPara.AppendTOC(1, 2);
});
// 保存结果文件
const outputFileName = '在 Word 文档中创建两级目录.docx';
report.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
report.Dispose();
// 从 VFS 读取生成的文件,触发下载
download(outputFileName);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>为新建文档添加两级目录</h1>
<button onClick={addTwoLevelToc}>
生成
</button>
</div>
);
}
export default App;
设置收录级别后,目录中的条目止于二级标题,2.2.1 与 2.2.2 不再出现:

添加自定义目录
添加自定义目录时,改为构造 TableOfContent 对象,并在构造时传入域开关串。与 AppendTOC 的区别在于插入方式:需要手动把目录对象加入段落、补齐域分隔符与域结束标记,再将对象赋给 Document.TOC。
常用的域开关如下:
| 开关 | 说明 |
|---|---|
\o "1-3" |
按内置标题样式收集条目,此处表示收录 Heading 1 至 Heading 3 |
\h |
为目录条目写入超链接,点击后跳转到对应章节 |
\z |
在 Web 版式视图中隐藏页码与制表符前导符 |
\u |
按段落的大纲级别收集条目 |
将开关串改为 \o "1-2",目录就只收录一级和二级标题;去掉 \h,条目则不再写入超链接。示例沿用 \o "1-3" \h \z \u,收录范围与默认目录一致。
具体操作步骤如下:
- 获取 Spire.Doc 的 WASM 模块,确认已就绪。
- 将正文字体
msyh.ttc与粗体字体msyhbd.ttc载入虚拟文件系统(VFS)。 - 调用
buildReport创建示例文档,在目录页构造TableOfContent对象并设置域开关,加入段落后补齐域标记,再赋给Document.TOC。 - 使用
SaveToFile方法保存文档,文件名为「在 Word 文档中创建自定义目录.docx」。 - 调用
download方法从 VFS 读取结果文件,触发浏览器下载。
添加自定义目录的代码如下,同样内联了示例文档的构建代码,可整段替换 App.js 的全部内容:
function App() {
const addCustomToc = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 确保 WASM 模块完全加载完成
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将字体载入 VFS
await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
await window.spire.FetchFileToVFS('msyhbd.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
// 创建示例文档,并在目录页插入自定义目录
const report = buildReport(docModule, (doc, section) => {
// 构造带自定义域开关的目录对象
const toc = new docModule.TableOfContent(doc, '{\\o "1-3" \\h \\z \\u}');
const tocPara = section.AddParagraph();
tocPara.Items.Add(toc);
// 补齐域分隔符与域结束标记
tocPara.AppendFieldMark(docModule.FieldMarkType.FieldSeparator);
tocPara.AppendText('TOC');
tocPara.AppendFieldMark(docModule.FieldMarkType.FieldEnd);
// 将该目录绑定到文档
doc.TOC = toc;
});
// 保存结果文件
const outputFileName = '在 Word 文档中创建自定义目录.docx';
report.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
report.Dispose();
// 从 VFS 读取生成的文件,触发下载
download(outputFileName);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>为新建文档添加自定义目录</h1>
<button onClick={addCustomToc}>
生成
</button>
</div>
);
}
export default App;
自定义目录的条目层级与超链接由开关串决定,示例的收录范围与默认目录一致:

修改目录条目样式
修改目录条目样式时,改写 TOC 1 至 TOC 3 三个段落样式即可。目录更新后,各层级的条目分别使用对应样式,统一修改样式就能改变全部条目的字体、字号与缩进,不必逐条设置。
| 样式 | 控制对象 |
|---|---|
TOC 1 |
一级标题生成的目录条目 |
TOC 2 |
二级标题生成的目录条目 |
TOC 3 |
三级标题生成的目录条目 |
示例将一级条目设为 11 磅、加粗并留出 3 磅段前间距,二级与三级条目为 10.5 磅,并按级别递增 18 磅左缩进。
具体操作步骤如下:
- 获取 Spire.Doc 的 WASM 模块,确认已就绪。
- 将正文字体
msyh.ttc与粗体字体msyhbd.ttc载入虚拟文件系统(VFS)。 - 调用
buildReport创建示例文档,并在目录页使用AppendTOC(1, 3)插入默认目录域。 - 目录更新完成后,取得 TOC 1 至 TOC 3 样式,设置字体、字号、加粗、行距与左缩进。
- 使用
SaveToFile方法保存文档,再调用download方法从 VFS 读取结果文件触发下载。
修改目录条目样式的代码如下:
function App() {
const styleTocEntries = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 确保 WASM 模块完全加载完成
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将字体载入 VFS
await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
await window.spire.FetchFileToVFS('msyhbd.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
// 创建示例文档,并在目录页插入默认目录域
const report = buildReport(docModule, (doc, section) => {
const tocPara = section.AddParagraph();
tocPara.AppendTOC(1, 3);
});
// 目录更新完成后,改写 TOC 1 至 TOC 3 三个条目样式
for (let level = 1; level <= 3; level++) {
const style = report.Styles.get_Item('TOC ' + level);
if (!style) continue;
style.CharacterFormat.FontName = '微软雅黑';
style.CharacterFormat.FontSize = level === 1 ? 11 : 10.5;
style.CharacterFormat.Bold = level === 1;
style.ParagraphFormat.LineSpacingRule = docModule.LineSpacingRule.Multiple;
style.ParagraphFormat.LineSpacing = 16;
style.ParagraphFormat.BeforeSpacing = level === 1 ? 3 : 0;
style.ParagraphFormat.AfterSpacing = 1;
style.ParagraphFormat.LeftIndent = (level - 1) * 18;
}
// 保存结果文件
const outputFileName = '设置 Word 目录条目样式.docx';
report.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
report.Dispose();
// 从 VFS 读取生成的文件,触发下载
download(outputFileName);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>修改 Word 目录条目样式</h1>
<button onClick={styleTocEntries}>
生成
</button>
</div>
);
}
export default App;
改写样式后,一级条目加粗放大,二级与三级条目按层级缩进:

常见问题
更新后目录提示「未找到目录项」怎么办
原因:目录按标题样式或大纲级别收集条目。段落若只是加粗、放大字号,没有应用 Heading 1 至 Heading 3 等内置标题样式,更新时就找不到可收录的条目。
解决:对需要进入目录的段落调用 ApplyStyle 应用内置标题样式:
const p = section.AddParagraph();
p.AppendText('第一章 项目概况');
p.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading1 });
目录只显示一个 TOC 字样,没有条目和页码
原因:AppendTOC 与 TableOfContent 插入的是目录域,条目与页码需要在更新域时写入。保存前没有调用 UpdateTableOfContents 时,页面只显示占位文字。
解决:在 SaveToFile 之前更新目录:
doc.UpdateTableOfContents();
自定义目录更新时报 ArgumentOutOfRange 错误
原因:TableOfContent 对象加入段落后,若缺少域分隔符、域结束标记,或没有赋给 Document.TOC,目录域就不完整,更新时可能抛出索引越界异常。
解决:以下三步需要同时完成:
const toc = new docModule.TableOfContent(doc, '{\\o "1-3" \\h \\z \\u}');
const tocPara = section.AddParagraph();
tocPara.Items.Add(toc);
tocPara.AppendFieldMark(docModule.FieldMarkType.FieldSeparator);
tocPara.AppendText('TOC');
tocPara.AppendFieldMark(docModule.FieldMarkType.FieldEnd);
doc.TOC = toc;
目录页码与实际页面对不上
原因:目录页码在更新域时按当时的排版结果写入,之后如果继续增删正文或修改标题,目录不会自动同步。示例中封面与目录各占一页,正文第一章因此从第 3 页开始,这属于正常结果。
解决:保存前再次调用 UpdateTableOfContents,让条目与页码按最新内容重新计算:
doc.UpdateTableOfContents();
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
能否只收录部分章节的标题
解决:可以通过 AppendTOC 的起始与结束级别限定收录的层级,例如 AppendTOC(1, 2) 只收录两级标题;不需要进入目录的段落可以不套用标题样式,改用直接格式设置外观;自定义目录则可以通过 \o 开关限定收集范围。
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。







