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 样式兼容性问题。

相关推荐
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄2 天前
JavaScript 隐式全局变量解析
javascript
沙蒿同学2 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端
paopaokaka_luck2 天前
智慧社区综合服务小程序(人脸识别、AI问答、Echarts图形化分析)
前端·javascript·spring boot·spring·数据分析·echarts
Hilaku2 天前
为什么死磕 1px 的团队,用户体验反而更差?
前端·javascript·程序员
陈拉斯2 天前
给公司年会写了个大屏抽奖系统:动画在前端跑,凭什么说结果没被改?
javascript
志尊宝2 天前
Vue3 零基础每日笔记(055):组件里正确使用 store——storeToRefs 解构与 $patch 批量修改
笔记·vue·html·前端开发·软件开发
yzy852 天前
Vue中下载或打开文件的JavaScript方法
前端·javascript·vue
用户298698530142 天前
在 React 中用 JavaScript 将 Word 转换为文本
前端·javascript·react.js