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

Spire.Cloud 纯前端文档控件

PDF 的属性面板里记着标题、作者、主题、关键字这几个字段,知识库、归档系统和全文检索都拿它们做归类依据。实际收到的文件往往相反:标题还是上一个模板留下的名字,作者一栏空着,关键字干脆没有。补全要在阅读器里逐字段手填,核对一批文档的作者或主题也只能一份份点开属性对话框——桌面软件做不了批量,把文件传到服务端又意味着内容离开了用户的设备。

本文介绍用 Spire.PDF for JavaScript 设置和获取 PDF 文档属性。它基于 WebAssembly 在浏览器端直接加载、修改与保存 PDF 文档,全过程在本地完成,通过虚拟文件系统(VFS)读写文件,无需后端配合。

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

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


设置 PDF 文档属性

Spire.PDF for JavaScript 提供 doc.DocumentInformation,用于写入标准文档属性——标题、作者、主题、关键字各占一个字段,Creator 与 Producer 记录生成方,取值都是字符串。

function App() {
  const setPdfProperties = async () => {
    // 获取 Spire.PDF WASM 模块
    const pdfModule = window.wasmModule?.spirepdf;

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

    // 将待处理的 PDF 文件载入 VFS
    const inputFileName = '产品介绍.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // 创建 PdfDocument 对象并加载 PDF 文档
    const doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // 写入标准文档属性
    doc.DocumentInformation.Title = '2026 年度产品介绍';
    doc.DocumentInformation.Author = '市场部';
    doc.DocumentInformation.Subject = '产品线与报价';
    doc.DocumentInformation.Keywords = '产品介绍, 报价, 2026';
    doc.DocumentInformation.Creator = '内容中心';
    doc.DocumentInformation.Producer = 'Spire.PDF for JavaScript';

    const outputFileName = '已设置属性的文档.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // 从 VFS 读取生成的文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    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>设置 PDF 文档属性</h1>
      <button onClick={setPdfProperties}>
        开始设置
      </button>
    </div>
  );
}

export default App;

设置完成后在阅读器文档属性面板中看到的标准字段:

设置完成后在阅读器文档属性面板中看到的标准字段


获取 PDF 文档属性

读取走的是同一个 DocumentInformation:标准字段取回字符串。把取到的值拼成文本写出,就能在批量流程里直接比对或入库,不必依赖阅读器的属性面板。

function App() {
  const getPdfProperties = async () => {
    // 获取 Spire.PDF WASM 模块
    const pdfModule = window.wasmModule?.spirepdf;

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

    // 将待读取的 PDF 文件载入 VFS
    const inputFileName = '已设置属性的文档.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // 创建 PdfDocument 对象并加载 PDF 文档
    const doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // 逐个取回标准属性与创建/修改时间
    const info = doc.DocumentInformation;

    const lines = [
      `Title: ${info.Title}`,
      `Author: ${info.Author}`,
      `Subject: ${info.Subject}`,
      `Keywords: ${info.Keywords}`,
      `Creator: ${info.Creator}`,
      `Producer: ${info.Producer}`,
      `CreationDate: ${info.CreationDate.toString()}`,
      `ModificationDate: ${info.ModificationDate.toString()}`,
    ];

    // 把结果写成文本文件
    const outputFileName = '文档属性.txt';
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, lines.join('\n'));
    doc.Close();

    // 从 VFS 读取生成的文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { 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>获取 PDF 文档属性</h1>
      <button onClick={getPdfProperties}>
        开始获取
      </button>
    </div>
  );
}

export default App;

导出的文本文档,逐行列出读到的标准属性:

导出的文本文档,逐行列出读到的标准属性


常见问题

设置的属性重新打开文件后没有变化

原因:DocumentInformation 上的字段改的是内存中的文档对象,只有调用 SaveToFile 才会写进文件。赋完值直接 Close(),或者打开的还是原来那份输入文件,看到的自然还是旧值。

解决:赋值之后另存为新的输出文件,再打开该产物查看:

doc.DocumentInformation.Title = '2026 年度产品介绍';
doc.DocumentInformation.Author = '市场部';

// 保存后属性才会落进文件
doc.SaveToFile('已设置属性的文档.pdf');

获取免费许可证

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

尊敬的顾客朋友:

2026年国庆将至,成都冰蓝科技有限公司全体职工在此向您致以最诚挚的祝福与问候!为满足节日期间的客户需求,我司就国庆放假安排做出如下通知:

因放假给您带来的不便,我们深表歉意。

恭祝:节日快乐,万事顺遂!

邮件地址:

几百页的手册、报告发出去之后,最常被抱怨的不是内容,而是找不到想看的章节。读者想在开头就拿到一份按页排好的章节清单,点一下直接翻过去;而很多 PDF 在生成时就没有目录,翻页只能靠滚动条或者页内搜索。

本文介绍用 Spire.PDF for JavaScript 创建目录页、并为目录条目添加跳转。它基于 WebAssembly 在浏览器端直接加载、修改与保存 PDF 文档,全过程在本地完成,通过虚拟文件系统(VFS)读写文件,无需后端配合。

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

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


生成目录页

目录页要落在文档里某个位置,用 Pages.Insert({ index }) 插入一页并拿到它,标题、章节条目、前导点和页码都用这页的 Canvas.DrawString 绘制。条目的横向位置按文字宽度推进,前导点从标题右端逐个补到页码左端。

function App() {
  const createTocPage = async () => {
    // 获取 Spire.PDF WASM 模块
    const pdfModule = window.wasmModule?.spirepdf;

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

    // 将中文字体与待处理的 PDF 文件载入 VFS
    await window.spire.FetchFileToVFS('SIMSUN.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = '章节文档.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // 创建 PdfDocument 对象并加载 PDF 文档
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // 在封面之后插入目录页,原来的正文各页整体后移一位
    const tocPage = doc.Pages.Insert({ index: 1 });

    // 标题与条目使用的字体
    const titleFont = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: 20, style: pdfModule.PdfFontStyle.Bold });
    const entryFont = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: 14 });
    const centerFormat = new pdfModule.PdfStringFormat({ alignment: pdfModule.PdfTextAlignment.Center });

    // 绘制居中的目录标题
    const title = '目录';
    tocPage.Canvas.DrawString({
      s: title,
      font: titleFont,
      brush: pdfModule.PdfBrushes.get_Black(),
      point: new pdfModule.PointF(tocPage.Canvas.ClientSize.Width / 2, 50),
      format: centerFormat
    });

    // 章节标题与插入目录页后的显示页码
    const chapters = [
      { title: '第一章 系统概述', page: 3 },
      { title: '第二章 功能架构', page: 4 },
      { title: '第三章 部署要求', page: 5 },
      { title: '第四章 维护与支持', page: 6 }
    ];

    const width = tocPage.Canvas.ClientSize.Width;
    let y = 110;
    for (const chapter of chapters) {
      // 条目文字
      const titleSize = entryFont.MeasureString({ text: chapter.title });
      tocPage.Canvas.DrawString({ s: chapter.title, font: entryFont, brush: pdfModule.PdfBrushes.get_Black(), x: 40, y: y });

      // 右对齐的页码
      const pageText = chapter.page.toString();
      const pageSize = entryFont.MeasureString({ text: pageText });
      tocPage.Canvas.DrawString({ s: pageText, font: entryFont, brush: pdfModule.PdfBrushes.get_Black(), x: width - 40 - pageSize.Width, y: y });

      // 前导点:从条目右端补齐到页码左端
      const dotStart = 40 + titleSize.Width + 6;
      const dotEnd = width - 40 - pageSize.Width - 6;
      for (let x = dotStart; x < dotEnd; x += 6) {
        tocPage.Canvas.DrawString({ s: '.', font: entryFont, brush: pdfModule.PdfBrushes.get_Gray(), x: x, y: y });
      }

      y += 24;
    }

    // 定义输出文件名并保存
    const outputFileName = '带目录的文档.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // 从 VFS 读取生成的文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    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 id="btn-1" onClick={createTocPage}>
        生成目录
      </button>
    </div>
  );
}

export default App;

带目录页的文档,封面之后的第二页列出了各章节标题与页码:

带目录页的文档,封面之后的第二页列出了各章节标题与页码


为目录条目添加跳转

目录页画好之后,条目还只是一行文字。要让条目可点,得给每条覆盖一块 PdfActionAnnotation 命中区,用 PdfGoToAction 携带 PdfDestination 指定跳转页实现点击目录条目进行页面跳转。命中区的位置不用按行距去推算——用 PdfTextFinder 在目录页上按条目文字搜索,拿到的矩形就是这行字在页面上的真实位置,直接就能当命中区用。

function App() {
  const addTocNavigation = async () => {
    // 获取 Spire.PDF WASM 模块
    const pdfModule = window.wasmModule?.spirepdf;

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

    // 载入上一步生成的带目录文档
    const inputFileName = '带目录的文档.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // 目录页是文档的第 2 页(索引 1)
    const tocPage = doc.Pages.get_Item(1);

    // 条目文字与各自要跳转的页
    const chapters = [
      { title: '第一章 系统概述', page: 3 },
      { title: '第二章 功能架构', page: 4 },
      { title: '第三章 部署要求', page: 5 },
      { title: '第四章 维护与支持', page: 6 }
    ];

    // 在目录页上按关键字搜索
    const finder = new pdfModule.PdfTextFinder(tocPage);

    for (const chapter of chapters) {
      const found = finder.Find(chapter.title);
      if (found.length === 0) {
        continue;
      }

      // 根据关键字位置定义命中区域
      const lineBounds = found.get(0).Bounds[0];
      const bounds = new pdfModule.RectangleF({
        location: new pdfModule.PointF(0, lineBounds.Y),
        size: new pdfModule.SizeF({ width: tocPage.Canvas.ClientSize.Width, height: lineBounds.Height })
      });

      // 跳转目标是章节所在页,位置对齐到正文左上角
      const targetPage = doc.Pages.get_Item(chapter.page - 1);
      const destination = new pdfModule.PdfDestination({
        page: targetPage,
        location: new pdfModule.PointF(0, 0)
      });

      // 添加上跳转动作,并把边框宽度设为 0
      const action = new pdfModule.PdfActionAnnotation(bounds, new pdfModule.PdfGoToAction({ destination }));
      action.Border = new pdfModule.PdfAnnotationBorder({ borderWidth: 0 });
      tocPage.Annotations.Add(action);
    }

    // 定义输出文件名并保存
    const outputFileName = '可点击的目录.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // 从 VFS 读取生成的文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    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 id="btn-2" onClick={addTocNavigation}>
        添加跳转
      </button>
    </div>
  );
}

export default App;

点击目录里的章节标题即可跳到对应页面:

点击目录里的章节标题即可跳到对应页面


常见问题

目录页码与实际页面对不上

原因:目录页是插进原文档的,插入点之后的页面整体后移一位。页码列如果仍沿用插入前的页序,就会整体差一页。

解决:页码按插入后的显示位置写。例如封面原本是第 1 页、第一章原本是第 2 页,在封面之后插入目录页后,第一章落在第 3 页,目录里就该写 3。

点击目录条目跳到了别的章节,或者没有反应

原因:条目是用 Canvas.DrawString 画上去的,命中区却要按页面坐标给。如果靠绘制时的 y 加行距去推算,字体度量、行距、页边距里任何一项和实际排版对不上,误差就会一行行累积,点到的是别的条目,甚至点空。

解决:别推算,直接在目录页上按条目文字(关键字)搜索,拿命中的矩形当命中区。PdfTextFinder 返回的坐标本来就是页面坐标,不用再补页边距;page 用 doc.Pages.get_Item(...) 取文档里真实的页对象:

const finder = new pdfModule.PdfTextFinder(tocPage);
const found = finder.Find(chapter.title);
const lineBounds = found.get(0).Bounds[0];
const bounds = new pdfModule.RectangleF({
  location: new pdfModule.PointF(0, lineBounds.Y),
  size: new pdfModule.SizeF({ width: tocPage.Canvas.ClientSize.Width, height: lineBounds.Height })
});
const targetPage = doc.Pages.get_Item(chapter.page - 1);
const action = new pdfModule.PdfActionAnnotation(bounds, new pdfModule.PdfGoToAction({ destination }));

获取免费许可证

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

Word 文档与 OpenXML 之间的相互转换是文档处理中高频出现的需求——将 Word 文档导出为 XML,可以让正文内容脱离二进制包结构,便于跨系统交换、批量提取与归档;将 XML 还原为 Word 文档,则可以直接用已有的结构化数据生成规范文档。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成双向转换,通过虚拟文件系统(VFS)管理字体和文件资源,无需后端服务支持。OpenXML 的平面文件分为两种格式:Word 2003 的 WordML 与 Word 2007 及以上的 WordXml(Flat OPC),保存时可按目标 Word 版本选择。

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

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


将 Word 转换为 OpenXML(WordML)

Word 转 OpenXML 的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 Word 文档载入 WASM 虚拟文件系统;然后实例化 Document 加载文件,调用 SaveToFile 并指定 FileFormat.WordML,将文档另存为 Word 2003 格式的 XML 平面文件;最后从 VFS 读取生成的 XML 文件,封装为 Blob 后触发浏览器下载。

function App() {
  const WordToWordML = async () => {
    const docModule = window.wasmModule?.spiredoc;
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

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

    // 创建文档实例
    const doc = new docModule.Document();

    // 从虚拟文件系统加载文档
    doc.LoadFromFile(inputFileName);

    // 定义输出文件名,保存为 Word 2003 格式的 OpenXML 文件
    const outputFileName = 'WordToWordXML-result.xml';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.WordML });

    // 释放资源
    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>将 Word 文档转换为 WordML 文件</h1>
      <button onClick={WordToWordML}>开始</button>
    </div>
  );
}
export default App;

通过 SaveToFile 指定 FileFormat.WordML 后,Word 文档被转换为一份 Word 2003 格式的 XML 平面文件

通过 FileFormat.WordML 转换后生成的 Word 2003 XML 文件


将 Word 转换为 OpenXML(WordXml)

Word 2007 及以上版本的平面文件格式称为 WordXml(Flat OPC),它把 docx 包内的各个部件——正文、样式、关系等——打包进同一个 XML 文件,根节点为 <pkg:package>,在 Word 中同样可以直接打开。转换流程与 WordML 完全一致,仅保存格式不同。核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 Word 文档载入 WASM 虚拟文件系统;然后实例化 Document 加载文件,调用 SaveToFile 并指定 FileFormat.WordXml,将文档另存为 Word 2007 格式的 XML;最后从 VFS 读取生成的 XML 文件,封装为 Blob 后触发浏览器下载。

function App() {
  const WordToWordXml = async () => {
    const docModule = window.wasmModule?.spiredoc;
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

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

    // 创建文档实例
    const doc = new docModule.Document();

    // 从虚拟文件系统加载文档
    doc.LoadFromFile(inputFileName);

    // 定义输出文件名,保存为 Word 2007 格式的 OpenXML 文件
    const outputFileName = 'WordToWordXML-result-2007.xml';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.WordXml });

    // 释放资源
    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>将 Word 文档转换为 WordXml 文件</h1>
      <button onClick={WordToWordXml}>开始</button>
    </div>
  );
}
export default App;

通过 SaveToFile 指定 FileFormat.WordXml 后,Word 文档的各个部件被合并为一份 Word 2007 格式的 Flat OPC XML 文件

通过 FileFormat.WordXml 转换后生成的 Word 2007 Flat OPC 文件


将 OpenXML 转换为 Word

OpenXML 转 Word 的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 XML 文件载入 WASM 虚拟文件系统;然后实例化 Document 并通过 LoadFromFile 直接加载 XML 文件——Spire.Doc 会根据文件内容自动识别 WordML 与 WordXml 格式,再调用 SaveToFile 将其保存为 Docx2013 格式的 Word 文档;最后从 VFS 读取生成的 .docx 文件,封装为 Blob 后触发浏览器下载。

function App() {
  const XMLToWord = async () => {
    const docModule = window.wasmModule?.spiredoc;
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

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

    // 创建文档实例
    const doc = new docModule.Document();

    // 从虚拟文件系统加载 OpenXML 文件
    doc.LoadFromFile(inputFileName);

    // 定义输出文件名并保存为 Word 文档
    const outputFileName = 'XMLToWord-result.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>将 XML 文件转换为 Word 文档</h1>
      <button onClick={XMLToWord}>开始</button>
    </div>
  );
}
export default App;

通过 LoadFromFile 加载 OpenXML 文件并另存为 Word 文档后,XML 中的段落、列表与表格结构被完整还原

通过 LoadFromFile 加载 OpenXML 文件并转换为 Word 文档后的结果


常见问题

生成的 XML 在 Word 中打开时提示格式错误

原因:保存格式与目标 Word 版本不匹配。FileFormat.WordML 对应 Word 2003 的 XML 格式,FileFormat.WordXml 对应 Word 2007 及以上的 Flat OPC 格式,两者的结构不同,用低版本 Word 打开高版本格式的文件会失败。

解决:按目标 Word 版本选择对应的枚举值,并将输出文件命名为 .xml:

// Word 2003 及更早版本
doc.SaveToFile({ fileName: 'out.xml', fileFormat: wasmModule.FileFormat.WordML });

// Word 2007 及以上版本
doc.SaveToFile({ fileName: 'out.xml', fileFormat: wasmModule.FileFormat.WordXml });

加载 XML 时报错或转换后内容为空

原因:OpenXML 转 Word 要求输入文件是完整的 WordprocessingML 平面文件,普通的自定义数据 XML 无法被 LoadFromFile 识别。

解决:确认输入的 XML 由 Word 或 Spire.Doc 导出,WordML 格式的根节点是 <w:wordDocument>,WordXml 格式的根节点是 <pkg:package>:

const doc = new wasmModule.Document();
doc.LoadFromFile('XMLToWord.xml');

获取免费许可证

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

纯文本(TXT)是最轻量的文本载体,常用于日志导出、数据交换与内容归档;Word 则承担排版与正式交付。在浏览器端把两者互转是文档处理中的常见需求:TXT 转 Word 可以把采集到的纯文本整理成规范文档,Word 转 TXT 则用于从文档中抽取可被程序继续处理的正文。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成这两个方向的转换,通过虚拟文件系统(VFS)管理字体和文件资源,无需后端服务支持。

需要说明的是,TXT 是纯文本格式,本身不承载任何排版信息,因此两个方向的转换都只在文字层面成立:Word 一侧的样式、表格结构与图片不会进入 TXT,反过来 TXT 转出的 Word 也只有正文段落。明确这条边界后再使用,可以避免对转换结果产生过高预期。

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

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


将 TXT 文件转换为 Word

TXT 转 Word 的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 TXT 文件载入 WASM 虚拟文件系统;然后实例化 Document 并调用 LoadFromFile 加载文件——这里没有显式传入 fileFormat,Spire.Doc 会按文件扩展名自动识别为纯文本;最后调用 SaveToFile 将结果保存为 docx,从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。

function App() {
  const TxtToWord = async () => {
    const docModule = window.wasmModule?.spiredoc;
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

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

    // 创建文档实例
    const doc = new docModule.Document();

    // 从虚拟文件系统加载文档
    doc.LoadFromFile(inputFileName);

    // 定义输出文件名
    const outputFileName = "TxtToWord-result.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>将 TXT 文件转换为 Word 文档</h1>
      <button onClick={TxtToWord}>开始</button>
    </div>
  );
}
export default App;

由于 TXT 不携带样式信息,转换结果完全由 Spire.Doc 的默认排版规则决定,有两点需要留意:文件中的每一个换行都会被转换为 Word 中的一个段落,空行则保留为空段落,若输入文件以换行结尾,文末会多出一个空段落;输出的所有段落都使用默认的 Normal 样式,标题、编号与项目符号的层级不会自动产生,写在 TXT 里的 1.、- 等符号只会作为普通字符保留。

通过 LoadFromFile 加载 TXT 文件并另存为 Word 文档后,文本按行还原为段落,全部使用 Normal 样式

通过 LoadFromFile 加载 TXT 文件并转换为 Word 文档后的结果


将 Word 文档转换为 TXT

Word 转 TXT 的流程与前一个方向基本一致:通过 FetchFileToVFS 将字体文件和目标 Word 文档载入虚拟文件系统,实例化 Document 后调用 LoadFromFile 加载文档;区别在于保存时指定 FileFormat.Txt,由 Spire.Doc 按纯文本规则抽取文档内容;最后从 VFS 读取生成的 txt 文件,封装为 text/plain 类型的 Blob 后触发浏览器下载。

function App() {
  const WordToTxt = async () => {
    const docModule = window.wasmModule?.spiredoc;
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

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

    // 创建文档实例
    const doc = new docModule.Document();

    // 从虚拟文件系统加载文档
    doc.LoadFromFile(inputFileName);

    // 定义输出文件名
    const outputFileName = "WordToTxt-result.txt";

    // 将文档保存为纯文本格式
    doc.SaveToFile({fileName: outputFileName, fileFormat: docModule.FileFormat.Txt});

    // 释放资源
    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>将 Word 文件转换为 TXT 文档</h1>
      <button onClick={WordToTxt}>开始</button>
    </div>
  );
}
export default App;

FileFormat.Txt 的导出遵循“只取文字”的规则,以一份包含标题、项目符号列表、编号列表、表格与图片的 Word 文档为例,导出结果可以归纳为以下几点:

  • 标题、正文与列表项的字体、字号、颜色、加粗等样式全部丢弃,统一还原为普通文本行;
  • 项目符号列表写出 * 前缀,编号列表写出 1. 前缀,列表层级信息随之丢失;
  • 表格被逐单元格展开为独立行,行与列的对应关系不再保留;
  • 图片不会被导出,其所在段落变成空行。

此外,导出的 txt 采用 UTF-8 编码并带 BOM,换行为 CRLF,封装 Blob 时使用 text/plain 类型即可。

通过 SaveToFile 指定 FileFormat.Txt 后,Word 文档中的样式、表格与图片均不保留,仅按顺序写出文本内容

通过 FileFormat.Txt 转换后生成的纯文本文件


常见问题

Word 转 TXT 后表格错位、列表符号变成星号

原因:TXT 没有行列与列表结构的概念,FileFormat.Txt 只做文本抽取:表格按单元格顺序逐行写出,项目符号被替换成 *,编号被替换成序号文本。这是格式本身的限制,而不是转换出错。

解决:如果下游需要的只是可读文本,可以按上述规则在读取端还原结构;如果必须保留表格与列表的版式,应当改用能够承载结构的格式,例如导出为 HTML 或 OpenXML。

转换出的 Word 文档里没有标题和列表样式

原因:TXT 不携带样式信息,LoadFromFile 加载纯文本时只能把每一行按默认的 Normal 样式写成段落,TXT 中的 1.、- 等符号只是普通字符,不会被解析为标题或列表。

解决:转换完成后按约定对指定段落重新设置样式;如果不希望逐段设置,也可以改用本身带结构的输入格式(如 HTML)来完成这一步。

加载 TXT 时提示无法识别文件类型

原因:LoadFromFile 未指定 fileFormat 时按文件扩展名判断格式。当输入文件的扩展名不是 .txt(例如 .log、.dat 或不带扩展名)时,Spire.Doc 无法识别,会抛出 Cannot detect current file type 一类的异常。

解决:显式指定纯文本格式:

doc.LoadFromFile({ fileName: inputFileName, fileFormat: wasmModule.FileFormat.Txt });

获取免费许可证

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

HTML 是 Web 端内容呈现与富文本编辑最通用的载体,而 Word 依然是文档交付与归档时的标准格式,两者之间的互转在报表导出、内容归档、富文本落库等场景中使用频率很高。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成此转换,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


HTML 文件转 Word

HTML 文件转 Word 的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 HTML 文件载入 WASM 虚拟文件系统;然后实例化 Document,调用 LoadFromFile 并指定 FileFormat.Html 加载文件,由 Spire.Doc 解析其中的标签、列表与表格结构;最后调用 SaveToFile 将结果保存为 docx,从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。

function App() {
  const htmlFileToWord = async () => {
    const docModule = window.wasmModule?.spiredoc;
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

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

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

    // 定义输出文件名,保存为 Word
    const outputFileName = 'HtmlFileToWord-result.docx';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx });

    // 释放资源
    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>将 HTML 文件转换为 Word 文档</h1>
      <button onClick={htmlFileToWord}>开始</button>
    </div>
  );
}
export default App;

HTML 文件通过 LoadFromFile 转换后生成的 Word 文档

HTML 文件通过 LoadFromFile 转换后生成的 Word 文档


HTML 字符串转 Word

在实际开发中,待转换的 HTML 内容往往并不存在于磁盘上,而是富文本编辑器的提交结果、后端接口返回的片段,或由前端模板拼装出的字符串。这类输入无需载入文件,直接将字符串追加到段落即可,转换流程与 HTML 文件转 Word 类似。

function App() {
  const htmlStringToWord = async () => {
    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}static/font/`);

    // 定义待转换的 HTML 字符串
    let HTML = "<html><head><meta charset=\"utf-8\" /><style type=\"text/css\">body{font-family:'Arial Unicode MS';font-size:11pt;}h2{font-family:'Arial Unicode MS';font-size:16pt;color:#1F4E79;}p,li,td,th{font-family:'Arial Unicode MS';font-size:11pt;}</style></head><body>";
  HTML += "<h2 align=\"center\">四川省概况</h2><p><b>省情概况:</b></p> ";
  HTML += "<ul type=\"disc\"><li><span style='color:#548235;'>省会成都,简称“川”或“蜀”</span></li><li>位于中国西南部,地处长江上游</li><li><span style='color:#C00000'>川西高原与四川盆地气候差异大,出行需按区域准备</span></li></ul><p><b>代表景区:</b></p>";
  HTML += "<ul type=\"square\"><li>九寨沟</li><li>峨眉山 — 乐山大佛</li><li>都江堰 — 青城山</li></ul>";
  HTML += "<table border=\"1\" width=\"90%\" cellspacing=\"0\" cellpadding=\"6\"><tr><th>四川省名片</th></tr><tr><td>面积约 48.6 万平方公里,居全国第五位</td></tr><tr><td>常住人口约 8300 万,多民族聚居</td></tr>";
  HTML += "<tr><td>自古有“天府之国”之称,是长江上游重要生态屏障</td></tr><tr><td>国宝大熊猫的主要栖息地,野生种群数量居全国首位</td></tr></table></body></html>";
    // 创建文档,并添加节与段落
    const doc = new docModule.Document();
    const section = doc.AddSection();
    const paragraph = section.AddParagraph();

    // 将 HTML 字符串追加到段落
    paragraph.AppendHTML(HTML);

    // 定义输出文件名,保存为 Word
    const outputFileName = 'HtmlStringToWord-result.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>将 HTML 字符串写入 Word 文档</h1>
      <button onClick={htmlStringToWord}>开始</button>
    </div>
  );
}
export default App;

HTML 字符串通过 AppendHTML 转换后生成的 Word 文档

HTML 字符串通过 AppendHTML 转换后生成的 Word 文档


常见问题

转换后的 Word 中文显示为方块或乱码

原因:WASM 虚拟文件系统中缺少渲染所需的字体文件。LoadFromFile 在解析 HTML 时会为其中的文本匹配字体,AppendHTML 写入的内容同样需要按字体名查找字形,若 VFS 中没有对应字体,中文等非 ASCII 字符会显示为方块或乱码。

解决:转换前通过 FetchFileToVFS 将字体文件载入 VFS:

await window.spire.FetchFileToVFS(
  'ARIALUNI.TTF', '/Library/Fonts/', '/'
);

加载 HTML 文件时抛出 Validation 相关异常

原因:XHTMLValidationType 的取值过于严格。实际 HTML 文件通常存在未闭合标签、属性大小写不规范或非标准写法,并不符合 XHTML 规范,使用严格校验会在解析阶段直接抛出异常。

解决:对来源不受控的 HTML,统一使用 XHTMLValidationType.None 关闭校验:

wordDocument.LoadFromFile({
  fileName: inputFileName,
  fileFormat: docModule.FileFormat.Html,
  validationType: docModule.XHTMLValidationType.None,
});

获取免费许可证

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

Spire.Office for Java 11.9.0 已正式发布。在该版本中,Spire.Doc for Java 支持根据水印文本长度自适应字体大小;Spire.PDF for Java新增了对 PdfGridCell 富文本的支持,以及 PdfToWordConverter 流输入接口的支持。除此之外,一些在转换和操作 Word、Excel、PDF和 PPT 文档时出现的问题也已成功被修复。更多新功能及问题修复详情如下。


获取 Spire.Office for Java 11.9.0,请点击:

https://www.e-iceblue.cn/Downloads/Spire-Office-JAVA.html

Spire.Doc for Java

新功能:

问题修复:

Spire.XLS for Java

问题修复:

Spire.Presentation for Java

问题修复:

Spire.PDF for Java

新功能:

问题修复:

工作表里的数据很少是一块齐整的矩形。明细之间常夹着小计行、分组标题或说明行,而图表要画的恰恰是明细本身;另有一些数字根本不落在单元格里,它们由接口返回、由代码算出,或者只是本次要展示的一组目标值。这两种情形都会让「先框选一块区域、再插入图表」的做法失效。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成此操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


用不连续数据源创建图表

小计行、分组标题、隔行的说明文字,都会把一张明细表切成若干段。插入图表时若直接框选整列,这些行与明细行并无区别,一并会被画成柱子:示例数据里每两个季度之后跟着一行小计,整列取数会让柱子从 6 根变成 9 根,每个区域的高度也凭空翻了约一倍。为系列指明数据来源时,允许把几段互不相邻的区域拼成一串引用,图表只取这些段落里的行,中间被跳过的部分不参与绘制。这样一来,明细表不必为了作图而重排,小计行也可以留在原位。具体操作步骤如下:

  1. 将字体与测试数据文件载入 VFS。
  2. 用 workbook.LoadFromFile 加载工作簿,以 workbook.Worksheets.get(0) 取得工作表。
  3. 用 sheet.Charts.Add 添加柱状图,并把 chart.SeriesDataFromRange 设为 false,声明系列的数据由代码逐段给出,不取自 chart.DataRange。
  4. 用 chart.Series.Add 添加系列,把 serie.Name 设为表头单元格的 Value。
  5. 用 Range.get 取到每个区域的季度行,再以 AddCombinedRange 依次拼接,分别得到分类标签与数值两串引用。
  6. 对第二个系列重复同样的拼接,使两个系列共用同一组分类标签,最后保存工作簿。

以下为完整的代码示例,演示如何在 React 中用不连续数据源创建图表:

function App() {
  const chartFromDiscontinuousData = async () => {
    // 获取 Spire.XLS WASM 模块
    const xlsModule = window.wasmModule?.spirexls;

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

    // 将字体和测试数据文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'ChartSourceData.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 加载工作簿,取第一个工作表
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });
    const sheet = workbook.Worksheets.get(0);

    // 添加柱状图,并声明系列的数据由代码逐段指定,不取自整块数据区域
    const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
    chart.ChartTitle = "各区域季度销售";
    chart.ChartTitleArea.Size = 12;
    chart.SeriesDataFromRange = false;

    // 设置图表在工作表中的位置
    chart.TopRow = 12;
    chart.BottomRow = 28;
    chart.LeftColumn = 1;
    chart.RightColumn = 10;

    // 把三个区域的季度行拼成一段引用,中间的小计行被跳过
    const categoryLabels = sheet.Range.get("A2:A3")
      .AddCombinedRange(sheet.Range.get("A5:A6"))
      .AddCombinedRange(sheet.Range.get("A8:A9"));

    // 线上销售系列:名称取自表头,数值同样由三段区域拼成
    const onlineSerie = chart.Series.Add();
    onlineSerie.Name = sheet.Range.get("B1").Value;
    onlineSerie.CategoryLabels = categoryLabels;
    onlineSerie.Values = sheet.Range.get("B2:B3")
      .AddCombinedRange(sheet.Range.get("B5:B6"))
      .AddCombinedRange(sheet.Range.get("B8:B9"));

    // 门店销售系列:与上一系列共用同一组分类标签
    const storeSerie = chart.Series.Add();
    storeSerie.Name = sheet.Range.get("C1").Value;
    storeSerie.CategoryLabels = categoryLabels;
    storeSerie.Values = sheet.Range.get("C2:C3")
      .AddCombinedRange(sheet.Range.get("C5:C6"))
      .AddCombinedRange(sheet.Range.get("C8:C9"));

    // 保存工作簿
    const outputFileName = "DiscontinuousData.xlsx";
    workbook.SaveToFile({ fileName: outputFileName });

    // 释放 workbook 对象以释放资源
    workbook.Dispose();

    // 从 VFS 读取结果文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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 id="discontinuous-data" onClick={chartFromDiscontinuousData}>用不连续数据源创建图表</button>
    </div>
  );
}

export default App;

运行后,用不连续数据源创建图表的效果:

用不连续数据源创建图表


用无数据源创建图表

图表的数据通常取自单元格,但并非总是如此:目标值可能写在配置里,汇总数字由接口返回,或者只是演示用的一组常数。这些数字既然没有落进工作表,图表也就无区域可指。系列允许把数值直接写在自身内部,图表因此可以脱离工作表的数据独立存在;数值随代码改动,重新生成即可,不必为了作图在工作表里再维护一份副本。由于数值不来自单元格,图表也不带任何文字分类,横坐标按 1、2、3 的顺序编号。具体操作步骤如下:

  1. 将字体与测试数据文件载入 VFS。
  2. 用 workbook.LoadFromFile 加载工作簿,以 workbook.Worksheets.get(0) 取得工作表;新图表仍要挂在一张已有工作表上。
  3. 用 sheet.Charts.Add 添加柱状图,并设置图表的标题与位置。
  4. 用 chart.Series.Add 添加系列,把 serie.Name 设成系列名。
  5. 用 xlsModule.Int32.Create 把每个数值装箱,按顺序放进 serie.EnteredDirectlyValues,最后保存工作簿。

以下为完整的代码示例,演示如何在 React 中用无数据源创建图表:

function App() {
  const chartWithoutSourceData = async () => {
    // 获取 Spire.XLS WASM 模块
    const xlsModule = window.wasmModule?.spirexls;

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

    // 将字体和测试数据文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'ChartSourceData.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 加载工作簿,取第一个工作表
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });
    const sheet = workbook.Worksheets.get(0);

    // 添加柱状图,这次的数值不来自任何单元格
    const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
    chart.ChartTitle = "各区域销售目标";
    chart.ChartTitleArea.Size = 12;

    // 设置图表在工作表中的位置
    chart.TopRow = 12;
    chart.BottomRow = 28;
    chart.LeftColumn = 1;
    chart.RightColumn = 10;

    // 添加系列,把数值逐个写入 EnteredDirectlyValues
    // 分类标签不另行给出,图表按 1、2、3 的顺序编号
    const targetSerie = chart.Series.Add();
    targetSerie.Name = "销售目标";
    targetSerie.EnteredDirectlyValues = [
      xlsModule.Int32.Create(260),
      xlsModule.Int32.Create(210),
      xlsModule.Int32.Create(190),
    ];

    // 保存工作簿
    const outputFileName = "NoSourceData.xlsx";
    workbook.SaveToFile({ fileName: outputFileName });

    // 释放 workbook 对象以释放资源
    workbook.Dispose();

    // 从 VFS 读取结果文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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 id="no-source-data" onClick={chartWithoutSourceData}>用无数据源创建图表</button>
    </div>
  );
}

export default App;

运行后,用无数据源创建图表的效果:

用无数据源创建图表


常见问题

系列名称必须指向单元格吗

原因:serie.Name 接受一段普通文字,赋值后即为系列名,图例照此显示,保存后落在系列自身而不是某个单元格的引用里;取表头单元格的 Value 只是另一种取法,并非必需。数值与分类标签仍需从区域取得,只有名称可以脱开单元格。

解决:直接把文字赋给 serie.Name 即可:

const serie = chart.Series.Add();
serie.Name = "销售目标";
serie.CategoryLabels = sheet.Range.get("A2:A3").AddCombinedRange(sheet.Range.get("A5:A6")).AddCombinedRange(sheet.Range.get("A8:A9"));
serie.Values = sheet.Range.get("B2:B3").AddCombinedRange(sheet.Range.get("B5:B6")).AddCombinedRange(sheet.Range.get("B8:B9"));

两个系列共用一组分类标签,要各设一遍吗

原因:CategoryLabels 属于系列自身,添加第二个系列时不会自动沿用上一个系列的设置,需要再赋一次。

解决:拼接只做一次,把结果存进变量后分别赋给各系列,不必重复写 AddCombinedRange:

const labels = sheet.Range.get("A2:A3")
  .AddCombinedRange(sheet.Range.get("A5:A6"))
  .AddCombinedRange(sheet.Range.get("A8:A9"));

onlineSerie.CategoryLabels = labels;
storeSerie.CategoryLabels = labels;

获取免费许可证

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

插入图表时,Excel 会自动确定一套刻度:纵轴的下限、上限与单位,均由依据数据范围推算而来。这套默认值在多数情况下可以满足需要,但在两种情形下会显出不足。其一,数据集中在较窄的区间内、整体又远离零点时,自动刻度会把纵轴起点抬到数据下沿附近,幅度有限的波动因而被画成接近满高的涨落,柱体之间的高低差与数值的差距不成比例;其二,刻度标签沿用数据源的格式,带小数的数字会使纵轴排满小数位,既不易辨识,也占用宽度。坐标轴格式用于调整这两处:给定刻度范围与单位,可使纵轴不随数据浮动,柱高与数值成比例,各图表也落在同一尺度上;指定刻度线与标签位置,可控制读数的落点;改写数字格式,可将标签整理为便于比较的形式;再为两轴添加标题,读者方能明确横纵两轴各自的含义。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成上述操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


设置数值轴的刻度格式

自动刻度由 Excel 依据数据范围推算,其目标是让图表整体匀称,而非让读数便于比较。数据集中在较窄的区间内、整体又远离零点时,这种推算会把纵轴的下限抬到数据下沿附近:下限既不从 0 起,柱高便只体现彼此的差值,幅度有限的波动被画成接近满高的涨落,图形与数值之间不成比例。此外,刻度标签默认沿用源单元格的显示格式,数据带小数时,纵轴上会出现一串带小数位的刻度,既不易辨识,也占用宽度。手动接管数值轴的刻度可以同时解决这两点:刻度范围与刻度间隔由代码指定后,纵轴的起点与上限不再随数据浮动,柱高与数值成比例,同一批图表也可采用同一把尺子;标签的显示格式独立于源单元格后,读数更为整齐。具体操作步骤如下:

  1. 将字体与测试数据文件载入 VFS。
  2. 用 workbook.LoadFromFile 加载工作簿,以 workbook.Worksheets.get(0) 取得工作表。
  3. 用 sheet.Charts.Add 添加柱状图,将 chart.DataRange 指向销售额一列,再把月份一列赋给 serie.CategoryLabels。
  4. 在 chart.PrimaryValueAxis 上用 MinValue 与 MaxValue 指定刻度范围,用 MajorUnit 与 MinorUnit 给出主、次单位的步长。
  5. 用 MajorTickMark 与 MinorTickMark 指定主、次刻度线的类型,用 TickLabelPosition 指定刻度标签的位置。
  6. 将 NumberFormat 设为 #,##0 以指定标签的数字格式,并把 IsSourceLinked 设为 false 关闭与数据源的格式联动,最后保存工作簿。

以下为完整的代码示例,演示如何在 React 中设置数值轴的刻度格式:

function App() {
  const formatValueAxis = async () => {
    // 获取 Spire.XLS WASM 模块
    const xlsModule = window.wasmModule?.spirexls;

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

    // 将字体和测试数据文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'ChartAxisData.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 加载工作簿,取第一个工作表
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });
    const sheet = workbook.Worksheets.get(0);

    // 添加柱状图,数据区域指向销售额一列
    const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
    chart.ChartTitle = "各月销售额";
    chart.ChartTitleArea.IsBold = true;
    chart.ChartTitleArea.Size = 12;
    chart.DataRange = sheet.Range.get("B1:B9");
    chart.SeriesDataFromRange = false;
    chart.PlotArea.Visible = false;

    // 设置图表在工作表中的位置
    chart.TopRow = 10;
    chart.BottomRow = 28;
    chart.LeftColumn = 2;
    chart.RightColumn = 10;

    // 取到系列,把月份一列设为分类标签
    const serie = chart.Series.get(0);
    serie.CategoryLabels = sheet.Range.get("A2:A9");

    // 给定数值轴的边界:下限 0,上限 5000
    const valueAxis = chart.PrimaryValueAxis;
    valueAxis.MinValue = 0;
    valueAxis.MaxValue = 5000;

    // 给定主刻度单位 1000,次刻度单位 500
    valueAxis.MajorUnit = 1000;
    valueAxis.MinorUnit = 500;

    // 主刻度线画在轴线外侧,次刻度线画在内侧
    valueAxis.MajorTickMark = xlsModule.TickMarkType.TickMarkOutside;
    valueAxis.MinorTickMark = xlsModule.TickMarkType.TickMarkInside;

    // 刻度标签紧贴轴线,横坐标轴在数值 0 处与数值轴交叉
    valueAxis.TickLabelPosition = xlsModule.TickLabelPositionType.TickLabelPositionNextToAxis;
    valueAxis.CrossesAt = 0;

    // 刻度标签补千位分隔符、去掉小数位
    valueAxis.NumberFormat = "#,##0";

    // 关闭与数据源的格式联动,标签格式以 NumberFormat 为准
    valueAxis.IsSourceLinked = false;

    // 保存工作簿
    const outputFileName = "AxisFormat.xlsx";
    workbook.SaveToFile({ fileName: outputFileName });

    // 释放 workbook 对象以释放资源
    workbook.Dispose();

    // 从 VFS 读取结果文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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={formatValueAxis}>Start</button>
    </div>
  );
}

export default App;

运行后,设置数值轴刻度格式的效果:

设置数值轴的刻度格式


为坐标轴添加标题

坐标轴上只有刻度与分类项,图表本身并不说明每条数据代表什么、数值以什么为单位;隔一段时间重新打开文件,往往需要先查看图表标题才能确认图表的主题。坐标轴标题补充的正是这一层语境:分类轴标题说明每个数据点代表的对象,数值轴标题说明这组数字的单位,二者与图表标题一同把图表补全为可以脱离上下文阅读的形式。此外,轴标题的字号可以单独调整,由此与图表标题区分开。具体操作步骤如下:

  1. 将字体与测试数据文件载入 VFS。
  2. 用 workbook.LoadFromFile 加载工作簿,以 workbook.Worksheets.get(0) 取得工作表。
  3. 用 sheet.Charts.Add 添加柱状图,将 chart.DataRange 指向销售额一列,再把月份一列赋给 serie.CategoryLabels。
  4. 将标题文字分别赋给 chart.PrimaryCategoryAxis.Title 与 chart.PrimaryValueAxis.Title。
  5. 用两个轴对象的 Font.Size 将标题字号统一设为 12,保存工作簿。

以下为完整的代码示例,演示如何在 React 中为坐标轴添加标题:

function App() {
  const setAxisTitle = async () => {
    // 获取 Spire.XLS WASM 模块
    const xlsModule = window.wasmModule?.spirexls;

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

    // 将字体和测试数据文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'ChartAxisData.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 加载工作簿,取第一个工作表
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });
    const sheet = workbook.Worksheets.get(0);

    // 添加柱状图,数据区域指向销售额一列
    const chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ColumnClustered });
    chart.ChartTitle = "各月销售额";
    chart.ChartTitleArea.IsBold = true;
    chart.ChartTitleArea.Size = 12;
    chart.DataRange = sheet.Range.get("B1:B9");
    chart.SeriesDataFromRange = false;
    chart.PlotArea.Visible = false;

    // 设置图表在工作表中的位置
    chart.TopRow = 10;
    chart.BottomRow = 28;
    chart.LeftColumn = 2;
    chart.RightColumn = 10;

    // 取到系列,把月份一列设为分类标签
    const serie = chart.Series.get(0);
    serie.CategoryLabels = sheet.Range.get("A2:A9");

    // 给定分类轴与数值轴的标题
    chart.PrimaryCategoryAxis.Title = "月份";
    chart.PrimaryValueAxis.Title = "销售额(元)";

    // 两个轴标题的字号都设为 12
    chart.PrimaryCategoryAxis.Font.Size = 12;
    chart.PrimaryValueAxis.Font.Size = 12;

    // 保存工作簿
    const outputFileName = "AxisTitle.xlsx";
    workbook.SaveToFile({ fileName: outputFileName });

    // 释放 workbook 对象以释放资源
    workbook.Dispose();

    // 从 VFS 读取结果文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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={setAxisTitle}>Start</button>
    </div>
  );
}

export default App;

运行后,为坐标轴添加标题的效果:

为坐标轴添加标题


常见问题

设置了 MinorUnit,却看不到次刻度线

原因:MinorUnit 仅决定次刻度的间隔,即主刻度之间被划分为几格;是否将这些格子绘制为短线,由另一属性决定。MinorTickMark 的默认值为 TickMarkNone,此时间隔已经生效,但不会绘制任何短线。

解决:同时设置 MinorTickMark,其取值来自 TickMarkType:

const valueAxis = chart.PrimaryValueAxis;
valueAxis.MinorUnit = 500;
valueAxis.MinorTickMark = xlsModule.TickMarkType.TickMarkInside;

设置了 CrossesAt,横坐标轴的位置却没有变化

原因:CrossesAt 决定横坐标轴与数值轴相交的位置,只有将其设为横坐标轴当前所在刻度以外的位置,才会看到移动发生。数值轴自 0 起步时,横坐标轴原本就位于轴线的底端,此时将 CrossesAt 设为 0,等同于维持原位。

解决:将 CrossesAt 设为数值轴范围内的其他刻度,例如 3000,横坐标轴将随之移升至该刻度处:

const valueAxis = chart.PrimaryValueAxis;
valueAxis.MinValue = 0;
valueAxis.MaxValue = 5000;
valueAxis.CrossesAt = 3000;

获取免费许可证

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

报表交付时经常要在两种载体之间切换:Excel 适合留档与二次编辑,HTML 适合嵌进网页、邮件或工单系统直接查看。手工另存为网页既繁琐,也难以在批量流程里串联起来。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端完成这一转换,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


将工作表转换为 HTML 文件

将一张工作表导出为 HTML 后,页面中无需再嵌入任何表格控件,复制到任何位置都能直接打开。转换会将单元格的字体、字号、加粗、填充色与列宽一并写成 CSS,工作表内的图片也会一并导出,但默认不作为 HTML 的一部分,而是另存到 HTML 同级的文件夹中。具体操作步骤如下:

  1. 加载工作簿,通过 workbook.Worksheets.get(0) 取得要导出的工作表。
  2. 创建 HTMLOptions 对象承载导出选项,此处保留默认设置。
  3. 调用 sheet.SaveToHtml({ fileName, saveOption }) 写出 HTML 文件。
  4. 图片输出到同名的 <文件名>_files 文件夹,HTML 中以相对路径引用,分发时需要与文件夹一并保留。
  5. 调用 workbook.Dispose() 释放资源。

下面是一个完整的代码示例,展示了在 React 中将工作表转换为 HTML:

function App() {
  const worksheetToHtml = async () => {
    // 获取 Spire.XLS WASM 模块
    const xlsModule = window.wasmModule?.spirexls;

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

    // 将中文字体载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // 将 Excel 文件载入 VFS
    const inputFileName = 'ExcelToHtml.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 加载工作簿
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile(inputFileName);

    // 获取第一张工作表
    const sheet = workbook.Worksheets.get(0);

    // 创建 HTML 保存选项,此处保留默认设置
    const options = new xlsModule.HTMLOptions();

    // 将工作表保存为 HTML 文件
    const outputFileName = 'WorksheetToHtml.html';
    sheet.SaveToHtml({ fileName: outputFileName, saveOption: options });

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

    // 从 VFS 读取结果文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: "text/html" });
    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</h1>
      <button onClick={worksheetToHtml}>Start</button>
    </div>
  );
}

export default App;

运行后,将工作表转换为 HTML 的效果:

将工作表转换为 HTML


将图片内嵌为 Base64,生成单个 HTML 文件

上一步得到的是 HTML 文件与同名文件夹的组合,两者必须一并分发;若通过邮件或接口只传递 HTML 文件,图片便会失效。HTMLOptions.ImageEmbedded 让工作表中的图片以 Base64 数据 URI 直接写进 HTML,导出的结果是一个自包含文件,放置在任意位置都能完整显示,代价是文件体积随图片增大。具体操作步骤如下:

  1. 加载工作簿并取得要导出的工作表。
  2. 创建 HTMLOptions 对象。
  3. 将 options.ImageEmbedded 设为 true。
  4. 调用 sheet.SaveToHtml({ fileName, saveOption }) 写出 HTML 文件,此时不会再生成同名文件夹。
  5. 调用 workbook.Dispose() 释放资源。

下面是一个完整的代码示例,展示了在 React 中生成图片内嵌的单个 HTML 文件:

function App() {
  const embeddedImages = async () => {
    // 获取 Spire.XLS WASM 模块
    const xlsModule = window.wasmModule?.spirexls;

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

    // 将中文字体载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // 将 Excel 文件载入 VFS
    const inputFileName = 'ExcelToHtml.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 加载工作簿
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile(inputFileName);

    // 获取第一张工作表
    const sheet = workbook.Worksheets.get(0);

    // 创建 HTML 保存选项
    const options = new xlsModule.HTMLOptions();
    // 将图片以 Base64 内嵌到 HTML 中
    options.ImageEmbedded = true;

    // 将工作表保存为单个自包含的 HTML 文件
    const outputFileName = 'EmbeddedImages.html';
    sheet.SaveToHtml({ fileName: outputFileName, saveOption: options });

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

    // 从 VFS 读取结果文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: "text/html" });
    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</h1>
      <button onClick={embeddedImages}>Start</button>
    </div>
  );
}

export default App;

运行后,将图片内嵌为 Base64 的效果:

将图片内嵌为 Base64


将整个工作簿转换为 HTML

一份报表往往不止一张工作表,逐张取出再分别导出较为繁琐。workbook.SaveToHtml({ fileName }) 一次把工作簿中的每张工作表各写成一个 HTML 页面,并额外生成一个入口文件与底部的工作表标签栏,打开入口文件即可在各页面之间切换。具体操作步骤如下:

  1. 加载工作簿。
  2. 调用 workbook.SaveToHtml({ fileName }) 转换工作簿中的全部工作表。
  3. 入口 HTML 是一个 frameset,通过相对路径引用同名文件夹中的各工作表页面与 tabs.html。
  4. 调用 workbook.Dispose() 释放资源。

下面是一个完整的代码示例,展示了在 React 中将整个工作簿转换为 HTML:

function App() {
  const workbookToHtml = async () => {
    // 获取 Spire.XLS WASM 模块
    const xlsModule = window.wasmModule?.spirexls;

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

    // 将中文字体载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // 将 Excel 文件载入 VFS
    const inputFileName = 'ExcelToHtml.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 加载工作簿
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile(inputFileName);

    // 将工作簿中所有工作表一次性转换为 HTML
    const outputFileName = 'WorkbookToHtml.html';
    workbook.SaveToHtml({ fileName: outputFileName });

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

    // 从 VFS 读取入口文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: "text/html" });
    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</h1>
      <button onClick={workbookToHtml}>Start</button>
    </div>
  );
}

export default App;

运行后,将整个工作簿转换为 HTML 的效果:

将整个工作簿转换为 HTML


将 HTML 中的表格转换为 Excel

反向的场景同样常见:从网页抓取或由系统导出的 HTML 表格需要交付为 Excel 文件,供其他同事继续编辑。workbook.LoadFromHtml({ fileName }) 会把 HTML 中的表格还原成工作表,表头落在第一行,数字保持数值型而不是文本;表格以外的段落类内容则会依次落到同一列中。具体操作步骤如下:

  1. 创建空白工作簿。
  2. 调用 workbook.LoadFromHtml({ fileName }) 载入 HTML,其中的表格成为第一张工作表。
  3. 调用 workbook.SaveToFile({ fileName, version }) 保存为 Excel 文件,保存时通过 ExcelVersion 指定文件版本。
  4. 调用 workbook.Dispose() 释放资源。

下面是一个完整的代码示例,展示了在 React 中将 HTML 中的表格转换为 Excel:

function App() {
  const htmlToWorksheet = async () => {
    // 获取 Spire.XLS WASM 模块
    const xlsModule = window.wasmModule?.spirexls;

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

    // 将中文字体载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // 将 HTML 文件载入 VFS
    const inputFileName = 'HtmlToExcel.html';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 创建空白工作簿
    const workbook = new xlsModule.Workbook();
    // 将 HTML 中的表格载入为工作表
    workbook.LoadFromHtml({ fileName: inputFileName });

    // 保存为 Excel 文件
    const outputFileName = 'HtmlToWorksheet.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

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

    // 从 VFS 读取结果文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
    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 转换为 Excel</h1>
      <button onClick={htmlToWorksheet}>Start</button>
    </div>
  );
}

export default App;

运行后,将 HTML 转换为 Excel 的效果:

将 HTML 转换为 Excel


常见问题

单元格的边框、填充色、合并单元格与超链接会保留吗

原因:会保留。转换时这些格式都有对应的写法。用文本编辑器打开时,这些内容藏在 CSS 与标签属性里,不像 Excel 那样直观,容易被误认为没有导出。

解决:用浏览器打开生成的 HTML 即可看到格式完整,需要调整外观时直接修改 <head> 中生成的 CSS 规则。图片是唯一的例外,它默认不在 HTML 内部,处理方式见下一条。

为什么生成的 HTML 中图片显示不出来

原因:默认情况下图片不写入 HTML 本身,而是输出到同名的 _files 文件夹中,HTML 通过相对路径引用。只保存或只发送 HTML 文件时,相对路径失效。

解决:设置 options.ImageEmbedded = true,将图片以 Base64 直接内嵌进 HTML:

const options = new xlsModule.HTMLOptions();
options.ImageEmbedded = true;
sheet.SaveToHtml({ fileName: outputFileName, saveOption: options });

获取免费许可证

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