域是 Word 中“会随环境变化的内容”:日期随打开时间变化,页码随分页变化,文件名随保存位置变化。固定写在正文里的日期一旦过了时点就失去意义;改用域以后,每次打开文档都能得到当时的值。在由程序批量生成的报告、合同与模板中,域还能把“哪一行该显示什么”交给文档自身维护,程序只需要写入域代码。
本文用 Spire.Doc for JavaScript 在一份季度经营报告的末尾添加、刷新并摆放域。整个过程基于 WebAssembly 在浏览器端完成,无需安装 Office,也不依赖任何后端服务,文档在本地读写,库本身不会把文件发往服务器。
本文介绍三个核心功能点:
一个域由两部分组成:域代码(例如 DATE \@ "yyyy年M月d日",说明要计算什么)与域结果(Word 计算后显示的文字)。程序插入域时写入的是域代码,域结果则取决于是否执行过更新。文章用到的成员如下:
| 成员 | 作用 |
|---|---|
paragraph.AppendField(name, FieldType) |
在段落末尾插入域,第一个参数同时作为初始显示文字 |
field.Code |
读写域的代码,可带格式开关 |
field.Update() |
重新计算该域的结果 |
field.FieldText |
读取域当前显示的文字 |
field.IsLocked |
锁定域结果,锁定后不再随更新变化 |
Document.Fields |
文档中的全部域,含页眉页脚里的域 |
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。下面的示例假定 Spire.Doc 已安装、WebAssembly 模块已初始化;示例文档为一份一页的季度经营报告 Sample.docx,域将追加在报告末尾的附注栏中。
添加日期、文件名与页码域
添加日期、文件名与页码域使用 paragraph.AppendField(name, FieldType)。第一参数与第二参数分别决定域的初始显示文字和域的类型,插入后再用 field.Code 写出完整的域代码。三个域在文档中的作用各不相同:
| 写法 | 插入的内容 | 典型用途 |
|---|---|---|
AppendField('Date', FieldType.FieldDate) |
日期域,按域代码中的格式显示 | 报告生成日期、签署日期 |
AppendField('FileName', FieldType.FieldFileName) |
文件名域,结果为当前文档的文件名 | 打印稿的文件标识 |
AppendField('1', FieldType.FieldPage) |
页码域,结果为所在页的页码 | 正文或页脚中的页序 |
具体操作步骤如下:
- 获取 Spire.Doc 的 WASM 模块,确认已就绪。
- 载入字体与示例文档,创建
Document对象并加载Sample.docx。 - 使用
Section.AddParagraph()在正文末尾追加一个段落。 - 依次写入提示文字,并调用
Paragraph.AppendField()插入日期域、文件名域与页码域,设置日期域与文件名域的域代码。 - 使用
Document.SaveToFile()保存结果文件,再从 VFS 读出并触发浏览器下载。
添加日期、文件名与页码域的代码如下:
import React from 'react';
function App() {
const addFields = async () => {
// 获取 Spire.Doc 的 WASM 模块
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 载入示例文档
await window.spire.FetchFileToVFS('Sample.docx', '', `${process.env.PUBLIC_URL}/data/`);
const doc = new docModule.Document();
doc.LoadFromFile('Sample.docx');
// 在正文末尾追加一个段落,域将依次放入其中
const paragraph = doc.Sections.get_Item(0).AddParagraph();
// 日期域:第一个参数是初始显示文字,随后用 Code 指定域代码
paragraph.AppendText('生成日期:');
const dateField = paragraph.AppendField('Date', docModule.FieldType.FieldDate);
dateField.Code = 'DATE \\@ "yyyy年M月d日"';
dateField.Update();
// 文件名域:结果为当前文档的文件名
paragraph.AppendText('\n文件名:');
const fileField = paragraph.AppendField('FileName', docModule.FieldType.FieldFileName);
fileField.Update();
// 页码域:初始文字给一个占位数字,打开文档后按所在页显示
paragraph.AppendText('\n页码:');
const pageField = paragraph.AppendField('1', docModule.FieldType.FieldPage);
// 保存结果文件
const outputFileName = 'ReportWithFields.docx';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
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 = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>添加域</h1>
<button onClick={addFields}>插入域</button>
</div>
);
}
export default App;
在报告末尾添加日期、文件名与页码域后的文档:

域代码里的格式开关
日期域的结果格式由域代码决定。格式开关写作反斜杠加 @,例如 DATE \@ "yyyy年M月d日";在 JavaScript 字符串里,反斜杠本身要再转义一次,因此写成 'DATE \\@ "yyyy年M月d日"'。格式符号区分大小写:M 表示月份,m 在部分组合中表示分钟;d 表示日,dd 表示两位日期。
不写格式开关时,日期域按默认格式输出,实测为 11/11/2026,与正文中常见的中文日期写法并不一致。需要统一显示效果的文档,应在域代码中写明格式。
更新域与锁定显示结果
更新域与锁定显示结果,指的是让域代码重新计算一次、并把算出的文字固定下来。Field.Update() 负责重新计算,Field.IsLocked 负责冻结结果。更新前的域只带有初始显示文字,更新后才会得到与当前时间、文件名相符的内容。
具体操作步骤如下:
- 获取 Spire.Doc 的 WASM 模块,确认已就绪。
- 载入字体与示例文档,创建
Document对象并加载Sample.docx。 - 追加一个段落,插入归档日期域并设置域代码。
- 遍历
Document.Fields,对每个域调用Field.Update()并读取FieldText。 - 将归档日期域设为锁定,保存结果文件并触发下载。
更新域与锁定显示结果的代码如下:
function App() {
const updateFields = async () => {
// 获取 Spire.Doc 的 WASM 模块
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 载入示例文档
await window.spire.FetchFileToVFS('Sample.docx', '', `${process.env.PUBLIC_URL}/data/`);
const doc = new docModule.Document();
doc.LoadFromFile('Sample.docx');
// 在附注栏插入归档日期域
const paragraph = doc.Sections.get_Item(0).AddParagraph();
paragraph.AppendText('归档日期:');
const archiveField = paragraph.AppendField('Date', docModule.FieldType.FieldDate);
archiveField.Code = 'DATE \\@ "yyyy年M月d日"';
// 逐个刷新文档中的域,Document.Fields 同时包含页脚里的页码域
for (let i = 0; i < doc.Fields.Count; i++) {
const field = doc.Fields.get_Item(i);
field.Update();
console.log(field.FieldText);
}
// 锁定归档日期,结果固定为本次计算的日期
archiveField.IsLocked = true;
paragraph.AppendText('(已锁定)');
// 保存结果文件
const outputFileName = 'ReportWithArchiveDate.docx';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
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 = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>更新域</h1>
<button onClick={updateFields}>更新并锁定</button>
</div>
);
}
export default App;
更新全部域并锁定归档日期后的文档:

一次更新文档中的全部域
Document.Fields 是文档中所有域的集合,Count 与 get_Item(index) 用于逐个访问,页眉页脚里的域同样包含在内。集合本身没有批量更新的方法,需要按索引循环调用 Field.Update():
// 逐个更新,索引从 0 开始
for (let i = 0; i < doc.Fields.Count; i++) {
doc.Fields.get_Item(i).Update();
}
需要判断哪些域需要更新时,可以先用 field.Code 或 field.Type 做筛选,只刷新日期、文件名一类会随时间变化的域,页脚中不需要变动的域可以跳过。
锁定与解除锁定
Field.IsLocked 为 true 时,该域不会随更新而变化,适合存档、审计一类需要固定时点的文档。锁定只阻止更新,不影响读取:Field.FieldText 仍可正常取值。需要恢复动态更新时,把 IsLocked 设回 false 即可。
在段落首部、中部与末尾插入日期域
在段落首部、中部与末尾插入日期域,靠的是调整 AppendField 的调用时机。AppendField 只把域追加到段落末尾,因此域在段落中的位置由它与 AppendText 的先后顺序决定:先插入域再写文字,域就在首部;文字与域交替写入,域就落在中间。
具体操作步骤如下:
- 获取 Spire.Doc 的 WASM 模块,确认已就绪。
- 载入字体与示例文档,创建
Document对象并加载Sample.docx。 - 追加一个空段落,先调用
Paragraph.AppendField()插入首部的日期域。 - 写入中部文字并插入第二个日期域,再写入末尾文字并插入第三个日期域。
- 逐个调用
Field.Update(),保存结果文件并触发下载。
在段落首部、中部与末尾插入日期域的代码如下:
function App() {
const positionFields = async () => {
// 获取 Spire.Doc 的 WASM 模块
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 载入示例文档
await window.spire.FetchFileToVFS('Sample.docx', '', `${process.env.PUBLIC_URL}/data/`);
const doc = new docModule.Document();
doc.LoadFromFile('Sample.docx');
const paragraph = doc.Sections.get_Item(0).AddParagraph();
// 首部:段落中没有文字时插入的域,位于最前面
const headField = paragraph.AppendField('Date', docModule.FieldType.FieldDate);
headField.Code = 'DATE \\@ "yyyy年M月d日"';
// 中部:先写前文,再插入域,然后写后文
paragraph.AppendText(' 本报告的数据截至');
const middleField = paragraph.AppendField('Date', docModule.FieldType.FieldDate);
middleField.Code = 'DATE \\@ "yyyy年M月d日"';
// 末尾:正文写完之后插入的域,位于段落最后
paragraph.AppendText(',于');
const tailField = paragraph.AppendField('Date', docModule.FieldType.FieldDate);
tailField.Code = 'DATE \\@ "yyyy年M月d日"';
paragraph.AppendText(' 完成编制。');
// 三个域分别刷新,得到当前的日期
[headField, middleField, tailField].forEach((field) => field.Update());
// 保存结果文件
const outputFileName = 'ReportWithDatePositions.docx';
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
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 = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>定位域</h1>
<button onClick={positionFields}>按位置插入</button>
</div>
);
}
export default App;
日期域分别位于段落首部、中部与末尾的文档:

在段落中核对域的位置
段落的子对象顺序与调用顺序完全一致。需要核对域实际落在第几个位置时,可以遍历 paragraph.ChildObjects:每个文字区间、域标记与域各占一个元素,序号从 0 开始。
// 打印段落中所有子对象的类型,确认域实际落在第几个位置
for (let i = 0; i < paragraph.ChildObjects.Count; i++) {
console.log(i, paragraph.ChildObjects.get_Item(i).DocumentObjectType);
}
一个完整的域会展开为四个子对象:Field(开始标记)、FieldMark(分隔符)、承载结果文字的 TextRange,以及结尾的 FieldMark(结束标记)。因此按序号估算位置时,一段文字与一个域之间并不只隔一个元素。
常见问题
插入的域为什么显示的是 Date 或 1 这样的文字
原因:对日期、页码这类普通域,AppendField 的第一个参数就是域在更新之前的初始显示文字。设置 Code 只是改写了域代码,尚未重新计算,域结果仍停留在初始文字上。
解决:设置域代码后调用一次 Field.Update(),再读取 Field.FieldText 确认结果:
const dateField = paragraph.AppendField('Date', docModule.FieldType.FieldDate);
dateField.Code = 'DATE \\@ "yyyy年M月d日"';
dateField.Update();
console.log(dateField.FieldText); // 运行时日期,例如 2026年11月11日
更新后日期为什么显示成 11/11/2026
原因:域代码中没有写格式开关时,日期域按默认格式输出,实测结果为 11/11/2026,与正文中的中文日期写法并不一致。
解决:在域代码中写明日期格式,让日期域的显示与文档其余部分保持一致:
// 固定为“年-月-日”的中文写法
dateField.Code = 'DATE \\@ "yyyy年M月d日"';
页码域调用 Update() 后为什么没有变化
原因:页码取决于文档的排版结果,不同页上的同一个域会得到不同的值。这类域的结果由排版计算,实测调用 Field.Update() 不会改写它已有的显示文字。
解决:把页码域放在页脚,由 Word 在排版与导出时按所在页填充;需要事先知道总页数时,用 Document.GetPageCount() 取得分页结果来核对,不要以页码域的文字为准。
设置了 IsUpdateFields,Word 打开文档时为什么没有自动更新域
原因:Document.IsUpdateFields 是一个可读写的属性,但在当前版本的 Spire.Doc for JavaScript 中实测,设为 true 后保存的 .docx 并未写入 w:updateFields 标记,Word 打开文档时不会据此刷新域。
解决:需要确定的域结果时,在保存之前逐个调用 Field.Update()(见上文“一次更新文档中的全部域”),把算好的结果写进文档,不依赖打开时再更新。
可以把域插入到已有段落的中间吗
原因:AppendField 只向段落末尾追加,没有提供按位置插入的参数。从 ChildObjects 中移除域再重新插入也不行:域在段落中由开始标记、分隔符、结果文字与结束标记共同组成,单独移动其一,域结构会被破坏,再次调用 Field.Update() 会抛出异常。
解决:在段落成型之前按目标顺序写入。已有段落需要把域放在中间时,改为新建段落,按“文字—域—文字”的顺序重新写入,原文字可以用 paragraph.Text 取出后分段填入:
// 新段落的写入顺序,决定域在段落中的位置
const paragraph = doc.Sections.get_Item(0).AddParagraph();
paragraph.AppendText('报告编制于');
const dateField = paragraph.AppendField('Date', docModule.FieldType.FieldDate);
dateField.Code = 'DATE \\@ "yyyy年M月d日"';
paragraph.AppendText(',共三个部分。');
dateField.Update();
获取免费许可证
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。







