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

在内容导出、报告生成、邮件合并等场景中,将富文本 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 安装依赖包

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

bash 复制代码
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 模块的方式,这是所有转换示例共用的前置步骤:

jsx 复制代码
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

jsx 复制代码
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 字符串追加到文档段落中:

jsx 复制代码
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 样式兼容性问题。

相关推荐
BreezeJiang33 分钟前
为什么生成了 dist,项目还是可能跑不起来?从一个 React + Express + MySQL 项目看部署闭环
nginx·react.js
奴心35 分钟前
我受够了 value / defaultValue / onChange 三连——一个 prop 重构 React 受控组件
react.js
mONESY42 分钟前
我用 Next.js 16 + Supabase 从零做了个「背单词」H5 应用
javascript
boooooooom1 小时前
手把手做一个图 RAG 烹饪问答系统:Neo4j + Milvus + LLM 的工程实践
前端·javascript·后端
染指11104 小时前
103.RAG-LLamaIndex后端rag问答-聊天接口
前端·javascript·vue.js·人工智能
浮生望11 小时前
React JWT 登录鉴权实战:Zustand 状态管理 + Axios 拦截器 + 路由守卫完整链路
react.js
单线程_0112 小时前
从案例分析 Vue3 Tokenizer+Parser 源码三
前端·javascript·vue.js
小磊哥er14 小时前
深入解构Claude Code - 第 8 篇 · 数据放哪、钱怎么算
javascript·ai编程
kyriewen15 小时前
我装了30多个Skill,给AI安排了8个岗位
前端·javascript·ai编程