冰蓝科技
|
028-81705109
|
|
微信扫一扫
|

Spire.Cloud 纯前端文档控件

页眉和页脚是 Word 文档中不可或缺的组成部分——页眉通常用于放置公司 Logo、文档标题,页脚则用于显示页码、版权声明等辅助信息。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成 Word 文档的创建与编辑,通过虚拟文件系统(VFS)管理字体和文件资源,无需后端服务支持。

本文介绍三个核心功能点:

有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。


添加页眉页脚(图片、文字、页码)

在实际开发中,最常遇到的需求是给文档添加页眉页脚:页眉中插入公司 Logo 和文档标题,页脚中插入页码和版权信息。Spire.Doc 通过 HeadersFooters.Header 和 HeadersFooters.Footer 获取页眉和页脚对象,然后使用 AppendPicture 插入图片、AppendText 插入文字、AppendField 插入页码字段。核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 Word 文件载入 WASM 虚拟文件系统;然后实例化 Document 加载文件,获取节(Section)后调用自定义函数添加页眉页脚内容;最后将文档保存并从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。

function InsertHeaderAndFooter(section, inputImgFileName, inputImgFileName_1) {
    let wasmModule = window.wasmModule.spiredoc;
    let header = section.HeadersFooters.Header;
    let footer = section.HeadersFooters.Footer;

    // 在页眉中插入图片和文字
    let headerParagraph = header.AddParagraph();

    let headerPicture = headerParagraph.AppendPicture({ imgFile: inputImgFileName });
    // 页眉文字
    let text = headerParagraph.AppendText("Demo of Spire.Doc");
    text.CharacterFormat.FontName = "Arial";
    text.CharacterFormat.FontSize = 10;
    text.CharacterFormat.Italic = true;
    headerParagraph.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Right;

    // 页眉底部边框
    headerParagraph.Format.Borders.Bottom.BorderType = wasmModule.BorderStyle.Single;
    headerParagraph.Format.Borders.Bottom.Space = 0.05;

    // 页眉图片布局 - 文字环绕
    headerPicture.TextWrappingStyle = wasmModule.TextWrappingStyle.Behind;

    // 页眉图片布局 - 位置
    headerPicture.HorizontalOrigin = wasmModule.HorizontalOrigin.Page;
    headerPicture.HorizontalAlignment = wasmModule.ShapeHorizontalAlignment.Left;
    headerPicture.VerticalOrigin = wasmModule.VerticalOrigin.Page;
    headerPicture.VerticalAlignment = wasmModule.ShapeVerticalAlignment.Top;

    // 在页脚中插入图片
    let footerParagraph = footer.AddParagraph();

    let footerPicture = footerParagraph.AppendPicture({ imgFile: inputImgFileName_1 });

    // 页脚图片布局
    footerPicture.TextWrappingStyle = wasmModule.TextWrappingStyle.Behind;
    footerPicture.HorizontalOrigin = wasmModule.HorizontalOrigin.Page;
    footerPicture.HorizontalAlignment = wasmModule.ShapeHorizontalAlignment.Left;
    footerPicture.VerticalOrigin = wasmModule.VerticalOrigin.Page;
    footerPicture.VerticalAlignment = wasmModule.ShapeVerticalAlignment.Bottom;

    // 插入页码
    footerParagraph.AppendField("page number", wasmModule.FieldType.FieldPage);
    footerParagraph.AppendText(" of ");
    footerParagraph.AppendField("number of pages", wasmModule.FieldType.FieldNumPages);
    footerParagraph.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Right;

    // 页脚顶部边框
    footerParagraph.Format.Borders.Top.BorderType = wasmModule.BorderStyle.Single;
    footerParagraph.Format.Borders.Top.Space = 0.05;
}

function App() {
  const AddHeaderAndFooter = async () => {
    // 获取 Spire.Doc WASM 模块
    const wasmModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // 将示例 Word 文件载入 VFS
    const inputFileName = "Sample.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    const inputImgFileName = "Header.png";
    await window.spire.FetchFileToVFS(inputImgFileName, "", `${process.env.PUBLIC_URL}/data/`);

    const inputImgFileName_1 = "Footer.png";
    await window.spire.FetchFileToVFS(inputImgFileName_1, "", `${process.env.PUBLIC_URL}/data/`);

    // 加载文档
    let doc = new wasmModule.Document();
    doc.LoadFromFile(inputFileName);
    let section = doc.Sections.get_Item(0);

    // 插入页眉和页脚
    InsertHeaderAndFooter(section, inputImgFileName, inputImgFileName_1);

    // 定义输出文件名
    const outputFileName = "HeaderAndFooter.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={AddHeaderAndFooter}>
        Generate
      </button>
    </div>
  );
}

export default App;

生成的 Word 文档效果如下:

添加图片、文字和页码后的页眉页脚


设置首页不同的页眉页脚

在实际开发中,很多文档的首页(封面页)需要使用与其他页面不同的页眉页脚,甚至不需要页眉页脚。Spire.Doc 通过设置 PageSetup.DifferentFirstPageHeaderFooter = true 来启用首页独立设置,然后分别通过 HeadersFooters.FirstPageHeader / FirstPageFooter 设置首页内容,通过 HeadersFooters.Header / Footer 设置其他页内容。


function App() {
  const DifferentFirstPage = async () => {
    // 获取 Spire.Doc WASM 模块
    const wasmModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

     // 将示例文件载入 VFS
    let inputFileName = "MultiplePages.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
    let inputImgFileName = "E-iceblue.png";
    await window.spire.FetchFileToVFS(inputImgFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // 加载文档
    let doc = new wasmModule.Document();
    doc.LoadFromFile(inputFileName);

    // 获取节并启用首页独立页眉页脚
    let section = doc.Sections.get_Item(0);
    section.PageSetup.DifferentFirstPageHeaderFooter = true;

    // 设置首页页眉:右侧插入图片
    let paragraph1 = section.HeadersFooters.FirstPageHeader.AddParagraph();
    paragraph1.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Right;
    let headerimage = paragraph1.AppendPicture({ imgFile: inputImgFileName });

    // 设置首页页脚:居中显示文字
    let paragraph2 = section.HeadersFooters.FirstPageFooter.AddParagraph();
    paragraph2.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
    let FF = paragraph2.AppendText("First Page Footer");
    FF.CharacterFormat.FontSize = 10;

    // 设置其他页的页眉和页脚
    let paragraph3 = section.HeadersFooters.Header.AddParagraph();
    paragraph3.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
    let NH = paragraph3.AppendText("Spire.Doc for JavaScript");
    NH.CharacterFormat.FontSize = 10;

    let paragraph4 = section.HeadersFooters.Footer.AddParagraph();
    paragraph4.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
    let NF = paragraph4.AppendText("E-iceblue");
    NF.CharacterFormat.FontSize = 10;

    // 定义输出文件名
    const outputFileName = "DifferentFirstPage.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>单击以下按钮以设置首页不同的页眉和页脚。</h1>
      <button onClick={DifferentFirstPage}>
        Generate
      </button>
    </div>
  );
}

export default App;

首页显示单独的页眉页脚内容,而其他页使用统一的页眉页脚,生成的 Word 文档效果如下:

首页与其他页使用不同的页眉页脚


设置奇偶页不同的页眉页脚

对于需要双面打印或书籍排版的文档,通常需要为奇偶页设置不同的页眉页脚——例如奇数页页眉显示章节名称并靠右对齐,偶数页页眉显示书名并靠左对齐。Spire.Doc 通过设置 PageSetup.DifferentOddAndEvenPagesHeaderFooter = true 来启用奇偶页独立设置,然后分别通过 OddHeader / OddFooter 和 EvenHeader / EvenFooter 设置各自的内容。


function App() {
  const OddAndEvenHeaderFooter = async () => {
    // 获取 Spire.Doc WASM 模块
    const wasmModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

      // 将示例文件载入 VFS
    let inputFileName = "MultiplePages.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // 加载文档
    let doc = new wasmModule.Document();
    doc.LoadFromFile(inputFileName);

    // 获取第一个节
    let section = doc.Sections.get_Item(0);

    // 启用奇偶页不同的页眉页脚
    section.PageSetup.DifferentOddAndEvenPagesHeaderFooter = true;

    // 添加奇数页页眉
    let P3 = section.HeadersFooters.OddHeader.AddParagraph();
    let OH = P3.AppendText("Odd Header");
    P3.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
    OH.CharacterFormat.FontName = "Arial";
    OH.CharacterFormat.FontSize = 10;

    // 添加偶数页页眉
    let P4 = section.HeadersFooters.EvenHeader.AddParagraph();
    let EH = P4.AppendText("Even Header from E-iceblue Using Spire.Doc");
    P4.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
    EH.CharacterFormat.FontName = "Arial";
    EH.CharacterFormat.FontSize = 10;

    // 添加奇数页页脚
    let P2 = section.HeadersFooters.OddFooter.AddParagraph();
    let OF = P2.AppendText("Odd Footer");
    P2.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
    OF.CharacterFormat.FontName = "Arial";
    OF.CharacterFormat.FontSize = 10;

    // 添加偶数页页脚
    let P1 = section.HeadersFooters.EvenFooter.AddParagraph();
    let EF = P1.AppendText("Even Footer from E-iceblue Using Spire.Doc");
    EF.CharacterFormat.FontName = "Arial";
    EF.CharacterFormat.FontSize = 10;
    P1.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;

    // 定义输出文件名
    const outputFileName = "OddAndEvenHeaderFooter_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>单击以下按钮以设置奇偶页不同的页眉和页脚。</h1>
      <button onClick={OddAndEvenHeaderFooter}>
        Generate
      </button>
    </div>
  );
}

export default App;

奇偶页分别设置了不同的页眉和页脚文字,生成的 Word 文档效果如下:

奇偶页使用不同的页眉页脚


常见问题

首页页眉页脚未按预期显示

原因:DifferentFirstPageHeaderFooter 属性默认为 false,直接编辑 FirstPageHeader 或 FirstPageFooter 不会生效。

解决:编辑首页页眉页脚前先设置该属性为 true:

section.PageSetup.DifferentFirstPageHeaderFooter = true;
// 然后再编辑 FirstPageHeader / FirstPageFooter

奇偶页设置不生效

原因:DifferentOddAndEvenPagesHeaderFooter 属性默认为 false,直接编辑 OddHeader / EvenHeader 不会生效。

解决:启用该属性后再编辑奇偶页内容:

section.PageSetup.DifferentOddAndEvenPagesHeaderFooter = true;
// 然后再编辑 OddHeader / EvenHeader / OddFooter / EvenFooter

获取免费许可证

如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。

在日常的 Word 文档开发中,合并与拆分表格单元格是最常见的表格编辑操作之一。无论是制作带有跨列标题的报表表头,还是实现跨行合并的产品分组展示,单元格合并都能让表格结构更加清晰合理。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成 Word 文档的创建与编辑,通过虚拟文件系统(VFS)管理字体和文件资源,无需后端服务支持。

本文介绍三个核心功能点:

有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。


合并与拆分单元格

在实际开发中,最常遇到的需求是对已有表格进行结构调整:将多个相邻的单元格合并为一个,或者将一个单元格拆分为多行多列。Spire.Doc 提供了 ApplyHorizontalMerge、ApplyVerticalMerge 和 SplitCell 三个方法分别实现水平合并、垂直合并和拆分操作。核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 Word 文件载入 WASM 虚拟文件系统;然后实例化 Document 加载文件,获取目标表格后调用合并与拆分方法;最后将文档保存并从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。

function App() {
  const MergeAndSplitTableCell = async () => {
    // 获取 Spire.Doc WASM 模块
    const wasmModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }
 
    // 将示例 Word 文件载入 VFS
    let inputFileName = "TableSample.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // 加载文档
    let doc = new wasmModule.Document();
    doc.LoadFromFile(inputFileName);
    let section = doc.Sections.get_Item(0);
    let table = section.Tables.get_Item(0);

    // 水平合并:将第 6 行的第 2、3 列合并
    table.ApplyHorizontalMerge(6, 2, 3);
    // 垂直合并:将第 2 列的第 4、5 行合并
    table.ApplyVerticalMerge(2, 4, 5);
    // 拆分单元格:将第 8 行第 3 列的单元格拆分为 2 行 2 列
    table.Rows.get_Item(8).Cells.get_Item(3).SplitCell(2, 2);

    // 定义输出文件名
    const outputFileName = "MergeAndSplitTableCell_output.docx";

    // 保存文档
    doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.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={MergeAndSplitTableCell}>
        Generate
      </button>
    </div>
  );
}

export default App;

生成的 Word 文档效果如下:

合并与拆分单元格后的 Word 输出


合并单元格后格式化

合并单元格后,通常还需要对合并后的区域进行格式化,例如设置字体样式、对齐方式和背景色,以提升表格的可读性和美观度。下面的示例演示了如何创建一个产品价格表,合并表头"Product"单元格和左侧的版本类别单元格,并对其应用自定义样式。

function AddTable(section) {
  let wasmModule = window.wasmModule.spiredoc;
  let table = section.AddTable({ showBorder: true });
  table.ResetCells(4, 3);
  // 表格数据
  let dt = [["产品", "", "库存(kg)"],
    ["水果", "苹果", "150"],
    ["", "葡萄", "200"],
    ["", "柠檬", "100"]];

  for (let r = 0; r < dt.length; r++) {
    let dataRow = table.Rows.get_Item(r);
    dataRow.Height = 20;
    dataRow.HeightType = wasmModule.TableRowHeightType.Exactly;
    for (let i = 0; i < dataRow.Cells.Count; i++) {
      dataRow.Cells.get_Item(i).CellFormat.Shading.BackgroundPatternColor = wasmModule.Color.Empty;
    }
    for (let c = 0; c < dataRow.Cells.Count; c++) {
      if (dt[r][c] !== "") {
        let range = dataRow.Cells.get_Item(c).AddParagraph().AppendText(dt[r][c]);
        range.CharacterFormat.FontName = "Arial";
      }
    }
  }
  return table;
}

function App() {
  const FormatMergedCells = async () => {
    // 获取 Spire.Doc WASM 模块
    const wasmModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }
 
     // 将字体和文档文件载入 VFS
    await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    
    // 创建 Word 文档
    let doc = new wasmModule.Document();
    let section = doc.AddSection();

    // 添加表格
    let table = AddTable(section);

    // 创建自定义样式
    let style = new wasmModule.ParagraphStyle(doc);
    style.Name = "Style";
    style.CharacterFormat.TextColor = wasmModule.Color.get_DeepSkyBlue();
    style.CharacterFormat.Italic = true;
    style.CharacterFormat.Bold = true;
    style.CharacterFormat.FontSize = 13;
    doc.Styles.Add(style);

    // 水平合并:将第一行的第 0、1 列合并
    table.ApplyHorizontalMerge(0, 0, 1);
    // 应用样式
    table.Rows.get_Item(0).Cells.get_Item(0).Paragraphs.get_Item(0).ApplyStyle(style.Name);
    // 设置垂直和水平对齐
    table.Rows.get_Item(0).Cells.get_Item(0).CellFormat.VerticalAlignment = wasmModule.VerticalAlignment.Middle;
    table.Rows.get_Item(0).Cells.get_Item(0).Paragraphs.get_Item(0).Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;

    // 垂直合并:将第 0 列的第 1、2、3 行合并
    table.ApplyVerticalMerge(0, 1, 3);
    // 应用样式
    table.Rows.get_Item(1).Cells.get_Item(0).Paragraphs.get_Item(0).ApplyStyle(style.Name);
    // 设置垂直和水平对齐
    table.Rows.get_Item(1).Cells.get_Item(0).CellFormat.VerticalAlignment = wasmModule.VerticalAlignment.Middle;
    table.Rows.get_Item(1).Cells.get_Item(0).Paragraphs.get_Item(0).Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Left;
    // 设置列宽占比
    table.Rows.get_Item(1).Cells.get_Item(0).SetCellWidth(20, wasmModule.CellWidthType.Percentage);

    // 定义输出文件名
    const outputFileName = "FormatMergedCells_output.docx";

    // 保存文档
    doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.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={FormatMergedCells}>
        Generate
      </button>
    </div>
  );
}

export default App;

生成的 Word 文档效果如下:

合并单元格并格式化后的 Word 输出


检测单元格合并状态

在处理他人创建的文档或自动化流程生成的表格时,常常需要先了解哪些单元格已被合并,以避免操作索引越界。Spire.Doc 通过 CellFormat.VerticalMerge 和 Cell.GridSpan 两个属性来检测单元格的合并状态:VerticalMerge 指示垂直合并情况,GridSpan 表示水平合并跨越的列数。

function App() {
  const CellMergeStatus = async () => {
    // 获取 Spire.Doc WASM 模块
    const wasmModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }
 
    // 将示例文件载入 VFS
    let inputFileName = "CellMergeStatus.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // 加载文档
    let doc = new wasmModule.Document();
    doc.LoadFromFile(inputFileName);

    // 获取第一个节和第一个表格
    let section = doc.Sections.get_Item(0);
    let table = section.Tables.get_Item(0);

    // 遍历所有单元格,检测合并状态
    let stringBuidler = [];
    for (let i = 0; i < table.Rows.Count; i++) {
      let tableRow = table.Rows.get_Item(i);
      for (let j = 0; j < tableRow.Cells.Count; j++) {
    let tableCell = tableRow.Cells.get_Item(j);
    let verticalMerge = tableCell.CellFormat.VerticalMerge;
    let horizontalMerge = tableCell.GridSpan;
    if (verticalMerge === wasmModule.CellMerge.None && horizontalMerge === 1) {
      stringBuidler.push("Row " + i + ", cell " + j + ": ");
      stringBuidler.push("This cell isn't merged.\n");
    } else {
      stringBuidler.push("Row " + i + ", cell " + j + ": ");
      stringBuidler.push("This cell is merged.\n");
    }
      }
      stringBuidler.push("\n");
    }

    // 定义输出文件名
    const outputFileName = "CellMergeStatus_output.txt";

    // 将检测结果写入文本文件
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, stringBuidler.join('\n'));

    // 从 VFS 读取生成的文件,触发下载
    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], { type: "text/plain" });
    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={CellMergeStatus}>
        Generate
      </button>
    </div>
  );
}

export default App;

检测结果以文本文件输出:

单元格合并状态检测结果输出


常见问题

合并后的单元格如何判断是否合并成功

原因:合并操作本身不返回状态值,需要通过读取单元格属性来确认。

解决:使用 CellFormat.VerticalMerge 判断垂直合并类型(CellMerge.None 表示未合并),使用 Cell.GridSpan 判断水平合并跨越的列数(值为 1 表示未合并):

let verticalMerge = tableCell.CellFormat.VerticalMerge;
let horizontalMerge = tableCell.GridSpan;
if (verticalMerge === wasmModule.CellMerge.None && horizontalMerge === 1) {
  // 未合并
} else {
  // 已合并
}

拆分单元格后内容丢失

原因:SplitCell 方法将单元格拆分为多个子单元格后,原有内容默认保留在第一个子单元格中。

解决:拆分后手动遍历子单元格重新分配内容,或拆分前通过 Paragraphs 集合备份单元格文本:

// 备份内容
let cell = table.Rows.get_Item(row).Cells.get_Item(col);
let text = cell.Paragraphs.get_Item(0).Text;

// 拆分为 2 行 2 列
cell.SplitCell(2, 2);

// 将内容写入新的单元格
table.Rows.get_Item(row).Cells.get_Item(col).Paragraphs.get_Item(0).AppendText(text);

合并单元格时索引超出范围

原因:ApplyHorizontalMerge(row, startCol, endCol) 和 ApplyVerticalMerge(col, startRow, endRow) 的索引从 0 开始,传入的索引超出表格的实际行列数会引发错误。

解决:合并前检查表格的行列数,确保结束索引不超过 Rows.Count - 1 和 Cells.Count - 1:

if (endCol < table.Rows.get_Item(row).Cells.Count && endRow < table.Rows.Count) {
  table.ApplyHorizontalMerge(row, startCol, endCol);
}

获取免费许可证

如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。

在实际的文档开发流程中,页眉页脚的维护操作与添加同样重要——从模板文档复制页眉页脚到新文档实现快速复用、移除旧页眉页脚进行文档清理、锁定页眉防止内容被篡改,这些都是日常工作中频繁遇到的需求。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成 Word 文档的处理,通过虚拟文件系统(VFS)管理字体和文件资源,无需后端服务支持。

本文介绍三个核心功能点:

有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。


复制页眉页脚

在企业文档生产中,通常有一个标准模板文档定义了统一的页眉(公司 Logo + 文档标题)和页脚(页码 + 版权声明)。当创建新文档时,需要将模板中的页眉页脚复制过来,以保持企业文档风格的一致性。Spire.Doc 通过 ChildObjects 集合和 Clone 方法实现页眉对象的跨文档复制。核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和两个 Word 文件(源文档和目标文档)载入 WASM 虚拟文件系统;然后分别实例化两个 Document,从源文档获取页眉的子对象集合,遍历并克隆到目标文档各节的页眉中;最后将目标文档保存并从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。

function App() {
  const CopyHeaderAndFooter = async () => {
    // 获取 Spire.Doc WASM 模块
    const wasmModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // 将源文件和目标文件载入 VFS
    let inputFileName = "HeaderAndFooter.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
    const inputFileName_1 = "Template.docx";
    await window.spire.FetchFileToVFS(inputFileName_1, "", `${process.env.PUBLIC_URL}/data/`);

    // 加载源文档
    let doc1 = new wasmModule.Document();
    doc1.LoadFromFile(inputFileName);

    // 获取源文档的页眉
    let header = doc1.Sections.get_Item(0).HeadersFooters.Header;

    // 加载目标文档
    let doc2 = new wasmModule.Document();
    doc2.LoadFromFile(inputFileName_1);

    // 将源文档页眉中的每个子对象克隆到目标文档的所有节
    for (let i = 0; i < doc2.Sections.Count; i++) {
      let section = doc2.Sections.get_Item(i);
      for (let j = 0; j < header.ChildObjects.Count; j++) {
        let obj = header.ChildObjects.get_Item(j);
        section.HeadersFooters.Header.ChildObjects.Add(obj.Clone());
      }
    }

    // 定义输出文件名
    const outputFileName = "CopyHeaderAndFooter_output.docx";

    // 保存文档
    doc2.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });

    // 释放资源
    doc1.Close();
    doc2.Close();
    doc1.Dispose();
    doc2.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={CopyHeaderAndFooter}>
        Generate
      </button>
    </div>
  );
}

export default App;

上述代码将源文档中已有的页眉内容克隆到目标文档的所有节中,生成的 Word 文档效果如下:

页眉页脚从源文档复制到目标文档


移除页眉和页脚

在文档清理或模板替换场景中,经常需要移除文档中现有的页眉或页脚。例如:接手他人文档需要重新设计页眉页脚时,先清空原有内容;或者从客户系统导出的文档包含默认页眉,需要移除后替换为企业模板。Spire.Doc 通过 HeadersFooters.get_Item 按类型获取首页、奇数页、偶数页的页眉或页脚对象,然后调用 ChildObjects.Clear() 清除其所有内容。

function App() {
  const RemoveHeaderFooter = async () => {
    // 获取 Spire.Doc WASM 模块
    const wasmModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // 将示例文件载入 VFS
    let inputFileName = "HeaderAndFooter.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // 加载文档
    let doc = new wasmModule.Document();
    doc.LoadFromFile(inputFileName);

    // 获取第一个节
    let section = doc.Sections.get_Item(0);

    // 清除所有类型的页眉内容(首页、奇数页、偶数页)
    let header;
    header = section.HeadersFooters.get_Item({ hfType: wasmModule.HeaderFooterType.HeaderFirstPage });
    if (header != null)
      header.ChildObjects.Clear();

    header = section.HeadersFooters.get_Item({ hfType: wasmModule.HeaderFooterType.HeaderOdd });
    if (header != null)
      header.ChildObjects.Clear();

    header = section.HeadersFooters.get_Item({ hfType: wasmModule.HeaderFooterType.HeaderEven });
    if (header != null)
      header.ChildObjects.Clear();

    // 清除所有类型的页脚内容(首页、奇数页、偶数页)
    let footer;
    footer = section.HeadersFooters.get_Item({ hfType: wasmModule.HeaderFooterType.FooterFirstPage });
    if (footer != null)
      footer.ChildObjects.Clear();

    footer = section.HeadersFooters.get_Item({ hfType: wasmModule.HeaderFooterType.FooterOdd });
    if (footer != null)
      footer.ChildObjects.Clear();

    footer = section.HeadersFooters.get_Item({ hfType: wasmModule.HeaderFooterType.FooterEven });
    if (footer != null)
      footer.ChildObjects.Clear();
      
    // 定义输出文件名
    const outputFileName = "RemoveHeaderFooter_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={RemoveHeaderFooter}>
        Generate
      </button>
    </div>
  );
}

export default App;

移除页眉后的文档效果如下:

移除页眉后的 Word 文档


锁定页眉禁止编辑

在将文档分发给客户或团队成员时,通常希望页眉中的公司 Logo、文档编号等固定信息不被修改,同时允许正文区域正常编辑。Spire.Doc 通过文档保护功能实现这一点:将保护类型设置为 AllowOnlyFormFields,然后将节的 ProtectForm 属性设为 false,这样正文区域保持可编辑状态,而页眉区域则受到保护不可修改。

function App() {
  const LockHeader = async () => {
    // 获取 Spire.Doc WASM 模块
    const wasmModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // 将示例文件载入 VFS
    let inputFileName = "HeaderAndFooter.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

      // 加载文档
    let doc = new wasmModule.Document();
    doc.LoadFromFile(inputFileName);

    // 获取第一个节
    let section = doc.Sections.get_Item(0);

    // 保护文档,设置保护类型为 AllowOnlyFormFields
    doc.Protect({ type: wasmModule.ProtectionType.AllowOnlyFormFields, password: "123" });

    // 将节设置为可编辑,使正文区域不被锁定
    section.ProtectForm = false;

    // 定义输出文件名
    const outputFileName = "LockHeader_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={LockHeader}>
        Generate
      </button>
    </div>
  );
}

export default App;

页眉被锁定后,在 Word 中打开文档时页眉区域不可编辑,而正文区域可以正常修改,生成的 Word 文档效果如下:

页眉被锁定不可编辑


常见问题

复制后页眉内容在目标文档中不显示

原因:目标文档包含多个节(Section),但复制操作只处理了第一节的页眉,其他节的页眉未被更新。

解决:遍历目标文档的所有节,将源页眉内容复制到每一节:

for (let i = 0; i < doc2.Sections.Count; i++) {
  let section = doc2.Sections.get_Item(i);
  for (let j = 0; j < header.ChildObjects.Count; j++) {
    let obj = header.ChildObjects.get_Item(j);
    section.HeadersFooters.Header.ChildObjects.Add(obj.Clone());
  }
}

锁定页眉后整个文档都无法编辑

原因:doc.Protect({ type: AllowOnlyFormFields }) 默认会锁定整个文档,包括正文区域。

解决:设置保护后,将节的 ProtectForm 属性设为 false,使正文区域保持可编辑状态:

doc.Protect({ type: wasmModule.ProtectionType.AllowOnlyFormFields, password: "123" });
section.ProtectForm = false;

移除页眉后页脚内容也被清空

原因:误将移除页眉的代码逻辑应用到页脚,或者使用了相同的 ChildObjects.Clear() 操作作用于了错误的对象。

解决:移除页眉和移除页脚使用不同的 HeaderFooterType 参数,确保操作的对象是正确的:

// 移除页眉时使用 Header 类型
section.HeadersFooters.get_Item({ hfType: wasmModule.HeaderFooterType.HeaderFirstPage });
// 移除页脚时使用 Footer 类型
section.HeadersFooters.get_Item({ hfType: wasmModule.HeaderFooterType.FooterFirstPage });

获取免费许可证

如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。

在实际的文档开发中,表格的结构往往需要根据数据动态调整——报表数据行数不确定时需要动态添加行,字段调整时需要新增或移除列,冗余数据行需要一键删除。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成 Word 文档的编辑,通过虚拟文件系统(VFS)管理字体和文件资源,无需后端服务支持。

本文介绍两个核心功能点:

有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。


添加和删除行

在报表生成和数据展示场景中,表格的行数通常是动态变化的。Spire.Doc 提供了 Rows.RemoveAt 方法删除指定行,Rows.Insert 方法在指定位置插入新行,以及 AddRow 方法在表格末尾追加行。核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 Word 文件载入 WASM 虚拟文件系统;然后实例化 Document 加载文件,获取目标表格后调用行列操作方法;最后将文档保存并从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。


function App() {
  const AddOrDeleteRow = async () => {
    // 获取 Spire.Doc WASM 模块
    const wasmModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!wasmModule) {
      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 = "TableSample.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // 加载文档
    let doc = new wasmModule.Document();
    doc.LoadFromFile(inputFileName);
    let section = doc.Sections.get_Item(0);
    let table = section.Tables.get_Item(0);

    // 删除第 8 行
    table.Rows.RemoveAt(7);

    // 创建一个新行并插入到指定位置(第 2 行之后)
    let row = new wasmModule.TableRow(doc);
    for (let i = 0; i < table.Rows.get_Item(0).Cells.Count; i++) {
      let tc = row.AddCell();
      let paragraph = tc.AddParagraph();
      paragraph.Format.HorizontalAlignment = wasmModule.HorizontalAlignment.Center;
      paragraph.AppendText("Added");
    }
    table.Rows.Insert(2, row);

    // 在表格末尾追加一行
    table.AddRow();

    // 定义输出文件名
    const outputFileName = "AddOrDeleteRow_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={AddOrDeleteRow}>
        Generate
      </button>
    </div>
  );
}

export default App;

上述代码在已有表格上执行了三种行操作:删除第 8 行、在第 2 行之后插入一个包含 "Added" 文字的新行、在表格末尾追加一个空行,生成的 Word 文档效果如下:

在 Word 表格中删除和添加行


添加和删除列

在表格结构调整场景中,经常需要新增字段列或移除冗余列。由于 Spire.Doc 的表格列操作是通过逐行操作单元格来实现的,因此需要自定义 AddColumn 和 RemoveColumn 辅助函数:添加列时遍历每一行并在指定索引处插入一个新的空白单元格;删除列时遍历每一行并移除指定索引处的单元格。

function AddColumn(table, columnIndex) {
  let wasmModule = window.wasmModule.spiredoc;
  for (let r = 0; r < table.Rows.Count; r++) {
    let addCell = new wasmModule.TableCell(table.Document);
    table.Rows.get_Item(r).Cells.Insert(columnIndex, addCell);
  }
}

function RemoveColumn(table, columnIndex) {
  for (let r = 0; r < table.Rows.Count; r++) {
    table.Rows.get_Item(r).Cells.RemoveAt(columnIndex);
  }
}

function App() {
  const AddOrRemoveColumn = async () => {
    // 获取 Spire.Doc WASM 模块
    const wasmModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // 将示例文件载入 VFS
    let inputFileName = "TableSample.docx";
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // 加载文档
    let doc = new wasmModule.Document();
    doc.LoadFromFile(inputFileName);

    // 获取第一个节和第一个表格
    let section = doc.Sections.get_Item(0);
    let table = section.Tables.get_Item(0);

    // 在第 0 列之前添加一个空白列
    let columnIndex1 = 0;
    AddColumn(table, columnIndex1);

    // 删除第 2 列
    let columnIndex2 = 2;
    RemoveColumn(table, columnIndex2);

    // 定义输出文件名
    const outputFileName = "AddOrRemoveColumn_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={AddOrRemoveColumn}>
        Generate
      </button>
    </div>
  );
}

export default App;

上述代码在表格第 0 列插入了一个空白列,同时删除了第 2 列,生成的 Word 文档效果如下:

在 Word 表格中添加和删除列


常见问题

删除行后表格数据错乱

原因:表格中存在合并单元格时,直接按索引删除行会导致后续行的索引偏移,破坏合并结构。

解决:删除前检查目标行是否参与了合并。如果行中有合并单元格,可先解除合并再执行删除操作:

// 删除前检查单元格的合并状态
let cell = table.Rows.get_Item(rowIndex).Cells.get_Item(0);
let verticalMerge = cell.CellFormat.VerticalMerge;
if (verticalMerge === wasmModule.CellMerge.None) {
  table.Rows.RemoveAt(rowIndex);
} else {
  console.warn("该行包含合并单元格,建议先处理合并状态");
}

添加列后新列在文档中显示为空白

原因:AddColumn 函数仅插入了空白 TableCell 对象,未向新单元格中添加段落和文字内容。

解决:插入单元格后,遍历新列并添加段落和文字:

function AddColumn(table, columnIndex) {
  let wasmModule = window.wasmModule.spiredoc;
  for (let r = 0; r < table.Rows.Count; r++) {
    let addCell = new wasmModule.TableCell(table.Document);
    let paragraph = addCell.AddParagraph();
    paragraph.AppendText("New Column");
    table.Rows.get_Item(r).Cells.Insert(columnIndex, addCell);
  }
}

删除列时索引越界

原因:传入的列索引超过了当前表格的最大列数,或者表格各行的列数不一致。

解决:删除前获取表格第一行的列数作为参考,确保索引不超出范围:

let maxColIndex = table.Rows.get_Item(0).Cells.Count - 1;
if (columnIndex <= maxColIndex) {
  RemoveColumn(table, columnIndex);
}

获取免费许可证

如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。

在 Web 应用开发中,合并 Word 文档是一个常见需求——将多份合同附件拼接成一份完整文档、在报告末尾追加补充内容、或者将多个章节的文档合并输出。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成文档合并,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

本文介绍两个核心功能点:

有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。


以节为单位合并

以节为单位合并的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和两个 Word 文档载入 WASM 虚拟文件系统;然后实例化 Document 加载目标文档和源文档,遍历源文档的所有节(Section),通过 Sections.Add(section.Clone()) 将每个节克隆并追加到目标文档末尾;最后从 VFS 读取合并后的文件,封装为 Blob 后触发浏览器下载。这种方式会保留源文档的独立分节结构,每个节在合并后文档中从新的一页开始。

function App() {
  const mergeBySection = async () => {
    // 获取 Spire.Doc WASM 模块
    const docModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    const inputFileName1 = 'Template_Docx_1.docx';
    await window.spire.FetchFileToVFS(inputFileName1, '', `${process.env.PUBLIC_URL}data/`);
    const inputFileName2 = 'Template_Docx_2.docx';
    await window.spire.FetchFileToVFS(inputFileName2, '', `${process.env.PUBLIC_URL}data/`);

    // 加载目标文档
    const TarDoc = new docModule.Document();
    TarDoc.LoadFromFile(inputFileName1);

    // 加载源文档
    const SouDoc = new docModule.Document();
    SouDoc.LoadFromFile(inputFileName2);

    // 将源文档的所有节克隆并追加到目标文档
    for (let i = 0; i < SouDoc.Sections.Count; i++) {
      let section = SouDoc.Sections.get_Item(i);
      TarDoc.Sections.Add(section.Clone());
    }

    // 定义输出文件名
    const outputFileName = 'MergeBySection_out.docx';

    // 保存合并后的文档
    TarDoc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // 从 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);

    // 释放资源
    TarDoc.Dispose();
    SouDoc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Merge Word By Section</h1>
      <button onClick={mergeBySection}>
        Generate
      </button>
    </div>
  );
}

export default App;

以节为单位合并后,源文档的每个节在目标文档中独立成页

以节为单位合并后,源文档的每个节在目标文档中独立成页


同页合并

同页合并与以节合并不同,它不会为源文档创建新的节,而是将源文档中的段落、表格、图片等文档元素逐一克隆并追加到目标文档的同一个节中。核心流程同样分为三个阶段:首先通过 FetchFileToVFS 将字体文件和两个 Word 文档载入 WASM 虚拟文件系统;然后加载目标文档和源文档,通过双层循环遍历源文档每个节下的 Body.ChildObjects,将每个文档元素通过 ChildObjects.Add(obj.Clone()) 追加到目标文档第一个节的 ChildObjects 集合末尾;最后从 VFS 读取合并后的文件,封装为 Blob 后触发浏览器下载。这种方式使内容在页面上连续排列,不会产生新的分节符。

function App() {
  const mergeOnSamePage = async () => {
    // 获取 Spire.Doc WASM 模块
    const docModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    const inputFileName1 = 'Template_Docx_1.docx';
    await window.spire.FetchFileToVFS(inputFileName1, '', `${process.env.PUBLIC_URL}data/`);
    const inputFileName2 = 'Template_Docx_2.docx';
    await window.spire.FetchFileToVFS(inputFileName2, '', `${process.env.PUBLIC_URL}data/`);

    // 加载目标文档
    const destinationDocument = new docModule.Document();
    destinationDocument.LoadFromFile(inputFileName1);

    let count = destinationDocument.Sections.Count;

    // 加载源文档
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName2);


    // 遍历源文档的所有内容元素,克隆到目标文档的第一个节中
    for (let i = 0; i < doc.Sections.Count; i++) {
      let section = doc.Sections.get_Item(i);
      for (let j = 0; j < section.Body.ChildObjects.Count; j++) {
        let obj = section.Body.ChildObjects.get_Item(j);
        destinationDocument.Sections.get_Item(count-1).Body.ChildObjects.Add(obj.Clone());
      }
    }

    // 定义输出文件名
    const outputFileName = 'MergeOnSamePage_out.docx';

    // 保存合并后的文档
    destinationDocument.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // 从 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);

    // 释放资源
    destinationDocument.Dispose();
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Merge On Same Page</h1>
      <button onClick={mergeOnSamePage}>
        Generate
      </button>
    </div>
  );
}

export default App;

同页合并后,源文档的内容连续追加到目标文档同一节中,不产生分页

同页合并后,源文档的内容连续追加到目标文档同一节中,不产生分页


常见问题

合并后文档格式错乱

原因:源文档与目标文档的样式定义(如字体、字号、段落样式)不一致,合并后样式冲突导致显示异常。以节合并时,每个节保留自身的样式设置,而跨节的样式引用可能丢失。

解决:合并前通过 KeepSameFormat 属性保持源文档的原始格式:

srcDoc.KeepSameFormat = true;

合并后文档无法打开

原因:输出文件的 MIME 类型或文件扩展名错误,导致浏览器或 Word 无法正确识别文件格式。或者在保存文档后未正确释放资源,导致 VFS 文件句柄未释放。

解决:确保使用正确的 DOCX MIME 类型:

const blob = new Blob([data], {
  type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});

同时确保在每次合并操作后调用 Dispose() 释放 Document 对象,避免 WASM 内存泄漏。


获取免费许可证

如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。

表格是 Word 文档中组织和展示数据的核心元素,合理的表格排版直接影响文档的可读性与专业性。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成表格的自动调整操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

本文介绍三个核心功能点:

有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。


根据内容自动调整表格

根据内容自动调整表格的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标文档载入 WASM 虚拟文件系统;然后实例化 Document 加载文档,获取目标表格后调用 AutoFit 方法并传入 AutoFitToContents 参数;最后保存文档并从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。

function App() {
  const autoFitToContents = 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 inputFileName = 'TableSample.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 table = section.Tables.get_Item(0);

    // 根据单元格内容自动调整列宽
    table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToContents);

    // 定义输出文件名
    const outputFileName = "AutoFitToContents_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>自动适应内容。</h1>
      <button onClick={autoFitToContents}>
        Generate
      </button>
    </div>
  );
}

export default App;

通过 AutoFitToContents 模式调整后,表格各列的宽度会根据单元格内实际内容的长度自动收缩,使表格紧凑且无多余空白。

根据内容自动调整表格后生成的 Word 文档输出


根据窗口自动调整表格

根据窗口自动调整表格可以让表格宽度自适应页面宽度,适合需要表格撑满页面宽度的场景。

function App() {
  const autoFitToWindow = 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 inputFileName = 'TableSample.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 table = section.Tables.get_Item(0);

    // 根据页面宽度自动调整表格
    table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToWindow);

    // 定义输出文件名
    const outputFileName = "AutoFitToWindow_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>自动适应窗口。</h1>
      <button onClick={autoFitToWindow}>
        Generate
      </button>
    </div>
  );
}

export default App;

通过 AutoFitToWindow 模式调整后,表格宽度会扩展至与页面宽度一致,各列按比例均匀分布。

根据窗口自动调整表格后生成的 Word 文档输出


固定列宽

当表格已有精心设计的列宽,不希望因内容增减而发生变化时,可以使用固定列宽模式,禁止 Word 自动调整列的大小。

function App() {
  const fixedColumnWidths = 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 inputFileName = 'TableSample.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 table = section.Tables.get_Item(0);

    // 固定表格列宽,禁止自动调整
    table.AutoFit(docModule.AutoFitBehaviorType.FixedColumnWidths);

    // 定义输出文件名
    const outputFileName = "FixedColumnWidths_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>固定列宽。</h1>
      <button onClick={fixedColumnWidths}>
        Generate
      </button>
    </div>
  );
}

export default App;

使用固定列宽模式后,无论单元格内容如何变化,表格的列宽都会保持原有尺寸,确保排版的一致性。

固定列宽后生成的 Word 文档输出


常见问题

AutoFit 方法调用后表格排版未发生变化

原因:调用 AutoFit 方法时传入的参数类型不正确,或者表格被锁定不允许调整布局。

解决:确保使用正确的 AutoFitBehaviorType 枚举值:

// 根据内容调整
table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToContents);

// 根据窗口调整
table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToWindow);

// 固定列宽
table.AutoFit(docModule.AutoFitBehaviorType.FixedColumnWidths);

获取表格时提示索引越界

原因:文档中不存在指定索引的节或表格,通常是因为索引从 0 开始但文档中实际没有对应的对象。

解决:先检查节和表格的数量,再访问对应索引:

if (document.Sections.Count > 0 && document.Sections.get_Item(0).Tables.Count > 0) {
  let section = document.Sections.get_Item(0);
  let table = section.Tables.get_Item(0);
  table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToContents);
}

获取免费许可证

如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。

在 Word 文档中创建表格是日常办公开发中最常见的需求之一,无论是数据报表、产品清单还是统计分析,表格都能以结构化的方式清晰呈现信息。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成 Word 文档的创建与编辑,通过虚拟文件系统(VFS)管理字体和文件资源,无需后端服务支持。

本文介绍三个核心功能点:

有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。


创建带格式的数据表格

在实际开发中,最常见的场景是将后端返回的数据以表格形式写入 Word 文档。核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件载入 WASM 虚拟文件系统;然后实例化 Document 创建文档,通过 AddTable 添加表格并填充数据,设置表头、对齐方式、交替行颜色等格式;最后将文档保存并从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。

function addTable(section) {
  let docModule = window.wasmModule.spiredoc;
  let header = ["名称", "首都", "大洲", "面积", "人口"];
  let data =
  [
    ["阿根廷", "布宜诺斯艾利斯", "南美洲", "2777815", "32300003"],
    ["玻利维亚", "拉巴斯", "南美洲", "1098575", "7300000"],
    ["巴西", "巴西利亚", "南美洲", "8511196", "150400000"],
    ["加拿大", "渥太华", "北美洲", "9976147", "26500000"],
    ["智利", "圣地亚哥", "南美洲", "756943", "13200000"],
    ["哥伦比亚", "波哥大", "南美洲", "1138907", "33000000"],
    ["古巴", "哈瓦那", "北美洲", "114524", "10600000"],
    ["厄瓜多尔", "基多", "南美洲", "455502", "10600000"],
    ["萨尔瓦多", "圣萨尔瓦多", "北美洲", "20865", "5300000"],
    ["圭亚那", "乔治敦", "南美洲", "214969", "800000"],
    ["牙买加", "金斯敦", "北美洲", "11424", "2500000"],
    ["墨西哥", "墨西哥城", "北美洲", "1967180", "88600000"],
    ["尼加拉瓜", "马那瓜", "北美洲", "139000", "3900000"],
    ["巴拉圭", "亚松森", "南美洲", "406576", "4660000"],
    ["秘鲁", "利马", "南美洲", "1285215", "21600000"],
    ["美利坚合众国", "华盛顿", "北美洲", "9363130", "249200000"],
    ["乌拉圭", "蒙得维的亚", "南美洲", "176140", "3002000"],
    ["委内瑞拉", "加拉加斯", "南美洲", "912047", "19700000"]
  ];
  let table = section.AddTable({ showBorder: true });
  table.ResetCells(data.length + 1, header.length);

  // 设置表头行
  let row = table.Rows.get_Item(0);
  row.IsHeader = true;
  row.Height = 20;
  row.HeightType = docModule.TableRowHeightType.Exactly;
  for (let i = 0; i < row.Cells.Count; i++) {
    row.Cells.get_Item(i).CellFormat.Shading.BackgroundPatternColor = docModule.Color.get_Gray();
  }

  for (let i = 0; i < header.length; i++) {
    row.Cells.get_Item(i).CellFormat.VerticalAlignment = docModule.VerticalAlignment.Middle;
    let p = row.Cells.get_Item(i).AddParagraph();
    p.Format.HorizontalAlignment = docModule.HorizontalAlignment.Center;
    let txtRange = p.AppendText(header[i]);
    txtRange.CharacterFormat.Bold = true;
  }

  // 填充数据行并设置交替行颜色
  for (let r = 0; r < data.length; r++) {
    let dataRow = table.Rows.get_Item(r + 1);
    dataRow.Height = 20;
    dataRow.HeightType = docModule.TableRowHeightType.Exactly;
    for (let i = 0; i < dataRow.Cells.Count; i++) {
      dataRow.Cells.get_Item(i).CellFormat.Shading.BackgroundPatternColor = docModule.Color.Empty();
    }
    for (let c = 0; c < data[r].length; c++) {
      dataRow.Cells.get_Item(c).CellFormat.VerticalAlignment = docModule.VerticalAlignment.Middle;
      dataRow.Cells.get_Item(c).AddParagraph().AppendText(data[r][c]);
    }
  }

  // 偶数行设置浅蓝色背景
  for (let j = 1; j < table.Rows.Count; j++) {
    if (j % 2 == 0) {
      let row2 = table.Rows.get_Item(j);
      for (let f = 0; f < row2.Cells.Count; f++) {
        row2.Cells.get_Item(f).CellFormat.Shading.BackgroundPatternColor = docModule.Color.get_LightBlue();
      }
    }
  }
}

function App() {
  const CreateTable = async () => {
    // 获取 Spire.Doc WASM 模块
    const docModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // 将字体和文档文件载入 VFS
    await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
 
    // 创建空白文档
    let doc = new docModule.Document();
    let section = doc.AddSection();

    // 添加表格
    addTable(section);

    // 定义输出文件名
    const outputFileName = "CreateTable_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={CreateTable}>
        Generate
      </button>
    </div>
  );
}

export default App;

上述代码创建了一个包含 18 行国家数据的表格,表头使用灰色背景和加粗字体居中对齐,数据区域使用交替行颜色(偶数行浅蓝背景)提升可读性,生成的 Word 文档效果如下:

创建带格式的数据表格生成的 Word 输出


通过 HTML 创建表格

在 Web 开发中,HTML 表格是展示数据的通用格式。Spire.Doc 提供了 AppendHTML 方法,可以直接将 HTML 字符串解析为 Word 文档中的表格,极大简化了从 Web 内容到 Word 文档的转换流程。这对于需要将网页上的表格数据导出为 Word 文档的场景尤为实用。

function App() {
  const CreateTableFromHTML = 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/`);
 
    // HTML 字符串
    let HTML = "<table border='2px'>" +
      "<tr>" +
      "<td>第1行,第1列</td>" +
      "<td>第1行,第2列</td>" +
      "</tr>" +
      "<tr>" +
      "<td>第2行,第1列</td>" +
      "<td>第2行,第2列</td>" +
      "</tr>" +
      "</table>";

    // 创建 Word 文档
    let doc = new docModule.Document();

    // 添加节
    let section = doc.AddSection();

    // 添加段落并追加 HTML 字符串
    section.AddParagraph().AppendHTML(HTML);

    // 定义输出文件名
    const outputFileName = "CreateTableFromHTML_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>点击以下按钮,使用 HTML 在 Word 文档中创建表格。</h1>
      <button onClick={CreateTableFromHTML}>
        Generate
      </button>
    </div>
  );
}

export default App;

通过 AppendHTML 转换后生成的 Word 表格完美保留了 HTML 中定义的边框样式和单元格结构:

通过 HTML 创建的表格在 Word 中的输出


创建嵌套表格

嵌套表格是指在一个表格的单元格中再插入一个子表格,常用于实现复杂的文档布局,例如在产品目录中,主表格展示产品名称和描述,描述单元格内再嵌入一个包含规格参数(编号、项目、价格)的子表格。Spire.Doc 通过 Cell.AddTable 方法轻松实现嵌套表格的创建。

function App() {
  const CreateNestedTable = 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/`);
 
    // 创建新文档
    let doc = new docModule.Document();
    let section = doc.AddSection();

    // 添加主表格(2行2列)
    let table = section.AddTable({ showBorder: true });
    table.ResetCells(1, 2);

    // 设置列宽
    table.Rows.get_Item(0).Cells.get_Item(0).SetCellWidth(70, docModule.CellWidthType.Point);
    table.Rows.get_Item(0).Cells.get_Item(1).SetCellWidth(150, docModule.CellWidthType.Point);
    table.Rows.get_Item(0).Height=150;
    table.AutoFit(docModule.AutoFitBehaviorType.AutoFitToWindow);

    // 向单元格插入内容
    table.Rows.get_Item(0).Cells.get_Item(0).AddParagraph().AppendText("Spire.Doc for JavaScript");
    let text = "Spire.Doc for JavaScript 是一个专业的 Word " +
      "JavaScript 库,专为开发人员设计,可在任何 JavaScript " +
      "平台上快速、高质量地创建、读取、写入、转换和打印 Word 文档文件。";
    table.Rows.get_Item(0).Cells.get_Item(1).AddParagraph().AppendText(text);
    
    table.Rows.get_Item(0).Cells.get_Item(1).AddParagraph();

    // 在(第一行第二列)单元格中添加嵌套表格
    let nestedTable = table.Rows.get_Item(0).Cells.get_Item(1).AddTable({ showBorder: true });
    nestedTable.ResetCells(5, 2);
    nestedTable.AutoFit(docModule.AutoFitBehaviorType.AutoFitToContents);

    // 填充嵌套表格内容
    nestedTable.Rows.get_Item(0).Cells.get_Item(0).AddParagraph().AppendText("功能模块");
    nestedTable.Rows.get_Item(0).Cells.get_Item(1).AddParagraph().AppendText("典型应用场景");

    nestedTable.Rows.get_Item(1).Cells.get_Item(0).AddParagraph().AppendText("文档生成");
    nestedTable.Rows.get_Item(2).Cells.get_Item(0).AddParagraph().AppendText("格式转换");
    nestedTable.Rows.get_Item(3).Cells.get_Item(0).AddParagraph().AppendText("内容编辑");
    nestedTable.Rows.get_Item(4).Cells.get_Item(0).AddParagraph().AppendText("打印服务");

    nestedTable.Rows.get_Item(1).Cells.get_Item(1).AddParagraph().AppendText("动态生成合同、发票、数据报告及邮件合并");
    nestedTable.Rows.get_Item(2).Cells.get_Item(1).AddParagraph().AppendText("Word 与 PDF、HTML、RTF、XML、图片等格式互转");
    nestedTable.Rows.get_Item(3).Cells.get_Item(1).AddParagraph().AppendText("提取文本/图片、添加水印、修订追踪与表单填写");
    nestedTable.Rows.get_Item(4).Cells.get_Item(1).AddParagraph().AppendText("后台静默打印、自定义纸张与页面设置");

    // 定义输出文件名
    const outputFileName = "CreateNestedTable_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={CreateNestedTable}>
        Generate
      </button>
    </div>
  );
}

export default App;

嵌套表格生成的效果:主表格左侧显示产品名称,右侧的描述单元格内嵌了一个包含编号、项目和价格的规格明细表:

嵌套表格在 Word 中的输出


常见问题

下载的文件无法打开或提示损坏

原因:创建 Blob 时 MIME 类型设置错误,浏览器无法正确识别文件格式。

解决:确保使用准确的 Word 文档 MIME 类型:

const modifiedFile = new Blob([fileArray], {
  type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});

表格边框不显示或样式不一致

原因:通过 AddTable({ showBorder: true }) 添加表格时边框默认启用,但通过 Table 构造函数直接创建的表格需要手动设置边框样式。

解决:使用构造函数创建表格时,显式设置边框类型:

let table = new docModule.Table(doc, false);
table.Format.Borders.BorderType = docModule.BorderStyle.Single;

获取免费许可证

如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。

在文档管理系统中,拆分 Word 文档是一个常见需求——将合并后的多章节文档按分节符拆分为独立文件,或将长文档按分页符分割为多个短文档。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成文档拆分,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

本文介绍两个核心功能点:

有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。


按分节符拆分

Word 文档中的分节符(Section Break)用于分隔不同章节或版面布局,每个节可以拥有独立的页眉页脚、页码格式和页面设置。按分节符拆分是最常用、最稳定的拆分方式,适用于将合并后的多章节文档还原为独立文件。

核心流程分为三个阶段:首先将字体文件和目标 Word 文件载入 WASM 虚拟文件系统;然后实例化 Document 加载文件,遍历所有节,通过 Section.Clone() 将每个节克隆到新的 Document 对象中;最后将拆分后的文件打包为 ZIP 触发浏览器下载。

function App() {
  const splitBySectionBreak = async () => {
    // 获取 Spire.Doc WASM 模块
    const docModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }


    const inputFileName = 'Template_Docx_4.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 创建输出目录
    let outputDir = 'output/';
    window.dotnetRuntime.Module.FS.mkdirTree(outputDir);

    // 加载文档
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // 遍历每个节,克隆为独立文档
    for (let i = 0; i < doc.Sections.Count; i++) {
      const newWord = new docModule.Document();
      newWord.Sections.Add(doc.Sections.get_Item(i).Clone());
      newWord.SaveToFile({
        fileName: outputDir + `Section-${i}.docx`,
        fileFormat: docModule.FileFormat.Docx2013
      });
      newWord.Dispose();
    }
    doc.Dispose();

    // 从 VFS 读取输出文件,打包为 ZIP 下载
    const JSZip = require('jszip');
    const zip = new JSZip();
    let items = window.dotnetRuntime.Module.FS.readdir(outputDir);
    items = items.filter(item => item !== '.' && item !== '..');
    for (const item of items) {
      const fileData = window.dotnetRuntime.Module.FS.readFile(outputDir + item);
      zip.file(item, fileData);
    }
    const zipBlob = await zip.generateAsync({ type: 'blob' });
    const url = URL.createObjectURL(zipBlob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'SplitBySectionBreak.zip';
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Split Word Document By Section Break</h1>
      <button onClick={splitBySectionBreak}>Generate</button>
    </div>
  );
}

export default App;

按分节符拆分后生成的独立 Word 文档

按分节符拆分后生成的独立 Word 文档


按分页符拆分

分页符(Page Break)是文档中手动插入或自动生成的分页标记。按分页符拆分适用于将长篇文档按页分割,每页内容保存为一个独立文档,常用于报告分页、合同条款拆分等场景。

与按分节符拆分不同,分页符位于段落内部的子对象层级,需要逐层遍历文档的节(Section)、段落(Paragraph)以及段落子对象,检测 Break 类型为 PageBreak 的元素。拆分时还需通过 CloneDefaultStyleTo、CloneThemesTo、CloneCompatibilityTo 等方法克隆原文档的样式和主题,确保拆分后的文档格式完整。

function App() {
  const splitByPageBreak = async () => {
    // 获取 Spire.Doc WASM 模块
    const docModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    const inputFileName = 'SplitWordFileByPageBreak.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 创建输出目录
    let outputDir = 'output/';
    window.dotnetRuntime.Module.FS.mkdirTree(outputDir);

    // 加载文档
    const original = new docModule.Document();
    original.LoadFromFile(inputFileName);

    // 创建新文档,克隆样式和主题
    let newWord = new docModule.Document();
    let section = newWord.AddSection();
    original.CloneDefaultStyleTo(newWord);
    original.CloneThemesTo(newWord);
    original.CloneCompatibilityTo(newWord);

    let index = 0;

    // 遍历所有节
    for (let i = 0; i < original.Sections.Count; i++) {
      let sec = original.Sections.get_Item(i);

      // 遍历节中的所有子对象(段落、表格等)
      for (let j = 0; j < sec.Body.ChildObjects.Count; j++) {
        let obj = sec.Body.ChildObjects.get_Item(j);

        if (obj instanceof docModule.Paragraph) {
          let para = obj;
          sec.CloneSectionPropertiesTo(section);
          section.Body.ChildObjects.Add(para.Clone());

          // 检测段落中的分页符
          for (let k = 0; k < para.ChildObjects.Count; k++) {
            let parobj = para.ChildObjects.get_Item(k);
            if (parobj instanceof docModule.Break &&
                parobj.BreakType === docModule.BreakType.PageBreak) {

              let breakIndex = para.ChildObjects.IndexOf(parobj);

              // 从段落中移除分页符
              section.Body.LastParagraph.ChildObjects.RemoveAt(breakIndex);

              // 保存当前文档
              newWord.SaveToFile({
                fileName: outputDir + `Page-${index}.docx`,
                fileFormat: docModule.FileFormat.Docx2013
              });
              index++;

              // 创建新文档继续
              newWord = new docModule.Document();
              section = newWord.AddSection();
              original.CloneDefaultStyleTo(newWord);
              original.CloneThemesTo(newWord);
              original.CloneCompatibilityTo(newWord);
              sec.CloneSectionPropertiesTo(section);

              // 处理分页符后的剩余内容
              section.Body.ChildObjects.Add(para.Clone());
              if (section.Paragraphs.get_Item(0).ChildObjects.Count === 0) {
                section.Body.ChildObjects.RemoveAt(0);
              } else {
                while (breakIndex >= 0) {
                  section.Paragraphs.get_Item(0).ChildObjects.RemoveAt(breakIndex);
                  breakIndex--;
                }
              }
            }
          }
        }

        if (obj instanceof docModule.Table) {
          section.Body.ChildObjects.Add(obj.Clone());
        }
      }
    }

    // 保存最后一份文档
    newWord.SaveToFile({
      fileName: outputDir + `Page-${index}.docx`,
      fileFormat: docModule.FileFormat.Docx2013
    });

    original.Dispose();
    newWord.Dispose();

    // 打包为 ZIP 下载
    const JSZip = require('jszip');
    const zip = new JSZip();
    let items = window.dotnetRuntime.Module.FS.readdir(outputDir);
    items = items.filter(item => item !== '.' && item !== '..');
    for (const item of items) {
      const fileData = window.dotnetRuntime.Module.FS.readFile(outputDir + item);
      zip.file(item, fileData);
    }
    const zipBlob = await zip.generateAsync({ type: 'blob' });
    const url = URL.createObjectURL(zipBlob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'SplitByPageBreak.zip';
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Split Word Document By Page Break</h1>
      <button onClick={splitByPageBreak}>Generate</button>
    </div>
  );
}

export default App;

按分页符拆分后生成的 Word 文档

按分页符拆分后生成的 Word 文档


常见问题

拆分后的文档格式与原文档不一致

原因:按分页符拆分时,仅克隆了段落内容,未同步克隆原文档的样式、主题和节属性,导致拆分后的文档丢失字体、颜色、页面设置等格式信息。

解决:创建新文档后,使用以下方法克隆原文档的样式和主题:

original.CloneDefaultStyleTo(newWord);
original.CloneThemesTo(newWord);
original.CloneCompatibilityTo(newWord);
sec.CloneSectionPropertiesTo(section);

分页符检测不到

原因:分页符位于段落子对象中,通过 BreakType 枚举判断。若遍历层级不正确,或未使用 instanceof 判断对象类型,会导致无法正确识别分页符。

解决:确保按 Paragraph.ChildObjects → instanceof Break → BreakType == BreakType.PageBreak 的顺序检测:

for (let k = 0; k < para.ChildObjects.Count; k++) {
  let parobj = para.ChildObjects.get_Item(k);
  if (parobj instanceof docModule.Break &&
      parobj.BreakType === docModule.BreakType.PageBreak) {
    // 找到分页符
  }
}

获取免费许可证

如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。

批注是 Word 文档协作中不可或缺的功能,广泛用于审阅、校对和团队讨论场景。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成批注的读写操作,通过虚拟文件系统(VFS)管理文档文件,无需后端服务支持,也无需安装 Microsoft Word。

本文介绍三个核心功能点:

有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。


添加批注到指定文本

在实际文档审阅场景中,我们经常需要针对文档中的某段特定文字添加批注。实现思路是:首先通过 FindString 方法搜索定位目标文本,然后在该文本的前后分别插入 CommentMarkStart 和 CommentMarkEnd 标记,最后将 Comment 对象添加到段落中,从而将批注与指定文本关联起来。

import React from 'react';

function App() {
  const AddCommentForSpecificText = async () => {
    // 获取 Spire.Doc WASM 模块
    const docModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }
   
    // 将目标 Word 文档载入 VFS
    const inputFileName = "CommentTemplate.docx";
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // 创建 Document 实例并加载文档
    let doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // 调用自定义函数,为指定文本添加批注
    InsertComments(doc, "办公室", docModule);

    // 保存文档
    const outputFileName = "AddCommentForSpecificText.docx";
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
   
    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });

    // 释放资源
    doc.Dispose();

    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  // 自定义函数:为指定关键词添加批注
  function InsertComments(doc, keystring, wasmModule) {
      // 在文档中查找目标字符串
      let find = doc.FindString(keystring, false, true);

      // 创建批注起始标记和结束标记
      let commentMarkStart = new wasmModule.CommentMark(doc, 1, wasmModule.CommentMarkType.CommentStart);
      let commentMarkEnd = new wasmModule.CommentMark(doc, 1, wasmModule.CommentMarkType.CommentEnd);

      // 创建批注对象,设置内容和作者
      let comment = new wasmModule.Comment(doc);
      comment.Body.AddParagraph().Text = "测试评论";
      comment.Format.Author = "Administrator";

      // 获取查找到的文本范围及其所在段落
      let range = find.GetAsOneRange();
      let para = range.OwnerParagraph;

      // 获取文本范围在段落中的索引位置
      let index = para.ChildObjects.IndexOf(range);

      // 将批注添加到段落
      para.ChildObjects.Add(comment);

      // 在目标文本前后插入批注起始和结束标记
      para.ChildObjects.Insert(index, commentMarkStart);
      para.ChildObjects.Insert(index + 2, commentMarkEnd);
  }

  return (
      <div style={{ textAlign: 'center', height: '300px' }}>
        <h1>为特定文本添加评论。</h1>
        <button onClick={AddCommentForSpecificText}>
          Generate
        </button>
      </div>
    );
};

export default App;

通过上述代码,可以在文档中搜索到指定关键词后,在其位置插入批注标记,从而实现将批注精准地添加到特定文本上。

为指定文本添加批注的效果


提取文档中的批注

当文档经过多人审阅后,往往包含大量批注。批量提取这些批注内容,便于汇总审阅意见或进行后续处理。Spire.Doc 提供了 Comments 集合,我们可以遍历该集合逐一读取每条批注的正文内容,然后导出为文本文件。

import React from 'react';

function App() {
  const ExtractComment = async () => {
    // 获取 Spire.Doc WASM 模块
    const docModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }
   
    // 将目标 Word 文档载入 VFS
    const inputFileName = "CommentSample.docx";
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // 创建 Document 实例并加载文档
    let doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // 遍历所有批注,提取文本内容
    let SB = [];
    for (let i = 0; i < doc.Comments.Count; i++) {
        let comment = doc.Comments.get_Item(i);
        for (let j = 0; j < comment.Body.Paragraphs.Count; j++) {
            let p = comment.Body.Paragraphs.get_Item(j);
            SB.push(p.Text + "\n");
        }
    }

    // 将提取的批注内容保存为文本文件
    const outputFileName = 'ExtractComment.txt';

    const blob = new Blob([SB.toString()], { type: "text/plain;charset=utf-8" });

    // 释放资源
    doc.Dispose();

    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={ExtractComment}>
          Generate
        </button>
      </div>
    );
};

export default App;

通过 doc.Comments 集合可以访问文档中的所有批注,每条批注的正文内容通过 comment.Body.Paragraphs 获取。提取后的内容可以保存为纯文本文件,方便汇总查阅或导入其他系统。

提取文档批注的效果


回复批注与修改批注

在团队协作场景中,对已有批注进行回复和内容修正是常见的需求。Spire.Doc 支持通过 ReplyToComment 方法为批注添加回复,同时也支持修改批注内容或删除不需要的批注。以下示例演示了如何获取文档中的第一个批注,为其添加一条带图片的回复内容。

import React from 'react';

function App() {
  const ReplyToComment = async () => {
    // 获取 Spire.Doc WASM 模块
    const docModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }
      
    // 将目标文档和图片载入 VFS
    const inputFileName = "Comment.docx";
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    const imageFile = "logo.png";
    await window.spire.FetchFileToVFS(imageFile, '', `${process.env.PUBLIC_URL}/data/`);

    // 创建 Document 实例并加载文档
    let doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // 获取文档中的第一个批注
    let comment1 = doc.Comments.get_Item(0);

    // 创建回复批注,设置作者和内容
    let replyComment1 = new docModule.Comment(doc);
    replyComment1.Format.Author = "E-iceblue";
    replyComment1.Body.AddParagraph().AppendText("Spire.Doc is a professional Word library for operating Word documents.");

    // 将回复批注添加到原始批注
    comment1.ReplyToComment(replyComment1);

    // 加载图片并插入到回复批注中
    let docPicture = new docModule.DocPicture(doc);
    docPicture.LoadImage(imageFile);
    replyComment1.Body.Paragraphs.get_Item(0).ChildObjects.Add(docPicture);

    // 保存文档
    const outputFileName = "ReplyToComment.docx";
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx });

    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });

    // 释放资源
    doc.Dispose();

    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={ReplyToComment}>
          Generate
        </button>
      </div>
    );
};

export default App;

除了回复批注外,还可以通过 Comments.RemoveAt(index) 方法删除指定批注,或通过 Body.Paragraphs.get_Item(0).Replace(...) 修改批注中的文本内容:

// 修改第一个批注的内容
doc.Comments.get_Item(0).Body.Paragraphs.get_Item(0)
    .Replace({ given: "原文本", replace: "修改后的文本", caseSensitive: false, wholeWord: false });

// 删除第二个批注
doc.Comments.RemoveAt(1);

回复批注的效果


常见问题

批注添加后无法在 Word 中正常显示

原因:批注标记 CommentMarkStart 和 CommentMarkEnd 的插入位置不正确,或 Comment 对象未正确添加到段落中。批注的起始标记必须位于被批注文本之前,结束标记位于被批注文本之后,且 Comment 对象本身也需要添加到同一段落中。

解决:确保按正确顺序操作——先获取目标文本的索引位置,然后依次插入 CommentMarkStart、将 Comment 添加到段落末尾,最后在文本后插入 CommentMarkEnd。

提取批注时输出为空

原因:文档中可能不存在批注,或者批注内容存储在嵌套结构中。另外,如果文档加载时路径错误或文件未成功载入 VFS,也会导致无法读取批注。

解决:提取前先检查 doc.Comments.Count 是否大于 0,确认文档中包含批注。同时确保通过 FetchFileToVFS 正确加载了文档文件:

await window.spire.FetchFileToVFS(
  'CommentSample.docx', '', `${process.env.PUBLIC_URL}/static/data/`
);

回复批注后原批注丢失

原因:ReplyToComment 方法的调用对象是原始批注,如果误将回复批注作为调用对象,或者调用了多次导致引用混乱,可能导致批注结构异常。

解决:确保始终以文档中已有的原始批注对象调用 ReplyToComment 方法,新创建的 Comment 对象作为参数传入:

// 正确:原始批注调用 ReplyToComment,新批注作为参数
existingComment.ReplyToComment(replyComment);

获取免费许可证

如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。

Word 文档变量(DocVariable)是一种轻量级的字段机制,允许在文档中定义占位符,并通过代码动态填充或更新内容。这种机制在模板化文档生成、批量信函制作和自动化报表输出等场景中非常实用。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成变量的全部操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

本文介绍三个核心功能点:

有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。


添加文档变量

添加文档变量的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件载入 WASM 虚拟文件系统;然后实例化 Document,在段落中插入 DocVariable 域字段,并通过 Variables.Add 方法为变量赋值;最后保存文档并从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。

import React from 'react';

function App() {
  const addVariables = async () => {
    // 获取 Spire.Doc WASM 模块
    const docModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }
      
    // 创建文档对象
    const doc = new docModule.Document();

    // 添加一个节
    const section = doc.AddSection();

    // 添加一个段落
    const paragraph = section.AddParagraph();

    // 在段落中插入一个 DocVariable 域字段
    paragraph.AppendField("A1", docModule.FieldType.FieldDocVariable);

    // 为 DocVariable 域字段对应的变量赋值
    doc.Variables.Add("A1", "12");

    // 更新域以显示变量值
    doc.IsUpdateFields = true;

    // 定义输出文件名
    const outputFileName = "AddVariables_out.docx";

    // 保存文档
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

    // 释放资源
    doc.Dispose();

    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={addVariables}>
          Generate
        </button>
      </div>
    );
};

export default App;

通过 Variables.Add 方法添加变量后,文档中的 DocVariable 域字段会被替换为对应的变量值。

添加文档变量后生成的 Word 文档输出


检索文档变量

对于已包含变量的 Word 模板文档,可以通过索引或变量名称检索变量信息。Spire.Doc 提供了多种检索方式:通过索引获取变量名称和值,或直接通过变量名获取对应的值。

import React from 'react';

function App() {
  const retrieveVariables = async () => {
    // 获取 Spire.Doc WASM 模块
    const docModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    const inputFileName = 'Template_Docx_6.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // 加载文档
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // 通过索引获取变量名称和值
    const nameByIndex = doc.Variables.GetNameByIndex(0);
    const valueByIndex = doc.Variables.GetValueByIndex(0);

    // 通过变量名直接获取值
    const valueByName = doc.Variables.get_Item("A1");

    // 遍历所有变量
    let stringBuilder = [];
    stringBuilder.push("This document has following variables:\n");
    for (let i = 0; i < doc.Variables.Count; i++) {
        let name = doc.Variables.GetNameByIndex(i);
        let value = doc.Variables.GetValueByIndex(i);
        stringBuilder.push("Name: " + name + ", " + "Value: " + value + "\n");
    }

    // 将结果写入文本文件
    const outputFileName = "RetrieveVariables_out.txt";
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, stringBuilder.join(""));

    // 从 VFS 读取生成的文件,触发下载
    const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([modifiedFileArray], { type: 'text/plain' });
    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={retrieveVariables}>
        Generate
      </button>
    </div>
  );
}

export default App;

检索结果以文本文件输出,清晰列出文档中所有变量的名称及对应的值。

检索文档变量输出的文本结果


移除文档变量

当模板文档中存在不再需要的变量时,可以通过 Variables.Remove 方法按变量名称将其移除。移除后配合 IsUpdateFields 属性更新域,确保生成的文档干净无冗余。

function App() {
  const removeVariables = async () => {
    // 获取 Spire.Doc WASM 模块
    const docModule = window.wasmModule?.spiredoc;

    // 检查模块是否就绪
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    const inputFileName = 'Template_Docx_6.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // 加载文档
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // 按变量名称移除变量
    doc.Variables.Remove("A1");

    let name = doc.Variables.GetNameByIndex(0);
    doc.Variables.Remove(name);

    doc.Variables.Remove(doc.Variables.GetNameByIndex(0));

    doc.IsUpdateFields = true;

    // 定义输出文件名
    const outputFileName = "RemoveVariables_out.docx";

    // 保存文档
    doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });

    // 从 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={removeVariables}>
        Generate
      </button>
    </div>
  );
}

export default App;

移除变量后生成的文档中,目标变量及其对应的 DocVariable 域字段将被清除,文档内容更加简洁。

移除变量后生成的 Word 文档输出


常见问题

添加变量后文档中仍显示域代码而非实际值

原因:DocVariable 域字段创建后,默认不会自动更新显示变量值。如果未设置 IsUpdateFields 属性,文档中会保留域代码文本。

解决:保存文档前将 IsUpdateFields 属性设置为 true:

document.IsUpdateFields = true;

按名称获取变量值时返回空

原因:传入的变量名称大小写或拼写与文档中实际定义的变量名不一致,导致匹配失败。

解决:先通过遍历 document.Variables 集合,使用 GetNameByIndex 方法确认文档中实际的变量名称,再按准确名称获取值:

for (let i = 0; i < document.Variables.Count; i++) {
    let name = document.Variables.GetNameByIndex(i);
    let value = document.Variables.GetValueByIndex(i);
    console.log("Name: " + name + ", Value: " + value);
}

获取免费许可证

如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。