React 中 HTML 内容转 Word 文档的实现方案

33 阅读5分钟

在内容导出、报告生成、邮件合并等场景中,将富文本 HTML 内容转换为 Word 文档格式(.docx)是一项常见需求。将 HTML 内容直接输出为 Word 文档,可以让用户更方便地进行本地编辑和存档。本文介绍在 React 环境中,利用基于 WebAssembly 的方案将 HTML 文件或 HTML 字符串导入并生成 Word 文档的两种方式。

一、技术原理

该方案的核心是通过运行在浏览器中的 WASM 模块加载文档处理引擎。文档的加载、处理和保存均在虚拟文件系统(VFS) 中完成,不直接操作本地文件系统。

转换流程如下:

  1. 将字体文件加载到 VFS,确保文本正确渲染
  2. 从 VFS 读取 HTML 文件,或通过 Paragraph.AppendHTML() 方法将 HTML 字符串追加到文档段落
  3. WASM 引擎将 HTML 内容转换为 Word 可识别的格式化内容
  4. 将文档保存为 .docx 格式并从 VFS 读取,触发浏览器下载

这一流程完全在客户端完成,文档内容无需上传至服务器。

二、环境配置

2.1 安装依赖包

在项目根目录执行以下命令:

npm i spire.office

2.2 迁移运行时文件

安装完成后,将 node_modules/spire.office/lib 中的以下文件复制到 React 项目的 public 文件夹:

  • spire.doc.js
  • Spire.Doc.Wasm.zip
  • spire.common.js
  • Spire.Common.Wasm.zip
  • _framework 文件夹

这些文件是 WASM 模块加载所必需的,放置在 public 目录下可以确保构建工具不会错误地处理它们。

2.3 准备字体资源

由于 WASM 环境不包含系统字体,如果 HTML 内容中使用了特定字体(如 Calibri 或中文字体),需要将对应的字体文件放入 public/static/font/ 目录,并通过 FetchFileToVFS 方法加载到 VFS 中。HTML 文件可放入 public/static/data/ 目录。

三、WASM 模块加载

以下代码展示了在 React 组件中异步加载 WASM 模块的方式,这是所有转换示例共用的前置步骤:

import React, { useState, useEffect } from 'react';

function App() {
  const [wasmModule, setWasmModule] = useState(null);

  useEffect(() => {
    (async () => {
      try {
        const publicUrl = process.env.PUBLIC_URL || '';
        const spireModule = await import(/* webpackIgnore: true */ `${publicUrl}/spire.doc.js`);
        const rawModule = spireModule.default || spireModule;
        window.wasmModule = typeof rawModule === 'function'
          ? await rawModule({ locateFile: p => p.endsWith('.wasm') ? `${publicUrl}/${p}` : p })
          : rawModule;
        setWasmModule(window.wasmModule);
      } catch (error) {
        console.error('Failed to load WASM module:', error);
      }
    })();
  }, []);

  // 转换函数将在后续定义
}

四、方式一:从 HTML 文件转换

这种方式适用于将已存在的 HTML 文件转换为 Word 文档。通过 Document.LoadFromFile() 方法直接加载 HTML 文件,并指定文件格式为 Html

const convertHtmlFileToWord = async () => {
  const wasmModule = window.wasmModule.spiredoc;
  if (wasmModule) {

  // 1. 加载字体到 VFS
  await window.spire.FetchFileToVFS('CALIBRI.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);

  // 2. 加载 HTML 文件到 VFS
  const inputFileName = 'sample1.html';
  await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

  // 3. 创建 Document 实例并加载 HTML 文件
  const doc = new wasmModule.Document();
  doc.LoadFromFile({ fileName: inputFileName, fileFormat: wasmModule.FileFormat.Html, validationType: wasmModule.XHTMLValidationType.None });

  // 4. 保存为 Word 文档
  const outputFileName = 'HtmlToWord.docx';
  doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx });

  // 5. 从 VFS 读取并下载
  const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
  const modifiedFile = new Blob([modifiedFileArray], { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
  
  const url = URL.createObjectURL(modifiedFile);
  const a = document.createElement('a');
  a.href = url;
  a.download = outputFileName;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);

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

五、方式二:从 HTML 字符串转换

这种方式适用于动态生成的 HTML 内容,例如从编辑器组件中获取用户输入的富文本。通过 Paragraph.AppendHTML() 方法将 HTML 字符串追加到文档段落中:

const convertHtmlStringToWord = async () => {
  const wasmModule = window.wasmModule.spiredoc; 
  if (wasmModule) {

  // 1. 加载字体到 VFS
  await window.spire.FetchFileToVFS('CALIBRI.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);

  // 2. 准备 HTML 字符串
  let HTML = "<html><head><title>HTML to Word Example</title><style>, body {font-family: 'Calibri';}, h1 {color: #FF5733; font-size: 24px; margin-bottom: 20px;}, p {color: #333333; font-size: 16px; margin-bottom: 10px;}"; 
  HTML += "ul {list-style-type: disc; margin-left: 20px; margin-bottom: 15px;}, li {font-size: 14px; margin-bottom: 5px;}, table {border-collapse: collapse; width: 100%; margin-bottom: 20px;}"; 
  HTML += "th, td {border: 1px solid #CCCCCC; padding: 8px; text-align: left;}, th {background-color: #F2F2F2; font-weight: bold;}, td {color: #0000FF;}</style></head>"; 
  HTML += "<body><h1>This is a Heading</h1><p>This is a paragraph demonstrating the conversion of HTML to Word document.</p><p>Here's an example of an unordered list:</p><ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>"; 
  HTML += "<p>Here's a table:</p><table><tr><th>Product</th><th>Quantity</th><th>Price</th></tr><tr><td>Jacket</td><td>30</td><td>$150</td></tr><tr><td>Sweater</td><td>25</td><td>$99</td></tr></table></body></html>";

  // 3. 创建文档并添加内容
  const doc = new wasmModule.Document();
  let section = doc.AddSection();
  let paragraph = section.AddParagraph();
  paragraph.AppendHTML(HTML.toString('utf8', 0, HTML.length));

  // 4. 保存为 Word 文档
  const outputFileName = 'HtmlStringToWord.docx';
  doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2016 });

  // 5. 从 VFS 读取并下载
  const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName); 
  const modifiedFile = new Blob([modifiedFileArray], { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
  const url = URL.createObjectURL(modifiedFile);
  const a = document.createElement('a');
  a.href = url;
  a.download = outputFileName;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);

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

六、两种方式对比

对比维度HTML 文件转换HTML 字符串转换
数据来源public/static/data/ 目录下的 .html 文件JavaScript 中的字符串变量
加载方式Document.LoadFromFile() 直接加载Paragraph.AppendHTML() 追加到段落
适用场景静态 HTML 模板文件动态生成的富文本内容
输入格式指定需要指定 FileFormat.Html无需指定输入格式
验证控制支持 XHTMLValidationType.None 跳过验证无验证参数

七、关键方法与参数说明

方法/参数说明
Document.LoadFromFile({ fileName, fileFormat, validationType })从 VFS 加载指定格式的文件。fileFormat: FileFormat.Html 用于加载 HTML 文件
XHTMLValidationType.None加载 HTML 时跳过 XHTML 格式验证,可提高兼容性
Paragraph.AppendHTML(htmlString)将 HTML 字符串追加到段落中,转换为 Word 可识别的格式化内容
Document.AddSection()在文档中添加一个节(Section),用于承载段落内容
Section.AddParagraph()在节中添加一个段落(Paragraph),用于追加内容
Document.SaveToFile({ fileName, fileFormat })将文档保存到 VFS 中,FileFormat.DocxDocx2016 表示输出 .docx 格式

八、常见问题与建议

字体缺失导致样式异常:HTML 内容中通过 CSS 指定的字体若未在 VFS 中加载,可能出现字体回退或排版偏移。建议将常用字体(如 Calibri、Times New Roman)预先加载。

HTML 样式支持范围AppendHTML() 方法对内联样式和 <style> 标签中定义的 CSS 规则均有一定支持,但对于复杂 CSS 布局(如 Flex、Grid)或交互式脚本的转换效果有限。建议保持 HTML 结构相对简洁。

HTML 文件加载的验证问题:当 HTML 文件格式不够严格时,可通过 validationType: XHTMLValidationType.None 跳过验证,避免加载失败。

资源释放:转换完成后调用 doc.Dispose() 释放文档对象,避免内存泄漏。在批量处理场景中尤为重要。

WASM 模块加载状态:由于 WASM 模块加载需要时间,建议在 UI 中显示加载状态,并在模块未就绪时禁用操作按钮。

九、总结

本文介绍了在 React 应用中基于 WebAssembly 方案将 HTML 内容转换为 Word 文档的两种方式:从 HTML 文件转换和从 HTML 字符串转换。前者适用于静态模板文件,后者适用于动态生成的富文本内容。核心方法包括 Document.LoadFromFile() 加载 HTML 文件,以及 Paragraph.AppendHTML() 将 HTML 字符串导入文档段落。该方案在浏览器端完成所有处理,适用于将富文本内容导出为可编辑 Word 文档的场景。开发者可根据实际数据来源选择合适的方式,并注意字体加载和 HTML 样式兼容性问题。