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

Spire.Cloud 纯前端文档控件

形状是 Excel 中用来增强工作表视觉效果、直观传达信息的图形元素,例如箭头、矩形、椭圆、星形等。借助形状,您可以在数据旁添加标注、流程示意或装饰元素,让报表更加生动易读。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成对 Excel 形状的添加、读取与删除操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。它提供了完整的 API 用于添加形状并自定义其外观(如填充、旋转角度、文本与阴影)、读取形状中的文本和图片,以及删除指定或全部形状。

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

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


在 Excel 中添加形状

在 Excel 中添加形状可以突出重点数据、美化工作表布局。Spire.XLS for JavaScript 支持通过 sheet.PrstGeomShapes.AddPrstGeomShape() 方法添加形状并同时设置其位置(行、列)与大小(宽度、高度),并通过形状的 Fill 属性设置单色、渐变、纹理或图片填充,通过 Text 属性向形状添加文本,通过 Rotation 属性设置形状的倾斜角度,通过 Shadow 属性设置阴影效果,通过 Visible 属性控制形状的显示与隐藏。具体操作步骤如下:

  1. 创建 Workbook 对象并获取默认工作表。
  2. 使用 PrstGeomShapes.AddPrstGeomShape() 方法添加形状,并通过参数设置形状类型、位置和大小。
  3. 通过 Fill 属性为形状设置单色、渐变、纹理或图片填充。
  4. 通过 Text 属性向形状添加文本,通过 Rotation 属性设置形状的倾斜角度。
  5. 通过 Shadow 属性为形状设置阴影效果。
  6. 使用 SaveToFile() 保存工作簿为 Excel 文件。

下面是一个完整的代码示例,展示了在 React 中添加并自定义各种形状:

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

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

    // 将图片加载到虚拟文件系统(VFS)
    await window.spire.FetchFileToVFS('SpireXls.png', '', `${process.env.PUBLIC_URL}/image/`);

    // 创建新工作簿并获取默认工作表
    const workbook = new xlsModule.Workbook();
    let sheet = workbook.Worksheets.get(0);

    // 添加三角形形状并填充纯色
    let triangle = sheet.PrstGeomShapes.AddPrstGeomShape(2, 2, 100, 100, xlsModule.PrstGeomShapeType.Triangle);
    triangle.Fill.ForeColor = xlsModule.Color.get_Yellow();
    triangle.Fill.FillType = xlsModule.ShapeFillType.SolidColor;
    // 向三角形添加文本并设置倾斜角度
    triangle.Text = 'Triangle';
    triangle.Rotation = 45;

    // 添加心形形状并填充渐变色
    let heart = sheet.PrstGeomShapes.AddPrstGeomShape(2, 5, 100, 100, xlsModule.PrstGeomShapeType.Heart);
    heart.Fill.ForeColor = xlsModule.Color.get_Red();
    heart.Fill.FillType = xlsModule.ShapeFillType.Gradient;
    // 为心形设置阴影效果
    heart.Shadow.Angle = 90;
    heart.Shadow.Distance = 10;
    heart.Shadow.Size = 150;
    heart.Shadow.Color = xlsModule.Color.get_Gray();
    heart.Shadow.Blur = 30;
    heart.Shadow.Transparency = 1;
    heart.Shadow.HasCustomStyle = true;

    // 添加箭头形状
    let arrow = sheet.PrstGeomShapes.AddPrstGeomShape(10, 2, 100, 100, xlsModule.PrstGeomShapeType.CurvedRightArrow);

    // 添加云朵形状并填充图片
    let cloud = sheet.PrstGeomShapes.AddPrstGeomShape(10, 5, 100, 100, xlsModule.PrstGeomShapeType.Cloud);
    cloud.Fill.CustomPicture({ im: new xlsModule.Stream('SpireXls.png'), name: 'SpireXls.png' });

    // 保存工作簿
    const outputFileName = 'AddShapes.xlsx';
    workbook.SaveToFile(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 Shapes</h1>
      <button onClick={addShapes}>
        Generate
      </button>
    </div>
  );
}

export default App;

使用 Spire.XLS for JavaScript 在 Excel 中添加形状后的效果

使用 Spire.XLS for JavaScript 在 Excel 中添加形状


读取 Excel 形状中的文本和图片

读取形状中的文本和图片,可以帮助您批量提取形状中的数据,或对形状资源进行复用与归档。Spire.XLS for JavaScript 支持加载包含形状的 Excel 文件,通过 PrstGeomShapes.get() 方法按索引获取指定形状,再通过形状的 Text 属性读取其文本内容,通过 Fill.Picture 属性获取其填充图片。具体操作步骤如下:

  1. 创建 Workbook 对象并加载包含形状的已有 Excel 文件。
  2. 通过 workbook.Worksheets.get() 获取工作表。
  3. 使用 sheet.PrstGeomShapes.get() 按索引获取指定形状。
  4. 通过形状的 Text 属性读取形状中的文本。
  5. 通过 Fill.Picture 属性读取形状中的填充图片。
  6. 将读取的文本和图片保存为 txt 和 png 文件。

下面是一个完整的代码示例,展示了在 React 中读取形状中的文本和图片(示例加载上一节生成的 AddShapes.xlsx 文件):

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

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

    // 将包含形状的示例文件加载到虚拟文件系统(VFS)
    let excelFileName = 'AddShapes.xlsx';
    await window.spire.FetchFileToVFS(excelFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 创建 Workbook 对象并加载已有文件
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: excelFileName });

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

    // 获取第一个形状(三角形)并读取其中的文本
    let triangle = sheet.PrstGeomShapes.get(0);
    let text = triangle.Text;

    // 获取第四个形状(云朵)并读取其中的图片
    let cloud = sheet.PrstGeomShapes.get(3);
    let image = cloud.Fill.Picture;
    const imageFileName = 'ExtractImageFromShape.png';
    image.Save(imageFileName);

    workbook.Dispose();

    // 将读取的文本保存为 txt 文件并触发下载
    const textFileName = 'ExtractTextFromShape.txt';
    const textBlob = new Blob([`The text in the first shape is: ${text}`], { type: 'text/plain;charset=utf-8' });
    const textUrl = URL.createObjectURL(textBlob);
    const a1 = document.createElement('a');
    a1.href = textUrl;
    a1.download = textFileName;
    a1.click();
    URL.revokeObjectURL(textUrl);

    // 从 VFS 读取图片文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(imageFileName);
    const blob = new Blob([fileArray], { type: 'application/png' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = imageFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Read Text and Image from Shapes</h1>
      <button onClick={readShapes}>
        Generate
      </button>
    </div>
  );
}

export default App;

使用 Spire.XLS for JavaScript 读取形状中的文本和图片

使用 Spire.XLS for JavaScript 读取形状中的文本和图片


删除 Excel 中的形状

当形状不再需要时,及时删除可以让工作表保持整洁、减小文件体积。Spire.XLS for JavaScript 支持通过 Remove() 方法删除指定形状,也支持遍历形状集合并逐个调用 Remove() 方法清除工作表中的全部形状。具体操作步骤如下:

  1. 创建 Workbook 对象并加载包含形状的已有 Excel 文件。
  2. 通过 workbook.Worksheets.get() 获取工作表。
  3. 使用 sheet.PrstGeomShapes.get() 获取指定形状,并调用其 Remove() 方法删除该形状。
  4. 使用 SaveToFile() 保存工作簿为 Excel 文件。

下面是一个完整的代码示例,展示了在 React 中删除形状(示例加载上一节生成的 AddShapes.xlsx 文件):

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

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

    // 将包含形状的示例文件加载到虚拟文件系统(VFS)
    let excelFileName = 'AddShapes.xlsx';
    await window.spire.FetchFileToVFS(excelFileName, '', `${process.env.PUBLIC_URL}data/`);

    // 创建 Workbook 对象并加载已有文件
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: excelFileName });

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

    // 删除工作表中的第一个形状
    sheet.PrstGeomShapes.get(0).Remove();

    // 删除工作表中的全部形状
    // for (let i = sheet.PrstGeomShapes.Count - 1; i >= 0; i--) {
    //   sheet.PrstGeomShapes.get(i).Remove();
    // }

    // 保存工作簿
    const outputFileName = 'DeleteShapes.xlsx';
    workbook.SaveToFile(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>Delete Shapes</h1>
      <button onClick={deleteShapes}>
        Generate
      </button>
    </div>
  );
}

export default App;

使用 Spire.XLS for JavaScript 删除指定形状后的效果

使用 Spire.XLS for JavaScript 删除指定形状后的效果


常见问题

如何获取形状的名称和类型?

原因:当工作表中形状较多时,您可能需要通过形状的名称或类型来识别和定位形状,而不是依赖索引。

解决:读取形状的 Name 和 PrstShapeType 属性即可获取形状的名称和类型:

// 获取工作表
let sheet = workbook.Worksheets.get(0);
// 获取第一个形状
let shape = sheet.PrstGeomShapes.get(0);
// 获取形状的名称
let shapeName = shape.Name;
// 获取形状的类型
let shapeType = shape.PrstShapeType;

如何判断形状当前是否可见?

原因:从文件中加载形状后,有时需要判断形状是否被隐藏,以便决定是否对其进行进一步处理。

解决:读取形状的 Visible 属性即可获知形状的可见状态:

// 获取工作表
let sheet = workbook.Worksheets.get(0);
// 获取第一个形状
let shape = sheet.PrstGeomShapes.get(0);
// 判断形状是否可见
let isVisible = shape.Visible;

获取免费许可证

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

将 Python 代码转换为 Word 文件

开发人员经常需要将 Python 代码添加到 Word 文档中,用于技术文档、教程、代码审查、内部报告或客户交付材料。对于少量代码片段,手动复制粘贴即可完成;但在处理较长的脚本或多个文件时,自动化方案能够提供更好的一致性、更强的格式控制能力以及更高的可扩展性。

本教程将介绍多种使用 Python 将 Python 代码导出到 Word 文档的实用方法。每种方法都有各自的优势,具体可以根据你对格式设置、自动化、语法高亮或可读性的需求进行选择。

安装所需库

在运行示例之前,请先安装必要的依赖:

pip install spire.doc pygments

库概览:

  • Spire.Doc for Python — 用于以编程方式创建和操作 Word 文档
  • Pygments — 用于将代码生成带语法高亮的 RTF、HTML 或图片格式
  • Pathlib(内置库)— 用于从磁盘读取 Python 文件
  • textwrap(内置库)— 用于在生成图片格式的代码之前对过长的代码行进行换行

将 Python 代码以纯文本形式导出到 Word

将代码以纯文本形式插入是将代码嵌入 Word 最直接的方法。它可以使脚本保持完全可编辑,同时保留缩进和换行等格式。

方法 1:将原始 Python 代码插入 Word 文档

此方法读取 .py 文件,并将代码直接插入 Word,同时应用等宽字体样式。

from pathlib import Path
from spire.doc import *

# 读取 Python 文件
code_string = Path("demo.py").read_text(encoding="utf-8")

# 创建一个 Word 文档
doc = Document()

# 添加一个节
section = doc.AddSection()
section.PageSetup.Margins.All = 60

# 添加一个段落
paragraph = section.AddParagraph()

# 将代码字符串插入段落
paragraph.AppendText(code_string)

# 创建一个段落样式
style = ParagraphStyle(doc)
style.Name = "code"
style.CharacterFormat.FontName = "Consolas"
style.CharacterFormat.FontSize = 12
style.ParagraphFormat.LineSpacing = 12
doc.Styles.Add(style)

# 将样式应用于段落
paragraph.ApplyStyle("code")

# 保存文档
doc.SaveToFile("Output.docx", FileFormat.Docx2019)
doc.Dispose()

工作原理:

此方法将 Python 代码作为纯文本处理,并将其直接插入 Word 段落中。脚本通过 Path.read_text() 读取 .py 文件,同时保留缩进、空行以及整体代码结构。

插入文本后,创建一个自定义段落样式并应用到代码段落。使用 Consolas 这样的等宽字体可确保代码对齐并提高可读性,而固定的行距则能保持各行格式一致。

由于整个过程不需要使用中间格式,因此这是最简单、最快速的方法。但是,它不提供语法高亮或语义样式——Word 仅将代码显示为格式化文本。

输出:

将原始 Python 代码插入 Word 文档

你可能还喜欢: 使用 Python 生成 Word 文档

方法 2:从 Markdown 包裹的代码生成 Word 文件

如果你的工作流已使用 Markdown,将 Python 代码包裹在围栏代码块中,可为将脚本转换为 Word 文档提供一种结构化的方法。

from pathlib import Path
from spire.doc import *

# 读取 Python 文件
code = Path("demo.py").read_text(encoding="utf-8")

# 转换为 Markdown
md_content = f"```python\n{code}\n```"
Path("temp.md").write_text(md_content, encoding="utf-8")

# 将 Markdown 加载到 Word 中
doc = Document()
doc.LoadFromFile("temp.md")

# 更新页面设置
doc.Sections[0].PageSetup.Margins.All = 60

# 保存为 DOCX 文件
doc.SaveToFile("Output.docx", FileFormat.Docx)
doc.Dispose()

工作原理:

与直接插入文本不同,此方法会将 Python 代码包裹在 Markdown 围栏代码块中。然后,使用 Spire.Doc 的 Markdown 解析功能将生成的 Markdown 文件加载到 Word 中。

当 Word 导入 Markdown 时,它会自动保留缩进和换行等代码格式。该方法适用于已经使用 Markdown 作为文档工作流程的场景,也适合代码需要与标题、列表以及说明性文字共存的技术文档。

由于 Markdown 本身并不会自动在 Word 中为代码应用语法颜色,因此最终结果仍然是纯代码格式。不过,在技术文档处理流程中,这种方式的结构更加清晰,也更容易管理。

输出:

从 Markdown 包裹的代码生成 Word 文件

将带语法高亮的 Python 代码添加到 Word

语法高亮可以让代码更易于阅读和理解。通过集成 Pygments,Python 脚本可以在嵌入 Word 之前转换为带样式的格式。

本节将探讨三种方法——RTF、HTML 和图片渲染。每种方法都有不同的优势,可以根据具体的格式需求进行选择。

方法 1:使用 RTF 创建预格式化代码块

RTF 允许语法高亮的代码在 Word 中保持完全可编辑状态。

from pathlib import Path
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import RtfFormatter
from spire.doc import *

# 读取 Python 文件
code = Path("demo.py").read_text(encoding="utf-8")

# 设置字体
formatter = RtfFormatter(fontface ="Consolas")

# 指定词法分析器
rtf_text = highlight(code, PythonLexer(), formatter)
rtf_text = rtf_text.replace(r"\f0", r"\f0\fs24") # 字体大小(24 对应 12 磅字体)

# 创建一个 Word 文档
doc = Document()

# 添加一个节
section = doc.AddSection()
section.PageSetup.Margins.All = 60

# 添加一个段落
paragraph = section.AddParagraph()

# 将语法高亮的代码作为 RTF 插入
paragraph.AppendRTF(rtf_text)

# 保存文档
doc.SaveToFile("Output.docx", FileFormat.Docx2019)
doc.Dispose()

工作原理:

Pygments 使用 **lexer(词法分析器)**分析 Python 语法,识别关键字、字符串和注释等不同类型的代码标记。RTF 格式化程序应用样式规则,使用 RTF 控制字来表示颜色和字体。

生成的 RTF 字符串通过 AppendRTF() 直接插入 Word。由于 RTF 是一种与 Word 原生兼容的格式,文档无需额外渲染步骤即可保留字体、颜色和间距。

通过修改 RTF 控制字(例如 \fs24)可以控制字体大小,从而精确调整代码的显示效果。此方法能在 Word 中生成可编辑、可选择并带有语法高亮的代码。

输出:

将带语法高亮的 Python 代码添加到 Word

方法 2:通过 HTML 格式渲染高亮代码

HTML 渲染提供视觉丰富的语法高亮和自动文本换行。

from pathlib import Path
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
from spire.doc import *

# 读取 Python 文件
code = Path("demo.py").read_text(encoding="utf-8")

# 从 Python 代码生成带有语法高亮的 HTML
html_text = highlight(code, PythonLexer(), HtmlFormatter(full=True))

# 创建一个 Word 文档
doc = Document()

# 添加一个节
section = doc.AddSection()
section.PageSetup.Margins.All = 60

# 添加一个段落
paragraph = section.AddParagraph()

# 将 HTML 字符串添加到段落
paragraph.AppendHTML(html_text)

# 保存文档
doc.SaveToFile("Output.docx", FileFormat.Docx2019)
doc.Dispose()

工作原理:

此处,Pygments 使用 HtmlFormatter 将 Python 代码转换为带样式的 HTML。HTML 输出包含表示语法颜色和格式的内联样式或 CSS 规则。

Spire.Doc 随后解析 HTML 内容,并将其渲染到 Word 中。在这一过程中,HTML 元素会被转换为 Word 的格式结构,使带语法高亮的代码在视觉效果上与网页中的代码块保持相似。

当代码来源于网页内容、静态文档网站或 Markdown 转 HTML 工作流时,这种方法尤其适用。

输出:

通过 HTML 格式渲染高亮代码

你可能还喜欢: 在 Python 中将 HTML 转换为 Word DOC 或 DOCX

方法 3:将带语法高亮的代码作为图片插入

在视觉一致性比可编辑性更重要的情况下,可以先将代码渲染为图片,然后再插入 Word。

from pathlib import Path
import textwrap
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import ImageFormatter
from spire.doc import *

# 读取 Python 文件
code = Path("demo.py").read_text(encoding="utf-8")

# 手动换行长行
def wrap_code_lines(code_text, max_width=75):
    wrapped_lines = []
    for line in code_text.splitlines():
        if len(line) > max_width:
            wrapped_lines.extend(textwrap.wrap(
                line,
                width=max_width,
                replace_whitespace=False,
                drop_whitespace=False
            ))
        else:
            wrapped_lines.append(line)
    return "\n".join(wrapped_lines)

code = wrap_code_lines(code, max_width=75)

# 生成图片
formatter = ImageFormatter(
    font_name="Consolas",
    font_size=18,
    scale=2,            
    image_pad=10,
    line_pad=2,
    background_color="#ffffff"
)

img_bytes = highlight(code, PythonLexer(), formatter)

with open("code.png", "wb") as f:
    f.write(img_bytes)

# 创建一个 Word 文档
doc = Document()
section = doc.AddSection()
section.PageSetup.Margins.All = 60

# 插入到 Word
paragraph = section.AddParagraph()
picture = paragraph.AppendPicture("code.png")

# 确保图片适应页面宽度
page_width = (
    section.PageSetup.PageSize.Width
    - section.PageSetup.Margins.Left
    - section.PageSetup.Margins.Right
)
picture.Width = page_width

# 保存文档
doc.SaveToFile("Output.docx", FileFormat.Docx2019)
doc.Dispose()

工作原理:

此方法将 Python 代码渲染为图片而非可编辑文本。Pygments 使用 ImageFormatter 生成带语法高亮的位图,允许对字体、颜色、内边距和 DPI 进行全面视觉控制。

由于图片渲染不会自动处理过长代码行,因此脚本会先使用 Python 的 textwrap 模块手动对较长的代码行进行换行,再生成图片。这样可以避免生成超出页面宽度的图片。

将图片插入 Word 后,会动态调整图片宽度,使其适应页面的可打印区域。由于代码以图形形式嵌入,因此可以在不同平台上保持一致的视觉效果,并避免格式不一致的问题;但文本将不再可编辑。

输出:

将带语法高亮的代码作为图片插入

结论

根据具体需求,可以通过多种方式将 Python 代码转换为 Word 文档。纯文本方法简单灵活,而 RTF 和 HTML 方法可以在保留可选择文本的同时提供强大的语法高亮功能。基于图片的代码块能够提供一致的视觉格式,但需要注意代码换行和图片缩放问题。

对于大多数文档工作流程:

  • 对于可编辑的技术内容,使用纯文本
  • 对于带语法高亮的文档,使用 HTML 或 RTF
  • 当格式一致性至关重要时,使用图片

常见问题

问题 1:哪种方法最适合教程?

HTML 或 RTF 方法提供清晰的语法高亮,同时保留文本的可选择性。

问题 2:如何保留缩进和空行?

使用 .read_text() 读取 .py 文件时,不要对代码行进行删除或修改。

问题 3:为什么基于图片的代码块会变得太小?

Word 会将图片缩放到适合页面宽度的大小。增加图片格式化程序的缩放比例或调整换行宽度可以提高可读性。

问题 4:读者可以从 Word 中复制代码吗?

可以,除非代码是以图片形式插入的。

问题 5:转换时必须使用 Markdown 吗?

不需要。Markdown 是可选的,但在使用文档处理工作流程时非常有用。

问题 6:可以将生成的 Word 文档导出为 PDF 文件吗?

可以。在保存文档时,只需要在 Document.SaveToFile() 方法中指定 PDF 作为输出格式即可。

获取免费许可证

要充分体验 Spire.Doc for Python 的功能,不受任何评估限制,你可以申请 30 天试用许可证。

在浏览包含大量数据的 Excel 工作表时,固定表头或关键列能显著提升数据查看效率。冻结窗格功能可以让指定行或列在滚动时保持可见;查询冻结窗格范围可以确认当前工作表中哪些区域被冻结;取消冻结窗格则能在不需要固定显示时恢复常规浏览方式。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成这些操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


冻结窗格

当工作表包含大量数据时,冻结窗格可以固定表头或特定区域,使你在滚动查看数据时始终能看到关键的行或列。Spire.XLS for JavaScript 通过 FreezePanes 方法冻结指定行列上方及左侧的窗格。例如 FreezePanes(2, 1) 会冻结第一行,使其在上下滚动时保持可见。

function App() {
  const sheetToSVG = 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 = 'FreezePanes.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

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

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

    // 冻结第一行
    sheet.FreezePanes(2, 1);

    // 设置第二列的列宽
    sheet.SetColumnWidth(2, 10);

    const outputFileName = "FreezePanes_output.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>Freeze Panes</h1>
      <button onClick={sheetToSVG}>
        Start
      </button>
    </div>
  );
}

export default App;

原文档 原文档 冻结首行后 冻结首行后


查询冻结窗格范围

在处理冻结窗格时,有时需要确认当前工作表中冻结窗格所在的位置。Spire.XLS for JavaScript 通过 GetFreezePanes 方法获取冻结窗格所在的行索引和列索引,返回值为 0 时表示该方向未冻结。

function App() {
  const sheetToSVG = 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 = 'GetFreezePaneRange.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

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

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

    // 获取冻结窗格所在的行索引和列索引
    const indexs = sheet.GetFreezePanes();
    const rowIndex = indexs[0];
    const colIndex = indexs[1];

    // 将查询结果写入文本文件
    const outputFileName = "GetFreezePaneRange_output.txt";
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, `Row index: ${rowIndex}, column index: ${colIndex}`);

    // 释放 workbook 对象以释放资源
    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>Get Freeze Pane Range</h1>
      <button onClick={sheetToSVG}>
        Start
      </button>
    </div>
  );
}

export default App;

含冻结窗格原文档 含冻结窗格原文档 查询结果 查询结果


取消冻结窗格

当不再需要固定显示时,可以通过 RemovePanes 方法取消工作表中已设置的冻结窗格,恢复正常滚动浏览。

function App() {
  const sheetToSVG = 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 = 'Template_Xls_2.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

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

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

    // 取消冻结窗格
    sheet.RemovePanes();

    const outputFileName = "UnfreezeExcelPanes_output.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>Unfreeze Panes</h1>
      <button onClick={sheetToSVG}>
        Start
      </button>
    </div>
  );
}

export default App;

取消冻结前 取消冻结前 取消冻结后 取消冻结后

常见问题

冻结窗格后首行仍会滚动

原因:FreezePanes 方法的参数设置不正确,导致冻结的区域不是预期的行或列。

解决:FreezePanes 方法以指定位置为分界,冻结该位置上方及左侧的窗格。例如冻结首行使用 FreezePanes(2, 1),冻结前两行使用 FreezePanes(3, 1),同时冻结首行和首列使用 FreezePanes(2, 2)。

查询冻结窗格范围时返回 0

原因:工作表尚未设置冻结窗格,因此查询到的行列索引为 0。

解决:先调用 FreezePanes 方法设置冻结窗格,再调用 GetFreezePanes 查询冻结范围。

取消冻结窗格后仍显示冻结效果

原因:取消冻结后未正确保存工作簿,或者打开的是修改前的文件。

解决:调用 RemovePanes 方法后务必通过 SaveToFile 保存工作簿,并打开输出文件确认取消效果。


获取免费许可证

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

在采购与销售场景中,比价是最核心也最耗时的环节之一,采购部门收到来自不同供应商的报价表,有的按行排列,有的按列组织,有的包含多项隐形成本,有的单位不统一。Spire.Agent.Office Excel AI 智能体能够理解不同格式的报价表,自动将各供应商的报价对齐到统一模板,计算各项合计与总价,并对最低价进行标记。

本文介绍如何使用 Spire.Agent.Office Excel AI 能力,将多个不同供应商报价表自动对齐到统一模板,计算总价对比并标记最低价。

有关产品安装和 SpireToken 配置,请参考 在 .NET 项目中集成 Spire.Agent.Office。以下示例默认已安装 Spire.Agent.Office 并完成 SpireToken 配置。


Excel格式报价单对比汇总

多版本报价单对比的核心挑战在于:每家供应商的报价表存在差异。

using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Xls;

// 不同供应商多个报价单文件
string[] attachmentPaths = new string[]
{
    @"vendor_A.xlsx",
    @"vendor_B.xlsx",
    @"vendor_C.xlsx",
    @"vendor_D.xlsx"
};

// 输出模板文件
string inputPath = @"template.xlsx";  
// 结果文档
string savePath = @"quote-comparison.xlsx";  
string key = "**************************";  
string instruction =
    "读取附件中的供应商报价表(供应商A、供应商B、供应商C、供应商D),按以下要求处理:" +
    "1.识别每家报价表中的品名、单价、数量、总价列,将其对齐到模板中对应供应商(A、B、C、D)的单价和金额列中;" +
    "2.如果某供应商未对某产品报价,对应单价和金额单元格留空并标记为'未报价';" +
    "3.为有报价的每家供应商计算各项产品的金额(数量×单价)并填入对应列,在模板底部计算各供应商的总报价;" +
    "4.在总价行中,将总报价最低的供应商单元格填充绿色背景(RGB:198,224,180);" +
    "5.在'最低价供应商'列标注每个产品的最低报价供应商名称,并在'最低价'列填入对应的最低单价;" +
    "6.保证同原模板的布局样式、字体和列宽设置;" +
    "最终输出保存为Excel文件";

// 调用Excel文档处理函数
AIResult result = ExecuteDemoExcel(instruction, inputPath, savePath, key,  attachmentPaths);


// 执行Excel文档AI处理
static AIResult ExecuteDemoExcel(string instruction, string inputPath, string savePath, string key, string[] attachmentPaths)
{
    // 创建AIOptions选项配置对象
    AIOptions options = new AIOptions();
    options.SpireToken = key; 

    // 使用Workbook对象处理Excel文档
    using (Workbook workbook = new Workbook())
    {
        // 从文件加载Excel模板
        if (!string.IsNullOrEmpty(inputPath) && File.Exists(inputPath))
        {
            workbook.LoadFromFile(inputPath);
        }
        // 创建AI文档处理器
        AIWorkbookProcessor processor = workbook.AI(options);

        // 执行AI指令
        return processor.ExecuteInstruction(workbook, instruction, savePath, attachmentPaths);
    }
}

原各供应商Excel报价表 原各供应商报价表 原Excel模板 原模板 Excel AI对比后的汇总表 AI对比后的汇总表


PDF格式报价单对比汇总

原报价单为PDF格式,使用Spire.Agent.Office同样能轻松提取所需数据,并自动完成汇总统计。只需添加不同格式的源文档,AI指令即可复用,无需重复配置,大幅提升处理效率。

using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Xls;

// 不同供应商多个报价单文件
string[] attachmentPaths = new string[]
{
    @"vendor_A.pdf",
    @"vendor_B.pdf",
    @"vendor_C.pdf",
    @"vendor_D.pdf"
};

// 输出模板文件
string inputPath = @"template.xlsx";  
// 结果文档
string savePath = @"quote-comparison.xlsx";  
string key = "**************************";  
string instruction =
    "读取附件中的供应商报价表(供应商A、供应商B、供应商C、供应商D),按以下要求处理:" +
    "1.识别每家报价表中的品名、单价、数量、总价列,将其对齐到模板中对应供应商(A、B、C、D)的单价和金额列中;" +
    "2.如果某供应商未对某产品报价,对应单价和金额单元格留空并标记为'未报价';" +
    "3.为有报价的每家供应商计算各项产品的金额(数量×单价)并填入对应列,在模板底部计算各供应商的总报价;" +
    "4.在总价行中,将总报价最低的供应商单元格填充绿色背景(RGB:198,224,180);" +
    "5.在'最低价供应商'列标注每个产品的最低报价供应商名称,并在'最低价'列填入对应的最低单价;" +
    "6.保证同原模板的布局样式、字体和列宽设置;" +
    "最终输出保存为Excel文件";

// 调用Excel文档处理函数
AIResult result = ExecuteDemoExcel(instruction, inputPath, savePath, key,  attachmentPaths);


// 执行Excel文档AI处理
static AIResult ExecuteDemoExcel(string instruction, string inputPath, string savePath, string key, string[] attachmentPaths)
{
    // 创建AIOptions选项配置对象
    AIOptions options = new AIOptions();
    options.SpireToken = key; 

    // 使用Workbook对象处理Excel文档
    using (Workbook workbook = new Workbook())
    {
        // 从文件加载Excel模板
        if (!string.IsNullOrEmpty(inputPath) && File.Exists(inputPath))
        {
            workbook.LoadFromFile(inputPath);
        }
        // 创建AI文档处理器
        AIWorkbookProcessor processor = workbook.AI(options);

        // 执行AI指令
        return processor.ExecuteInstruction(workbook, instruction, savePath, attachmentPaths);
    }
}

原各供应商PDF报价表 原各供应商报价表 原Excel模板 原模板 Excel AI对比后的汇总表 AI对比后的汇总表


对比传统SDK API处理

Spire.Office for .NET API Spire.Agent.Office
代码量 数据读取、行列映射、公式填充、条件格式等需要大量代码 1 条自然语言指令智能处理
格式适配 不同格式报价单,传统SDK API要分别使用不同产品处理数据 只需使用Excel AI 即可处理不同格式的数据源
计算逻辑 需API设置公式等 AI 理解自动完成计算和格式设置
需求变更 改代码,重新调试 修改指令,即刻生效

常见问题

报价表中存在合并单元格导致数据读取错位

原因:供应商报价表可能包含标题合并单元格、跨行合并的分类标签等,影响 AI 对行列结构的判断。

解决:在指令中明确说明"忽略表头合并行,从第 X 行开始读取数据",或提供模板文件作为结构参考。如果仍遇到问题,可以在指令中添加"将合并单元格视为普通单元格,取其左上角值"的描述。

处理后的格式与预期不符

原因:AI 模型在理解复杂表格布局时,可能对列宽、行高、字体等细节的保留不够精确。

解决:在指令中添加"保留模板中现有的列宽、行高、字体、边框和对齐方式"等具体描述。

部分产品的供应商报价缺失

原因:不同供应商提供的产品清单不完全一致,部分供应商可能未对某些产品报价。

解决:在指令中明确缺失项的处理方式,如"将未报价的单元格标记为'未报价'或留空",AI 会自动识别并按要求处理。


获取SpireToken Key

  • 联系 该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。 或访问 https://www.e-iceblue.com/TemLicense.html 获得试用/商业 API 密钥

在代码中配置:

AIOptions options = new AIOptions();
options.SpireToken = key;

图片是内容呈现与传播中最直观的形式之一,PDF 文档则保留了原始排版,广泛用于正式文件的存储与传输。在网页、小程序、社交平台或邮件中展示 PDF 内容时,直接分发 PDF 往往不够方便,先将其转换为 PNG、JPEG 等图片格式可以快速预览与分享;反之,将扫描件或图片素材统一转换为 PDF,则便于批量归档与跨平台分发。实际业务中经常需要在两种形式间灵活切换:将 PDF 合同转为图片用于在线预览和快速分享,或者将扫描的图片素材转为 PDF 以便统一归档与传阅。

Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接完成 PDF 与图片之间的双向转换,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


转换 PDF 到图片

PDF 转图片的核心在于将 PDF 文档中的每一页内容、字体和图形元素渲染为独立的位图数据。Spire.PDF for JavaScript 通过 PdfDocument 对象的 SaveAsImage 方法逐页生成图像流,循环遍历 Pages.Count 张页面后使用 stream.Save 将每一页保存为 PNG 图片,最后借助 JSZip 把多张图片打包为 ZIP 文件以便一次性下载,全程无需手动处理像素与页面坐标的映射。

import JSZip from "jszip";

function App() {
  const convertToImage = 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('ARIAL.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);

    // 创建输出目录(存放转换后的图片)
    let outputDirectoryName = "图片/";
    window.dotnetRuntime.Module.FS.mkdirTree(outputDirectoryName);

    // 循环每一张页面并保存到图片
    for (let i = 0; i < doc.Pages.Count; i++) {
      const outputFileName = outputDirectoryName + "转换_" + i + ".png";
      let stream = doc.SaveAsImage({ pageIndex: i });
      stream.Save(outputFileName);
      stream.Dispose();
    }

    doc.Dispose();

    // 从 VFS 读取转换后的文件,触发下载
    const zip = new JSZip();
    let items = await window.dotnetRuntime.Module.FS.readdir(outputDirectoryName);
    items = items.filter((item) => item !== "."

      && item !== "..");
    for (const item of items) {
      const itemPath = `${outputDirectoryName}/${item}`;
      const fileData = await window.dotnetRuntime.Module.FS.readFile(itemPath);
      zip.file(item, fileData);
    }

    // 将 ZIP 转为 Blob 并触发浏览器下载
    const zipBlob = await zip.generateAsync({ type: "blob" });
    const url = URL.createObjectURL(zipBlob);
    const a = document.createElement('a');
    a.href = url;
    a.download = '图片';
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert PDF To Image</h1>
      <button onClick={convertToImage}>
        Generate
      </button>
    </div>
  );
}

export default App;

通过 SaveAsImage 方法将 PDF 每一页导出为 PNG 图片并打包为 ZIP 后下载

通过 SaveAsImage 方法将 PDF 每一页导出为 PNG 图片并打包为 ZIP 后下载

调整导出图片的 DPI 分辨率

通过 SaveAsImage 方法导出图片时默认使用 96 DPI,图片适合屏幕预览,但放大查看时文字和线条可能出现锯齿或模糊。若需要更清晰的图片,可以在 SaveAsImage 的参数中通过 dpiX 和 dpiY 指定分辨率,例如设置为 150 DPI:

// 以 150 DPI 的分辨率逐页导出图片
for (let i = 0; i < doc.Pages.Count; i++) {
  let stream = doc.SaveAsImage({ pageIndex: i, dpiX: 150, dpiY: 150 });
  stream.Save(outputDirectoryName + "高清_" + i + ".png");
  stream.Dispose();
}

DPI 值越大,导出图片越清晰,但文件体积也会随之增大,建议根据实际使用场景在清晰度与文件大小之间权衡取舍。


转换图片到 PDF

图片转 PDF 通常用于将扫描件或设计素材统一归档为 PDF。Spire.PDF for JavaScript 通过 PdfDocument 对象创建新文档,用 PdfImage.FromFile 方法加载图片,再借助 Pages.Add 添加页面并通过 Canvas.DrawImage 方法将图片按原始尺寸绘制到页面上,最后使用 SaveToFile 方法指定 FileFormat.PDF 保存为标准 PDF 文档。

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

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

    // 将图片文件载入 VFS
    const inputFileName = '风景图.png';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // 创建 PdfDocument 对象
    let doc = new pdfModule.PdfDocument();

    // 添加一页
    let page = doc.Pages.Add();

    // 加载图片
    let image = pdfModule.PdfImage.FromFile(inputFileName);

    // 计算缩放比例,使图片完整适配页面
    let widthFitRate = image.PhysicalDimension.Width / page.Canvas.ClientSize.Width;
    let heightFitRate = image.PhysicalDimension.Height / page.Canvas.ClientSize.Height;
    let fitRate = Math.max(widthFitRate, heightFitRate);

    // 计算图片缩放后的尺寸
    let fitWidth = image.PhysicalDimension.Width / fitRate;
    let fitHeight = image.PhysicalDimension.Height / fitRate;

    // 绘制图片到页面
    page.Canvas.DrawImage({ image: image, x: 0, y: 30, width: fitWidth, height: fitHeight });

    const outputFileName = '图片转PDF.pdf';

    // 保存为 PDF 格式
    doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.PDF });
    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>Convert Image To PDF</h1>
      <button onClick={convertImageToPDF}>
        Generate
      </button>
    </div>
  );
}

export default App;

通过 PdfImage.FromFile 加载并使用 Canvas.DrawImage 绘制图片后生成的 PDF 文档

通过 PdfImage.FromFile 加载并使用 Canvas.DrawImage 绘制图片后生成的 PDF 文档

从内存流加载图片

除了通过 PdfImage.FromFile 从文件直接加载,还支持通过 PdfImage.FromStream 方法从内存流加载图片。这种方式适合图片数据来自接口响应、数据库字段,或需要先读取字节再处理的场景。参考下面代码:

// 从 VFS 读取图片字节并构造内存流
let bytes = window.dotnetRuntime.Module.FS.readFile(inputFileName);
let stream = new pdfModule.Stream(bytes);

// 通过内存流加载图片
let image = pdfModule.PdfImage.FromStream(stream);

后续同样可以沿用 page.Canvas.DrawImage 方法将图片绘制到 PDF 页面,再使用 SaveToFile 方法保存为标准 PDF 文档。


常见问题

转换后的图片文字出现乱码

原因:PDF 依赖字体嵌入来保证跨平台显示一致性,如果输入 PDF 使用了未嵌入的字体且 VFS 中未加载对应字体文件,转换后可能出现文字显示异常。

解决:确保在调用转换前已将所需的 TrueType 字体文件(如 ARIALUNI.TTF)加载到 VFS 的 /Library/Fonts/ 目录下。ARIALUNI.TTF 包含了常用的中日韩文字符,是保证转换质量的首选字体。

支持转换为哪些图片格式

原因:不同的业务场景需要不同的位图格式,例如网页预览常用 PNG、摄影图片常用 JPEG 等。

解决:Spire.PDF for JavaScript 支持将 PDF 渲染为 PNG、JPEG、BMP等常见位图格式。使用 SaveAsImage 生成图像流后,在 stream.Save 保存时只需将输出文件名的扩展名替换为目标格式(如 .jpg、.bmp、.png),即可输出对应的图片格式。


获取免费许可证

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

AI合同审查 -- 在 .NET 中实现合同审阅与生成自动化

**AI合同审查(AI Contract Review)**指用 AI 自动审阅、提取和生成合同文档,例如从协议中抽取当事方与付款条款、标出异常条款,或基于模板批量产出合同。用 C# 进行 AI 合同审查,则是把这种语言理解与文档处理能力结合进 .NET 应用,让开发者用自然语言描述任务即可完成上述流程,而不必为每个模板编写字段映射与排版代码。在实践层面,就是用一条自然语言指令替代字段匹配代码,实现合同处理自动化。Spire.Agent.Office 是一款文档 AI 智能体(AI Agent)SDK,负责语言理解;其下的确定性文档层保证输出真实、格式正确的 Word 与 PDF 文件。

快速导航

  1. 为什么合同审查适合 AI
  2. AI 合同智能体能做什么、不能做什么
  3. 常见的合同自动化场景
  4. .NET 中实现合同处理自动化的三种方式
  5. 完整示例:用 C# 审阅和生成合同
  6. 为什么用 Spire.Agent.Office 做 AI 合同自动化
  7. 常见问题

1. 为什么合同审查适合 AI

在开发者的世界里,合同工作可以拆成三种重复劳动:阅读(从以 PDF 和 Word 形式到达的协议中提取当事方、日期、付款条款和履约义务)、核查(识别缺失条款或异常措辞)、产出(把一批员工或供应商名单变成可签署的合同)。

对 .NET 开发者而言,难点不只是理解合同内容,而是把非结构化文档转成应用自己就能维护的结构化、可重复工作流。

三个特性使这类任务特别适合用语言模型而不是手写规则完成:

  • 输入是非结构化的。 对方发来的合同格式各不相同。针对某一种排版写的规则,换一份文档就失效;而大语言模型能直接读文本。
  • 输出是文档形态的。 交付物是格式正确的 .docx 或 .pdf,不是一段纯文本。这正是文档处理层能发挥作用的地方。
  • 数量持续变化。 一个月入职 50 名员工,或审阅 200 份供应商协议,意味着需要配置化方案,而不是为每个模板重新编码。

实践中,审阅与生成总是结伴出现:团队既想汇总已有合同并标出异常,也想基于模板加结构化数据生成新合同。


2. AI 合同智能体能做什么、不能做什么

能做什么 不能做什么
提取当事方、生效日期、付款条款、履约义务 替代专业律师对高风险协议的法律审查
把冗长协议概括成一页简报 保证符合当地法律
基于模板 + 数据源批量生成合同 代替你谈判或接受条款
保持排版、表格样式与字体不变 不经复核就保证输出无误
在你的应用内运行(无需上传云端) 解读新的或模糊的法规;转交法律顾问
标出相对标准协议的异常条款 识破刻意含糊的条款背后的隐藏风险

分工很清晰:智能体负责阅读、提取和起草的自动化(省去原本交给助理律师的工时),最终判断权仍在人类律师手里。这条边界既让工具可用,也让流程经得起审视。


3. 常见的合同自动化场景

合同自动化不止一种场景。同一个模式(一条指令 + 一个模板 + 可选数据)覆盖了团队搜索最多的几类场景:

场景 示例指令
供应商协议审阅 "审阅这份供应商协议,标出与我们的标准条款不一致的付款条款、责任上限和终止条件。"
员工合同生成 "按 employees.xlsx 中的每一行,用该模板生成一份劳动合同,保持排版与样式。"
NDA 处理 "概括这份保密协议:保密期限、允许披露的情形以及违约救济。"
租赁协议分析 "从这份租赁合同中提取租金、租期、续租选项和维护义务,并列出任何异常条款。"

每个场景都是同一套架构:一条指令进,一份真实文档出。


4. .NET 中实现合同处理自动化的三种方式

实现方式 代码量 格式保真 维护成本 适用场景
文档 AI 智能体(LLM + 文档层) 一条指令 + 约 10 行代码 高(真实 Word/PDF 文件) 低(改指令即改行为) 不想自建 LLM 管线的合同自动化团队
原生 LLM API(OpenAI/Claude + 自己的代码) 高(提示词、解析、文件 I/O) 低(LLM 不原生读写 Office 文件) 高(RAG、路由、错误处理都要自己来) 已有 LLM 技术栈的团队
传统 SDK(Spire.Office 或同类) 每种文档类型几十行 高(确定性) 高(每个映射都是代码) 固定、规范、很少变更的文档

关键点:没有文档处理层的 LLM 无法编辑合同模板,没有自然语言理解能力的传统 SDK 无法理解自然语言请求。 文档 AI 智能体把两者结合了起来。

这并非否定传统路线。对固定、规范、极少变更的文档,确定性 SDK 常常是正确选择,Spire.Office 仍能满足这类需求。当模板、输入和需求变更频繁、重新编码成为瓶颈时,智能体才真正体现价值。

为什么只靠原生 LLM API 不够

直接调用 gpt-4 或 claude 让它"生成一份合同",在生产环境中会在三个方面出问题:

  1. 无法可靠读写 Office 文件。 LLM 看到的是文本,不是 .docx 和 .pdf 的结构。读取 Word 模板、保持表格完整、产出有效 PDF,通常都需要另建一套提取与重建管线。
  2. 格式没有保障。 合同模板承载着对接收方至关重要的条款编号、表格和字体。原生 LLM 只返回文本,而丢掉的格式恰恰是法务和 HR 最在意的。
  3. 整个编排都要重新实现。 提示词设计、字段映射、错误处理、文件 I/O 和输出校验都变成你要维护的代码。

文档 AI 智能体把模型的语义理解与确定性的文档 API 结合:模型决定提取或填写什么,文档层保证文件真实且格式正确。这就是演示与可交付工作流的区别。


5. 完整示例:用 C# 审阅和生成合同

下面是法务和采购团队每周都会重复的任务:审阅新到的供应商协议,然后为审核通过的供应商签发合同。实现使用 Spire.Agent.Office for .NET:一个通过自然语言指令处理 Word、Excel、PowerPoint 和 PDF 文档的 AI 智能体。示例围绕该工作流设计而非照搬教程;官方集成入门与批量生成合同教程分步说明了 API 的搭建过程,本节重点讲 C# 集成模式。

Spire.Agent.Office 工作流:供应商协议与供应商数据进入智能体,产出 Markdown 审阅简报与已签发的 PDF 合同

1. 审阅本周到达的每一份协议。 只配置一次智能体,然后读取收件箱文件夹,让智能体把每份协议概括成可粘贴进审阅跟踪表的 Markdown 简报:

using System.IO;
using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Doc;
using Spire.Pdf;

AIOptions agentOptions = new AIOptions();
agentOptions.WorkDir = @"C:\legal-ops\output";
agentOptions.SpireToken = spireToken;

string reviewPrompt =
    "审阅这份供应商协议并写出 Markdown 简报:用一张单行表格列出当事方、" +
    "生效日期、付款条款和终止条款,再用项目符号列出任何对标准供应商协议" +
    "来说异常的条款,并把简报以 Markdown 格式保存到指定输出路径。";

Directory.CreateDirectory(@"C:\legal-ops\output");

foreach (string file in Directory.GetFiles(@"C:\legal-ops\inbox", "*.pdf"))
{
    string briefPath = Path.Combine(
        @"C:\legal-ops\output", Path.GetFileNameWithoutExtension(file) + ".md");

    using (PdfDocument agreement = new PdfDocument())
    {
        agreement.LoadFromFile(file);
        AIResult result = agreement.AI(agentOptions).ExecuteInstruction(
            agreement, reviewPrompt, briefPath, new string[] { });

        if (result == null || !result.Success)
        {
            throw new InvalidOperationException(
                $"审阅失败 {Path.GetFileName(file)}: {result?.ErrorMessage}");
        }
    }
}

关键 API 调用

  • PdfDocument.LoadFromFile() -- 打开供应商协议 PDF
  • agreement.AI(agentOptions) -- 挂载 AI 文档处理器
  • ExecuteInstruction(doc, instruction, savePath, attachments) -- 执行审阅并写出 Markdown 简报
  • AIResult.Success / AIResult.ErrorMessage -- 校验结果并暴露错误

输出结果

输出示例:协议与审阅简报保存为 Markdown

2. 为审核通过的供应商签发合同。 一个模板加一份审批名单。模板用 {{Placeholder}} 占位符承载供应商数据;输出路径传 null,智能体就会把每位供应商的独立 PDF 写进工作目录:

string[] attachments = { @"C:\legal-ops\data\approved-vendors.xlsx" };

using (Document contract = new Document())
{
    contract.LoadFromFile(@"C:\legal-ops\templates\supplier-contract.docx");
    AIResult result = contract.AI(agentOptions).ExecuteInstruction(
        contract,
        "为每位审核通过的供应商签发一份采购合同:逐行读取 approved-vendors.xlsx," +
        "用每家供应商的数据填充本模板中的 {{Placeholder}} 占位符,保持模板的排版与样式," +
        "并把每份合同另存为工作目录下的独立 PDF 文件。",
        null,   // 输出路径传 null -> 智能体将每份合同写入 WorkDir
        attachments);

    if (result == null || !result.Success)
    {
        throw new InvalidOperationException(
            $"合同签发失败: {result?.ErrorMessage}");
    }
}

关键 API 调用

  • Document.LoadFromFile() -- 加载合同模板
  • contract.AI(agentOptions) -- 挂载 AI 文档处理器
  • ExecuteInstruction(doc, instruction, savePath, attachments) -- 为每个供应商行签发一份独立合同
  • AIResult.Success / AIResult.ErrorMessage -- 校验结果并暴露错误

输出结果

每份合同都会被写入智能体在 WorkDir 下管理的会话子文件夹(例如 output\.office_use_tmp\Word\<会话>\output_contracts),所以把 WorkDir 指向归档目录,再从该处收集签发的合同即可。

输出示例:一份模板加一份数据源批量签发的供应商合同 PDF

一个模板、一份电子表格、同一条指令驱动每一份合同,每份都保持原有格式。输出可保存为 PDF、DOCX、DOC、HTML、Markdown 或 XPS,以配合你的归档工作流。还可以构建比简单填字段更复杂的模板。官方生成多种 Word 模板教程覆盖了智能体可填充的占位符、条件章节等模板模式。

差别在哪:传统 SDK vs AI 智能体

传统 SDK 处理时,你需要手动定位每个 {{Placeholder}} 占位符并逐个替换,每个字段一行代码,把电子表格的每一列映射到对应占位符,再循环行记录、每行导出一份文件。每次模板或数据布局变化,你都要维护这几十行代码。下面的示意代码(为说明而简化)展示了这类工作的形态:

// 传统 SDK(示意):逐个定位并替换每个 {{Placeholder}} 占位符
// -- 每个字段一行代码
Document doc = new Document();
doc.LoadFromFile(@"C:\legal-ops\templates\supplier-contract.docx");

doc.Replace("{{SupplierName}}", vendor.SupplierName, false, true);
doc.Replace("{{Amount}}", vendor.Amount.ToString(), false, true);
doc.Replace("{{PaymentTerms}}", vendor.PaymentTerms, false, true);
doc.Replace("{{EffectiveDate}}", vendor.EffectiveDate.ToString("yyyy-MM-dd"), false, true);

doc.SaveToFile(@"C:\legal-ops\output\PO-001.pdf"); // ...每个供应商行重复一次

AI 智能体用一条指令替代上述编排:

contract.AI(agentOptions).ExecuteInstruction(
    contract,
    "为每位审核通过的供应商签发一份采购合同:逐行读取 approved-vendors.xlsx," +
    "用每家供应商的数据填充本模板中的 {{Placeholder}} 占位符,保持模板的排版与样式," +
    "并把每份合同另存为工作目录下的独立 PDF 文件。",
    null,
    attachments);

两者产出相同的合同。区别在于:SDK 为每个占位符增加一行 Replace、为每一列增加一段映射,而智能体把同样的工作收进一条指令。当模板或数据布局变化时,你改的是指令,而不是代码。

对比:几十行传统 SDK 代码被一条自然语言指令替代


6. 为什么用 Spire.Agent.Office 做 AI 合同自动化

上面的三方对比刻意保持产品中立,同样的模式对任何够格的 LLM 都成立。Spire.Agent.Office 对 .NET 团队的价值集中在三方面:

  1. 原生 Office 文档处理。 Word、Excel、PowerPoint 和 PDF 是一等公民,而不是事后拼装的格式。智能体在四种格式之间读写真实文件。
  2. 格式保真。 企业合同承载着必须保留下来的条款编号、表格和字体。智能体的文档层能保持它们完整。在指令中加入"保持原文档排版与样式",输出就会忠于模板。
  3. 原生 .NET 集成。 它是能直接嵌入现有 .NET 应用的 C# SDK。无需另建或维护文档处理服务,也没有跨服务管线。上面的示例就是完整的集成面。

如果已经在用 Spire.Office 处理文档,智能体就是自然的下一层:同一个 Document 对象获得一个把指令变成已执行工作流的 AI() 处理器。


7. 常见问题

AI 合同审查能处理文本型 PDF 吗?

能。上面的审阅示例直接加载 supplier-agreement.pdf,智能体以原生格式读取并分析文档。支持标准与加密的文本型 PDF。纯图片扫描件没有可提取的文本层,需要先用 OCR 转成可搜索文本再进行审阅。

合同数据能留在我自己的环境里吗?

能,但有一个重要前提。Spire.Agent.Office 从你自己的应用运行,SDK、模板和文档处理都留在你的环境内,合同文件不会上传到第三方文档服务做存储或转换。要分析合同内容,AI 需要相关的文本,这部分文本会发送给模型处理,这是任何 AI 工作流固有的一步。如果你在自己的内网部署模型,内容就完全留在你的基础设施内;如果通过 OpenAI 或 Azure OpenAI 这类托管模型 API 接入,相关内容会按你的配置经网络发送给该服务商。

我可以用自己的 AI 模型吗?

可以。Spire.Agent.Office 支持灵活的 AI 模型集成,兼容主流 AI 基础设施,包括托管模型 API 和私有化部署的模型。你可以把智能体指向自己的端点。集成细节见集成教程;关于你的部署支持哪些服务商,请联系我们。

用哪个模型做合同审查?

Spire.Agent.Office 在 SpireToken 密钥背后连接一个大语言模型。你用自然语言描述审阅或生成任务,智能体编排底层的文档处理工具。模型负责语义理解,文档层负责格式与文件保真。

能批量生成合同吗?

能。一份合同模板加一个数据源(如 Excel 表),一条指令即可为数据表每一行产出一份合同,字段填充与占位符替换都支持。为保证智能体识别每一行,数据源首行保持为表头、每个供应商占一行、避免空行;如果生成的合同数量与数据行数不一致,先检查数据源。

AI 会改变我合同的格式吗?

只要你说不就不会。在指令中加入"保持原文档排版、样式和字体"即可;官方教程也记载了这个确切做法。

与直接用原生 LLM API 有何不同?

原生 LLM 无法可靠地自行读写 Word 和 PDF 文件,它需要一层文档处理能力。文档 AI 智能体把 LLM 的语义理解与确定性的文档 API 结合,因此输出是真实、格式正确的文件。

开始自动化你的合同工作流

合同审阅与批量生成是最快见效的两个切入点:一个模板、一个数据源、一条自然语言指令,输出真实可用的 Word 或 PDF 文件。跟随集成入门教程,在 .NET 中运行你的第一个文档工作流。

延伸阅读

在日常业务中,技术知识的高效传递是所有企业的核心挑战。大量技术规范文档,例如操作手册、安全维护指南、供应链标准文件等,这些文档往往长达数十甚至上百页。如何将这些厚重技术规范中的核心知识快速转化为易于理解的PPT材料,是企业知识管理的核心痛点。

本文介绍如何使用 Spire.Agent.Office Presentation AI能力,针对各种格式的数据源进行分析归纳,提炼核心要点生成专业的PPT演示文稿。

对比传统SDK API处理

对比维度 传统 Spire.Office for .NET API Spire.Agent.Office
驱动方式 需调用 Word、Excel、PDF、PowerPoint 四个产品的API接口,分别从各类文档中通过代码指定提取内容,再调用PowerPoint API逐页创建幻灯片、添加元素、手动计算布局 直接使用自然语言描述需求,AI 自动理解并生成PPT
开发复杂度 需熟悉4套不同API接口,针对不同格式(.docx/.xlsx/.pdf)编写不同的解析代码,再拼接PowerPoint生成逻辑,代码量大且耦合度高 一条自然语言指令完成全流程
文档解析方式 需人为指定从各类文档中提取哪些数据,解析逻辑硬编码,文档结构调整需同步修改代码 AI 自动深度分析文档结构,精准提取关键信息
通用性与维护性 每种文档格式需单独编写解析逻辑,格式变更或新增文档类型需大范围改动代码,复用性差 同一套自然语言指令可适配不同文档
处理周期 数天(大型文档需高级工程师全身投入编写/调试代码) 几分钟(上传文档 + 模板 + 一条指令)

有关产品安装和 SpireToken 配置,请参考 在 .NET 项目中集成 Spire.Agent.Office。以下示例默认已安装 Spire.Agent.Office 并完成 SpireToken 配置。


基于Word文档分析生成PPT

根据Word文档内容,按照自然指令生成简约风格的PPT演示文稿。

using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Presentation;

// 源数据文档
string inputPath = @"安全技术要求.docx";
// 结果文档路径
string savePath = @"安全技术要求.pptx";
// SpireToken Key
string key = "sk-TF***************************r";
// 自然语言指令
string instruction = "提炼‘安全技术要求.docx’核心要点生成PPT,1.保证排版布局效果 2.使用简约风格浅黄色主题 3.生成20页";
// AI 生成
PPTGenerationResult result = GeneratePPT(inputPath, instruction, savePath, key);

// AI 生成PPT处理
static PPTGenerationResult GeneratePPT(string input, string instruction, string savePath, string key)
{
    AIOptions options = new AIOptions();
    options.SpireToken = key;
    options.TimeoutMs = 1000000;
    using (Presentation ppt = new Presentation())
    {
        AIDocumentProcessor processor = ppt.AI(options);
        return processor.GeneratePresentation(input, instruction, savePath);
    }
}

word生成PPT


基于PDF文档分析生成PPT

自动分析PDF文档的内部层级结构,精准提取关键的信息,按照指令生成复古绿色主题风格的PPT演示文稿。

using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Presentation;

// 源数据文档
string inputPath = @"安全操作规程.pdf";
// 结果文档路径
string savePath = @"安全操作规程.pptx";
// SpireToken Key
string key = "sk-TF***************************r";
// 自然语言指令
string instruction = "提炼‘安全操作规程.pdf’核心要点生成PPT,1.保证排版布局效果 2.有相应图例图表效果 3.使用复古绿色主题风格 4.生成9页";
// AI 生成
PPTGenerationResult result = GeneratePPT(inputPath, instruction, savePath, key);


// AI 生成PPT处理
static PPTGenerationResult GeneratePPT(string input, string instruction, string savePath, string key)
{
    AIOptions options = new AIOptions();
    options.SpireToken = key;
    options.TimeoutMs = 1000000;
    using (Presentation ppt = new Presentation())
    {
        AIDocumentProcessor processor = ppt.AI(options);
        return processor.GeneratePresentation(input, instruction, savePath);
    }
}

pdf生成PPT


基于Markdown文档分析生成PPT

基于Markdown格式的源数据内容,自动汇总归纳生成科技风格的PPT。

using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Presentation;

// 源数据文档
string inputPath = @"供应链管理文件.md";
// 结果文档路径
string savePath = @"供应链管理.pptx";
// SpireToken Key
string key = "sk-TF***************************r";
// 自然语言指令
string instruction = "基于 '供应链管理文件.md' 生成PPT,1.采用科技风格; 2.以淡蓝色为主色调; 3.确保核心内容完整,层次清晰、布局规整,关键数据以图表形式直观呈现";
// AI 生成
PPTGenerationResult result = GeneratePPT(inputPath, instruction, savePath, key);


// AI 生成PPT处理
static PPTGenerationResult GeneratePPT(string input, string instruction, string savePath, string key)
{
    AIOptions options = new AIOptions();
    options.SpireToken = key;
    options.TimeoutMs = 1000000;
    using (Presentation ppt = new Presentation())
    {
        AIDocumentProcessor processor = ppt.AI(options);
        return processor.GeneratePresentation(input, instruction, savePath);
    }
}

Markdown生成PPT


基于Excel文档分析生成PPT

基于Excel格式的源数据内容,自动汇总归纳生成科技风格的PPT。

using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Presentation;

// 源数据文档
string inputPath = @"data.xlsx";
// 结果文档路径
string savePath = @"out.pptx";
// SpireToken Key
string key = "sk-TF***************************r";
// 自然语言指令
string instruction = "根据data.xlsx生成PPT,1.保证排版布局效果 2.使用简约风格浅红色主题 3.保证图表效果 4.生成10页";
// AI 生成
PPTGenerationResult result = GeneratePPT(inputPath, instruction, savePath, key);


// AI 生成PPT处理
static PPTGenerationResult GeneratePPT(string input, string instruction, string savePath, string key)
{
    AIOptions options = new AIOptions();
    options.SpireToken = key;
    options.TimeoutMs = 1000000;
    using (Presentation ppt = new Presentation())
    {
        AIDocumentProcessor processor = ppt.AI(options);
        return processor.GeneratePresentation(input, instruction, savePath);
    }
}

Excel生成PPT


常见问题

生成的PPT页数与预期不一致

原因:如果数据源内容过多,AI处理分析相对会耗时些,AIOptions.TimeoutMs超时设置默认为5分钟,如果超出,AI分析会中断。

解决:设置AIOptions.TimeoutMs足够时间,同时在指令中指定页数范围,如"最终生成的PPT控制在8-12页"。

AI提取的重点内容不够准确

原因:源文档结构复杂,AI可能未完全理解层级关系。

解决:在指令中明确指定需要提取的内容类型,如"重点提取第2章表数据"。


获取SpireToken Key

  • 联系 该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。 或访问 https://www.e-iceblue.com/TemLicense.html 获得试用/商业 API 密钥

在代码中配置:

AIProcessorOptions options = new AIProcessorOptions();
options.SpireToken = key;

在企业人力资源场景中,批量生成合同是最常见的文档处理需求之一,例如每月新员工入职、合同续签、劳务协议变更,往往一次就要处理几十甚至上百份合同。每份合同需要写入员工的姓名、岗位、薪资、合同期限等个性化信息。

对比传统SDK API处理

传统 Spire.Office for .NET API Spire.Agent.Office
驱动方式 编写代码传统API处理:加载模板→获取字段→读取数据→逐行填充→保存,每步需代码控制 自然语言描述目标,AI 自动编排并完成全部处理步骤
代码量 需数行代码处理数据读取、字段映射、循环写入和格式控制 仅需配置代码 + 1 条自然语言指令
字段映射 硬编码指定合并字段与 Excel 列的对应关系,数据源变更需同步改代码 AI 自动理解列名与模板字段的语义对应,数据源变更无需改代码
灵活性 模板字段变更需改代码 → 编译 → 重新部署 调整模板或数据源即可,已有指令可复用
维护性 依赖开发团队维护代码 模板和数据源可由业务人员直接维护

本文介绍如何使用 Spire.Agent.Office Word AI 能力通过邮件合并和占位符替换两种方式,将Excel员工数据自动写入 Word 模板,并批量输出PDF格式合同。您也可以自由选择保存为 DOCX、DOC、HTML ,OFD, Markdwon,XPS 等格式满足不同场景的归档需求。

有关产品安装和 SpireToken 配置,请参考 在 .NET 项目中集成 Spire.Agent.Office。以下示例默认已安装 Spire.Agent.Office 并完成 SpireToken 配置。


邮件合并方式

邮件合并是 Word 文档批量生成的标准方案,也是人力资源场景中最常用的模式。其核心思路是:根据合并字段的合同模板 Word 文档 + 数据源,让 AI 完成数据与模板的合并。

using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Doc;

// 多个文档路径(数据源文件)
string[] attachmentPaths = new string[] { @"E:\data.xlsx" };

// Word模板文件路径
string inputPath = @"E:\template-mailmerge.docx";  
// 结果文档路径(此处为null,将使用下面设置的输出文件夹路径)
string savePath = null ;  
// 输出目录
string OutDir = @"E:\output";  
// SpireToken Key
string key = "**************************";  
// 自然语言指令
string instruction =
      "执行邮件合并:将附件 ‘data.xlsx’中的员工数据逐条填充到合同模板的合并字段中;合并后保证同原文档布局样式;" +
      "每位员工生成一份独立的合同文档,最终保存输出PDF格式"; 

// 调用Word文档处理函数
AIResult result = ExecuteDemoWord(instruction, inputPath, savePath, key, OutDir, attachmentPaths);


// 执行Word文档AI处理
static AIResult ExecuteDemoWord(string instruction, string inputPath, string savePath, string key, string output, string[] attachmentPaths)
{
    // 创建AIOptions选项配置对象
    AIOptions options = new AIOptions();
    // 设置工作目录为输出目录
    options.WorkDir = output; 
    // 设置SpireToken Key 
    options.SpireToken = key;  

    // 使用Document对象处理Word文档
    using (Document doc = new Document())
    {
        // 从文件加载Word模板
        if (!string.IsNullOrEmpty(inputPath) && File.Exists(inputPath))
        {
            doc.LoadFromFile(inputPath);  
        }
        // 创建AI文档处理器
        AIDocumentProcessor processor = doc.AI(options);
    
        // 执行AI指令
        return processor.ExecuteInstruction(doc, instruction, savePath, attachmentPaths);
    }
}

原Word模板(包含邮件合并域)和Excel数据 原Word模板和Excel数据 邮件合并批量生成的PDF合同 邮件合并批量生成合同

生成的每份合同完整保留了模板的格式、表格样式和字体设置,所有合并字段均被替换为对应的员工数据。如果有 50 名新员工入职,只需一份模板 + 一份 Excel,一条指令即可完成全部合同生成。


占位符替换方式

占位符替换方式不需要在模板中预定义邮件合并字段,而是在文档中直接使用自定义的占位符标记(如 {{Name}}、{{Salary}}),由 AI智能体识别并替换。

// 多个文档路径(数据源文件)
string[] attachmentPaths = new string[] { @"E:\data.xlsx" };

// 合同模板文件路径
string inputPath = @"E:\template.docx";  
// 保存路径(此处为null,将使用下面设置的输出文件夹路径)
string savePath = null;  
// 输出目录
string OutDir = @"E:\output"; 
// SpireToken Key 
string key = "**************************";  

// 自然语言指令
string instruction =
    "读取'data.xlsx' 中的员工数据,逐条替换合同模板中对应字段的占位符" +  
    "替换的字段内容高亮,替换后的结果保证同原文档布局样式,字体等," +  
    "每位员工生成一份独立的合同文档,最终保存输出PDF格式"; 

// 调用AI处理Word文档的方法,执行指令并返回处理结果
AIResult result = ExecuteDemoWord1(instruction, inputPath, savePath, key, OutDir, attachmentPaths);



// 执行Word文档AI处理
static AIResult ExecuteDemoWord(string instruction, string inputPath, string savePath, string key, string output, string[] attachmentPaths)
{
    // 创建AIOptions选项配置对象
    AIOptions options = new AIOptions();
    // 设置工作目录为输出目录
    options.WorkDir = output;  
    // 设置SpireToken Key
    options.SpireToken = key; 

    // 使用Document对象处理Word文档
    using (Document doc = new Document())
    {
        // 从文件加载Word模板
        if (!string.IsNullOrEmpty(inputPath) && File.Exists(inputPath))
        {
            doc.LoadFromFile(inputPath);  
        }
        // 创建AI文档处理器
        AIDocumentProcessor processor = doc.AI(options);
    
        // 执行AI指令
        return processor.ExecuteInstruction(doc, instruction, savePath, attachmentPaths);
    }
}

原Word模板(包含{{}}占位符)和Excel数据 原Word模板和Excel数据 占位符替换批量生成的PDF合同 占位符替换生成合同


两种方式对比

邮件合并方式 占位符替换方式
模板制作 需插入邮件合并域字段 直接输入 {{}} 占位符
学习成本 需了解 Word 邮件合并功能 几乎零学习成本
灵活度 固定字段一一映射 支持替换中动态计算和格式化
数据源 需要结构化数据 支持结构化数据,也可在指令中定义

通过Spire.Agent.Office制作Word模板,请参考文章"使用 Spire.Agent.Office 生成各种Word模板"。

常见问题

生成的结果文档样式改变

原因:AI模型处理时,自动修改或添加了内容。

解决:在指令中添加"保证同原文档布局样式,字体等"的描述。

邮件合并后生成的文档数量与数据行数不一致

原因:数据源 Excel 中存在空行或合并单元格,导致读取行数不准确。

解决:确保数据源的首行为表头,后续每行对应一条员工记录,中间无空行。如果仍遇到此问题,可以在数据源中添加序号列用于校验:


获取SpireToken Key

  • 联系 该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。 或访问 https://www.e-iceblue.com/TemLicense.html 获得试用/商业 API 密钥

在代码中配置:

AIOptions options = new AIOptions();
options.SpireToken = key;

分页符是控制 Excel 打印布局的重要工具,它决定了数据在打印页面上的划分位置。合理设置分页符可以避免打印时数据被无意义地拆散,从而生成整洁、可读的纸质或 PDF 报表。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成分页符的添加、预览和删除操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


添加分页符

在打印报表时,我们常常希望按固定的行列数量拆分数据,例如每页打印固定的数据行数。Spire.XLS for JavaScript 通过 HPageBreaks.Add 方法添加水平分页符、VPageBreaks.Add 方法添加垂直分页符,从而实现精准的分页控制。

function App() {
  const sheetToSVG = 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 = 'Template_Xls_4.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

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

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

    // 在 E4 所在行添加水平分页符
    sheet.HPageBreaks.Add(sheet.Range.get("E4"));
    // 在 C4 所在列添加垂直分页符
    sheet.VPageBreaks.Add(sheet.Range.get("C4"));

    const outputFileName = "AddPageBreakInXlsFile.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>Add Page Break</h1>
      <button onClick={sheetToSVG}>
        Start
      </button>
    </div>
  );
}

export default App;

原文档 原文档 添加分页符 添加分页符


分页视图缩放比例设置

视图模式查看分页符位置时,Spire.XLS for JavaScript 支持通过 ZoomScalePageBreakView 属性设置分页预览视图的缩放比例。

function App() {
  const sheetToSVG = 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 = 'Template_Xls_4.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

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

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

    // 设置分页预览视图的缩放比例
    sheet.ZoomScalePageBreakView = 80;

    const outputFileName = "PageBreakPreview.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>Page Break Preview</h1>
      <button onClick={sheetToSVG}>
        Start
      </button>
    </div>
  );
}

export default App;

设置缩放比例前 设置缩放比例前 设置缩放比例后 设置缩放比例后


删除分页符

当分页符不再需要时,可以通过 Clear 方法清除某个方向上的全部分页符,或通过 RemoveAt 方法按索引删除指定位置的分页符。删除后还可以通过 ViewMode 属性将工作表切换到分页预览视图,直观确认分页效果。

function App() {
  const sheetToSVG = 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 = 'PageBreak.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

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

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

    // 清除所有垂直分页符
    sheet.VPageBreaks.Clear();

    // 删除第一个水平分页符
    sheet.HPageBreaks.RemoveAt(0);

    // 将视图模式设置为分页预览,查看分页符效果
    sheet.ViewMode = xlsModule.ViewMode.Preview;

    const outputFileName = "RemovePageBreak_output.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>Remove Page Break</h1>
      <button onClick={sheetToSVG}>
        Start
      </button>
    </div>
  );
}

export default App;

删除分页符前 删除分页符前 删除分页符后 删除分页符后

常见问题

添加分页符后打印时未生效

原因:分页符添加到了空白区域,或者工作表设置了固定的打印缩放比例,导致实际打印时的分页位置与预期不符。

解决:确认分页符添加在包含数据的单元格所在行或列上,并检查工作表的打印缩放设置,必要时通过 ZoomScalePageBreakView 等属性调整缩放,使分页符按预期生效。

删除分页符后仍显示分页线

原因:工作表仍处于分页预览视图模式,或者存在由数据量自动生成的自动分页符

解决:自动分页符无法通过程序直接删除;自动分页符由数据的行数、列数及页面大小决定,可通过调整行高、列宽或打印缩放比例来消除。


获取免费许可证

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

XPS(XML Paper Specification)是微软推出的版式文档格式,基于 XML 描述文档内容与页面布局,广泛用于电子文档的打印、存档与分发场景,尤其在 Windows 平台生态中得到原生支持。XPS 具有结构清晰、易于校验和数字签名等优势。与此同时,PDF 作为国际通用的文档格式在跨平台分发领域依然不可或缺。实际业务中经常需要在两种格式间灵活切换:将 PDF 合同转为 XPS 以便在 Windows 环境中打印存档,或者将 XPS 格式的文档转为 PDF 以方便跨平台分发与协作。

Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接完成 PDF 与 XPS 之间的双向转换,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


转换 PDF 到 XPS

PDF 转 XPS 的核心在于将 PDF 文档中的页面内容、字体和图形元素重新编码为符合 XPS 标准的 XML 描述结构。Spire.PDF for JavaScript 通过 PdfDocument 对象的 SaveToFile 方法配合 FileFormat.XPS 枚举值,一键完成格式转换,无需手动处理底层格式差异。

function App() {
  const convertToXPS = 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 文档
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // 定义为 XPS 格式的输出文件名
    const outputFileName = '输出XPS.xps';

    // 保存为 XPS 格式
    doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.XPS });
    doc.Close();

    // 从 VFS 读取转换后的文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.ms-xpsdocument' });
    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>Convert PDF To XPS</h1>
      <button onClick={convertToXPS}>
        Generate
      </button>
    </div>
  );
}

export default App;

通过 SaveToFile 方法指定 FileFormat.XPS 生成的 XPS 文档

通过 SaveToFile 方法指定 FileFormat.XPS 生成的 XPS 文档


转换 XPS 到 PDF

XPS 转 PDF 是文档跨平台分发场景中的常见需求。Spire.PDF for JavaScript 通过 PdfDocument 对象的 LoadFromXPS 方法加载 XPS 版式文档,再配合 SaveToFile 方法指定 FileFormat.PDF 导出为标准 PDF 文件,保持原始文档的版面布局和视觉效果不变。

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

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

    // 将 XPS 文件载入 VFS
    const inputFileName = '模板.xps';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

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

    // 定义为 XPS 格式的输出文件名
    const outputFileName = '输出PDF.pdf';

    // 保存为 XPS 格式
    doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.PDF });
    doc.Close();

    // 从 VFS 读取转换后的文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.ms-xpsdocument' });
    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>Convert XPS To PDF</h1>
      <button onClick={convertXPSToPDF}>
        Generate
      </button>
    </div>
  );
}

export default App;

通过 PdfDocument 的 LoadFromXPS 方法加载 XPS 并转换后生成的 PDF 文档

通过 PdfDocument 的 LoadFromXPS 方法加载 XPS 并转换后生成的 PDF 文档


常见问题

加密的 PDF 能否转换为 XPS

原因:受密码保护的加密 PDF 无法直接通过 SaveToFile 保存为 XPS 格式,需要先解密文档。

解决:在加载 PDF 时通过 LoadFromFile 的第二个参数传入密码进行解密,然后再保存为 XPS:

// 加载带密码的 PDF 文档
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName, "password");

// 保存为 XPS 格式
doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.XPS });
doc.Close();

获取免费许可证

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