在 Excel 里维护商品清单、订单明细这类表格时,一个单元格常常需要承载不止一种格式:说明文字要分成几行,促销语里只有几个字要加粗,库存提示则要用红色标出来。手工做法是双击单元格、选中片段、逐个调整字体,条目一多就很难批量完成。HTML 字符串恰好适合描述这类「一段文字里各部分样式不同」的内容,Spire.XLS for JavaScript 提供了 HtmlString 属性,把一段 HTML 直接写进单元格,换行、加粗、倾斜、下划线、颜色和字号都按标签渲染,批量处理只是循环里的一行赋值。它基于 WebAssembly 在浏览器端直接完成,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
在单元格里换行,通常要在编辑状态下按 Alt+Enter 手动插入。数据来自数据库或表单时,这类手工操作无法批量完成,而 HTML 里的 <br> 标签表达的正是「在此处断行」。HtmlString 会把 <br>、<div>、<p> 都解析成单元格内的换行符,一个字符串写进去就是一格多行。具体操作步骤如下:
<br> 分段的 HTML 字符串。HtmlString 把它们逐个写进 A 列的单元格。以下为完整的代码示例,演示如何在 React 中向 Excel 单元格写入带换行的 HTML 文本:
function App() {
const setMultilineText = 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/`);
// 新建工作簿并取第一个工作表,拉宽 A 列免得一段文字被折成好几段
const workbook = new xlsModule.Workbook();
const sheet = workbook.Worksheets.get(0);
sheet.Range.get('A1:A5').ColumnWidth = 30;
// 每行的说明由三段文字组成,用 <br> 在同一个单元格里换行
const notes = [
'蓝牙 5.3 双设备连接<br>续航 30 小时<br>Type-C 快充',
'静音微动按键<br>内置 800 mAh 电池<br>支持 2.4G 无线',
'双麦克风降噪<br>单次续航 6 小时<br>磁吸充电盒',
'1080P 高清分辨率<br>重量 780 克<br>Type-C 一线连',
'木质箱体<br>蓝牙 5.0 连接<br>附赠遥控器',
];
// 把 HTML 字符串逐格写进 A 列,一个单元格里显示三行
notes.forEach((html, index) => {
sheet.Range.get(`A${index + 1}`).HtmlString = html;
});
// 保存工作簿
const outputFileName = 'MultilineHtmlText.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>Excel HTML 富文本</h1>
<button onClick={setMultilineText}>多行 HTML 文本</button>
</div>
);
}
export default App;
运行后,在单元格中写入带换行的 HTML 文本的效果:

同一个单元格里的文字不必格式一致。同一段内容中,往往只有其中一部分需要突出显示,其余部分保持普通字重即可。HtmlString 支持 <b>、<i>、<u> 等标签,把颜色和字号写在 style 里交给 <span> 承载,被标签包住的片段会各自成为独立的一段格式,互不影响,标签本身不会出现在单元格里。具体操作步骤如下:
<b>、<i>、<u> 标出需要强调的文字,用 <span style="color:#C00000;font-size:14pt"> 包住需要突出的文字,颜色写十六进制色值,字号按磅值给出。以下为完整的代码示例,演示如何在 React 中为 Excel 单元格的部分文字设置格式:
function App() {
const setPartialFormatting = 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/`);
// 新建工作簿并取第一个工作表,拉宽 A 列免得一段文字被折成好几段
const workbook = new xlsModule.Workbook();
const sheet = workbook.Worksheets.get(0);
sheet.Range.get('A1:A5').ColumnWidth = 30;
// 一段促销语里的各段内容分别套上 <b>、<i>、<u> 和带样式的 <span>,
// 加粗、倾斜、下划线、颜色和字号都只作用于被标签包住的那段文字
const promotions = [
'<b>满 300 减 50</b>,<i>限时三天</i>,<u>每人限购两件</u>,<span style="color:#C00000;font-size:14pt">仅剩 3 件</span>',
'<b>第二件半价</b>,<i>仅限本周</i>,<u>不与其他优惠叠加</u>',
'<b>下单立减 100</b>,<i>今日 24 点结束</i>,<u>赠收纳包</u>,<span style="color:#C00000;font-size:14pt">库存紧张</span>',
'<b>以旧换新补贴 200</b>,<i>活动至月底</i>,<u>需出示旧机</u>',
'<b>买一赠一</b>,<i>限量 500 套</i>,<u>赠品随机发放</u>,<span style="color:#E36C0A;font-size:14pt">补货中</span>',
];
// 把 HTML 字符串逐格写进 A 列
promotions.forEach((html, index) => {
sheet.Range.get(`A${index + 1}`).HtmlString = html;
});
// 保存工作簿
const outputFileName = 'PartialFormattingHtml.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>Excel HTML 富文本</h1>
<button onClick={setPartialFormatting}>部分文字格式</button>
</div>
);
}
export default App;
运行后,为单元格中的部分文字设置加粗、倾斜、下划线、颜色与字号的效果:

原因:浏览器解析 HTML 时会把连续空格合并成一个,容易让人以为 Spire 也这么做,于是改用 逐个拼空格。
解决:不会被吞。A B 写进单元格后读回来仍是 5 个空格,行首的多个空格也原样保留; 同样有效,但一般的对齐场景不需要替换。
原因:HtmlString 会改动单元格的字体属性,于是担心它把先前设好的对齐方式一并重置掉。
解决:对齐方式不受影响。先设置 Style.HorizontalAlignment 再写 HtmlString,读回来仍是原来的对齐值,先设格式再填内容可以放心。
const cell = sheet.Range.get('A1');
cell.Style.HorizontalAlignment = xlsModule.HorizontalAlignType.Center;
cell.HtmlString = '<b>居中</b>';
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
一张图表能让人看清一组数据的走势,却很难让人同时看清几十组数据的走势。迷你图(Sparkline)正是为此而生:它把一条趋势线压缩进单个单元格,没有坐标轴也没有图例,却能让整列趋势在扫视中一览无余。把各地区、各季度的销售数字逐行配上迷你图,哪一行的曲线在爬升、哪一行在中途掉头,往往比读一列数字快得多。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成此操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍三个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
折线迷你图把一行数值按顺序连成一条细线,走势的升降一目了然。它不占单元格的位置,也不会遮住单元格里的数字,一小列宽度就能排下几十行的走势,哪一行在爬升、哪一行中途掉头,扫一眼便知。迷你图不能脱离迷你图组单独存在,同一批迷你图的类型、颜色和标记样式由组统一决定。具体操作步骤如下:
SparklineGroups.AddGroup() 新建折线迷你图组,设置线条粗细与数据点标记颜色。Add() 取到组内的迷你图集合,为每一行数据在 F 列生成一个迷你图。以下为完整的代码示例,演示如何在 React 中为 Excel 数据添加折线迷你图:
function App() {
const addLineSparkline = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和测试数据文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'SparklineData.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 sparklineGroup = sheet.SparklineGroups.AddGroup({ sparklineType: xlsModule.SparklineType.Line });
// 设置折线样式:加粗线条并显示数据点标记
sparklineGroup.LineWeight = 1.5;
sparklineGroup.ShowMarkers = true;
sparklineGroup.MarkersColor = xlsModule.Color.get_Red();
// 取到组内的迷你图集合
const sparklines = sparklineGroup.Add();
// 为每一行数据在 F 列生成一个迷你图
for (let row = 2; row <= 9; row++) {
sparklines.Add({
dataRange: sheet.Range.get(`B${row}:E${row}`),
referenceRange: sheet.Range.get(`F${row}`),
});
}
// 保存工作簿
const outputFileName = 'AddLineSparkline.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 从 VFS 读取结果文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>折线迷你图</h1>
<button onClick={addLineSparkline}>Start</button>
</div>
);
}
export default App;
运行后,添加折线迷你图的效果:

柱形迷你图用一排细柱表示数值大小,比较同一行里几个数的高低时比折线更直观。它的用法与折线迷你图一致,区别在于颜色落在每根柱子本身,而不是一条连线上。
真正让柱形迷你图好用的是高低点标记:一行里最高的那一季和最矮的那一季会被自动找出来单独着色,峰值和低谷不必再对着数字比较。具体操作步骤如下:
SparklineGroups.AddGroup() 新建柱形迷你图组(类型传 SparklineType.Column),并用 SparklineColor 设置系列颜色。ShowHighPoint 与 ShowLowPoint,分别用 HighPointColor 与 LowPointColor 指定颜色。Add() 取到组内的迷你图集合,为每一行数据在 G 列生成一个迷你图并保存工作簿。以下为完整的代码示例,演示如何在 React 中为 Excel 数据添加柱形迷你图并突出最高点与最低点:
function App() {
const addColumnSparkline = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和测试数据文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'SparklineData.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 sparklineGroup = sheet.SparklineGroups.AddGroup({ sparklineType: xlsModule.SparklineType.Column });
// 设置迷你图本身的颜色
sparklineGroup.SparklineColor = xlsModule.Color.get_CadetBlue();
// 打开最高点与最低点的标记,并分别指定颜色
sparklineGroup.ShowHighPoint = true;
sparklineGroup.HighPointColor = xlsModule.Color.get_Red();
sparklineGroup.ShowLowPoint = true;
sparklineGroup.LowPointColor = xlsModule.Color.get_Purple();
const sparklines = sparklineGroup.Add();
// 为每一行数据在 G 列生成一个迷你图
for (let row = 2; row <= 9; row++) {
sparklines.Add({
dataRange: sheet.Range.get(`B${row}:E${row}`),
referenceRange: sheet.Range.get(`G${row}`),
});
}
// 保存工作簿
const outputFileName = 'AddColumnSparkline.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 从 VFS 读取结果文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>柱形迷你图</h1>
<button onClick={addColumnSparkline}>Start</button>
</div>
);
}
export default App;
运行后,添加柱形迷你图并突出最高点与最低点的效果:

报表改版或数据口径变化时,原本标在旁边的迷你图可能不再适用,需要整批清掉重画。迷你图组由工作表持有,清掉之后单元格回到普通状态,其中的数据和其他格式都不受影响。具体操作步骤如下:
SparklineGroups.Clear() 清除该工作表中的全部迷你图组。以下为完整的代码示例,演示如何在 React 中清除工作表上的迷你图:
function App() {
const clearSparklines = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和测试数据文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'SparklineData.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(1);
// 清除该工作表中的全部迷你图组
sheet.SparklineGroups.Clear();
// 保存工作簿
const outputFileName = 'ClearSparklines.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 从 VFS 读取结果文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>清除迷你图</h1>
<button onClick={clearSparklines}>Start</button>
</div>
);
}
export default App;
运行后,清除工作表上的迷你图的效果:

原因:迷你图不是单元格内容。它归工作表上的迷你图组所有,画在落点单元格之上,自身的数据和设置则写在工作表的扩展区里,因此逐个单元格读取时落点列始终是空值,单元格上也没有接口可以问出「这里有没有迷你图」。
解决:改从工作表级的 SparklineGroups 读取。get_Item(0) 取到第一组,组上的 SparklineType 即可判断这一批是折线还是柱形,颜色等设置也在同一处读取:
const group = sheet.SparklineGroups.get_Item(0);
console.log(group.SparklineType); // SparklineType.Line 或 SparklineType.Column
原因:LineWeight 与 ShowMarkers 只对折线迷你图生效。柱形迷你图用柱子表示数值,既没有连线也没有数据点标记,即使赋值成功也不会写入结果文件。
解决:需要线条和标记时,把迷你图组建成折线类型再设置:
const sparklineGroup = sheet.SparklineGroups.AddGroup({ sparklineType: xlsModule.SparklineType.Line });
sparklineGroup.LineWeight = 1.5;
sparklineGroup.ShowMarkers = true;
sparklineGroup.MarkersColor = xlsModule.Color.get_Red();
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
打印、拼版、加水印之前,先得知道每一页是横版还是竖版:横竖混排的文档直接打印会留白边,拼版也会错位。页面还可能被整体旋转过,这时候记录的页面尺寸和肉眼看到的方向就对不上了。批量处理前想把这类页面挑出来,用桌面软件只能一页页翻。
本文介绍用 Spire.PDF for JavaScript 检测 PDF 页面的旋转角度与显示方向。它基于 WebAssembly 在浏览器端直接加载与解析 PDF 文档,全过程在本地完成,通过虚拟文件系统(VFS)读写文件,无需后端配合。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
PdfPageBase.Rotation 用来读取页面被旋转的角度,取值落在 PdfPageRotateAngle 枚举的 0°、90°、180°、270° 之内。
function App() {
const detectPageRotation = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 将待检测的 PDF 文件载入 VFS
const inputFileName = '页面方向样例.pdf';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载 PDF 文档
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 枚举的数值是序号而不是角度,先建立序号到角度的映射
const DEGREES = {
[pdfModule.PdfPageRotateAngle.RotateAngle0.value]: 0,
[pdfModule.PdfPageRotateAngle.RotateAngle90.value]: 90,
[pdfModule.PdfPageRotateAngle.RotateAngle180.value]: 180,
[pdfModule.PdfPageRotateAngle.RotateAngle270.value]: 270,
};
// 逐页读取旋转角度
const lines = [];
for (let i = 0; i < doc.Pages.Count; i++) {
const page = doc.Pages.get_Item(i);
lines.push(`第 ${i + 1} 页:旋转角度 ${DEGREES[page.Rotation.value]}°`);
}
// 检测结果写入 VFS
const outputFileName = '旋转角度检测结果.txt';
window.dotnetRuntime.Module.FS.writeFile(outputFileName, lines.join('\r\n'));
doc.Close();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>检测页面旋转角度</h1>
<button onClick={detectPageRotation}>
开始检测
</button>
</div>
);
}
export default App;
逐页记录旋转角度的检测结果:

判断页面是横是竖不能只看尺寸:PdfPageBase.Size 给出的是可见页面框的宽高,不含旋转;页面被旋转 90° 或 270° 时,显示出来的宽高要对调,才是页面实际的方向。
function App() {
const detectPageOrientation = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 将待检测的 PDF 文件载入 VFS
const inputFileName = '页面方向样例.pdf';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 创建 PdfDocument 对象并加载 PDF 文档
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
const { RotateAngle90, RotateAngle270 } = pdfModule.PdfPageRotateAngle;
// 逐页判断显示方向
const lines = [];
for (let i = 0; i < doc.Pages.Count; i++) {
const page = doc.Pages.get_Item(i);
const pageSize = page.Size;
// Size 不含旋转:旋转 90° 或 270° 时宽高对调,才是实际显示尺寸
const quarterTurn = page.Rotation === RotateAngle90 || page.Rotation === RotateAngle270;
const width = quarterTurn ? pageSize.Height : pageSize.Width;
const height = quarterTurn ? pageSize.Width : pageSize.Height;
const orientation = width >= height ? '横向' : '纵向';
lines.push(`第 ${i + 1} 页:${orientation}(${width.toFixed(0)} × ${height.toFixed(0)} 磅)`);
}
// 检测结果写入 VFS
const outputFileName = '页面方向检测结果.txt';
window.dotnetRuntime.Module.FS.writeFile(outputFileName, lines.join('\r\n'));
doc.Close();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>检测页面显示方向</h1>
<button onClick={detectPageOrientation}>
开始检测
</button>
</div>
);
}
export default App;
按显示方向逐页判断后的检测结果:

page.Rotation.value 读到的是 0、1、2、3,而不是 0、90、180、270原因:PdfPageRotateAngle 的四个成员 RotateAngle0、RotateAngle90、RotateAngle180、RotateAngle270 在 WebAssembly 绑定里的数值依次是 0、1、2、3,.value 取到的是这个序号,而不是角度值。写成 page.Rotation.value === 90 恒为 false。
解决:建立序号到角度的映射再使用。要修正旋转时也传序号——page.Rotation 收的是序号,传 90 会被当成序号 2,实际写进文档的是 180°:
// 序号 → 角度
const DEGREES = {
[pdfModule.PdfPageRotateAngle.RotateAngle0.value]: 0,
[pdfModule.PdfPageRotateAngle.RotateAngle90.value]: 90,
[pdfModule.PdfPageRotateAngle.RotateAngle180.value]: 180,
[pdfModule.PdfPageRotateAngle.RotateAngle270.value]: 270,
};
// 把当前页修正为 90°,.value 就是序号
page.Rotation = pdfModule.PdfPageRotateAngle.RotateAngle90.value;
page.Size 返回的却是竖版尺寸原因:PdfPageBase.Size 与 ActualSize 返回的是页面可见框的宽高——设过 CropBox 就是 CropBox,否则是 MediaBox——这个值不随 /Rotate 变化。示例文档第 3 页的页面框是 595 × 842 磅,/Rotate 为 90°,渲染出来是 842 × 595 的横版,Size 依旧返回 595 × 842。
解决:读到 Rotation 之后自己把宽高对调:
const { RotateAngle90, RotateAngle270 } = pdfModule.PdfPageRotateAngle;
const quarterTurn = page.Rotation === RotateAngle90 || page.Rotation === RotateAngle270;
const width = quarterTurn ? page.Size.Height : page.Size.Width;
const height = quarterTurn ? page.Size.Width : page.Size.Height;
doc.Sections 是空的,PageSettings.Orientation 也读不到原因:Sections 是页面设置的容器,LoadFromFile 载入已有 PDF 时不会为现成页面重建节,doc.Sections.Count 为 0,接着调 doc.Sections.get_Item(0) 会抛 Arg_IndexOutOfRangeException。PageSettings.Orientation 描述的是新建页面时的排版意图,也不是文件里记录的方向。
解决:检测已有页面只走页级属性,doc.Pages.get_Item(i) 之后读 Rotation 与 Size;PageSettings.Orientation 留给 Sections.Add() 新建页面时使用:
// 检测已有页面
const page = doc.Pages.get_Item(0);
const angle = DEGREES[page.Rotation.value];
// 新建页面时才用得到 Orientation
const section = doc.Sections.Add();
section.PageSettings.Orientation = pdfModule.PdfPageOrientation.Landscape;
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
我们很高兴地宣布 Spire.Doc 14.9.11 版本正式发布。本次更新新增 ImportOptions、ExportOptions 类,精细化管控文档加载与导出流程;支持 HTML 转 Word 时渲染 HSL 色彩;同时修复大量格式转换、文档编辑场景下的已知缺陷,详情如下。
调整:
net6.0 外部依赖:
SkiaSharp >= 3.116.1
Microsoft.Win32.Registry >= 5.0.0
System.Buffers >= 4.5.1
System.Memory >= 4.5.5
System.Text.Encoding.CodePages >= 6.0.0
HarfBuzzSharp >=8.3.0.1
net9.0 外部依赖:
SkiaSharp >= 3.116.1
Microsoft.Win32.Registry >= 5.0.0
System.Buffers >= 4.5.1
System.Memory >= 4.5.5
System.Text.Encoding.CodePages >= 9.0.0
HarfBuzzSharp >=8.3.0.1
net10.0 外部依赖:
SkiaSharp >= 3.116.1
Microsoft.Win32.Registry >= 5.0.0
System.Buffers >= 4.5.1
System.Memory >= 4.5.5
System.Text.Encoding.CodePages >= 10.0.0
HarfBuzzSharp >=8.3.0.1
Image img = document.SaveToImages(0, ImageType.Bitmap);
img.Save("sample.png", ImageFormat.Png);
SkiaSharp.SKImage images = document.SaveToImages(0, ImageType.Bitmap);
FileStream fileStream = new FileStream(outputFile, FileMode.Create, FileAccess.Write);
images.Encode(SkiaSharp.SKEncodedImageFormat.Png, 100).SaveTo(fileStream);
fileStream.Flush();
新功能:
using Spire.Doc;
using Spire.Doc.Importing;
var importOptions = new ImportOptions { Password = "123456" };
var doc = new Document();
doc.LoadFromFile("encrypted.docx", importOptions);
using Spire.Doc;
using Spire.Doc.Importing;
// 实现资源加载回调类
public class MyAssetLoader : IAssetLoadingCallback
{
public AssetLoadingAction AssetLoading(AssetLoadingArgs args)
{
// 拦截追踪类图片
if (args.Url.Contains("tracker"))
return AssetLoadingAction.Block;
// 带鉴权加载自定义图片资源
if (args.AssetType == AssetType.Image && args.Url.StartsWith("https://api."))
{
var data = LoadWithAuth(args.Url);
args.SetData(data);
return AssetLoadingAction.Custom;
}
// 重定向CDN地址至本地镜像
if (args.Url.Contains("cdn.example.com"))
{
args.Url = args.Url.Replace("cdn.example.com", "local.mirror.com");
return AssetLoadingAction.Download;
}
// 默认逻辑:从原地址下载资源
return AssetLoadingAction.Download;
}
}
// 传入回调加载文档
var doc = new Document();
var options = new ImportOptions
{
AssetLoadingCallback = new MyAssetLoader()
};
doc.LoadFromFile("document.html", options);
using Spire.Doc;
using Spire.Doc.Exporting;
var doc = new Document();
doc.LoadFromFile("input.docx");
// 使用默认DOC格式导出
//var options = new DocExportOptions();
// 使用默认DOCX格式导出
var options = new DocxExportOptions();
doc.SaveToFile("output.docx", options);
// 指定Word 2019版本格式导出
doc.SaveToFile("output.docx", new DocxExportOptions(FileFormat.Docx2019));
using Spire.Doc;
using Spire.Doc.Exporting;
// 1. 实现进度回调接口
public class ProgressReporter : IExportingProgressCallback
{
public void OnProgress(ExportingProgressArgs args)
{
// 进度取值范围0~100
Console.WriteLine($"导出进度: {args.EstimatedProgressPercentage:F1}%");
// 如需终止导出,可在此抛出异常
}
}
// 2. 绑定回调并执行导出
var doc = new Document();
doc.LoadFromFile("large-document.docx");
var options = new DocxExportOptions(FileFormat.Docx)
{
ProgressCallback = new ProgressReporter()
};
doc.SaveToFile("output.docx", options);
Document doc = ConvertUtil.GetNewEngineDocument();
Section section = doc.AddSection();
ParagraphStyle heading1 = (ParagraphStyle)doc.Styles["Heading 1"];
Assert.IsFalse(heading1.ParagraphFormat.NoSpaceBetweenSameStyle);
heading1.ParagraphFormat.NoSpaceBetweenSameStyle = true;
Spire.Doc.Documents.Paragraph sameStyle1 = AddParagraph(section, SameStyleText1, BuiltinStyle.Heading1);
Spire.Doc.Documents.Paragraph sameStyle2 = AddParagraph(section, SameStyleText2, BuiltinStyle.Heading1);
Spire.Doc.Documents.Paragraph otherStyle = AddParagraph(section, OtherStyleText, BuiltinStyle.Heading2);
doc.SaveToFile(outputFile_temp, FileFormat.Docx);
Document doc = new Document();
Section sec = doc.AddSection();
Paragraph para = sec.AddParagraph();
para.AppendHTML("<p style=\"color:hsl(0,75%,60%)\">Hello</p>");
doc.SaveToFile("HtmlTest_hsl.docx");
Document doc = new Document();
doc.LoadFromFile(@"input.docx");
foreach (Paragraph para in doc.Sections[0].Paragraphs)
{
foreach (DocumentObject obj in para.ChildObjects)
{
if (obj is ShapeObject shape && shape.Chart != null)
{
Chart chart = shape.Chart;
// 清除图表绘图区边框
chart.Format.Stroke.Fill.FillType = FillType.NoFill;
}
}
}
doc.SaveToFile("NoBorder.docx", FileFormat.Docx);
问题修复:
扫描件、图纸、电子发票这类 PDF 往往带着大片白边,页面尺寸却仍是原来的规格;反过来,也有人只想要页面上的一小块内容,其余部分都不必出现。要把多余的部分去掉,过去要么在桌面软件里一页页手动框选,要么把文件传到服务端处理——前者很难嵌进 Web 流程,后者意味着文档离开了用户的设备。
本文介绍用 Spire.PDF for JavaScript 裁切 PDF 页面。它基于 WebAssembly 在浏览器端直接加载、修改与保存 PDF 文档,全过程在本地完成,通过虚拟文件系统(VFS)读写文件,无需后端配合。本文以一个两页的示例文档演示这一过程。
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
裁切页面靠 page.CropBox 完成,它是一个 RectangleF:x、y 从页面的左上角量起,width 与 height 决定保留多大一块,框外的内容不再显示。整份文档按同一套边距裁,就是遍历 doc.Pages,在每页 MediaBox 的基础上让出四边的距离。
function App() {
const cropPdfPage = 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);
// 四边统一裁掉 60 磅
const margin = 60;
for (let i = 0; i < doc.Pages.Count; i++) {
const page = doc.Pages.get_Item(i);
// MediaBox 给出该页的完整范围,据此算出裁切框(x、y 自页面左上角量起)
const width = page.MediaBox.Width;
const height = page.MediaBox.Height;
page.CropBox = new pdfModule.RectangleF({
x: margin,
y: margin,
width: width - margin * 2,
height: height - margin * 2,
});
}
const outputFileName = '按边距裁切.pdf';
doc.SaveToFile(outputFileName);
doc.Close();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>裁切 PDF 页面</h1>
<button onClick={cropPdfPage}>
开始裁切
</button>
</div>
);
}
export default App;
两页都按 60 磅边距裁切,页面外侧的白边与边框一并去掉:

原因:CropBox 是页面级属性,没有“整份文档”这一层接口;上面的循环只是为了让每页套用同一套边距。
解决:只裁某一页时,把循环去掉、直接对目标页赋值即可,x、y 同样自页面左上角量起:
// 只裁第 1 页:保留自左上角 (80, 80) 起 400 × 500 磅的一块
const page = doc.Pages.get_Item(0);
page.CropBox = new pdfModule.RectangleF({ x: 80, y: 80, width: 400, height: 500 });
原因:CropBox 是就地改页面框,文档里没有另外记下“原来的框”。直觉上会把 page.MediaBox 赋回去,但这一步不会生效——赋值时的坐标是在当前可见区原点上叠加的,只换尺寸不改原点,可见区就停在裁切后的位置。裁掉 60 磅之后再把 MediaBox 赋回去,可见区仍然从 (60, 60) 起。
解决:裁切前留有原始文档时,重新载入它最省事。只能就着已经裁过的文件处理时,用负偏移把原点抵回页面左上角,再给回整页尺寸:
// 裁切偏移为 (offsetX, offsetY) 时的还原写法
page.CropBox = new pdfModule.RectangleF({
x: -offsetX,
y: -offsetY,
width: page.MediaBox.Width,
height: page.MediaBox.Height,
});
原因:CropBox 是软裁切,只改页面的可见框,框外的内容依旧留在文件里,文本搜索和复制仍能取到。
解决:如果目的是把内容真正从页面上去掉,就不能只设 CropBox,而要重建页面——新建一份同尺寸的文档,用 page.CreateTemplate() 取出内容后绘制到新页上,再另存为新文件。
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
把产品手册的封面补到项目说明前面、把报价单的第 2 到第 3 页并进合同、把几份报告合成一份汇总,说到底都是把页面从一份 PDF 搬到另一份 PDF 里。桌面软件的做法是开两个窗口来回拖拽,拖错一次就得重来;页数一多,搬过去的顺序也容易乱。还有一类麻烦是尺寸对不上:封面是 A5、目标文档是 A4,整页搬过去会露出一圈空白。
Spire.PDF for JavaScript 基于 WebAssembly 在浏览器端直接加载、修改与保存 PDF 文档,复制过程全部在本地完成,通过虚拟文件系统(VFS)读写文件,无需后端配合。
本文介绍四个核心功能点,前三个搬的是整页,源页的尺寸、旋转与页边距都原样带过来;第四个只取页面内容,画到多大的页面上由你决定。
整页搬运
内容复制(模板)
有关安装和项目配置,请参考 React 项目中集成 Spire.PDF for JavaScript。以下示例默认已安装 Spire.PDF 并完成 WebAssembly 模块初始化。
Spire.PDF for JavaScript 提供 PdfDocument.InsertPage 方法,用于把另一份文档里的某一页复制到当前文档,并指定它落在当前文档的第几位,不传落点下标时,追加到末尾。
function App() {
const copyPageAtPosition = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 把源文档与目标文档都载入 VFS
const sourceFileName = '源文档.pdf';
const targetFileName = '目标文档.pdf';
await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 分别载入两份文档
const sourceDoc = new pdfModule.PdfDocument();
sourceDoc.LoadFromFile(sourceFileName);
const targetDoc = new pdfModule.PdfDocument();
targetDoc.LoadFromFile(targetFileName);
// 把源文档的第 1 页复制到目标文档的最前面
// pageIndex 取自源文档,resultPageIndex 是复制结果在目标文档中的落点
targetDoc.InsertPage({ ldDoc: sourceDoc, pageIndex: 0, resultPageIndex: 0 });
// 保存结果文档
const outputFileName = '复制指定页面.pdf';
targetDoc.SaveToFile(outputFileName);
sourceDoc.Close();
targetDoc.Close();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>复制指定页面</h1>
<button onClick={copyPageAtPosition}>
开始复制
</button>
</div>
);
}
export default App;
resultPageIndex是四个功能点里唯一能控制落点的地方:传 0 插到首位、传 1 插到第 2 位,传当前页数则等于追加到末尾。
源文档的第 1 页排在目标文档最前面,文档由 2 页变为 3 页:

Spire.PDF for JavaScript 还提供 PdfDocument.InsertPageRange 方法,用于把源文档里一段连续的页复制过来。它接收源文档和起点、终点两个下标,没有落点参数,复制结果一律追加到当前文档末尾。
function App() {
const appendPageRange = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 把源文档与目标文档都载入 VFS
const sourceFileName = '源文档.pdf';
const targetFileName = '目标文档.pdf';
await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 分别载入两份文档
const sourceDoc = new pdfModule.PdfDocument();
sourceDoc.LoadFromFile(sourceFileName);
const targetDoc = new pdfModule.PdfDocument();
targetDoc.LoadFromFile(targetFileName);
// 把源文档的第 2 页到第 3 页追加到目标文档末尾
// 注意:这里是位置参数,不是对象;endIndex 含端点
targetDoc.InsertPageRange(sourceDoc, 1, 2);
// 保存结果文档
const outputFileName = '复制页面范围.pdf';
targetDoc.SaveToFile(outputFileName);
sourceDoc.Close();
targetDoc.Close();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>复制页面范围</h1>
<button onClick={appendPageRange}>
复制第 2-3 页
</button>
</div>
);
}
export default App;
源文档的第 2、3 页追加后文档为 4 页:

整份文档都要搬时不必先算下标,PdfDocument.AppendPage 接收源文档对象,按原顺序把它的全部页追加到当前文档末尾。合并几份文档、给报告加附件这类场景,把手上的文档依次传进去就行。
function App() {
const appendWholeDocument = async () => {
// 获取 Spire.PDF WASM 模块
const pdfModule = window.wasmModule?.spirepdf;
// 检查模块是否就绪
if (!pdfModule) {
alert('Spire.PDF is not ready yet');
return;
}
// 把源文档与目标文档都载入 VFS
const sourceFileName = '源文档.pdf';
const targetFileName = '目标文档.pdf';
await window.spire.FetchFileToVFS(sourceFileName, "", `${process.env.PUBLIC_URL}/data/`);
await window.spire.FetchFileToVFS(targetFileName, "", `${process.env.PUBLIC_URL}/data/`);
// 分别载入两份文档
const sourceDoc = new pdfModule.PdfDocument();
sourceDoc.LoadFromFile(sourceFileName);
const targetDoc = new pdfModule.PdfDocument();
targetDoc.LoadFromFile(targetFileName);
// 整份文档都要复制时用 AppendPage,按原顺序追加全部页
targetDoc.AppendPage({ doc: sourceDoc });
// 保存结果文档
const outputFileName = '复制全部页面.pdf';
targetDoc.SaveToFile(outputFileName);
sourceDoc.Close();
targetDoc.Close();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>复制整份文档</h1>
<button onClick={appendWholeDocument}>
开始复制
</button>
</div>
);
}
export default App;
源文档全部 4 页追加后文档为 6 页:

Spire.PDF for JavaScript 还提供 PdfPageBase.CreateTemplate 方法,用于把一页的内容取成一个 PdfTemplate,再由 Canvas.DrawTemplate 画到新建的页面上。前面三个方法搬的是整页,新页尺寸跟着源页走;模板取的是内容,画到多大的页面、画在什么位置、画几次都由你定,同一份模板可以反复使用。
function App() {
const copyPageWithTemplate = 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/`);
// 载入文档
const doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName);
// 取出要复用的那一页,转成模板:内容取一次,后面可以画到多页上
const sourcePage = doc.Pages.get_Item(0);
const template = sourcePage.CreateTemplate();
// 第 1 处:在第 2 位插入一个 A4 新页,尺寸与源页不同,内容缩放到 297.6 x 421.6 画在 (80, 80)
const page1 = doc.Pages.Insert(1, new pdfModule.SizeF(595.0, 842.0), new pdfModule.PdfMargins({ margin: 0.0 }));
page1.Canvas.DrawTemplate(template, new pdfModule.PointF(80.0, 80.0), new pdfModule.SizeF(297.6, 421.6));
// 第 2 处:再插入一个 A4 新页,同一份模板缩得更小,画在右下角
const page2 = doc.Pages.Insert(2, new pdfModule.SizeF(595.0, 842.0), new pdfModule.PdfMargins({ margin: 0.0 }));
page2.Canvas.DrawTemplate(template, new pdfModule.PointF(320.0, 460.0), new pdfModule.SizeF(200.0, 283.3));
// 保存结果文档
const outputFileName = '模板复制页面.pdf';
doc.SaveToFile(outputFileName);
doc.Close();
// 从 VFS 读取生成的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>模板复制页面</h1>
<button onClick={copyPageWithTemplate}>
开始复制
</button>
</div>
);
}
export default App;
DrawTemplate 的第三个参数不传时,模板按原坐标绘制,不缩放——新页比源页大,内容就只占页面一角。新页的尺寸与页边距都由 Pages.Insert 决定,示例里四边页边距取 0,模板的绘制起点才是页面的左上角。
源文档第 1 页的内容被缩放到两个 A4 新页上,文档由 4 页变为 6 页:

new PdfMargins(0.0) 建页面时报 Arg_NullReferenceException原因:PdfMargins 的构造函数把单个数字参数当成内部句柄处理,new pdfModule.PdfMargins(0.0) 拿到的不是一个页边距对象——读它的 Left、Top 会抛 Arg_NullReferenceException,拿它去建页面也不会得到期望的页边距。
解决:用对象形式传页边距,四边都是 0 时写 { margin: 0.0 }:
// 四边页边距都为 0
const margins = new pdfModule.PdfMargins({ margin: 0.0 });
// 也可以四边分别指定
const custom = new pdfModule.PdfMargins({ left: 20.0, top: 20.0, right: 20.0, bottom: 20.0 });
原因:页面下标从 0 开始,endIndex 含端点,所以合法区间是 0 到 Pages.Count - 1。超出这个范围会抛 Index out of range,startIndex 大于 endIndex 则抛 The start index is greater then the end index.。
解决:用 Pages.Count 兜住上界再传进去:
// 想复制第 2 到第 4 页:start = 1、end = 3,用页数兜住上界
const start = 1;
const end = Math.min(3, sourceDoc.Pages.Count - 1);
targetDoc.InsertPageRange(sourceDoc, start, end);
原因:CreateTemplate() 取的是页面内容,页面的旋转角(/Rotate)不在模板里。源页带旋转角时,模板的坐标系与目标页对不上——直接 DrawTemplate 画到新页上,内容会落到可见区域之外,副本的 Rotation 也是 0。
解决:源页带旋转角时改用整页复制,内容和旋转角会一起带过去:
// 整页复制:旋转角随页面一起过来
targetDoc.InsertPage({ ldDoc: sourceDoc, pageIndex: 0, resultPageIndex: 1 });
如果必须用模板法,就先把源页的旋转角临时归零再取模板,复制完把角度补回源页和副本:
const rotation = sourcePage.Rotation.value;
// 临时归零,模板才会按页面的真实坐标导出
sourcePage.Rotation = 0;
const newPage = doc.Pages.Insert(1, sourcePage.Size, new pdfModule.PdfMargins({ margin: 0.0 }));
newPage.Canvas.DrawTemplate(sourcePage.CreateTemplate(), new pdfModule.PointF(0.0, 0.0));
// 源页还原,副本补上同样的角度
sourcePage.Rotation = rotation;
newPage.Rotation = rotation;
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
为 Word 文档设置页面背景,是合同、公文与品牌宣传类文档排版中最常用的美化手段之一:一层柔和的底色,或一张与企业视觉一致的背景图,就能为整份文档建立统一的视觉基调。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成此设置,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍三个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。
纯色背景的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件和目标 Word 文件载入 WASM 虚拟文件系统;然后实例化 Document 加载文件,将 Background.Type 指定为 BackgroundType.Color,并通过 Background.Color 赋值一个内置色值;最后调用 SaveToFile 将文档保存到 VFS,从 VFS 读取生成的 docx 文件,封装为 Blob 后生成下载链接。
function App() {
const SetSolidColorBackground = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将示例文件加载到虚拟文件系统(VFS)中
let inputFileName = "ScienceTemplate.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);
// 创建 Word 文档
let doc = new docModule.Document();
// 加载文件
doc.LoadFromFile(inputFileName);
// 设置背景类型为颜色
doc.Background.Type = docModule.BackgroundType.Color;
// 设置背景颜色
doc.Background.Color = docModule.Color.get_LightYellow();
// 定义输出文件名
const outputFileName = "SetSolidColorBackground_out.docx";
// 将文档保存到指定路径
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>为Word文档设置纯色背景</h1>
<button onClick={SetSolidColorBackground}>开始</button>
</div>
);
}
export default App;
通过 Background.Color 设置纯色背景后生成的 Word 文档

渐变背景的核心流程与纯色背景一致,区别在于中间阶段:将 Background.Type 指定为 BackgroundType.Gradient 后,通过 Background.Gradient 获取背景渐变对象,分别设置起点色 Color1 与终点色 Color2,再通过 ShadingStyle 与 ShadingVariant 控制渐变的方向与过渡方式。
function App() {
const SetGradientBackground = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将示例文件加载到虚拟文件系统(VFS)中
let inputFileName = "ScienceTemplate.docx";
await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}static/data/`);
// 创建Word文档
let doc = new docModule.Document();
// 加载文件
doc.LoadFromFile(inputFileName);
// 设置背景类型为渐变
doc.Background.Type = docModule.BackgroundType.Gradient;
let gradient = doc.Background.Gradient;
// 设置渐变的起始颜色和结束颜色
gradient.Color1 = docModule.Color.get_White();
gradient.Color2 = docModule.Color.get_LightBlue();
// 设置渐变的着色样式和方向
gradient.ShadingVariant = docModule.GradientShadingVariant.ShadingDown;
gradient.ShadingStyle = docModule.GradientShadingStyle.Horizontal;
// 定义输出文件名
const outputFileName = "SetGradientBackground_out.docx";
// 将文档保存到指定路径
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>为Word文档设置渐变背景</h1>
<button onClick={SetGradientBackground}>开始</button>
</div>
);
}
export default App;
通过 Background.Gradient 设置渐变背景后生成的 Word 文档

图片背景的核心流程与前面两种背景类似,区别在于资源准备与赋值方式:载入阶段除字体文件和目标 Word 文件外,还需将背景图片一并载入 VFS;然后将 Background.Type 指定为 BackgroundType.Picture,再调用 Background.SetPicture 传入 VFS 中的图片路径,即可将图片平铺为整页背景。
function App() {
const SetImageBackground = async () => {
const docModule = window.wasmModule?.spiredoc;
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
// 将示例文件加载到虚拟文件系统(VFS)中
let inputFileName1 = "ScienceTemplate.docx";
await window.spire.FetchFileToVFS(inputFileName1, "", `${process.env.PUBLIC_URL}static/data/`);
// 将背景图片加载到虚拟文件系统(VFS)中
let inputFileName2 = "Background.png";
await window.spire.FetchFileToVFS(inputFileName2, "", `${process.env.PUBLIC_URL}static/data/`);
// 加载一个Word文档
let doc = new docModule.Document();
doc.LoadFromFile(inputFileName1);
// 将背景类型设置为图片
doc.Background.Type = docModule.BackgroundType.Picture;
// 设置背景图片
doc.Background.SetPicture(inputFileName2);
// 定义输出文件名
const outputFileName = "SetImageBackground_out.docx";
// 将文档保存到指定路径
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
doc.Dispose();
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>在 Word 文档中设置图片背景</h1>
<button onClick={SetImageBackground}>开始</button>
</div>
);
}
export default App;
通过 Background.SetPicture 设置图片背景后生成的 Word 文档

原因:Word 默认不打印页面背景色和背景图片,这是 Word 客户端的打印设置,并非文档中的背景设置丢失。打开文档时背景可以正常显示,只有打印输出时会被忽略。
解决:如果需要在打印稿中保留背景,可在 Word 中通过「文件 > 选项 > 显示」勾选「打印背景色和图像」后再打印。如果要求背景在任何环境下都必须输出,建议改用页眉中的整页形状或水印来模拟。
原因:调用 SetPicture 之前未将 Background.Type 设为 BackgroundType.Picture,或背景图片没有通过 FetchFileToVFS 载入 VFS,导致 SetPicture 找不到图片文件。
解决:先指定背景类型,再传入已载入 VFS 的图片文件名:
document.Background.Type = wasmModule.BackgroundType.Picture;
document.Background.SetPicture("Background.png");
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
在日常办公和业务开发中,Excel 文件是最常见的数据交换格式之一。随着 XML 格式的广泛应用,越来越多的系统需要将 Excel 数据导出为 OpenXML(.xml)格式以便在其他平台或系统中读取和解析。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成 Excel 与 OpenXML 之间的双向转换,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
将 Excel 文件转换为 OpenXML 格式是一个将 .xlsx 文件保存为 .xml 文件的过程。以下是关键步骤:
xlsModule.Workbook() 方法创建一个工作簿对象。LoadFromFile() 方法加载 Excel 文件。SaveAsXml() 方法将工作簿保存为 OpenXML 文件。function App() {
const excelToOpenXML = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.XLS 尚未就绪');
return;
}
// 加载字体
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 将 Excel 输入文件载入 VFS
const inputFileName = 'Sample.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 创建工作簿并加载 Excel 文件
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 转换为 OpenXML 格式并保存
const outputFileName = 'ExcelToOpenXML.xml';
workbook.SaveAsXml({ fileName: outputFileName });
// 释放工作簿对象以释放资源
workbook.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/xml' });
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>Excel 转换为 OpenXML</h1>
<button onClick={excelToOpenXML}>
开始转换
</button>
</div>
);
}
export default App;
运行后,excel文件转为 OpenXML文件的效果:

将 OpenXML 文件转换回 Excel 格式是双向转换的另一方向。以下是关键步骤:
xlsModule.Workbook() 方法创建一个工作簿对象。xlsModule.Stream(inputFileName) 方法将 OpenXML 文件读取到流中。Workbook.LoadFromXml() 方法从流中加载 OpenXML 文件。Workbook.SaveToFile() 方法将 OpenXML 文件保存为 Excel 文件。function App() {
const openXMLToExcel = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.XLS 尚未就绪');
return;
}
// 加载字体
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
// 将 OpenXML 输入文件载入 VFS
const inputFileName = 'OfficeOpenXMLToExcel.xml';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 创建工作簿
const workbook = new xlsModule.Workbook();
// 使用 Stream 读取 OpenXML 文件并加载到工作簿中
const fileStream = new xlsModule.Stream(inputFileName);
workbook.LoadFromXml({ stream: fileStream });
// 保存为 Excel 格式(Excel 2013 版本)
const outputFileName = 'OpenXMLToExcel.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>OpenXML 转换为 Excel</h1>
<button onClick={openXMLToExcel}>
开始转换
</button>
</div>
);
}
export default App;
运行后, OpenXML文件转为Excel文件的效果:

原因:字体没有正确加载到 VFS。教程里加载了 simsun.ttc,但如果你的文档用了其他中文字体(比如微软雅黑、宋体之外的字体),或者字体文件路径不对,渲染和导出时就会缺字形。
解决:确认 FetchFileToVFS 的字体路径和 PUBLIC_URL 拼接正确,并且字体文件确实在 public/font/ 下。如果文档用了多种字体,需要把对应字体文件都加载进去,不能只加载一个 simsun.ttc。
原因:从 VFS 读取文件时用了错误的读取方式,或者 Blob 的 MIME 类型设置不对。比如 OpenXML 应该用 application/xml,Excel 应该用 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,写错了浏览器可能按文本处理,导致二进制内容被破坏。
解决:检查确认 a.download 的文件名扩展名和实际内容格式一致,不要出现 .xml 里装的是 xlsx 二进制的情况。
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
把数据写进 Excel 时,最直接的做法是逐个单元格赋值,写十行就要循环十次。但数据在 JavaScript 里往往本来就是成块的——接口返回的列表、页面上已有的表格、计算出的结果集——既然是数组,就没必要拆散了再一格一格塞回去。Spire.XLS for JavaScript 提供的 InsertArray 方法接受整段数组,配合起始行列与写入方向,一次调用就能把整列或整行数据落进工作表。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成上述操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
InsertArray 按数据类型分为多个重载:文本用 stringArray,整数用 intArray,小数用 doubleArray。分成三个重载并非多余的设计——Excel 会把单元格按数据类型区别对待,数值右对齐、可以直接参与求和与图表,而被写成文本的数字左对齐,公式里引用它只会得到 0。分数、金额、数量这类要参与计算的字段,就该走数值重载。
其余参数决定数据落在哪里:firstRow 与 firstColumn 指定起始位置,行列都从 1 开始计数;isVertical 决定数组的展开方向——取 false 时沿行铺开,取 true 时沿列铺开。同一个 ['一月', '二月', '三月'],换个方向就是从 A1 到 C1 的一行表头,或是从 A1 到 A3 的一列数据。具体操作步骤如下:
Workbook,用 Worksheets.get(0) 取得第一个工作表。InsertArray 的 stringArray 重载横向写入一行表头。stringArray 纵向写入姓名列,用 intArray 纵向写入分数列。AllocatedRange.AutoFitColumns 调整列宽,再以 SaveToFile 保存工作簿。以下为完整的代码示例,演示如何在 React 中把一维数组导入工作表:
function App() {
const importArray = 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);
// 横向写入一行表头:isVertical 为 false,数组沿行展开到 A1:B1
sheet.InsertArray({
stringArray: ['姓名', '分数'],
firstRow: 1,
firstColumn: 1,
isVertical: false,
});
// 纵向写入姓名列:isVertical 为 true,数组沿列展开到 A2:A4
sheet.InsertArray({
stringArray: ['张三', '李四', '王五'],
firstRow: 2,
firstColumn: 1,
isVertical: true,
});
// 纵向写入分数列:数值走 intArray 重载,单元格中保存的是数字而非文本
sheet.InsertArray({
intArray: [92, 85, 78],
firstRow: 2,
firstColumn: 2,
isVertical: true,
});
// 按内容调整列宽
sheet.AllocatedRange.AutoFitColumns();
// 保存工作簿
const outputFileName = 'ImportArray.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 从 VFS 读取结果文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>导入一维数组</h1>
<button onClick={importArray}>Start</button>
</div>
);
}
export default App;
运行后,将一维数组导入工作表的效果:

数据表的自然形式是二维数组——第一行表头,其余每行一条记录,像 [['姓名', '科目', '分数'], ['张三', '数学', 92]] 这样。要把二维数据表导入 Excel,需要把二维数组按列拆开,每一列当成一个一维数组单独写入:表头横向写一行,数据部分逐列纵向写入,文本列用 stringArray,数值列用 intArray。同一列的数据类型是统一的,所以每次调用只需选一种写法。若数据是分批到达的,用 LastRow 读出当前已用到的最后一行,下一批接着它的下一行写即可。具体操作步骤如下:
Workbook,用 Worksheets.get(0) 取得第一个工作表。InsertArray 的 stringArray 重载把表头横向写入第一行。stringArray 或 intArray 纵向写入。LastRow 定位起始行,同样逐列写入。AllocatedRange.AutoFitColumns 调整列宽,再以 SaveToFile 保存工作簿。以下为完整的代码示例,演示如何在 React 中把二维数据表导入工作表:
function App() {
const importDataTable = 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 rows = [
['姓名', '科目', '分数'],
['张三', '数学', 92],
['李四', '语文', 85],
];
// 新建工作簿,取第一个工作表
const workbook = new xlsModule.Workbook();
const sheet = workbook.Worksheets.get(0);
// 按列写入:文本列走 stringArray,数值列走 intArray
const writeColumn = (values, firstRow, firstColumn) => {
const isNumeric = values.every((value) => typeof value === 'number');
if (isNumeric) {
sheet.InsertArray({ intArray: values, firstRow, firstColumn, isVertical: true });
} else {
sheet.InsertArray({ stringArray: values, firstRow, firstColumn, isVertical: true });
}
};
// 表头横向写入第一行
sheet.InsertArray({
stringArray: rows[0],
firstRow: 1,
firstColumn: 1,
isVertical: false,
});
// 数据部分按列拆开,逐列纵向写入,从第二行开始
const body = rows.slice(1);
for (let column = 0; column < rows[0].length; column++) {
writeColumn(body.map((row) => row[column]), 2, column + 1);
}
// 第二批数据到达,用 LastRow 定位起始行后接在已有数据下方
const nextBatch = [
['王五', '英语', 78],
['赵六', '物理', 91],
];
const startRow = sheet.LastRow + 1;
for (let column = 0; column < rows[0].length; column++) {
writeColumn(nextBatch.map((row) => row[column]), startRow, column + 1);
}
// 按内容调整列宽
sheet.AllocatedRange.AutoFitColumns();
// 保存工作簿
const outputFileName = 'ImportDataTable.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 从 VFS 读取结果文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>导入二维数据表</h1>
<button onClick={importDataTable}>Start</button>
</div>
);
}
export default App;
运行后,将二维数据表导入工作表的效果:

原因:dateTimeArray 不能用来写 JavaScript 的 Date——传进去会得到 0001/1/1。逐个赋值可以用 Range.DateTimeValue,但它只认 Date 对象,传字符串会提示 Value is not a Date。
解决:整列导入时,把日期写成 yyyy-mm-dd 形式的字符串交给 stringArray 最为简便。保存后的单元格是真正的日期值——读回 NumberValue 得到日期序列号(如 46037),不必再设置数字格式:
sheet.InsertArray({
stringArray: ['2026-01-15', '2026-02-20'],
firstRow: 1,
firstColumn: 1,
isVertical: true,
});
若只需精确控制某一格,也可以逐个赋值,注意传 Date 对象而非字符串:
sheet.Range.get('A1').DateTimeValue = new Date(Date.UTC(2026, 0, 15));
原因:不会。null、undefined 与空字符串都只写成空单元格,InsertArray 按数组下标定位每一格,与值本身无关,所以后面的元素不会移位,也不会报错。
解决:无须额外处理,直接传入即可。下面这一行会写入 A1:D1 四格,丁 仍落在第四列:
sheet.InsertArray({
stringArray: ['甲', null, '', '丁'],
firstRow: 1,
firstColumn: 1,
isVertical: false,
});
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
报表里的表头和汇总行如果只有数据和边框,翻看起来并不容易一眼定位。给这些单元格加上底色是最常见的做法,但纯色填充能表达的层次有限:有时需要一道由浅到深的过渡来提示重点,有时需要一层网点或斜纹底纹来区分普通行与汇总行,同时又不至于盖住单元格里的数字。Spire.XLS for JavaScript 通过 Interior 对象同时支持渐变填充与图案填充,基于 WebAssembly 在浏览器端直接完成上述操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍两个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
渐变填充由三步组成:先把填充类型指定为渐变,再给出两端的颜色,最后说明过渡的方向与变化方式。这三步都落在同一个 Interior 对象上——FillPattern 决定填充类型,Gradient.ForeColor 与 Gradient.BackColor 给出两端颜色,Gradient.TwoColorGradient 接收两个参数:方向取自 GradientStyleType,横向、纵向、两个斜向以及自中心、自角落向外辐散都在这一个枚举里;明暗变体取自 GradientVariantsType,取值是 ShadingVariants1 到 ShadingVariants4。同一组颜色,换个方向与变体就是完全不同的观感。具体操作步骤如下:
Workbook,用 LoadFromFile 载入工作簿,再用 Worksheets.get(0) 取得第一个工作表。Range.get 取到表头区域,把 Style.Interior.FillPattern 设为 ExcelPatternType.Gradient。Gradient.ForeColor 与 Gradient.BackColor 指定渐变的两端颜色。Gradient.TwoColorGradient 指定渐变方向与明暗变体。SaveToFile 保存工作簿。以下为完整的代码示例,演示如何在 React 中为 Excel 单元格添加渐变填充:
function App() {
const applyGradient = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和测试数据载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'FillSample.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);
// 取表头区域 A1:E1
const header = sheet.Range.get('A1:E1');
// 将填充类型指定为渐变
header.Style.Interior.FillPattern = xlsModule.ExcelPatternType.Gradient;
// 指定渐变的两端颜色
header.Style.Interior.Gradient.ForeColor = xlsModule.Color.FromArgb(255, 255, 255);
header.Style.Interior.Gradient.BackColor = xlsModule.Color.FromArgb(79, 129, 189);
// 双色渐变:第一个参数决定渐变方向,第二个参数决定明暗变体
header.Style.Interior.Gradient.TwoColorGradient(
xlsModule.GradientStyleType.Horizontal,
xlsModule.GradientVariantsType.ShadingVariants1,
);
// 保存工作簿
const outputFileName = 'GradientFill.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 从 VFS 读取结果文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>添加渐变填充</h1>
<button onClick={applyGradient}>Start</button>
</div>
);
}
export default App;
运行后,为表头添加渐变填充的效果:

图案填充不是一种单一效果,而是一组内置样式的总称:网点、横纹、竖纹、斜纹、棋盘格等都归在 ExcelPatternType 之下,取值有几十种,密度从 5% 到 75% 不等。与渐变填充相比,图案填充占用的视觉重量更轻,铺在整行数据上也不会干扰阅读,因此更适合用来标记汇总行或需要区分的数据段。
设置方式与渐变同源:Interior.FillPattern 选定图案样式后,再给出两种颜色。此处有一处容易写反的地方——Interior.Color 决定的是图案线本身的颜色,Interior.PatternColor 决定的则是铺在下面的底色,属性名与实际作用正好相反。把深色交给 Interior.Color、浅色交给 Interior.PatternColor,网点才会浮在浅底之上。具体操作步骤如下:
Workbook,用 LoadFromFile 载入工作簿,再用 Worksheets.get(0) 取得第一个工作表。Range.get 取到汇总行区域,把 Style.Interior.FillPattern 设为所需的 ExcelPatternType 取值。Interior.Color 指定图案线的颜色,用 Interior.PatternColor 指定底色。SaveToFile 保存工作簿。以下为完整的代码示例,演示如何在 React 中为 Excel 单元格添加图案填充:
function App() {
const applyPattern = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和测试数据载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'FillSample.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);
// 取汇总行区域 A5:E5
const total = sheet.Range.get('A5:E5');
// 选定内置图案样式,这里用 12.5% 灰度网点
total.Style.Interior.FillPattern = xlsModule.ExcelPatternType.Percent125Gray;
// Interior.Color 决定图案线本身的颜色,Interior.PatternColor 决定铺在下面的底色
total.Style.Interior.Color = xlsModule.Color.FromArgb(192, 80, 77);
total.Style.Interior.PatternColor = xlsModule.Color.FromArgb(255, 242, 204);
// 保存工作簿
const outputFileName = 'PatternFill.xlsx';
workbook.SaveToFile({ fileName: outputFileName });
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 从 VFS 读取结果文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>添加图案填充</h1>
<button onClick={applyPattern}>Start</button>
</div>
);
}
export default App;
运行后,为汇总行添加图案填充的效果:

原因:Style.Interior 只负责填充。填充、字体、边框是 Style 之下三个彼此独立的成员,改动其中一个不会波及另外两个——底色刷深了字还是原来的黑色,自然就糊成一片;反过来给 Style.Font 换了颜色,底色也不会跟着动。
解决:填充与字体分开设置,需要对比度时两处一起调:
// 填充
sheet.Range.get('A1:E1').Style.Interior.Color = xlsModule.Color.FromArgb(79, 129, 189);
// 字体
sheet.Range.get('A1:E1').Style.Font.Color = xlsModule.Color.FromArgb(255, 255, 255);
sheet.Range.get('A1:E1').Style.Font.IsBold = true;
Style.Font 下还有 FontName、Size、IsItalic、Underline 等属性,与填充互不干涉。
原因:换一种颜色只是换了一种填充,并不会把填充去掉——填充类型仍是非 None 的取值,图案或底色依旧写在单元格的样式里。
解决:把填充类型设回 ExcelPatternType.None,原有的图案与配色一并失效:
sheet.Range.get('A5:E5').Style.Interior.FillPattern = xlsModule.ExcelPatternType.None;
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。