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

Spire.Cloud 纯前端文档控件

Spire.Barcode 7.5.13 现已正式发布。该版本成功修复了条码数据读取不正确的问题。更多详情如下。

问题修复:


获取 Spire.Barcode 7.5.13 请点击:

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

当需要同时对比多个维度的指标时,雷达图(Radar Chart)是一种非常直观的呈现方式——它把每个维度放在一条从中心向外辐射的轴上,用折线把各轴上的数值连成多边形,形状的"胖瘦"一眼就能看出强弱项。Spire.XLS for JavaScript 提供了完整的图表 API,支持在浏览器端通过 WebAssembly 直接创建雷达图,无需后端服务。

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

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


创建雷达图

通过 sheet.Charts.Add() 方法可以为工作表添加雷达图。具体操作步骤如下:

  1. 加载包含数据的 Excel 文件,并获取第一个工作表。
  2. 通过 Charts.Add({ chartType: ExcelChartType.Radar }) 添加雷达图。
  3. 设置 DataRange 属性指定图表数据区域:首行为系列名称,首列为各条轴上的分类名称,其余单元格为数值。
  4. 设置 SeriesDataFromRange = false 表示数据不从行列布局获取。
  5. 设置图表标题、位置和图例位置。
  6. 通过 Workbook.SaveToFile() 方法保存工作簿。

下面是一个完整的代码示例,展示了在 React 中创建雷达图:

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

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

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

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

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

    // 添加雷达图
    let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.Radar });

    // 设置图表数据区域
    chart.DataRange = sheet.Range.get("A1:C5");
    chart.SeriesDataFromRange = false;

    // 设置图表位置
    chart.LeftColumn = 7;
    chart.TopRow = 6;
    chart.RightColumn = 16;
    chart.BottomRow = 29;

    // 设置图表标题
    chart.ChartTitle = "各地区的产品销售情况";
    chart.ChartTitleArea.IsBold = true;
    chart.ChartTitleArea.Size = 12;

    // 设置图例位置
    chart.Legend.Position = xlsModule.LegendPositionType.Corner;

    // 保存工作簿
    const outputFileName = 'CreateRadarChart.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>创建雷达图</h1>
      <button onClick={createRadarChart}>Start</button>
    </div>
  );
}

export default App;

运行后,创建雷达图的效果:

创建雷达图


设置雷达图样式

在创建雷达图后,可以通过设置图表区域、绘图区域和系列线条颜色等属性,进一步美化雷达图的外观。具体操作步骤如下:

  1. 获取已创建的雷达图对象。
  2. 设置 ChartArea.Fill.ForeColor 属性设置图表背景色。
  3. 设置 PlotArea.Fill.ForeColor 属性设置绘图区域背景色。
  4. 设置 Series[i].Format.LineProperties.Color 属性指定每个系列的线条颜色,Series.get(0) 与 Series.get(1) 分别对应数据区域中的两个系列。
  5. 保存工作簿。

下面是一个完整的代码示例,展示了如何设置雷达图样式:

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

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

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

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

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

    // 添加雷达图
    let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.Radar });

    // 设置图表数据区域
    chart.DataRange = sheet.Range.get("A1:C5");
    chart.SeriesDataFromRange = false;

    // 设置图表位置
    chart.LeftColumn = 7;
    chart.TopRow = 6;
    chart.RightColumn = 16;
    chart.BottomRow = 29;

    // 设置图表标题
    chart.ChartTitle = "各地区的产品销售情况";
    chart.ChartTitleArea.IsBold = true;
    chart.ChartTitleArea.Size = 12;

    // 设置雷达图样式
    // 设置图表区域背景色
    chart.ChartArea.Fill.ForeColor = xlsModule.Color.get_LightCyan();
    // 设置绘图区域背景色
    chart.PlotArea.Fill.ForeColor = xlsModule.Color.get_LightYellow();
    // 设置第一个系列的线条颜色
    chart.Series.get(0).Format.LineProperties.Color = xlsModule.Color.get_Orange();
    // 设置第二个系列的线条颜色
    chart.Series.get(1).Format.LineProperties.Color = xlsModule.Color.get_CornflowerBlue();

    // 保存工作簿
    const outputFileName = 'StyledRadarChart.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>设置雷达图样式</h1>
      <button onClick={styleRadarChart}>Start</button>
    </div>
  );
}

export default App;

运行后,设置雷达图样式的效果:

设置雷达图样式


常见问题

设置系列颜色后雷达图没有变化

原因:雷达图的系列以线条形式绘制,用 Series.get(i).Format.Fill 设置填充色不会生效。

解决:通过 Series.get(i).Format.LineProperties.Color 设置线条颜色,例如:

chart.Series.get(0).Format.LineProperties.Color = xlsModule.Color.get_Orange();

如何调整雷达图的图例位置?

原因:图例默认停靠在图表右侧,会与绘图区域争夺宽度,在雷达图这种横向占位较大的图表上尤其明显。

解决:通过 chart.Legend.Position 属性设置图例位置,可取 LegendPositionType.Bottom、Corner、Top、Right、Left、NotDocked。例如把图例移到右上角:

chart.Legend.Position = xlsModule.LegendPositionType.Corner;

获取免费许可证

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

在数据分析场景中,气泡图可以帮助我们从多维度直观地展示数据关系。气泡图的每个数据点由三个值定义:X 轴值、Y 轴值和气泡大小。Spire.XLS for JavaScript 提供了丰富的图表 API,支持在浏览器端通过 WebAssembly 直接创建气泡图,无需后端服务。

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

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


创建气泡图

通过 sheet.Charts.Add() 方法可以为工作表添加气泡图。具体操作步骤如下:

  1. 创建一个 Workbook 对象并获取第一个工作表。
  2. 通过 Charts.Add(ExcelChartType.Bubble) 添加气泡图。
  3. 设置 DataRange 属性指定图表数据区域。
  4. 设置 SeriesDataFromRange = false 表示数据不从行列布局获取。
  5. 设置 Series[0].Bubbles 指定气泡大小数据范围。
  6. 设置图表标题、位置和尺寸。
  7. 通过 Workbook.SaveToFile() 方法保存工作簿。

下面是一个完整的代码示例,展示了在 React 中创建气泡图:

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

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

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

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

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

    // 添加气泡图
    let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.Bubble });

    // 设置图表数据区域
    chart.DataRange = sheet.Range.get("A1:C5");
    chart.SeriesDataFromRange = false;

    // 设置气泡大小
    chart.Series.get(0).Bubbles = sheet.Range.get("C2:C5");

    // 设置图表位置
    chart.LeftColumn = 7;
    chart.TopRow = 6;
    chart.RightColumn = 16;
    chart.BottomRow = 29;

    // 设置图表标题
    chart.ChartTitle = "气泡图";
    chart.ChartTitleArea.IsBold = true;
    chart.ChartTitleArea.Size = 12;

    // 保存工作簿
    const outputFileName = 'CreateBubbleChart.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>创建气泡图</h1>
      <button onClick={createBubbleChart}>Start</button>
    </div>
  );
}

export default App;

运行代码后,得到创建气泡图的效果:

创建气泡图


设置气泡图样式

在创建气泡图后,可以通过设置图表区域、绘图区域、系列颜色和标记样式等属性,进一步美化气泡图的外观。具体操作步骤如下:

  1. 获取已创建的气泡图对象。
  2. 设置 ChartArea.Fill.ForeColor 属性设置图表背景色。
  3. 设置 PlotArea.Fill.ForeColor 属性设置绘图区域背景色。
  4. 设置 Series[0].Format.Fill.ForeColor 属性设置系列颜色。
  5. 设置 Series[0].HasDataLabels 属性启用数据标签,并通过 DataPoints.DefaultDataPoint.DataLabels 指定标签内容。
  6. 保存工作簿。

下面是一个完整的代码示例,展示了如何设置气泡图样式:

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

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

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

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

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

    // 添加气泡图
    let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.Bubble });

    // 设置图表数据区域
    chart.DataRange = sheet.Range.get("A1:C5");
    chart.SeriesDataFromRange = false;

    // 设置气泡大小
    chart.Series.get(0).Bubbles = sheet.Range.get("C2:C5");

    // 设置图表位置
    chart.LeftColumn = 7;
    chart.TopRow = 6;
    chart.RightColumn = 16;
    chart.BottomRow = 29;

    // 设置图表标题
    chart.ChartTitle = "气泡图";
    chart.ChartTitleArea.IsBold = true;
    chart.ChartTitleArea.Size = 12;

    // 设置气泡图样式
    // 设置图表区域背景色
    chart.ChartArea.Fill.ForeColor = xlsModule.Color.get_LightCyan();
    // 设置绘图区域背景色
    chart.PlotArea.Fill.ForeColor = xlsModule.Color.get_LightYellow();
    // 设置系列颜色
    chart.Series.get(0).Format.Fill.FillType = xlsModule.ShapeFillType.SolidColor;
    chart.Series.get(0).Format.Fill.ForeColor = xlsModule.Color.get_Orange();
    // 启用并设置数据标签
    chart.Series.get(0).HasDataLabels = true;
    chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasCategoryName = true;
    chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasValue = true;

    // 保存工作簿
    const outputFileName = 'StyledBubbleChart.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>设置气泡图样式</h1>
      <button onClick={styleBubbleChart}>Start</button>
    </div>
  );
}

export default App;

运行代码后,得到设置气泡图样式的效果:

设置气泡图样式


常见问题

DataRange 的 A、B、C 三列分别代表什么?

原因:气泡图的每个数据点需要 X 轴值、Y 轴值和气泡大小三个值,DataRange 指定的三列正是与之对应的。

解决:以 chart.DataRange = sheet.Range.get("A1:C5") 为例,A 列作为类别(X 轴),B 列作为 Y 轴值,C 列则通过 chart.Series.get(0).Bubbles = sheet.Range.get("C2:C5") 指定为气泡大小。三列的顺序不能互换,否则坐标位置和气泡大小都会错位。

为什么数据范围从 A1 开始,而不是 A2?

原因:数据区域的第一行会被用作系列名称。

解决:将表头行包含在 DataRange 中。示例中图例显示的"销售额"就来自单元格 B1,因此数据范围写作 A1:C5 而不是 A2:C5。


获取免费许可证

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

在数据分析场景中,趋势线可以帮助我们从 Excel 图表中直观地看出数据的变动趋势和预测方向。Spire.XLS for JavaScript 提供了丰富的趋势线 API,支持在浏览器端通过 WebAssembly 直接为图表系列添加多种类型的趋势线,无需后端服务。

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

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


为图表添加趋势线

通过 chart.Series.get(i).TrendLines.Add() 方法可以为图表中的任意系列添加趋势线。Spire.XLS for JavaScript 支持 TrendLineType 枚举中的 4 种趋势线类型:

  • Linear — 线性趋势线,适用于数据呈稳定增减的场景
  • Exponential — 指数趋势线,适用于增长或衰退速度越来越快的场景
  • Logarithmic — 对数趋势线,适用于数据快速增长后趋于平稳的场景
  • Moving_Average — 移动平均趋势线,适用于平滑数据波动的场景

具体操作步骤如下:

  1. 创建一个 Workbook 对象并获取第一个工作表。
  2. 通过 Worksheet.Charts.get(i) 获取需要添加趋势线的图表。
  3. 调用 chart.Series.get(0).TrendLines.Add() 方法,传入 type 参数指定趋势线类型。
  4. 通过 Workbook.SaveToFile() 方法保存工作簿。

下面是一个完整的代码示例,展示了在 React 中为 Excel 图表添加四种不同类型的趋势线:

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

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

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

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

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

    // 选择第 1 个图表并设置对数趋势线
    let chart = sheet.Charts.get(0);
    chart.ChartTitle = "对数趋势线";
    chart.Series.get(0).TrendLines.Add({ type: xlsModule.TrendLineType.Logarithmic });

    // 选择第 2 个图表并设置移动平均趋势线
    let chart1 = sheet.Charts.get(1);
    chart1.ChartTitle = "移动平均趋势线";
    chart1.Series.get(0).TrendLines.Add({ type: xlsModule.TrendLineType.Moving_Average });

    // 选择第 3 个图表并设置线性趋势线
    let chart2 = sheet.Charts.get(2);
    chart2.ChartTitle = "线性趋势线";
    chart2.Series.get(0).TrendLines.Add({ type: xlsModule.TrendLineType.Linear });

    // 选择第 4 个图表并设置指数趋势线
    let chart3 = sheet.Charts.get(3);
    chart3.ChartTitle = "指数趋势线";
    chart3.Series.get(0).TrendLines.Add({ type: xlsModule.TrendLineType.Exponential });

    // 保存文档
    const outputFileName = 'AddTrendline.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2013 });

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

    // 从 VFS 读取转换后的文件,触发下载
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>为图表添加趋势线</h1>
      <button onClick={addTrendline}>Start</button>
    </div>
  );
}

export default App;

运行代码后,得到为图表添加四种不同类型趋势线的效果:

添加趋势线


提取趋势线公式

通过 trendLine.Formula 属性可以获取趋势线的数学公式,便于在报告中展示趋势线的解析表达式。

具体操作步骤如下:

  1. 加载版块一生成的包含四个图表的 AddTrendline.xlsx 文件。
  2. 通过 Worksheet.Charts.get(i) 遍历获取四个图表。
  3. 读取每个图表中 trendLine.Formula 属性获取公式字符串,汇总保存为文本文件。

下面是一个完整的代码示例,展示了在 React 中提取趋势线公式:

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

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

    // 将版块一生成的 AddTrendline.xlsx 载入 VFS
    const inputFileName = 'AddTrendline.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

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

    // 获取第一个工作表
    let sheet = workbook.Worksheets.get(0);
    let result = "提取四个图表的趋势线公式如下:\n\n";

    // 遍历四个图表,依次提取趋势线公式
    for (let i = 0; i < 4; i++) {
      let chart = sheet.Charts.get(i);
      let trendLine = chart.Series.get(0).TrendLines.get(0);
      // 移动平均趋势线没有数学公式,跳过
      if (trendLine.Type === xlsModule.TrendLineType.Moving_Average) {
        result += `图表 ${i + 1} (${chart.ChartTitle}): 无公式(移动平均)\n`;
      } else {
        let formula = trendLine.Formula;
        result += `图表 ${i + 1} (${chart.ChartTitle}): ${formula}\n`;
      }
    }

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

    // 将公式保存为文本文件并触发下载
    const outputFileName = 'ExtractTrendline.txt';
    const blob = new Blob([result], { type: "text/plain;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>提取趋势线公式</h1>
      <button onClick={extractTrendlineFormula}>Start</button>
    </div>
  );
}

export default App;

运行代码后,得到提取趋势线公式的效果:

提取趋势线公式


常见问题

如何删除图表中已添加的趋势线?

原因:已经为图表添加了趋势线,但发现选择不当或需要修改。

解决:通过 TrendLines.RemoveAt(index) 方法可以删除指定索引位置的趋势线。索引从 0 开始计数。例如 chart.Series.get(0).TrendLines.RemoveAt(0) 删除第一个系列的第一个趋势线。

如何设置趋势线向前/向后预测的周期数?

原因:趋势线不仅可以拟合已有数据,还可以基于数据趋势进行未来(向前)或过去(向后)的预测。

解决:通过 trendLine.Forward 和 trendLine.Backward 属性分别设置向前和向后预测的周期数。例如 trendLine.Forward = 2 表示在当前数据基础上向后预测两个周期,trendLine.Backward = 1 表示向前回溯一个周期。


获取免费许可证

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

PDF 文档中的书签(Bookmark)以树形结构组织文档大纲,是长篇文档快速导航的核心工具。当文档包含多级书签时,默认的展开或折叠状态会直接影响读者打开文档时看到的大纲全貌。借助 Spire.PDF for JavaScript,可以在 React 应用中重新设置书签的展开与折叠状态,让文档打开时按预期呈现所需的大纲层级。

Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接处理 PDF 文档,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。控制书签展开或折叠的核心是书签对象的 ExpandBookmark 属性:设为 true 展开该节点及其子书签,设为 false 则折叠收起其子书签。对于多级书签,可以递归遍历 PdfBookmarkCollection 集合统一设置,也可以按索引精确定位某个节点单独控制。

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

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


展开所有 PDF 书签

Spire.PDF for JavaScript 可以通过 PdfDocument.Bookmarks 获取文档的书签集合。由于书签支持多级嵌套,需要编写一个递归函数遍历 PdfBookmarkCollection:先递归处理子书签,再把当前节点的 ExpandBookmark 属性统一设为 true,从而让文档打开时所有层级的书签全部展开。

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

    // 递归遍历书签集合,展开所有层级的书签
    function expandBookmarks(collection, expand) {
      // 集合为空时结束递归
      if (collection.Count === 0) {
        return;
      }

      for (let i = 0; i < collection.Count; i++) {
        let bookmark = collection.get_Item(i);

        // 先递归处理子书签
        expandBookmarks(bookmark, expand);

        // 再设置当前书签的展开状态
        bookmark.ExpandBookmark = expand;
      }
    }

    // 展开文档中的所有书签
    expandBookmarks(doc.Bookmarks, true);

    // 保存文档并触发下载
    const outputFileName = "展开所有书签.pdf";
    doc.SaveToFile(outputFileName);
    doc.Close();

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

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>展开所有 PDF 书签</h1>
      <button onClick={expandAllBookmarks}>
        开始展开
      </button>
    </div>
  );
}

export default App;

递归展开所有层级书签后的文档

递归展开所有层级书签后的文档


展开或折叠指定 PDF 书签

如果只需要控制某几个书签节点,可以通过 PdfBookmarkCollection.get_Item 按索引定位目标书签,再分别设置其 ExpandBookmark 属性。将某个节点的 ExpandBookmark 设为 true 会展开该节点下的子书签,设为 false 则会收起子书签,由此可以实现「部分展开、部分折叠」的精确控制。

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

    // 展开第一个书签(第一章),其下的子书签一并显示
    doc.Bookmarks.get_Item(0).ExpandBookmark = true;

    // 折叠第二个书签(第二章),其下的子书签将被收起
    doc.Bookmarks.get_Item(1).ExpandBookmark = false;

    // 展开第三个书签(第三章)
    doc.Bookmarks.get_Item(2).ExpandBookmark = true;

    // 保存文档并触发下载
    const outputFileName = "展开折叠指定书签.pdf";
    doc.SaveToFile(outputFileName);
    doc.Close();

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

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>展开或折叠指定 PDF 书签</h1>
      <button onClick={toggleSpecificBookmarks}>
        开始处理
      </button>
    </div>
  );
}

export default App;

按索引展开或折叠指定书签后的文档

按索引展开或折叠指定书签后的文档


常见问题

为什么设置了 ExpandBookmark 后子书签仍然不显示

原因:ExpandBookmark 控制的是「该节点的子书签是否显示」。如果某个书签的上层节点处于折叠状态,那么无论其自身 ExpandBookmark 设为 true,只要父节点是折叠的,它依然不会被显示出来。

解决:从根节点开始逐层展开,或直接使用递归方式把整棵书签树的 ExpandBookmark 都设为 true:

function expandBookmarks(collection) {
  for (let i = 0; i < collection.Count; i++) {
    let bookmark = collection.get_Item(i);
    bookmark.ExpandBookmark = true;
    expandBookmarks(bookmark);
  }
}

expandBookmarks(doc.Bookmarks);

如何只展开多级书签中的某一层

原因:书签是树形结构,PdfBookmarkCollection 的每个节点还可以继续通过 get_Item 访问其子集合,需要按层级逐级定位。

解决:先定位到目标层级的父节点,再设置该节点的 ExpandBookmark。例如只展开第二章下的第一个子书签:

// 取第二个书签(第二章)
let chapterTwo = doc.Bookmarks.get_Item(1);

// 取该章下的第一个子书签
let sectionOne = chapterTwo.get_Item(0);

// 展开该子书签
sectionOne.ExpandBookmark = true;

ExpandBookmark 的展开或折叠状态保存在哪里

原因:书签的展开与折叠状态会随书签本身写入 PDF 的大纲(Outlines)结构中,属于文档内容的一部分,而不是阅读器的临时设置。

解决:设置并保存后,用任何支持 PDF 标准大纲的阅读器(如 Adobe Acrobat、Edge、Chrome 内置阅读器的书签面板)打开,都会按保存时的状态呈现。若需还原为全部折叠,只需把对应节点的 ExpandBookmark 设为 false 后重新保存:

// 折叠第一个书签
doc.Bookmarks.get_Item(0).ExpandBookmark = false;
doc.SaveToFile(outputFileName);

获取免费许可证

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

PDF 文档中的书签(Bookmark)是导航文档内容的重要工具,尤其对于长篇文档,书签可以帮助读者快速定位到目标章节。借助 Spire.PDF for JavaScript 的书签管理能力,可以在 React 应用中直接为 PDF 添加多级书签、修改已有书签的标题与样式,或删除不需要的书签,所有操作均在浏览器端通过 WebAssembly 完成,无需依赖后端服务。

Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接处理 PDF 文档,通过虚拟文件系统(VFS)管理输入输出文件。

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

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


添加 PDF 书签

使用 Spire.PDF for JavaScript 可以为已有 PDF 文档批量添加多级书签。通过遍历 PdfDocument.Pages 集合,为每一页创建父级书签和子书签,使用 PdfDestination 指定跳转页面和位置,再通过 PdfBookmarkCollection.Add 添加子书签形成层级结构。

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

    // 遍历 PDF 中的每一页,为每页添加父书签和子书签
    for (let i = 0; i < doc.Pages.Count; i++) {
      let page = doc.Pages.get_Item(i);

      // 设置父书签标题和目标位置
      let bookmarkTitle = "书签-" + (i + 1);
      let bookmarkDest = new pdfModule.PdfDestination({ page: page, location: new pdfModule.PointF(0, 0) });

      // 创建并配置父书签
      let bookmark = doc.Bookmarks.Add(bookmarkTitle);
      bookmark.Color = new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_SaddleBrown() });
      bookmark.DisplayStyle = pdfModule.PdfTextStyle.Bold;
      bookmark.Action = new pdfModule.PdfGoToAction({ destination: bookmarkDest });

      // 设置子书签标题和目标位置
      let childBookmarkTitle = "子书签-" + (i + 1);
      let childBookmarkDest = new pdfModule.PdfDestination({ page: page, location: new pdfModule.PointF(0, 100) });

      // 通过父书签的 PdfBookmarkCollection.Add 创建子书签
      let childBookmark = bookmark.Add(childBookmarkTitle);
      childBookmark.Color = new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Coral() });
      childBookmark.DisplayStyle = pdfModule.PdfTextStyle.Italic;
      childBookmark.Action = new pdfModule.PdfGoToAction({ destination: childBookmarkDest });
    }

    // 保存文档并触发下载
    const outputFileName = "添加书签.pdf";
    doc.SaveToFile(outputFileName);
    doc.Close();

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

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>添加 PDF 书签</h1>
      <button onClick={addPdfBookmarks}>
        开始添加
      </button>
    </div>
  );
}

export default App;

遍历 PDF 页面批量添加多级书签后的文档

遍历 PDF 页面批量添加多级书签后的文档


编辑 PDF 书签

对于已存在的 PDF 文档,可以加载后编辑其中的书签。通过 PdfDocument.Bookmarks.get_Item 获取指定索引的书签节点,修改其 Title、Color 和 DisplayStyle 属性。书签支持层级结构,可以通过递归方式遍历并编辑所有子书签节点。

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

    // 递归编辑子书签的函数
    function editChildBookmarks(parentBookmark) {
      for (let i = 0; i < parentBookmark.Count; i++) {
        let childBookmark = parentBookmark.get_Item(i);
        childBookmark.Color = new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Blue() });
        childBookmark.DisplayStyle = pdfModule.PdfTextStyle.Regular;
        editChildBookmarks(childBookmark);
      }
    }

    // 获取第一个书签并修改其属性
    let bookmark = doc.Bookmarks.get_Item(0);
    bookmark.Title = "修改后的书签";
    bookmark.Color = new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Black() });
    bookmark.DisplayStyle = pdfModule.PdfTextStyle.Bold;

    // 递归编辑所有子书签
    editChildBookmarks(bookmark);

    // 保存文档并触发下载
    const outputFileName = "编辑书签.pdf";
    doc.SaveToFile(outputFileName);
    doc.Close();

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

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>编辑 PDF 书签</h1>
      <button onClick={editPdfBookmarks}>
        开始编辑
      </button>
    </div>
  );
}

export default App;

编辑 PDF 书签后的文档效果

编辑 PDF 书签后的文档效果


删除 PDF 书签

从 PDF 文档中删除不需要的书签,可以通过 PdfDocument.Bookmarks.RemoveAt 方法按索引移除指定书签。如果需要删除所有书签,可以遍历集合逐个删除,或使用 RemoveAt(0) 循环删除直到集合为空。

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

    // 删除第一个书签
    doc.Bookmarks.RemoveAt(0);

    // 保存文档并触发下载
    const outputFileName = "删除书签.pdf";
    doc.SaveToFile(outputFileName);
    doc.Close();

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

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>删除 PDF 书签</h1>
      <button onClick={deletePdfBookmarks}>
        开始删除
      </button>
    </div>
  );
}

export default App;

删除指定 PDF 书签后的文档

删除指定 PDF 书签后的文档


常见问题

如何添加多级嵌套书签

原因:PDF 书签支持层级结构,可以创建父书签和子书签,方便文档的目录导航。

解决:通过父书签的 Add 方法可以添加子书签,形成嵌套结构。以下代码演示了如何创建两级书签:

// 创建父级书签
let parentBookmark = doc.Bookmarks.Add("第一章");
parentBookmark.DisplayStyle = pdfModule.PdfTextStyle.Bold;

// 通过父书签的 Add 方法添加子书签
let childBookmark = parentBookmark.Add("1.1 小节");
childBookmark.DisplayStyle = pdfModule.PdfTextStyle.Regular;

编辑书签时可以修改哪些属性

原因:Spire.PDF for JavaScript 提供了丰富的书签属性设置,可以自定义书签的外观。

解决:可以修改的属性包括:

  • Title:书签标题文本
  • Color:书签颜色,使用 PdfRGBColor 设置
  • DisplayStyle:显示样式,可选 Bold(粗体)、Italic(斜体)、Underline(下划线)、Regular(常规)
  • Action:书签跳转动作,使用 PdfGoToAction 设置目标
let bookmark = doc.Bookmarks.get_Item(0);
bookmark.Title = "新标题";
bookmark.Color = new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Blue() });
bookmark.DisplayStyle = pdfModule.PdfTextStyle.Bold | pdfModule.PdfTextStyle.Italic;

如何删除 PDF 中的所有书签

原因:有时需要清空文档中所有书签,例如重新生成或去除敏感导航信息。

解决:可以通过循环调用 RemoveAt(0) 逐个删除,因为每次删除索引 0 处的书签后,后续书签会自动前移:

while (doc.Bookmarks.Count > 0) {
  doc.Bookmarks.RemoveAt(0);
}

获取免费许可证

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

在 React 中使用 JavaScript 将 Markdown 转换为 PDF 图文教程

Markdown 常用于编写文档、README 文件、笔记以及其他结构化内容。但当内容需要打印、归档或以固定版式进行分享时,PDF 往往更加实用。

在 React 应用中,可以使用 Spire.Doc for JavaScript 将 Markdown 转换为 PDF。该库通过 WebAssembly(WASM)运行,可直接在浏览器本地处理 Markdown 内容并生成 PDF,无需将文件发送到服务器。

本文主要介绍以下三种常见转换场景:

在 React 中配置 Spire.Doc for JavaScript

在开始转换 Markdown 文件之前,需要先将 Spire.Doc for JavaScript 集成到 React 项目中,并准备所需的 WebAssembly 运行时文件。

有关详细的配置方法,请参阅 如何在 React 中集成 Spire.Doc for JavaScript。

步骤 1:安装所需包

在 React 项目的根目录中打开终端,并通过 npm 安装所需包:

npm i spire.office

步骤 2:添加所需运行时文件

将 node_modules/spire.office 中的以下文件和文件夹复制到项目的 public 目录:

_framework
spire.doc.js
Spire.Doc.Wasm.zip
spire.common.js
Spire.Common.Wasm.zip

下面的示例还会使用 SimSun.ttf 进行 PDF 文本渲染。请将字体文件放置在:

public/static/font/

对于基于文件的转换示例,请将 Markdown 示例文件放置在:

public/static/data/

相关项目结构如下:

public/
├── _framework/
├── spire.doc.js
├── Spire.Doc.Wasm.zip
├── spire.common.js
├── Spire.Common.Wasm.zip
└── static/
    ├── data/
    │   └── MarkdownExample_CN.md
    └── font/
        └── SimSun.ttf

注意: 以下示例使用 process.env.PUBLIC_URL,这是 Create React App 中的常用写法。如果项目使用 Vite 或其他构建工具,请根据实际情况调整公共资源路径。

使用 JavaScript 将 Markdown 文件转换为 PDF

将 Markdown 文件转换为 PDF 的流程主要分为以下四个步骤:

  1. 将所需字体和 Markdown 文件加载到 WASM 虚拟文件系统(VFS)中。
  2. 调用 Document.LoadFromFile() 方法,并指定输入格式为 FileFormat.Markdown,将 Markdown 文件加载到 Document 对象中。
  3. 调用 Document.SaveToFile() 方法,并指定输出格式为 FileFormat.PDF,将加载后的文档保存为 PDF 文件。
  4. 从 VFS 中读取生成的 PDF,并在浏览器中下载。

以下 JavaScript 示例演示如何加载 .md 文件并将其保存为 .pdf 文件:

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

function App() {
  const [wasmModule, setWasmModule] = useState(null);

  // 初始化 Spire.Doc WebAssembly 模块
  useEffect(() => {
    (async () => {
      try {
        const publicUrl = process.env.PUBLIC_URL || '';

        const spireModule = await import(
          /* webpackIgnore: true */
          `${publicUrl}/spire.doc.js`
        );

        const rawModule = spireModule.default || spireModule;

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

        setWasmModule(window.wasmModule);
      } catch (error) {
        console.error(
          '加载 Spire.Doc WASM 模块失败:',
          error
        );
      }
    })();
  }, []);

  // 下载 WASM 虚拟文件系统中生成的文件
  const downloadVfsFile = (fileName, mimeType) => {
    const fileData =
      window.dotnetRuntime.Module.FS.readFile(fileName);

    const blob = new Blob(
      [fileData],
      { type: mimeType }
    );

    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');

    link.href = url;
    link.download = fileName;

    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);

    URL.revokeObjectURL(url);
  };

  const convertMarkdownToPdf = async () => {
    const docModule = window.wasmModule?.spiredoc;

    if (!docModule) return;

    const publicUrl = process.env.PUBLIC_URL || '';

    // 将字体加载到 VFS
    await window.spire.FetchFileToVFS(
      'SimSun.ttf',
      '/Library/Fonts/',
      `${publicUrl}/static/font/`
    );

    const inputFileName = 'MarkdownExample_CN.md';
    const outputFileName = 'MarkdownToPDF.pdf';

    // 将 Markdown 文件加载到 VFS
    await window.spire.FetchFileToVFS(
      inputFileName,
      '',
      `${publicUrl}/static/data/`
    );

    const doc = new docModule.Document();

    try {
      // 加载 Markdown 文件
      doc.LoadFromFile({
        fileName: inputFileName,
        fileFormat: docModule.FileFormat.Markdown
      });

      // 将文档保存为 PDF
      doc.SaveToFile({
        fileName: outputFileName,
        fileFormat: docModule.FileFormat.PDF
      });

      // 下载生成的 PDF
      downloadVfsFile(
        outputFileName,
        'application/pdf'
      );
    } finally {
      doc.Dispose();
    }
  };

  return (
    <div style={{ textAlign: 'center', padding: '40px' }}>
      <h1>将 Markdown 转换为 PDF</h1>

      <button
        onClick={convertMarkdownToPdf}
        disabled={!wasmModule}
      >
        转换并下载 PDF
      </button>
    </div>
  );
}

export default App;

运行应用,等待 WASM 模块加载完成后,点击 转换并下载 PDF,应用会加载 MarkdownExample_CN.md,将其转换为 MarkdownToPDF.pdf,并在浏览器中下载。

输出结果

生成的 PDF 会保留 Markdown 中的主要结构,包括标题、段落、列表和表格等:

输入 Markdown 文件与输出 PDF 文件对比

将 Markdown 转换为 PDF 并自定义页面设置

很多时候,默认页面布局并不一定适合所有场景。例如,包含宽表格的 Markdown 报告可能更适合横向页面,而用于打印的文档则可能需要指定纸张大小或页边距。

加载 Markdown 文件后,可以获取文档中的节(Section),并在生成 PDF 前调整其 PageSetup 属性。

下面的示例将第一个节设置为 A4 纸张、横向页面,并将四周页边距设置为 50 磅:

const section = doc.Sections.get_Item(0);

// 设置页面大小
section.PageSetup.PageSize =
  docModule.PageSize.A4();

// 设置页面方向
section.PageSetup.Orientation =
  docModule.PageOrientation.Landscape;

// 设置页边距
section.PageSetup.Margins.All = 50;

要在 Markdown 转 PDF 过程中应用这些设置,可以使用下面的函数:

const convertMarkdownToPdfWithPageSettings = async () => {
  const docModule = window.wasmModule?.spiredoc;

  if (!docModule) return;

  const publicUrl = process.env.PUBLIC_URL || '';

  // 将字体加载到 VFS
  await window.spire.FetchFileToVFS(
    'SimSun.ttf',
    '/Library/Fonts/',
    `${publicUrl}/static/font/`
  );

  const inputFileName = 'MarkdownExample_CN.md';
  const outputFileName =
    'MarkdownToPDFWithPageSettings.pdf';

  // 将 Markdown 文件加载到 VFS
  await window.spire.FetchFileToVFS(
    inputFileName,
    '',
    `${publicUrl}/static/data/`
  );

  const doc = new docModule.Document();

  try {
    // 加载 Markdown 文件
    doc.LoadFromFile({
      fileName: inputFileName,
      fileFormat: docModule.FileFormat.Markdown
    });

    // 获取第一个节
    const section = doc.Sections.get_Item(0);

    // 设置页面大小
    section.PageSetup.PageSize =
      docModule.PageSize.A4();

    // 设置为横向页面
    section.PageSetup.Orientation =
      docModule.PageOrientation.Landscape;

    // 将所有页边距设置为 50 磅
    section.PageSetup.Margins.All = 50;

    // 将文档保存为 PDF
    doc.SaveToFile({
      fileName: outputFileName,
      fileFormat: docModule.FileFormat.PDF
    });

    // 下载生成的 PDF
    downloadVfsFile(
      outputFileName,
      'application/pdf'
    );
  } finally {
    doc.Dispose();
  }
};

您可以根据 Markdown 文档的实际内容调整页面大小、方向和页边距。

使用 JavaScript 将 Markdown 字符串转换为 PDF

Markdown 内容不一定来自现有的 .md 文件。在 React 应用中,内容也可能来自 Markdown 编辑器、文本框、CMS、API 响应或应用状态,并以字符串形式存在。

这种情况下,可以先使用 FS.writeFile() 将 Markdown 字符串写入 WebAssembly 虚拟文件系统中的临时 .md 文件,再按照普通 Markdown 文件的方式将其转换为 PDF。

下面的示例将一段包含标题、列表和表格的 Markdown 字符串转换为 PDF:

const convertMarkdownStringToPdf = async () => {
  const docModule = window.wasmModule?.spiredoc;

  if (!docModule) return;

  const publicUrl = process.env.PUBLIC_URL || '';

  // 将字体加载到 VFS
  await window.spire.FetchFileToVFS(
    'SimSun.ttf',
    '/Library/Fonts/',
    `${publicUrl}/static/font/`
  );

  const markdownString = `# 项目记录

此 PDF 由 **React 应用中的 Markdown 字符串**生成。

## 待办事项

- 检查初稿
- 导出最终版本
- 分享 PDF

## 任务状态

| 项目 | 状态 |
| --- | --- |
| 初稿 | 已完成 |
| 审核 | 待处理 |
`;

  const inputFileName = 'MarkdownInput.md';
  const outputFileName = 'MarkdownStringToPDF.pdf';

  // 将 Markdown 字符串写入 VFS
  window.dotnetRuntime.Module.FS.writeFile(
    inputFileName,
    markdownString,
    { encoding: 'utf8' }
  );

  const doc = new docModule.Document();

  try {
    // 加载虚拟 Markdown 文件
    doc.LoadFromFile({
      fileName: inputFileName,
      fileFormat: docModule.FileFormat.Markdown
    });

    // 将文档保存为 PDF
    doc.SaveToFile({
      fileName: outputFileName,
      fileFormat: docModule.FileFormat.PDF
    });

    // 下载生成的 PDF
    downloadVfsFile(
      outputFileName,
      'application/pdf'
    );
  } finally {
    doc.Dispose();
  }
};

在实际应用中,可以将示例字符串替换为现有数据源中的 Markdown 内容,例如:

const markdownString = editorValue;

或者:

const markdownString = apiResponse.content;

其余 PDF 转换流程保持不变。

输出结果

从 Markdown 字符串生成的 PDF 文件

为什么需要将字体加载到 VFS?

生成 PDF 时需要使用字体数据来正确渲染文本。因此,上面的示例在处理 Markdown 文档之前,会先将 SimSun.ttf 加载到 WebAssembly 虚拟文件系统:

await window.spire.FetchFileToVFS(
  'SimSun.ttf',
  '/Library/Fonts/',
  `${publicUrl}/static/font/`
);

如果 Markdown 中包含当前字体不支持的字符,还需要将相应字体加载到 /Library/Fonts/。在生成包含中文、日文、韩文、阿拉伯文或其他多语言文本的 PDF 时,这一点尤其重要。

常见问题

Q:可以将用户上传的 Markdown 文件转换为 PDF 吗?

A:可以。无需从 public 目录加载固定的 .md 文件,可以先在浏览器中读取用户上传的 Markdown 文件,将其内容写入 WASM 虚拟文件系统,再使用 Document.LoadFromFile() 加载。这样,用户就可以直接在 React 应用中选择并转换自己的 Markdown 文件。

Q:为什么生成的 PDF 中缺少部分字符?

A:通常是因为 WASM 环境中没有用于显示这些字符的字体。请在生成 PDF 前,将支持 Markdown 中相应字符的字体加载到 /Library/Fonts/。

Q:Markdown 中的宽表格在 PDF 中被截断怎么办?

A:可以尝试将页面设置为横向、减小页边距,或使用更大的页面尺寸后再保存为 PDF。

Q:为什么 Markdown 中的图片没有出现在 PDF 中?

A:Markdown 通常通过文件路径或 URL 引用图片,而不是直接嵌入图片数据。请确保转换过程中能够访问 Markdown 中引用的图片文件,并且转换环境可以正确解析相应路径。如果使用相对图片路径,根据 Markdown 文件和图片文件的实际存放位置,可能需要额外处理路径。

Q:Markdown 转 PDF 是否在浏览器本地运行?

A:是。在上述 React 示例中,Spire.Doc for JavaScript 通过 WebAssembly 运行,Markdown 输入和生成的 PDF 都通过浏览器端的虚拟文件系统进行处理。转换本身不需要后端服务器。不过,如果应用还需要存储生成的文件、进行身份验证或执行其他服务器端操作,仍然可以配合后端服务使用。

总结

本文介绍了如何在 React 应用中使用 JavaScript 将 Markdown 转换为 PDF,包括基础文件转换、自定义页面设置,以及 Markdown 字符串转换。

借助 Spire.Doc for JavaScript,开发者可以加载 Markdown 内容、控制 PDF 页面布局,并通过 WebAssembly 直接在浏览器中生成 PDF。该方式适用于文档工具、Markdown 编辑器、报告系统,以及其他需要将 Markdown 内容导出为 PDF 的应用。

获取免费许可证

如果希望在不受评估版限制的情况下完整体验 Spire.Doc for JavaScript 的功能,可以申请 30 天免费试用许可证。

在制作流程图、关系图或数据标注时,向 Excel 工作表中插入线条(Lines)是常见需求。Spire.XLS for JavaScript 提供丰富的线条 API,支持创建多种线型(直线、曲线、肘形线等)及带箭头样式的连接器。所有操作均基于 WebAssembly 在浏览器端直接完成,无需后端服务支持。

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

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


插入不同类型的线条

sheet.Lines.AddLine() 方法可以在指定位置插入线条形状。通过 LineShapeType 枚举可以创建多种线型:直线(Line)、曲线(CurveLine)、肘形线(ElbowLine)和倒置线(LineInv)。线条的外观可以通过 DashStyle(虚线样式)、Color(颜色)和 Weight(粗细)等属性自定义。

具体操作步骤如下:

  1. 创建一个 Workbook 对象并获取第一个工作表。
  2. 调用 Worksheet.Lines.AddLine() 方法,传入位置参数和 LineShapeType 指定线型。
  3. 通过 DashStyle、Color、Weight 和 EndArrowHeadStyle 属性自定义线条外观。
  4. 通过 Workbook.SaveToFile() 方法保存工作簿。

下面是一个完整的代码示例,展示了在 React 中为 Excel 插入四种不同类型的线条:

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

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

    // 将中文字体载入 VFS,用于文本度量与列宽自适应
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // 创建一个新的工作簿并获取第一个工作表
    const workbook = new xlsModule.Workbook();
    const sheet = workbook.Worksheets.get(0);

    // 添加直线 - 实线、CadetBlue 色、粗 2、带箭头
    let line1 = sheet.Lines.AddLine({ row: 10, column: 2, width: 200, height: 1, lineShapeType: xlsModule.LineShapeType.Line });
    line1.DashStyle = xlsModule.ShapeDashLineStyleType.Solid;
    line1.Color = xlsModule.Color.get_CadetBlue();
    line1.Weight = 2;
    line1.EndArrowHeadStyle = xlsModule.ShapeArrowStyleType.LineArrow;

    // 添加曲线 - 点线、OrangeRed 色、粗 2
    let line2 = sheet.Lines.AddLine({ row: 12, column: 2, width: 200, height: 1, lineShapeType: xlsModule.LineShapeType.CurveLine });
    line2.DashStyle = xlsModule.ShapeDashLineStyleType.Dotted;
    line2.Color = xlsModule.Color.get_OrangeRed();
    line2.Weight = 2;

    // 添加肘形线 - DashDotDot 虚线、Purple 色、粗 2
    let line3 = sheet.Lines.AddLine({ row: 14, column: 2, width: 200, height: 1, lineShapeType: xlsModule.LineShapeType.ElbowLine });
    line3.DashStyle = xlsModule.ShapeDashLineStyleType.DashDotDot;
    line3.Color = xlsModule.Color.get_Purple();
    line3.Weight = 2;

    // 添加倒置线 - Dashed 虚线、Green 色、粗 2
    let line4 = sheet.Lines.AddLine({ row: 16, column: 2, width: 200, height: 1, lineShapeType: xlsModule.LineShapeType.LineInv });
    line4.DashStyle = xlsModule.ShapeDashLineStyleType.Dashed;
    line4.Color = xlsModule.Color.get_Green();
    line4.Weight = 2;

    // 保存文档
    const outputFileName = 'AddLineShapes.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 Line Shapes</h1>
      <button onClick={addLineShapes}>Start</button>
    </div>
  );
}

export default App;

运行代码后,得到插入不同类型的线条的效果:

插入不同类型的线条


插入带箭头的线条

sheet.TypedLines.AddLine() 方法用于插入带箭头样式的线条。与 Lines.AddLine() 不同,TypedLines 支持通过像素坐标或行列坐标精确定位线条,并支持在两端设置不同样式的箭头头(开始端 BeginArrowHeadStyle 和结束端 EndArrowHeadStyle)。

具体操作步骤如下:

  1. 创建一个 Workbook 对象并获取第一个工作表。
  2. 调用 Worksheet.TypedLines.AddLine() 方法创建线条。
  3. 通过 Top、Left、Width、Height 属性设置线条位置(基于像素)。
  4. 通过 BeginArrowHeadStyle 和 EndArrowHeadStyle 设置两端的箭头样式。
  5. 通过 LineShapeType 指定线型(直线、肘形线、曲线等)。
  6. 通过 Workbook.SaveToFile() 方法保存工作簿。

下面是一个完整的代码示例,展示了在 React 中为 Excel 插入六种不同类型的箭头线:

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

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

    // 将中文字体载入 VFS,用于文本度量与列宽自适应
    await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // 创建一个新的工作簿并获取第一个工作表
    const workbook = new xlsModule.Workbook();
    const sheet = workbook.Worksheets.get(0);

    // 添加双箭头线 - 实线蓝色
    let line = sheet.TypedLines.AddLine();
    line.Top = 10;
    line.Left = 20;
    line.Width = 100;
    line.Height = 0;
    line.Color = xlsModule.Color.get_Blue();
    line.BeginArrowHeadStyle = xlsModule.ShapeArrowStyleType.LineArrow;
    line.EndArrowHeadStyle = xlsModule.ShapeArrowStyleType.LineArrow;

    // 添加单箭头线 - 实线红色
    let line_1 = sheet.TypedLines.AddLine();
    line_1.Top = 50;
    line_1.Left = 30;
    line_1.Width = 100;
    line_1.Height = 100;
    line_1.Color = xlsModule.Color.get_Red();
    line_1.BeginArrowHeadStyle = xlsModule.ShapeArrowStyleType.LineNoArrow;
    line_1.EndArrowHeadStyle = xlsModule.ShapeArrowStyleType.LineArrow;

    // 添加肘形箭头连接线
    let line3 = sheet.TypedLines.AddLine();
    line3.LineShapeType = xlsModule.LineShapeType.ElbowLine;
    line3.Width = 30;
    line3.Height = 50;
    line3.EndArrowHeadStyle = xlsModule.ShapeArrowStyleType.LineArrow;
    line3.Top = 100;
    line3.Left = 50;

    // 添加肘形双箭头连接线
    let line2 = sheet.TypedLines.AddLine();
    line2.LineShapeType = xlsModule.LineShapeType.ElbowLine;
    line2.Width = 50;
    line2.Height = 50;
    line2.EndArrowHeadStyle = xlsModule.ShapeArrowStyleType.LineArrow;
    line2.BeginArrowHeadStyle = xlsModule.ShapeArrowStyleType.LineArrow;
    line2.Left = 120;
    line2.Top = 100;

    // 添加弧形箭头连接线
    line3 = sheet.TypedLines.AddLine();
    line3.LineShapeType = xlsModule.LineShapeType.CurveLine;
    line3.Width = 30;
    line3.Height = 50;
    line3.EndArrowHeadStyle = xlsModule.ShapeArrowStyleType.LineArrowOpen;
    line3.Top = 100;
    line3.Left = 200;

    // 添加弧形双箭头连接线
    line2 = sheet.TypedLines.AddLine();
    line2.LineShapeType = xlsModule.LineShapeType.CurveLine;
    line2.Width = 30;
    line2.Height = 50;
    line2.EndArrowHeadStyle = xlsModule.ShapeArrowStyleType.LineArrowOpen;
    line2.BeginArrowHeadStyle = xlsModule.ShapeArrowStyleType.LineArrowOpen;
    line2.Left = 250;
    line2.Top = 100;

    // 保存文档
    const outputFileName = 'AddArrowLines.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 Arrow Lines</h1>
      <button onClick={addArrowLines}>Start</button>
    </div>
  );
}

export default App;

运行代码后,得到插入带箭头的线条的效果:

插入带箭头的线条


常见问题

如何获取工作表中已存在的线条并进行修改?

原因:已导入含线条的 Excel 文件,但不知道如何读取或修改已有线条。

解决:通过 sheet.Shapes 集合遍历获取线条形状对象,再通过 ILineShape 接口修改其属性。例如 sheet.Shapes.get(0) 获取第一个形状,判断其是否为线条类型后可修改颜色、虚线样式等。

如何删除 Excel 工作表中的线条?

原因:需要移除已创建或导入的多余线条。

解决:通过 sheet.Shapes.Remove(index) 从形状集合中删除指定索引的线条对象,或循环遍历 Shapes 集合,按名称或类型条件逐个删除。


获取免费许可证

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

饼图和环形图是展示各数据项占比关系时最直观的图表类型。分离型(爆炸型)饼图和分离型环形图会将各个扇区彼此拉开,让每一部分的占比都更加醒目。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成此操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


创建分离型饼图

分离型饼图的扇区彼此分离,适合用来突出展示各个数据项所占的比例。创建分离型饼图的具体操作步骤如下:

  1. 创建 Workbook 对象,并使用 LoadFromFile() 方法载入包含数据的 Excel 文件。
  2. 通过 Workbook.Worksheets.get() 方法获取数据所在的工作表。
  3. 调用 Charts.Add() 方法添加图表,并将 ChartType 设置为 ExcelChartType.PieExploded。
  4. 通过 Series.CategoryLabels 与 Series.Values 属性指定图表的分类和数据。
  5. 设置图表的标题、位置以及数据标签等属性。
  6. 通过 Workbook.SaveToFile() 方法保存工作簿。

下面是一个完整的代码示例,展示了在 React 中将工作表中的产品销售数据创建为分离型饼图:

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

    // 加载工作簿,并获取数据所在的工作表
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });
    const sheet = workbook.Worksheets.get(0);

    // 添加一个图表,并将图表类型设置为分离型饼图
    const chart = sheet.Charts.Add();
    chart.ChartType = xlsModule.ExcelChartType.PieExploded;

    // 设置图表的数据区域和标题
    chart.DataRange = sheet.Range.get("B2:B7");
    chart.SeriesDataFromRange = false;
    chart.ChartTitle = "产品销售占比";
    chart.ChartTitleArea.IsBold = true;
    chart.ChartTitleArea.Size = 12;

    // 设置图表的分类标签和数据值,并显示数值标签
    const cs = chart.Series.get(0);
    cs.CategoryLabels = sheet.Range.get("A2:A7");
    cs.Values = sheet.Range.get("B2:B7");
    cs.DataPoints.DefaultDataPoint.DataLabels.HasValue = true;

    // 设置图表的位置
    chart.LeftColumn = 4;
    chart.TopRow = 1;
    chart.RightColumn = 15;
    chart.BottomRow = 25;

    // 隐藏绘图区背景并设置图例位置
    chart.PlotArea.Fill.Visible = false;
    chart.Legend.Position = xlsModule.LegendPositionType.Right;

    // 保存文档
    const outputFileName = 'ExplodedPieChart_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>Create Exploded Pie Chart</h1>
      <button onClick={createExplodedPieChart}>
        Start
      </button>
    </div>
  );
}

export default App;

运行代码后,得到的分离型饼图的效果:

创建分离型饼图


创建分离型环形图

环形图与饼图类似,但中心有一个圆孔,同样能展示各部分在整体中的占比。分离型环形图则进一步将各个扇区拉开。创建分离型环形图的具体操作步骤如下:

  1. 创建 Workbook 对象,并使用 LoadFromFile() 方法载入包含数据的 Excel 文件。
  2. 通过 Workbook.Worksheets.get() 方法获取数据所在的工作表。
  3. 调用 Charts.Add() 方法添加图表,并将 ChartType 设置为 ExcelChartType.DoughnutExploded。
  4. 通过 Series.CategoryLabels 与 Series.Values 属性指定图表的分类和数据。
  5. 设置图表的标题、位置以及数据标签等属性。
  6. 通过 Workbook.SaveToFile() 方法保存工作簿。

下面是一个完整的代码示例,展示了在 React 中将工作表中的产品销售数据创建为分离型环形图:

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

    // 加载工作簿,并获取数据所在的工作表
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });
    const sheet = workbook.Worksheets.get(0);

    // 添加一个图表,并将图表类型设置为分离型环形图
    const chart = sheet.Charts.Add();
    chart.ChartType = xlsModule.ExcelChartType.DoughnutExploded;

    // 设置图表的数据区域和标题
    chart.DataRange = sheet.Range.get("B2:B7");
    chart.SeriesDataFromRange = false;
    chart.ChartTitle = "各产品销售额占比";
    chart.ChartTitleArea.IsBold = true;
    chart.ChartTitleArea.Size = 12;

    // 设置图表的分类标签和数据值,并显示数值标签
    const cs = chart.Series.get(0);
    cs.CategoryLabels = sheet.Range.get("A2:A7");
    cs.Values = sheet.Range.get("B2:B7");
    cs.DataPoints.DefaultDataPoint.DataLabels.HasValue = true;

    // 设置图表的位置
    chart.LeftColumn = 4;
    chart.TopRow = 1;
    chart.RightColumn = 15;
    chart.BottomRow = 25;

    // 隐藏绘图区背景并设置图例位置
    chart.PlotArea.Fill.Visible = false;
    chart.Legend.Position = xlsModule.LegendPositionType.Right;

    // 保存文档
    const outputFileName = 'ExplodedDoughnutChart_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>Create Exploded Doughnut Chart</h1>
      <button onClick={createExplodedDoughnutChart}>
        Start
      </button>
    </div>
  );
}

export default App;

运行代码后,得到的分离型环形图的效果:

创建分离型环形图


常见问题

生成的图表是空白的,没有扇区

原因:未给图表指定有效的数据系列。例如 DataRange 或 Values 指向了空区域或没有数值的区域,图表没有可绘制的数据。

解决:为图表指定包含数据的单元格区域,例如:

chart.DataRange = sheet.Range.get("A1:B7");

const cs = chart.Series.get(0);
cs.CategoryLabels = sheet.Range.get("A2:A7");
cs.Values = sheet.Range.get("B2:B7");

想让数据标签显示百分比(而不是数值)

原因:饼图和环形图通常用来展示占比,但数据标签默认显示数值,或者未开启百分比标签。

解决:关闭数值标签并开启百分比标签,例如:

const cs = chart.Series.get(0);
cs.DataPoints.DefaultDataPoint.DataLabels.HasValue = false;
cs.DataPoints.DefaultDataPoint.DataLabels.HasPercentage = true;

获取免费许可证

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

默认情况下,Excel 会在每一页的顶部和底部显示相同的页眉和页脚。但在打印正式报表、手册或论文时,常常需要让不同的页面显示不同的页眉页脚——例如奇数页和偶数页使用不同的页眉,或者首页不显示页眉页脚、只让正文页显示页码。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成此操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。

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

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


为奇偶页设置不同的页眉页脚

在书籍、论文或双面打印的报表中,奇数页和偶数页通常使用不同的页眉页脚,例如奇数页页眉显示章节名,偶数页页眉显示书名。使用 Spire.XLS for JavaScript 为奇偶页设置不同的页眉页脚,具体操作步骤如下:

  1. 创建 Workbook 对象,并使用 LoadFromFile() 方法载入 Excel 文件。
  2. 通过 Workbook.Worksheets.get() 方法获取指定工作表。
  3. 将 PageSetup.DifferentOddEven 属性设置为 1,开启奇偶页页眉页脚不同。
  4. 使用 PageSetup.OddHeaderString 和 PageSetup.OddFooterString 属性设置奇数页的页眉和页脚。
  5. 使用 PageSetup.EvenHeaderString 和 PageSetup.EvenFooterString 属性设置偶数页的页眉和页脚。
  6. 通过 Workbook.SaveToFile() 方法保存工作簿。

下面是一个完整的代码示例,展示了在 React 中为工作表奇偶页设置不同的页眉页脚(示例输入文件包含跨越多页的数据,便于观察不同页的效果):

function App() {
  const setOddEvenHeaderFooter = 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 = 'DifferentHeaderFooter.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.PageSetup.DifferentOddEven = 1;

    // 设置奇数页的页眉和页脚(橙色、加粗)
    sheet.PageSetup.OddHeaderString = "&\"宋体\"&12&B&KFFC000奇数页页眉";
    sheet.PageSetup.OddFooterString = "&\"宋体\"&12&B&KFFC000奇数页页脚";

    // 设置偶数页的页眉和页脚(红色、加粗)
    sheet.PageSetup.EvenHeaderString = "&\"宋体\"&12&B&KFF0000偶数页页眉";
    sheet.PageSetup.EvenFooterString = "&\"宋体\"&12&B&KFF0000偶数页页脚";

    // 切换为页面布局视图,便于预览页眉页脚
    sheet.ViewMode = xlsModule.ViewMode.Layout;

    // 保存文档
    const outputFileName = 'DifferentHeaderFooterOddEven_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 Different Header and Footer for Odd and Even Pages</h1>
      <button onClick={setOddEvenHeaderFooter}>
        Start
      </button>
    </div>
  );
}

export default App;

设置完成后,即可实现为奇偶页设置不同页眉页脚的效果。

为奇偶页设置不同的页眉页脚


为首页设置与其他页不同的页眉页脚

很多正式文档要求首页(封面页)不显示或显示专用的页眉页脚,而正文页显示带文件信息的页眉页脚。此时可开启“首页不同”,单独设置首页的页眉页脚。具体操作步骤如下:

  1. 创建 Workbook 对象,并使用 LoadFromFile() 方法载入 Excel 文件。
  2. 通过 Workbook.Worksheets.get() 方法获取指定工作表。
  3. 将 PageSetup.DifferentFirst 属性设置为 1,开启首页页眉页脚与其他页不同。
  4. 使用 PageSetup.FirstHeaderString 和 PageSetup.FirstFooterString 属性设置首页的页眉和页脚。
  5. 使用 PageSetup.LeftHeader、CenterFooter 等属性设置其他页的页眉和页脚。
  6. 通过 Workbook.SaveToFile() 方法保存工作簿。

下面是一个完整的代码示例,展示了在 React 中为工作表首页设置与其他页不同的页眉页脚:

function App() {
  const setFirstPageHeaderFooter = 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 = 'DifferentHeaderFooter.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.PageSetup.DifferentFirst = 1;

    // 设置首页的页眉和页脚(蓝色、加粗)
    sheet.PageSetup.FirstHeaderString = "&\"宋体\"&16&B&K4253E2首页页眉";
    sheet.PageSetup.FirstFooterString = "&\"宋体\"&16&B&K4253E2首页页脚";

    // 设置其他页的页眉和页脚(灰色、加粗)
    sheet.PageSetup.LeftHeader = "&\"宋体\"&12&B&K808080其他页页眉";
    sheet.PageSetup.CenterFooter = "&\"宋体\"&12&B&K808080其他页页脚";

    // 切换为页面布局视图,便于预览页眉页脚
    sheet.ViewMode = xlsModule.ViewMode.Layout;

    // 保存文档
    const outputFileName = 'DifferentHeaderFooterFirstPage_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 Different Header and Footer on the First Page</h1>
      <button onClick={setFirstPageHeaderFooter}>
        Start
      </button>
    </div>
  );
}

export default App;

设置完成后,即可实现首页与其他页页眉页脚不同的效果。

为首页设置与其他页不同的页眉页脚


常见问题

首页的页眉页脚没有生效

原因:未开启“首页不同”。若只设置 FirstHeaderString/FirstFooterString,而没有将 PageSetup.DifferentFirst 设置为 1,Excel 会忽略首页专用页眉页脚。

解决:开启 sheet.PageSetup.DifferentFirst = 1; 后再设置首页页眉页脚。

页眉页脚中的中文显示为乱码或方框

原因:浏览器端的 Excel 处理依赖字体文件,若未将中文字体(如 simsun.ttc)载入虚拟文件系统(VFS),中文字符可能无法正常渲染。

解决:在调用 Workbook.LoadFromFile() 前,先通过 FetchFileToVFS() 将字体载入,例如:

await window.spire.FetchFileToVFS(
  'simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`
);

获取免费许可证

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