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

Spire.Cloud 纯前端文档控件

PDF 注释提供了一种便捷的方式,可以在不修改文档原始内容的情况下,为 PDF 添加评论、高亮、备注、链接、图章以及其他交互式元素。它们广泛应用于文档审阅、校对、协作和审批流程中。

借助 Spire.PDF for JavaScript,开发人员可以直接在 React 应用程序中通过编程方式向 PDF 文档添加多种类型的注释。本文将演示两种常用的注释类型:用于高亮指定文本的 文本标记注释,以及用于在页面上附加评论的 弹出式注释。

本文内容:

在 React 项目中安装 Spire.PDF for JavaScript

在处理 PDF 注释之前,需要先将 Spire.PDF for JavaScript 集成到 React 应用程序中。

可以通过 npm 安装所需的软件包:

npm i spire.office

然后,将所需的 JavaScript、WebAssembly 和框架文件复制到 React 项目的 public 文件夹中,以便应用程序在运行时加载这些文件。

有关详细的安装和配置说明,请参考:

如何在 React 项目中集成 Spire.PDF for JavaScript

以下示例假定 Spire.PDF 已完成配置,并且输入 PDF 文件已放置在 public 文件夹中。

使用 JavaScript 为 PDF 添加文本标记注释

文本标记注释通常用于文档审阅。开发人员可以利用它们对 PDF 中的指定文本进行高亮、下划线、删除线或其他形式的标记。

在本示例中,我们首先使用 PdfTextFinder 在 PDF 中查找指定句子。找到文本后,获取其边界矩形,并针对相应区域分别创建高亮注释。

以下 React 代码演示如何高亮 PDF 文档中的指定文本:

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

function App() {
  const [ready, setReady] = useState(false);

  useEffect(() => {
    (async () => {
      const publicUrl = process.env.PUBLIC_URL || '';
      await import(/* webpackIgnore: true */ `${publicUrl}/spire.common.js`);
      const spireModule = await import(/* webpackIgnore: true */ `${publicUrl}/spire.pdf.js`);
      const rawModule = spireModule.default || spireModule;

      window.wasmModule = typeof rawModule === 'function'
        ? await rawModule({ locateFile: p => p.endsWith('.wasm') ? `${publicUrl}/${p}` : p })
        : rawModule;

      setReady(true);
    })();
  }, []);

  const addMarkupAnnotation = async () => {
    const wasmModule = window.wasmModule.spirepdf;
    const inputFileName = 'input.pdf';
    const outputFileName = 'MarkupAnnotation.pdf';

    await window.spire.FetchFileToVFS(inputFileName, '/', `${process.env.PUBLIC_URL || ''}/`);

    const doc = new wasmModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    const page = doc.Pages.get_Item(0);
    const finder = new wasmModule.PdfTextFinder(page);

    const TARGET_TEXT = '月亮,作为夜空中最明亮的天体之一,自古以来就吸引着人类的注意。它不仅影响着地球的潮'+
        '汐变化,还在人类文化、神话和科学探索中占据重要地位。';
    const textFragment = finder.Find(TARGET_TEXT).get(0);
    const bounds = textFragment.Bounds.toArray();

    bounds.forEach((rect) => {
      const annotation = new wasmModule.PdfTextMarkupAnnotation(
        'Administrator',
        '这是一个高亮标注。',
        rect
      );

      annotation.TextMarkupAnnotationType =
        wasmModule.PdfTextMarkupAnnotationType.Highlight;

      annotation.TextMarkupColor =
        new wasmModule.PdfRGBColor({
          color: wasmModule.Color.get_LightYellow()
        });

      page.AnnotationsWidget.Add(annotation);
    });

    doc.SaveToFile(outputFileName);
    doc.Close();
    finder.Dispose();

    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const url = URL.createObjectURL(
      new Blob([fileArray], { type: 'application/pdf' })
    );

    const link = document.createElement('a');
    link.href = url;
    link.download = outputFileName;
    link.click();

    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', padding: 30 }}>
      <h1>Add Markup Annotation to PDF</h1>
      <button onClick={addMarkupAnnotation} disabled={!ready}>
        Add Markup Annotation
      </button>
    </div>
  );
}

export default App;

效果图:

为 PDF 添加文本标记注释

代码说明

添加注释的过程可以分为几个主要步骤。

首先,FetchFileToVFS() 会将 input.pdf 加载到 WebAssembly 虚拟文件系统中。随后创建 PdfDocument 对象,用于加载和操作 PDF 文档。

await window.spire.FetchFileToVFS(
  inputFileName,
  '/',
  `${process.env.PUBLIC_URL || ''}/`
);

const doc = new wasmModule.PdfDocument();
doc.LoadFromFile(inputFileName);

接下来,获取 PDF 的第一页,并将其传递给 PdfTextFinder。

const page = doc.Pages.get_Item(0);
const finder = new wasmModule.PdfTextFinder(page);

随后查找目标文本,并获取其对应的边界矩形:

const textFragment = finder.Find(TARGET_TEXT).get(0);
const bounds = textFragment.Bounds.toArray();

一个句子可能跨越多行,因此其位置可能由多个矩形组成。代码会遍历所有返回的边界矩形,并分别为每个区域创建一个 PdfTextMarkupAnnotation。

bounds.forEach((rect) => {
  const annotation = new wasmModule.PdfTextMarkupAnnotation(
    'Administrator',
    'This is a markup annotation.',
    rect
  );

  annotation.TextMarkupAnnotationType =
    wasmModule.PdfTextMarkupAnnotationType.Highlight;

  page.AnnotationsWidget.Add(annotation);
});

在本示例中,注释类型被设置为 Highlight,颜色则设置为浅黄色。

最后,将修改后的 PDF 保存到虚拟文件系统中,并转换为 Blob,从而允许浏览器下载生成的文件。

使用 JavaScript 为 PDF 添加弹出式注释

当需要在 PDF 页面的指定位置添加评论或备注时,可以使用弹出式注释。与直接标记现有文本不同,弹出式注释会在页面上创建一个注释图标,读者可以在兼容的 PDF 阅读器中与其交互。

以下示例在 PDF 的第一页添加一条评论注释:

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

function App() {
  const [ready, setReady] = useState(false);

  useEffect(() => {
    (async () => {
      const publicUrl = process.env.PUBLIC_URL || '';
      await import(/* webpackIgnore: true */ `${publicUrl}/spire.common.js`);
      const spireModule = await import(/* webpackIgnore: true */ `${publicUrl}/spire.pdf.js`);
      const rawModule = spireModule.default || spireModule;

      window.wasmModule = typeof rawModule === 'function'
        ? await rawModule({ locateFile: p => p.endsWith('.wasm') ? `${publicUrl}/${p}` : p })
        : rawModule;

      setReady(true);
    })();
  }, []);

  const addPopupAnnotation = async () => {
    const wasmModule = window.wasmModule.spirepdf;
    const inputFileName = 'input.pdf';
    const outputFileName = 'PopupAnnotation.pdf';

    await window.spire.FetchFileToVFS(inputFileName, '/', `${process.env.PUBLIC_URL || ''}/`);

    const doc = new wasmModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    const page = doc.Pages.get_Item(0);
    const rect = new wasmModule.RectangleF({ x: 155, y: 85, width: 0, height: 0 });
    const annotation = new wasmModule.PdfPopupAnnotation({
      rectangle: rect,
      text: '这是一个弹出式注释。',
    });

    annotation.Icon = wasmModule.PdfPopupIcon.Comment;
    annotation.Color = new wasmModule.PdfRGBColor({ color: wasmModule.Color.get_Red() });
    page.Annotations.Add(annotation);

    doc.SaveToFile(outputFileName);
    doc.Close();

    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const url = URL.createObjectURL(new Blob([fileArray], { type: 'application/pdf' }));
    const link = document.createElement('a');
    link.href = url;
    link.download = outputFileName;
    link.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', padding: 30 }}>
      <h1>Add Popup Annotation to PDF</h1>
      <button onClick={addPopupAnnotation} disabled={!ready}>
        Add Popup Annotation
      </button>
    </div>
  );
}

export default App;

效果图:

为 PDF 添加弹出式注释

代码说明

加载 PDF 后,首先获取第一页:

const page = doc.Pages.get_Item(0);

随后创建一个 RectangleF 对象,用于定义弹出式注释在页面上的位置。

const rect = new wasmModule.RectangleF({
  x: 155,
  y: 85,
  width: 0,
  height: 0
});

接下来创建 PdfPopupAnnotation 对象。其中,rectangle 属性决定注释显示的位置,而 text 则定义注释中显示的评论内容。

const annotation = new wasmModule.PdfPopupAnnotation({
  rectangle: rect,
  text: 'This is a popup annotation.'
});

还可以自定义注释图标的样式和颜色:

annotation.Icon = wasmModule.PdfPopupIcon.Comment;

annotation.Color =
  new wasmModule.PdfRGBColor({
    color: wasmModule.Color.get_Red()
  });

最后,通过以下代码将注释添加到页面中:

page.Annotations.Add(annotation);

随后保存修改后的 PDF,并在浏览器中下载。

文本标记注释与弹出式注释的区别

虽然这两种注释都用于向 PDF 文档添加审阅信息,但它们适用于不同的使用场景。

注释类型 常见用途 定位方式
文本标记注释 高亮或标记 PDF 中已有的文本 根据选中文本的边界确定位置
弹出式注释 在指定位置添加评论或备注 根据页面坐标确定位置

文本标记注释尤其适合审阅已有内容,因为注释可以精确跟随目标文本的位置。相比之下,当评论只需要关联到页面中的某个区域,而不是特定文本片段时,弹出式注释会更加灵活。

使用其他类型的 PDF 注释

以上示例仅介绍了两种注释类型,但在使用 Spire.PDF for JavaScript 创建其他类型的注释时,整体处理流程基本类似。

典型的注释处理流程如下:

  1. 加载 PDF 文档。
  2. 获取需要添加注释的页面。
  3. 确定注释的位置或目标内容。
  4. 创建相应的注释对象。
  5. 配置文本、颜色、图标、边框或目标位置等属性。
  6. 将注释添加到 PDF 页面。
  7. 保存修改后的文档。

例如,自由文本注释可以直接在页面上显示文字,图章注释可以表示“已批准”等审阅状态,而链接注释则可以将 PDF 内容连接到网页、外部文件或同一文档中的其他位置。

不同注释类型所使用的注释类和可配置属性会有所不同,但基本实现方式大体一致。

总结

注释可以增强 PDF 文档的交互性,在文档审阅、评论、协作和审批等场景中尤其有用。

借助 Spire.PDF for JavaScript,React 应用程序可以直接在浏览器中通过编程方式创建 PDF 注释。本文演示了如何查找指定文本并添加 文本标记注释,以及如何在 PDF 页面的指定坐标处添加 弹出式注释。

通过相同的基本思路,开发人员还可以根据实际应用需求进一步实现自由文本、图章、形状、链接以及其他类型的注释。

常见问题

1. Spire.PDF for JavaScript 支持添加哪些类型的 PDF 注释?

Spire.PDF for JavaScript 支持多种注释类型,包括文本标记注释、自由文本注释、弹出式注释、图章注释、形状注释、网页链接注释、文件链接注释以及文档链接注释等。

具体使用的类和属性取决于注释类型。

2. 是否可以自动高亮文本,而不需要手动指定坐标?

可以。你可以使用 PdfTextFinder 在 PDF 中搜索指定文本,并获取对应的边界矩形。然后,可以使用这些矩形自动确定文本标记注释的位置。

这种方式非常适合自动高亮关键词、审阅术语或指定句子等场景。

3. 为什么文本标记注释示例会为一个句子创建多个注释?

一个句子可能跨越 PDF 中的多行。在这种情况下,文本查找器可能会返回多个边界矩形,分别表示同一段文本的不同部分。

为每个矩形分别创建注释,可以确保跨行的目标文本都能够被完整、准确地高亮。

4. 是否可以自定义 PDF 注释的外观?

可以。根据注释类型的不同,可以自定义颜色、图标、注释文本、位置、文本标记类型以及其他外观属性。

例如,本教程中的文本标记注释使用浅黄色高亮,而弹出式注释则使用红色评论图标。

5. 下载 PDF 后,添加的注释是否仍然会保留?

会。当调用 SaveToFile() 时,这些注释会被写入输出 PDF 文件中。文件下载后,可以在支持标准 PDF 注释的 PDF 阅读器中查看这些注释。

获取免费许可证

如果您需要去除生成文档中的评估提示或解除功能限制,请联系我们获取有效期 30 天的临时许可证。

PDF 版式固定、便于分发,但其内容一经生成便不易在正文中修改。对于合同、报表、公告等文档,通常需要在不影响正文阅读的前提下标识机密等级、版权归属或“草稿”“样例”等使用状态。除了上一类常见的文字水印,使用公司 logo、印章或警示图片制作的图片水印也颇为常见:它以半透明的图片浮于内容之上,既能清晰传达品牌与状态信息,又不会破坏原文的可读性。

Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接完成 PDF 的加载、绘制与保存,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。添加图片水印通常有两种形态:一种是在页面指定位置(如页面中央)放置单张图片水印,可通过 PdfImage.FromFile 加载图片,并结合页面画布的透明度设置与 DrawImage 方法直接实现;另一种是让图片在整页重复平铺,可借助 PdfTilingBrush 平铺画刷完成。

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

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


将单个图片水印添加到 PDF

单张图片水印会在页面的指定位置(本示例为每页中央)放置一枚半透明的图片,适用于在文档醒目位置放置公司 logo 或警示标识。做法是:用 PdfImage.FromFile 从文件加载图片,随后在每页的画布上以 Save 保存状态、用 SetTransparency 设置透明度与混合方式,通过 DrawImage 将图片绘制到居中位置,最后用 Restore 恢复画布状态。

function App() {
  const addSingleImageWatermark = 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/`);

    // 将水印图片载入 VFS
    const inputImageName = 'logo.png';
    await window.spire.FetchFileToVFS(inputImageName, "", `${process.env.PUBLIC_URL}/data/`);

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

    // 从文件加载水印图片
    let image = pdfModule.PdfImage.FromFile(inputImageName);

    // 遍历文档中的所有页面
    for (let i = 0; i < doc.Pages.Count; i++) {
      // 获取指定页面
      let page = doc.Pages.get_Item(i);

      // 保存画布状态,并设置半透明(透明度 0.5)与正片叠底混合模式
      page.Canvas.Save();
      page.Canvas.SetTransparency({ alphaPen: 0.5, alphaBrush: 0.5, blendMode: pdfModule.PdfBlendMode.Multiply });

      // 计算居中绘制位置:用页面尺寸减去图片尺寸后取一半
      let position = new pdfModule.PointF(
        (page.Canvas.Size.Width - image.Width) / 2,
        (page.Canvas.Size.Height - image.Height) / 2
      );

      // 在页面中央绘制水印图片
      page.Canvas.DrawImage({ image: image, point: position });

      // 恢复画布之前的状态
      page.Canvas.Restore();
    }

    // 定义输出文件名并保存文档
    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>Add Single Image Watermark To PDF</h1>
      <button onClick={addSingleImageWatermark}>
        Generate
      </button>
    </div>
  );
}

export default App;

添加单个图片水印后的 PDF 文档

添加单个图片水印后的 PDF 文档


将平铺图片水印添加到 PDF

当需要让水印铺满整页、形成浅淡的背景纹理时,可使用平铺图片水印。做法是:用 PdfImage.FromFile 加载图片,按页面尺寸通过 PdfTilingBrush 划分平铺单元,在画刷的图形上下文中以 SetTransparency 降低透明度并调用 DrawImage 在单元内绘制图片,最后以 DrawRectangle 用该画刷填充整页,即可让图片按单元行列重复、平铺覆盖整个页面。

function App() {
  const addTiledImageWatermark = 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/`);

    // 将水印图片载入 VFS
    const inputImageName = 'logo.png';
    await window.spire.FetchFileToVFS(inputImageName, "", `${process.env.PUBLIC_URL}/data/`);

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

    // 从文件加载水印图片
    let image = pdfModule.PdfImage.FromFile(inputImageName);

    // 遍历文档中的所有页面
    for (let i = 0; i < doc.Pages.Count; i++) {
      // 获取指定页面
      let page = doc.Pages.get_Item(i);

      // 创建平铺画刷:以页面宽度的三分之一、高度的五分之一作为平铺单元
      let size = new pdfModule.SizeF({
        width: page.Canvas.Size.Width / 3,
        height: page.Canvas.Size.Height / 5
      });
      let brush = new pdfModule.PdfTilingBrush({ size: size });

      // 设置水印透明度为 30%
      brush.Graphics.SetTransparency({ alpha: 0.3 });

      // 在平铺单元中央绘制水印图片
      let point = new pdfModule.PointF(
        (brush.Size.Width - image.Width) / 2,
        (brush.Size.Height - image.Height) / 2
      );
      brush.Graphics.DrawImage({ image: image, point: point });

      // 用平铺画刷填充整页矩形,使图片水印平铺覆盖整个页面
      let rect = new pdfModule.RectangleF({
        location: new pdfModule.PointF(0, 0),
        size: page.Canvas.Size
      });
      page.Canvas.DrawRectangle({ brush: brush, rectangle: rect });
    }

    // 定义输出文件名并保存文档
    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>Add Tiled Image Watermark To PDF</h1>
      <button onClick={addTiledImageWatermark}>
        Generate
      </button>
    </div>
  );
}

export default App;

添加平铺图片水印后的 PDF 文档

添加平铺图片水印后的 PDF 文档


常见问题

如何控制图片水印的透明度

原因:图片水印以图片形式叠加在正文之上,若不降低透明度会遮挡正文内容。

解决:绘制前用 Save 保存画布状态,再用 SetTransparency 设置透明度:其透明度由 alphaPen 与 alphaBrush 指定,取值范围为 0(完全透明)到 1(不透明);如需让水印与底色自然融合,还可通过 blendMode 指定混合模式,如 PdfBlendMode.Multiply(正片叠底)。绘制完成后用 Restore 恢复画布状态,避免影响后续绘制:

// 保存画布状态,设置半透明与正片叠底混合模式后绘制水印图片
page.Canvas.Save();
page.Canvas.SetTransparency({ alphaPen: 0.5, alphaBrush: 0.5, blendMode: pdfModule.PdfBlendMode.Multiply });
page.Canvas.DrawImage({ image: image, point: position });
page.Canvas.Restore();

如何指定图片水印的位置

原因:DrawImage 若不传入位置,会按默认坐标绘制图片,无法精确控制水印落点。

解决:DrawImage 支持传入 PointF 位置或 x、y 坐标。想让图片居中时,用页面尺寸减去图片尺寸后取一半即可得到居中坐标:

// 计算居中位置:页面尺寸减去图片尺寸后取一半
let point = new pdfModule.PointF(
  (page.Canvas.Size.Width - image.Width) / 2,
  (page.Canvas.Size.Height - image.Height) / 2
);

// 在指定位置绘制水印图片
page.Canvas.DrawImage({ image: image, point: point });

如何调整平铺图片水印的疏密

原因:平铺图片水印的疏密由平铺画刷 PdfTilingBrush 的 size(平铺单元尺寸)决定。

解决:平铺单元越小,图片重复越密集;单元越大则越稀疏。把 size 设为页面宽高的一定比例即可按行列平铺,例如以页面宽度的三分之一、高度的五分之一为一个单元,得到间隔适中的水印纹理。想更稀疏可调大分母(如 Width / 4),想更密集则调小分母:

// 创建平铺画刷:以页面宽度的三分之一、高度的五分之一作为平铺单元
let size = new pdfModule.SizeF({
  width: page.Canvas.Size.Width / 3,
  height: page.Canvas.Size.Height / 5
});
let brush = new pdfModule.PdfTilingBrush({ size: size });

获取免费许可证

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

在日常 Excel 文档处理中,文本框常用于为数据添加说明性文字、标注或提示信息——无论是为报表添加注释,还是从既有文档中提取标注内容,都离不开文本框的增删改查操作。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成这些操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


添加文本框

在工作表中插入文本框可以为数据提供补充说明,例如添加操作指引或注意事项。Spire.XLS for JavaScript 通过 Worksheet.TextBoxes.AddTextBox() 方法在指定位置插入文本框,随后可设置文本框的文本、对齐方式、字体和背景色,也可以用图片填充文本框。具体操作步骤如下:

  1. 创建 Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。
  2. 通过 Workbook.Worksheets.get() 方法获取指定工作表。
  3. 使用 Worksheet.TextBoxes.AddTextBox() 方法插入第一个文本框,并设置其文本、水平垂直居中对齐、字体和背景色。
  4. 使用 Worksheet.TextBoxes.AddTextBox() 方法插入第二个文本框,并用图片填充。
  5. 通过 Workbook.SaveToFile() 方法保存文档到指定路径。

下面是一个完整的代码示例,展示了在 React 中向工作表插入两个文本框——一个包含文字,一个填充图片:

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

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

    // 将字体、Excel 文件和图片载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'TextBox.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
    await window.spire.FetchFileToVFS('logo.png', '', `${process.env.PUBLIC_URL}data/`);

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

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

    // 插入第一个文本框并设置位置和大小
    const textBox = sheet.TextBoxes.AddTextBox(3, 2, 50, 196);

    // 设置文本框文本
    textBox.Text = '插入Excel文本框';

    // 设置文本水平、垂直居中
    textBox.HAlignment = xlsModule.CommentHAlignType.Center;
    textBox.VAlignment = xlsModule.CommentVAlignType.Center;

    // 设置文本框字体(加粗、白色、12号)
    const font = workbook.CreateFont();
    font.FontName = 'SimSun';
    font.Size = 12;
    font.IsBold = true;
    font.Color = xlsModule.Color.get_White();
    const rt = xlsModule.RichTextShape.Convert(textBox.RichText);
    rt.SetFont(0, textBox.Text.length - 1, font);

    // 设置文本框背景色为蓝灰色
    textBox.Fill.FillType = xlsModule.ShapeFillType.SolidColor;
    textBox.Fill.ForeKnownColor = xlsModule.ExcelColors.BlueGray;

    // 插入第二个文本框并设置位置和大小
    const textBox2 = sheet.TextBoxes.AddTextBox(6, 5, 90, 90);

    // 加载图片并用图片填充文本框
    textBox2.Fill.CustomPicture('logo.png');
    textBox2.Fill.FillType = xlsModule.ShapeFillType.Picture;

    // 设置第二个文本框的边框为 0
    textBox2.Line.Weight = 0;

    // 保存文档
    const outputFileName = 'AddTextBox_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName });

    // 释放资源
    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>Add TextBox</h1>
      <button onClick={addTextBox}>
        Start
      </button>
    </div>
  );
}

export default App;

添加文本框的结果 添加文本框


提取文本框中的文本和图片

当需要汇总或复用既有文档中的标注信息时,可以通过遍历文本框并提取其中的文本内容和填充图片来实现。Spire.XLS for JavaScript 通过 Worksheet.TextBoxes.Count 获取文本框数量,配合 Worksheet.TextBoxes.get() 方法遍历每个文本框:读取其 Text 属性得到文本内容,通过 Fill.FillType 判断填充类型,并用 Fill.Picture 属性提取填充图片,最终将提取结果分别保存为 txt 文件和 png 图片文件。具体操作步骤如下:

  1. 创建 Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。
  2. 通过 Workbook.Worksheets.get() 方法获取指定工作表。
  3. 通过 Worksheet.TextBoxes.Count 和 Worksheet.TextBoxes.get() 方法遍历 TextBoxes 集合中的每个文本框。
  4. 读取文本框的 Text 属性,收集其中的文本内容。
  5. 对使用图片填充的文本框,通过其 Fill.Picture 属性获取填充图片,并保存为 png 文件。
  6. 将收集的文本写入 txt 文件。

下面是一个完整的代码示例,展示了在 React 中提取文本框中的文本和图片:

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

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

    // 将字体和 Excel 文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'TextBox.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 textLines = [];
    const pictureFiles = [];
    for (let i = sheet.TextBoxes.Count - 1; i >= 0; i--) {
      const shape = sheet.TextBoxes.get(i);

      // 提取文本框中的文本
      if (shape.Text) {
        textLines.push(shape.Text);
      }

      // 提取文本框中的填充图片
      if (shape.Fill.FillType === xlsModule.ShapeFillType.Picture) {
        const picture = shape.Fill.Picture;
        const imageFile = 'ExtractedImage' + i + '.png';
        picture.Save(imageFile);
        pictureFiles.push(imageFile);
      }
    }

    // 将提取的文本保存为 txt 文件
    const textFile = 'ExtractedText.txt';
    window.dotnetRuntime.Module.FS.writeFile(textFile, textLines.join('\r\n'));

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

    // 下载提取的文本文件
    const txtArray = window.dotnetRuntime.Module.FS.readFile(textFile);
    const txtBlob = new Blob([txtArray], { type: 'text/plain' });
    const txtUrl = URL.createObjectURL(txtBlob);
    const txtAnchor = document.createElement('a');
    txtAnchor.href = txtUrl;
    txtAnchor.download = textFile;
    txtAnchor.click();
    URL.revokeObjectURL(txtUrl);

    // 下载提取的图片文件
    for (const imageFile of pictureFiles) {
      const imageArray = window.dotnetRuntime.Module.FS.readFile(imageFile);
      const imageBlob = new Blob([imageArray], { type: 'application/png' });
      const imageUrl = URL.createObjectURL(imageBlob);
      const imageAnchor = document.createElement('a');
      imageAnchor.href = imageUrl;
      imageAnchor.download = imageFile;
      imageAnchor.click();
      URL.revokeObjectURL(imageUrl);
    }
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Extract Text And Image From TextBox</h1>
      <button onClick={extractTextAndImage}>
        Start
      </button>
    </div>
  );
}

export default App;

提取文本框中的文本和图片结果 提取文本框中的文本和图片


删除文本框

当文档中的标注信息不再需要时,可以将其删除以保持工作表整洁。Spire.XLS for JavaScript 通过 Worksheet.TextBoxes.RemoveAt() 方法按索引删除指定的文本框。具体操作步骤如下:

  1. 创建 Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。
  2. 通过 Workbook.Worksheets.get() 方法获取指定工作表。
  3. 使用 Worksheet.TextBoxes.RemoveAt() 方法删除指定索引的文本框。
  4. 通过 Workbook.SaveToFile() 方法保存文档到指定路径。

下面是一个完整的代码示例,展示了在 React 中删除工作表内的文本框:

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

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

    // 将字体和 Excel 文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'TextBox.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);

    // 删除第一个文本框
    sheet.TextBoxes.RemoveAt(0);

    // 保存文档
    const outputFileName = 'RemoveTextBox_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName });

    // 释放资源
    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>Remove TextBox</h1>
      <button onClick={removeTextBox}>
        Start
      </button>
    </div>
  );
}

export default App;

删除文本框的结果 删除文本框


常见问题

添加的文本框在结果文档中无法显示

原因:AddTextBox() 方法中指定的行、列坐标越界,或未将字体文件载入 VFS,导致文本框中的文字无法正常渲染。

解决:确认行、列坐标在工作表范围内,并确保在使用前已通过 FetchFileToVFS() 加载所需字体,例如:

await window.spire.FetchFileToVFS(
  'simsun.ttc', '/Library/Fonts/', '/'
);

提取图片时提示填充类型错误

原因:直接访问 Fill.Picture 属性仅适用于使用图片填充的文本框。若文本框未设置图片填充(例如使用纯色填充),访问该属性会抛出异常。

解决:在访问 Fill.Picture 前先判断文本框的 Fill.FillType 是否为 Picture,确认是图片填充后再获取图片并调用 Save() 方法保存。


获取免费许可证

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

在 Excel 文档处理中,公式和函数是最核心的能力之一——无论是求和、求平均,还是日期与三角运算,公式都能让数据处理自动化、高效化。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成公式与函数的插入和读取,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


插入公式和函数到 Excel 工作表

Spire.XLS for JavaScript 提供的 Worksheet.Range.get() 方法返回的单元格 Range 对象的 Formula 属性可用于向 Excel 工作表中的指定单元格添加公式或函数。添加公式和函数到 Excel 工作表的主要操作步骤如下:

  1. 创建一个 Workbook 的对象。
  2. 使用 Workbook.Worksheets.get() 方法获取指定的工作表。
  3. 在单元格中写入数据并设置单元格格式。
  4. 使用 Range.Formula 属性将公式和函数添加到工作表的指定单元格中。
  5. 使用 Workbook.SaveToFile() 方法保存工作簿。

下面是一个完整的代码示例,展示了在 React 中向 Excel 工作表插入数学运算、日期函数、三角函数、平均值函数和求和函数:

function App() {
  const insertFormulasAndFunctions = 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/`);

    // 创建 Workbook 对象
    const workbook = new xlsModule.Workbook();

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

    // 声明两个变量:currentRow 和 currentFormula
    let currentRow = 1;
    let currentFormula = "";

    // 设置列宽
    sheet.SetColumnWidth(1, 32);
    sheet.SetColumnWidth(2, 16);

    // 在单元格中写入数据
    sheet.Range.get({ row: currentRow, column: 1 }).Value = "测试数据";
    sheet.Range.get({ row: currentRow, column: 2 }).NumberValue = 1;
    sheet.Range.get({ row: currentRow, column: 3 }).NumberValue = 2;
    sheet.Range.get({ row: currentRow, column: 4 }).NumberValue = 3;
    sheet.Range.get({ row: currentRow, column: 5 }).NumberValue = 4;
    sheet.Range.get({ row: currentRow, column: 6 }).NumberValue = 5;
    currentRow += 2;
    sheet.Range.get({ row: currentRow, column: 1 }).Value = "公式或函数";
    sheet.Range.get({ row: currentRow, column: 2 }).Value = "结果";

    // 设置单元格格式
    let range = sheet.Range.get({ row: currentRow, column: 1, lastRow: currentRow, lastColumn: 2 });
    range.Style.Font.FontName = "黑体";
    range.Style.KnownColor = xlsModule.ExcelColors.LightGreen;
    range.Style.FillPattern = xlsModule.ExcelPatternType.Solid;
    range.Style.Borders.get(xlsModule.BordersLineType.EdgeBottom).LineStyle = xlsModule.LineStyleType.Medium;
    range.Style.Font.IsBold = true;

    // 数学运算
    currentFormula = "=1/2+3*4";
    currentRow += 1;
    sheet.Range.get({ row: currentRow, column: 1 }).NumberFormat = "@";
    sheet.Range.get({ row: currentRow, column: 1 }).Text = currentFormula;
    sheet.Range.get({ row: currentRow, column: 2 }).Formula = currentFormula;

    // 日期函数
    currentFormula = "=TODAY()";
    currentRow += 1;
    sheet.Range.get({ row: currentRow, column: 1 }).NumberFormat = "@";
    sheet.Range.get({ row: currentRow, column: 1 }).Text = currentFormula;
    sheet.Range.get({ row: currentRow, column: 2 }).Formula = currentFormula;
    sheet.Range.get({ row: currentRow, column: 2 }).Style.NumberFormat = "YYYY/MM/DD";

    // 三角函数
    currentFormula = "=SIN(PI()/6)";
    currentRow += 1;
    sheet.Range.get({ row: currentRow, column: 1 }).NumberFormat = "@";
    sheet.Range.get({ row: currentRow, column: 1 }).Text = currentFormula;
    sheet.Range.get({ row: currentRow, column: 2 }).Formula = currentFormula;

    // 平均值函数
    currentFormula = "=AVERAGE(B1:F1)";
    currentRow += 1;
    sheet.Range.get({ row: currentRow, column: 1 }).NumberFormat = "@";
    sheet.Range.get({ row: currentRow, column: 1 }).Text = currentFormula;
    sheet.Range.get({ row: currentRow, column: 2 }).Formula = currentFormula;

    // 求和函数
    currentFormula = "=SUM(B1:F1)";
    currentRow += 1;
    sheet.Range.get({ row: currentRow, column: 1 }).NumberFormat = "@";
    sheet.Range.get({ row: currentRow, column: 1 }).Text = currentFormula;
    sheet.Range.get({ row: currentRow, column: 2 }).Formula = currentFormula;

    // 保存工作簿
    const outputFileName = 'InsertFormulasAndFunctions_output.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>Insert Formulas and Functions</h1>
      <button onClick={insertFormulasAndFunctions}>
        Start
      </button>
    </div>
  );
}

export default App;

插入公式和函数到 Excel 工作表结果

插入公式和函数到 Excel 工作表


读取 Excel 工作表中的公式和函数

要读取 Excel 工作表中的公式和函数,需要循环遍历工作表中的所有已使用单元格,之后利用单元格的 HasFormula 属性找到包含公式或函数的单元格,然后用 Range.Formula 属性获取这些单元格中的公式或函数。详细操作步骤如下:

  1. 创建一个 Workbook 的对象。
  2. 使用 Workbook.LoadFromFile() 方法载入 Excel 工作簿。
  3. 使用 Workbook.Worksheets.get() 方法获取第一个工作表。
  4. 循环浏览工作表中的已使用单元格。
  5. 使用 HasFormula 属性检测一个单元格是否包含公式或函数。如果有,则使用 Range.RangeAddressLocal 属性和 Range.Formula 属性获取单元格名及其中的公式或函数,并输出获取的内容。

下面是一个完整的代码示例,展示了在 React 中遍历工作表并读取其中的公式和函数:

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

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

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

    // 创建 Workbook 对象
    const workbook = new xlsModule.Workbook();

    // 载入 Excel 工作簿
    workbook.LoadFromFile({ fileName: inputFileName });

    // 获取该工作簿的第一个工作表
    const sheet = workbook.Worksheets.get(0);

    // 获取该工作表已使用的单元格范围
    const usedRange = sheet.AllocatedRange;

    // 创建输出工作簿
    const output = new xlsModule.Workbook();
    const outSheet = output.Worksheets.get(0);
    let outRow = 1;

    // 循环遍历已使用的单元格
    for (const cell of usedRange.Cells) {
      // 判断单元格是否有公式或函数
      if (cell.HasFormula) {
        // 获取单元格名
        const cellname = cell.RangeAddressLocal;

        // 获取单元格中的公式或函数
        const formula = cell.Formula;

        // 写入读取到的单元格名和公式
        outSheet.Range.get({ row: outRow, column: 1 }).Value = "单元格" + cellname + "包含:" + formula;
        outRow += 1;
      }
    }

    // 设置输出列宽,确保文本完整显示
    outSheet.SetColumnWidth(1, 45);

    // 保存输出工作簿
    const outputFileName = 'ReadFormulasAndFunctions_output.xlsx';
    output.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

    // 释放资源
    output.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>Read Formulas and Functions</h1>
      <button onClick={readFormulasAndFunctions}>
        Start
      </button>
    </div>
  );
}

export default App;

读取 Excel 工作表中的公式和函数结果

读取 Excel 工作表中的公式和函数


常见问题

HasFormula 检测不到公式,循环没有结果

原因:目标单元格中的公式实际是以文本形式写入的(用 Text/Value 属性而非 Formula 属性),HasFormula 只对真正的公式返回 true。

解决:插入时确认使用 Range.Formula 属性;否则读取前需先把文本重新赋值为公式。

Formula 与 FormulaNumberValue 属性混淆

原因:Formula 属性返回的是单元格中的公式字符串,而 FormulaNumberValue 属性返回的是公式计算后的数值结果,两者返回的内容不同。

解决:需要公式字符串时使用 cell.Formula,需要公式计算后的数值结果时使用 cell.FormulaNumberValue,根据实际需求选择对应的属性。


获取免费许可证

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

Spire.PDF 12.9.1 现已发布。该版本修复了 XPS 转 PDF 时缩放图片丢失的问题,并优化了 PDF 转 SVG 的转换耗时。详情如下。

问题修复:

调整:


获取 Spire.PDF 12.9.1 请点击:

https://www.e-iceblue.cn/Downloads/Spire-PDF-NET.html

在日常 Excel 表格处理中,对行或列进行分组可以将明细数据折叠起来,只显示汇总信息,从而让大型表格更加简洁、便于阅读。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成分组与取消分组操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


设置行或列分组

对行或列进行分组后,可以将分组内的明细数据折叠起来,只保留需要的汇总行或汇总列,让工作表更清爽。Spire.XLS for JavaScript 通过 GroupByRows() 方法组合行、通过 GroupByColumns() 方法组合列。具体操作步骤如下:

  1. 创建 Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。
  2. 通过 Workbook.Worksheets.get() 方法获取指定工作表。
  3. 使用 Worksheet.GroupByRows() 方法组合行。
  4. 使用 Worksheet.GroupByColumns() 方法组合列。
  5. 通过 Workbook.SaveToFile() 方法保存文档到指定路径。

下面是一个完整的代码示例,展示了在 React 中设置行或列分组:

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

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

    // 将字体和 Excel 文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'GroupRowsAndColumns.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);

    // 设置行数据分组
    sheet.GroupByRows(6, 10, false);
    sheet.GroupByRows(14, 16, false);

    // 设置列数据分组
    sheet.GroupByColumns(2, 7, false);

    // 保存文档
    const outputFileName = 'GroupRowsAndColumns_output.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>Group Rows And Columns</h1>
      <button onClick={groupRowsAndColumns}>
        Start
      </button>
    </div>
  );
}

export default App;

设置行或列分组后,被分组区域左侧或上方会出现分组标记,点击标记即可折叠或展开明细数据。

设置行或列分组


取消设置行或列分组

当不再需要分组结构时,可以取消已有的分组,使所有行和列恢复为普通显示。Spire.XLS for JavaScript 通过 UngroupByRows() 方法取消行组合、通过 UngroupByColumns() 方法取消列组合。具体操作步骤如下:

  1. 创建 Workbook 对象,并使用 LoadFromFile() 方法加载包含分组的 Excel 文档。
  2. 通过 Workbook.Worksheets.get() 方法获取指定工作表。
  3. 使用 Worksheet.UngroupByRows() 方法取消行组合。
  4. 使用 Worksheet.UngroupByColumns() 方法取消列组合。
  5. 通过 Workbook.SaveToFile() 方法保存文档到指定路径。

下面是一个完整的代码示例,展示了在 React 中取消设置行或列分组:

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

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

    // 将字体和 Excel 文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'GroupRowsAndColumns.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);

    // 取消行数据组合
    sheet.UngroupByRows(6, 10);
    sheet.UngroupByRows(14, 16);

    // 取消列数据组合
    sheet.UngroupByColumns(2, 7);

    // 保存文档
    const outputFileName = 'UngroupRowsAndColumns_output.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>Ungroup Rows And Columns</h1>
      <button onClick={ungroupRowsAndColumns}>
        Start
      </button>
    </div>
  );
}

export default App;

取消分组后,行或列的分组标记消失,数据恢复为未分组的普通显示。

取消设置行或列分组


常见问题

分组后无法折叠或展开明细数据

原因:GroupByRows() 和 GroupByColumns() 方法的第三个参数 isCollapsed 设置为 false,分组默认以展开状态显示。

解决:将该参数设置为 true,保存后分组将以折叠状态显示:

sheet.GroupByRows(6, 10, true);

取消分组后,部分行或列仍然显示分组符号

原因:UngroupByRows() 和 UngroupByColumns() 方法仅取消指定范围内行或列的分组。如果这些行或列同时属于更高层级的分组,更高层级的分组符号仍然会保留。

解决:确认取消分组时传入的行列范围与设置分组时保持一致;若存在嵌套分组,可多次调用取消方法逐层取消:

sheet.UngroupByRows(6, 10);
sheet.UngroupByRows(14, 16);
sheet.UngroupByColumns(2, 7);

获取免费许可证

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

PDF 版式固定、便于分发,但其内容一经生成便不易在正文中修改。对于合同、报表、公告等文档,通常需要在不影响正文阅读的前提下标识机密等级、版权归属或“草稿”“样例”等使用状态。文本水印是解决这一问题的常见手段:它以半透明的文字浮于内容之上,既能清楚传达信息,又不破坏原文的可读性。

Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接完成 PDF 的加载、绘制与保存,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。添加文本水印通常有两种形态:一种是在每页中央沿对角线放置一行水印文字,可通过页面画布的透明度与坐标系变换直接实现;另一种是让文字在整页重复平铺,可借助 PdfTilingBrush 平铺画刷完成。

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

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


将单行文本水印添加到 PDF

单行文本水印会在每页中央生成一行斜向文字,适用于密级或版权标识。做法是:用中文字体的 PdfTrueTypeFont 搭配 MeasureString 测量文字尺寸并计算居中偏移,再逐页通过 SetTransparency、TranslateTransform、RotateTransform 设置透明度、旋转坐标系,最后用 DrawString 绘制水印文字。

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

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

    // 将支持中文的 TrueType 字体载入 VFS
    await window.spire.FetchFileToVFS('ARIAL UNICODE MS.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // 将待加水印的 PDF 文件载入 VFS
    const inputFileName = '财务报表.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

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

    // 创建 TrueType 字体:粗体、40 号
    let trueTypeFont = new pdfModule.PdfTrueTypeFont({
      fontFamily: 'Arial Unicode MS',
      size: 40,
      style: pdfModule.PdfFontStyle.Bold,
      unicode: true
    });


    // 创建水印笔刷并指定水印文本
    let brush = pdfModule.PdfBrushes.get_DarkGray();
    const text = '机密文档,不可外泄';

    // 测量水印文本的尺寸
    let textSize = trueTypeFont.MeasureString({ text: text });

    // 计算两个偏移量,用于确定坐标系平移量,使水印沿对角线居中
    let offset1 = (textSize.Width * Math.sqrt(2)) / 4;
    let offset2 = (textSize.Height * Math.sqrt(2)) / 4;
    let format = new pdfModule.PdfStringFormat({ alignment: pdfModule.PdfTextAlignment.Left });

    // 遍历文档中的所有页面
    for (let i = 0; i < doc.Pages.Count; i++) {
      // 获取指定页面
      let page = doc.Pages.get_Item(i);

      // 设置页面透明度
      page.Canvas.SetTransparency(0.8);

      // 将坐标系平移到页面中央并补偿文本尺寸带来的偏移
      page.Canvas.TranslateTransform(
        page.Canvas.ClientSize.Width / 2 - offset1 - offset2,
        page.Canvas.ClientSize.Height / 2 + offset1 - offset2
      );

      // 逆时针旋转坐标系 45 度
      page.Canvas.RotateTransform({ angle: -45 });

      // 在页面上绘制水印文本
      page.Canvas.DrawString({ s: text, font: trueTypeFont, brush: brush, x: 0, y: 0, format: format });
    }

    // 定义输出文件名并保存文档
    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>Add Single-line Text Watermark To PDF</h1>
      <button onClick={addSingleLineTextWatermark}>
        Generate
      </button>
    </div>
  );
}

export default App;

添加单行文本水印后的 PDF 文档

添加单行文本水印后的 PDF 文档


将多行文本水印添加到 PDF

当需要让水印铺满整页时,可使用多行文本水印。做法是:用 PdfTilingBrush 按页面尺寸划分平铺单元,在单元内以 SetTransparency、RotateTransform 调整透明度与角度并用 DrawString 绘制文字,最后以 DrawRectangle 将画刷填充整页即可。

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

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

    // 将支持中文的 TrueType 字体载入 VFS
    await window.spire.FetchFileToVFS('ARIAL UNICODE MS.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // 将待加水印的 PDF 文件载入 VFS
    const inputFileName = '财务报表.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

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

    // 获取文档的第一页
    let page = doc.Pages.get_Item(0);

    // 创建平铺画刷:以页面的一半宽度、三分之一高度作为平铺单元
    let size = new pdfModule.SizeF({
      width: page.Canvas.ClientSize.Width / 2,
      height: page.Canvas.ClientSize.Height / 3
    });
    let brush = new pdfModule.PdfTilingBrush({ size: size });

    // 设置水印透明度为 30%
    brush.Graphics.SetTransparency(0.3);

    // 保存画刷当前状态,并将坐标系平移、旋转,使水印呈斜向排列
    brush.Graphics.Save();
    brush.Graphics.TranslateTransform(brush.Size.Width / 2, brush.Size.Height / 2);
    brush.Graphics.RotateTransform({ angle: -45 });

    // 绘制平铺水印
    let format = new pdfModule.PdfStringFormat({ alignment: pdfModule.PdfTextAlignment.Center });
    
    // 创建 TrueType 字体:粗体、25 号
    let trueTypeFont = new pdfModule.PdfTrueTypeFont({
      fontFamily: 'Arial Unicode MS',
      size: 25,
      style: pdfModule.PdfFontStyle.Bold,
      unicode: true
    });

    // 绘制水印
    brush.Graphics.DrawString({
      s: "机密文档",
      font: trueTypeFont,
      brush: pdfModule.PdfBrushes.get_DarkRed(),
      x: 0,
      y: -18,
      format: format
    });

    // 恢复画刷之前的状态,并还原为不透明
    brush.Graphics.Restore();
    brush.Graphics.SetTransparency({ alpha: 1 });

    // 用平铺画刷填充整页矩形,使水印文字平铺覆盖整个页面
    let rect = new pdfModule.RectangleF({
      location: new pdfModule.PointF(0, 0),
      size: page.Canvas.ClientSize
    });
    page.Canvas.DrawRectangle({ brush: brush, rectangle: rect });

    // 定义输出文件名并保存文档
    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>Add Multiline Text Watermark To PDF</h1>
      <button onClick={addMultilineTextWatermark}>
        Generate
      </button>
    </div>
  );
}

export default App;

添加多行文本水印后的 PDF 文档

添加多行文本水印后的 PDF 文档


常见问题

如何设置水印文字的字体、字号与颜色

原因:DrawString 需要显式指定绘制文本所用的字体与画刷。

解决:英文字符可使用 PdfFont 配合内置的 PdfFontFamily(如 Helvetica),通过 PdfBrushes 的静态颜色属性指定水印颜色;需要中文等多语言文字或加粗等样式时,应先将对应的 TrueType 字体载入 VFS,再使用 PdfTrueTypeFont:

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

// 创建中文字体与水印画刷
let font = new pdfModule.PdfTrueTypeFont({
  fontFamily: '宋体',
  size: 24,
  style: pdfModule.PdfFontStyle.Regular,
  unicode: true
});
let brush = new pdfModule.PdfSolidBrush({
  pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_DarkRed() })
});

// 在页面上绘制中文水印(示例中绘制于页面画布)
page.Canvas.DrawString({ s: '机密文件', font: font, brush: brush, x: 0, y: 0, format: format });

如何给 PDF 的每一页都添加水印

原因:单行文本水印示例中已通过页面循环将水印应用到每一页,而多行文本水印示例仅通过 doc.Pages.get_Item(0) 作用于第一页。

解决:需要让多行水印也覆盖整篇文档时,将平铺画刷的创建与页面填充放入页面循环即可:

for (let i = 0; i < doc.Pages.Count; i++) {
  let page = doc.Pages.get_Item(i);

  // 创建平铺画刷并设置透明度、旋转与文字
  let size = new pdfModule.SizeF({
    width: page.Canvas.ClientSize.Width / 2,
    height: page.Canvas.ClientSize.Height / 3
  });
  let brush = new pdfModule.PdfTilingBrush({ size: size });
  // …… 设置透明度、旋转并绘制水印文字 ……

  // 用平铺画刷填充当前页面
  page.Canvas.DrawRectangle({
    brush: brush,
    rectangle: new pdfModule.RectangleF({ location: new pdfModule.PointF(0, 0), size: page.Canvas.ClientSize })
  });
}

如何控制水印的透明度与旋转角度

原因:透明度过高或过低会影响水印的观感,旋转角度决定水印文字的方向。

解决:通过 SetTransparency 设置透明度,取值范围为 0(完全透明)到 1(不透明);通过 RotateTransform 控制坐标系旋转角度,负值表示逆时针旋转。单行示例将透明度设为 0.8、旋转 -45 度,多行平铺示例在平铺画刷的图形上下文中做了相同设置:

// 单行水印:设置页面透明度并旋转页面画布
page.Canvas.SetTransparency(0.8);
page.Canvas.RotateTransform({ angle: -45 });

// 多行平铺水印:在平铺画刷的图形上下文中设置透明度并旋转
brush.Graphics.SetTransparency(0.3);
brush.Graphics.RotateTransform({ angle: -45 });

获取免费许可证

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

批注(Comments)是 Excel 中用于对单元格内容进行补充说明的重要工具,常用于数据审核、协作备注等场景。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成批注的添加、读取、编辑与删除操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

本文介绍几个常用功能点:

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


添加批注

添加批注同时可以携带作者信息,便于识别批注的来源。

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

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

    // 将字体和 Excel 文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'CommentsSample.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 range = sheet.Range.get('C1');

    // 设置作者和批注内容
    const author = 'E-iceblue';
    const text = '这是一个示例,展示如何添加带可编辑作者属性的批注。';

    // 向单元格添加批注并设置属性
    const comment = range.AddComment();
    comment.Width = 200;
    comment.IsVisible = true;
    comment.Text = author + ':\n' + text;

    // 为批注中的作者名称设置字体样式
    const font = workbook.CreateFont();
    font.FontName = 'Arial';
    font.KnownColor = xlsModule.ExcelColors.Black;
    font.IsBold = true;
    comment.RichText.SetFont(0, author.length, font);

    // 保存工作簿
    const outputFileName = 'AddCommentWithAuthor_output.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>Add Comment With Author</h1>
      <button onClick={addCommentWithAuthor}>Start</button>
    </div>
  );
}

export default App;

运行后,单元格 C1 上会出现一个包含作者名称和批注文本的批注。 添加批注并设置作者


读取批注内容

通过 CellRange.Comment 属性可以读取单元格上的批注。

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

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

    // 将 Excel 文件载入 VFS
    const inputFileName = 'CommentsSample.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 builder = [];
    builder.push(sheet.Range.get('A1').Comment.Text + '\n\t');
    builder.push(sheet.Range.get('A2').Comment.Text);

    // 将批注内容保存为 txt 文件
    const outputFileName = 'ReadComment_output.txt';
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, builder.join('\n'));
    workbook.Dispose();

    // 从 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>Read Comment</h1>
      <button onClick={readComment}>Start</button>
    </div>
  );
}

export default App;

读取A1,A2单元格的批注内容 读取的批注内容


编辑批注内容

通过 Comments.get(0) 按索引获取批注,然后修改其文本内容。

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

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

    // 将字体和 Excel 文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'CommentsSample.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 comment = sheet.Comments.get(0);

    // 编辑批注内容
    comment.Text = '该批注已由 Spire.XLS 编辑。';

    // 保存工作簿
    const outputFileName = 'EditExcelComment_output.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>Edit Excel Comment</h1>
      <button onClick={editComment}>Start</button>
    </div>
  );
}

export default App;

编辑A1的批注内容

编辑后批注内容


删除批注

通过 Comments.Clear 方法可以删除工作表中的所有批注。

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

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

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

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

    // 获取第一个工作表的全部批注
    const comments = workbook.Worksheets.get(0).Comments;

    // 清除批注,也可以使用 comments.RemoveAt(0) 按索引进行删除
    comments.Clear();

    // 保存工作簿
    const outputFileName = 'RemoveComment_output.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>Remove Comment</h1>
      <button onClick={removeComment}>Start</button>
    </div>
  );
}

export default App;

删除批注后

删除批注后


常见问题

添加批注后批注不可见

原因:添加批注后未设置 IsVisible 属性为 true,批注默认处于隐藏状态。

解决:添加批注后设置 comment.IsVisible = true,即可使批注在工作表中显示。

读取批注时得到空内容

原因:目标单元格上不存在批注,或者使用了错误的单元格引用。

解决:确认目标单元格已添加批注,并通过 sheet.Range.get('A1').Comment 等方式访问批注内容。


获取免费许可证

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

我们很高兴地宣布 Spire.Doc 14.9.1 版本正式发布。该版本新增强大的 Word 文档主题管理功能,同时优化图表样式与文档导出的字体嵌入相关配置。开发者现在可通过代码完整自定义、复制和编辑 Word 文档主题,统一多份文件的视觉风格。

此外,两项影响文档转换与页面删除的关键漏洞已完成修复,有效提升整体文档处理稳定性。详细更新内容如下。

新功能:

问题修复:


获取 Spire.Doc 14.9.1 请点击:

https://www.e-iceblue.cn/Downloads/Spire-Doc-NET.html

在制作报表时,为单元格设置背景颜色可以突出标题和重点数据,为工作表设置背景图片则能让整张报表更有辨识度。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成这两类设置,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


设置单元格背景颜色

为单元格设置背景颜色可以突出显示表头、重要数据或特定区域。Spire.XLS for JavaScript 通过 CellRange.Style.Color 属性为单元格或单元格区域设置背景颜色,支持丰富的内置颜色。具体操作步骤如下:

  1. 创建 Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。
  2. 通过 Workbook.Worksheets.get() 方法获取指定工作表。
  3. 使用 CellRange.Style.Color 属性为指定单元格区域设置背景颜色。
  4. 通过 Workbook.SaveToFile() 方法保存文档到指定路径。

下面是一个完整的代码示例,展示了在 React 中为单元格区域设置背景颜色:

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

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

    // 将字体和 Excel 文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'SetBackgroundColor.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);

    // 将表头行设置为黄色背景
    sheet.Range.get("A1:E1").Style.Color = xlsModule.Color.get_Yellow();

    // 将前两行数据设置为浅蓝色背景
    sheet.Range.get("A2:E2").Style.Color = xlsModule.Color.get_LightSkyBlue();
    sheet.Range.get("A3:E3").Style.Color = xlsModule.Color.get_LightSkyBlue();

    // 保存文档
    const outputFileName = 'SetBackgroundColor_output.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>Set Cell Background Color</h1>
      <button onClick={setBackgroundColor}>
        Start
      </button>
    </div>
  );
}

export default App;

设置背景颜色后,表头行显示为黄色背景,前两行数据显示为浅蓝色背景,可以直观地区分不同区域的单元格。

设置单元格背景颜色


设置工作表背景图片

除了为单元格设置背景颜色,还可以为整个工作表设置背景图片,让报表更具辨识度。Spire.XLS for JavaScript 通过 Worksheet.PageSetup.BackgroundImage 属性将图片设置为工作表背景。具体操作步骤如下:

  1. 创建 Workbook 对象,并使用 LoadFromFile() 方法加载 Excel 文档。
  2. 通过 Workbook.Worksheets.get() 方法获取指定工作表。
  3. 使用 Stream 对象读取要作为背景的图片文件。
  4. 使用 Worksheet.PageSetup.BackgroundImage 属性将图片设置为工作表背景。
  5. 通过 Workbook.SaveToFile() 方法保存文档到指定路径。

下面是一个完整的代码示例,展示了在 React 中为工作表设置背景图片:

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

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

    // 将字体、图片和 Excel 文件载入 VFS
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const backgroundImageName = 'Background.png';
    await window.spire.FetchFileToVFS(backgroundImageName, '', `${process.env.PUBLIC_URL}data/`);
    const inputFileName = 'SetBackgroundColor.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 bm = new xlsModule.Stream(backgroundImageName);

    // 将图片设置为工作表背景
    sheet.PageSetup.BackgroundImage = bm;

    // 保存文档
    const outputFileName = 'SetBackgroundImage_output.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>Set Worksheet Background Image</h1>
      <button onClick={setBackgroundImage}>
        Start
      </button>
    </div>
  );
}

export default App;

设置背景图片后,图片会作为工作表背景铺满在工作表后方,单元格内容和数据仍清晰显示在图片之上。

设置工作表背景图片


常见问题

设置背景颜色后,保存再打开颜色丢失

原因:Style.Color 属性设置的是单元格的背景(填充)颜色,而不是字体颜色。如果设置的颜色被其他样式覆盖,或者没有正确设置填充模式,颜色可能无法正常显示。

解决:直接为单元格区域设置颜色即可,例如 sheet.Range.get("A1:E1").Style.Color = xlsModule.Color.get_Yellow();。如果希望使用带图案的填充,可以结合 Style.Interior.FillPattern 和 Style.Interior.Gradient 一起使用。

背景图片没有显示在数据上方

原因:工作表背景图片始终显示在单元格内容的后方,仅作为背景装饰,不会被数据遮挡或遮挡数据。

解决:这是正常的显示层级关系。如果需要让图片显示在数据之上,请使用 Worksheet.Pictures.Add() 方法在工作表中插入浮动图片,而不是设置工作表背景。


获取免费许可证

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