随着企业数字化转型加速,纯前端文档处理已成为 Web 应用的核心需求之一。Angular 作为成熟的企业级前端框架,以其强类型校验、组件化架构和高效的状态管理能力,广泛应用于 OA 系统、文档平台、教育管理系统等复杂场景。
本文将详细介绍如何将 Spire.OfficeJS 集成到 Angular 项目中,实现本地文件上传、在线编辑、格式转换与下载等核心功能。
内容概览
Spire.OfficeJS 产品简介
Spire.OfficeJS 是一款企业级在线文档处理与编辑解决方案,包含 Spire.WordJS、Spire.ExcelJS、Spire.PresentationJS、Spire.PDFJS 四个模块。该产品无需依赖本地 Office 软件,也无需安装任何插件,即可在浏览器中实现 Word、Excel、PPT、PDF 等主流格式文件的在线预览、实时编辑、批注、与格式转换等功能,同时具备云原生、跨平台、高安全性等企业级特性。
核心优势包括:
- 模块化架构:四大核心模块各司其职,覆盖文档处理全场景 — Word 文档精细化编辑、Excel 数据计算与图表分析、PPT 演示文稿可视化编辑、PDF 文档快速预览。
- 纯前端渲染能力:基于 WebAssembly 自研引擎,无需后端参与文档转换,降低服务器压力,提升响应速度。
- 多格式兼容:支持 DOCX、XLSX、PPTX、PDF、WPS 等主流文件格式,支持跨格式导出(如 Word 转 PDF)。
- 安全可控:支持静态文件加密存储,保障文档数据安全。
- 轻量易集成:无缝适配 Angular、Vue、React 等主流前端框架,仅需简单配置即可快速集成。
环境准备与 Angular 项目初始化
在开始集成前,需先完成以下准备工作,确保开发环境符合要求。
1. 安装 Node.js 与 npm
- 下载安装:访问 Node.js 官方下载地址,选择对应系统版本,按默认向导完成安装。
- 版本验证:CMD 执行以下命令验证安装是否成功,若显示版本号则说明安装完成。
node -v
npm -v

2. 安装 Angular CLI 脚手架
Angular CLI 是快速构建 Angular 项目的工具,通过 npm 全局安装:
- 执行安装命令:
npm install -g @angular/cli
- 安装完成后,执行以下命令查看 Angular CLI 版本,确认安装成功:
ng version

3. 初始化 Angular 项目
通过 Angular CLI 创建基础项目,为后续集成 Spire.OfficeJS 搭建框架。
- 打开 CMD 命令行,切换到目标目录(示例:
F:\angular): - 执行以下命令创建 Angular 项目:
ng new spireOfficeJS --skip-git
- 项目创建向导配置(按如下选择确保兼容性):
- 是否创建无 zone.js 的 "zoneless" 应用:No
- 样式表格式:CSS
- 是否启用 SSR/SSG:No

- 等待依赖安装完成,项目创建成功后,目录结构如下:

4.验证项目初始化
使用 VS Code 打开项目,在终端执行以下命令启动开发服务器:
npm run start
浏览器打开 http://localhost:4200/,若显示 “Hello, spireOfficeJS” 及 Angular 默认页面,则项目初始化成功。
集成 Spire.OfficeJS 在线编辑器
1. 部署 Spire.OfficeJS 静态资源
下载 Spire.OfficeJS 产品包,解压后需将产品包中的核心 Web 资源复制到 Angular 项目的静态目录,确保编辑器脚本可被访问。具体操作如下:
- 在项目 public 目录下创建 spire.cloud 文件夹(路径:
public/spire.cloud)。 - 从下载的 Spire.OfficeJS 产品包中找到 web 文件夹,将其完整复制到
public/spire.cloud/目录下。 - 确保核心脚本 SpireCloudEditor.js 的最终路径为:
public/spire.cloud/web/editors/spireapi/SpireCloudEditor.js

⚠️ 注意:此路径需与后续配置的 office-js.ts 中路径完全一致,否则编辑器无法加载
2. 状态管理配置
用于同步文件上传数据(文件对象 + 二进制数据),确保编辑器组件可访问。
(1)安装依赖
使用 NgRx Signals 管理文件数据,在VS Code 终端执行以下安装命令:
npm install @ngrx/effects @ngrx/signals @ngrx/store
(2)创建状态管理 Store
- 在 src/app/ 目录下创建 store 文件夹,新建 index.ts 文件。
- 复制以下代码到 index.ts,定义文件状态与操作方法:
import { signalStore, withState, withMethods, patchState } from '@ngrx/signals';
// 定义文件状态接口
interface FileState {
file: File | null; // 上传的文件对象
fileUint8Data: Uint8Array | null; // 文件二进制数据
}
// 初始状态
const initialState: FileState = {
file: null,
fileUint8Data: null,
};
// 创建全局 Store
export const fileStore = signalStore(
{ providedIn: 'root' },
withState(initialState),
withMethods((store) => ({
// 更新文件对象
setFileData(data: File | null): void {
patchState(store, { file: data });
},
// 更新文件二进制数据
setFileUint8Data(uint8Data: Uint8Array | null): void {
patchState(store, { fileUint8Data: uint8Data });
}
}))
);
3. 核心组件开发(上传 + 编辑器)
(1)创建组件
在终端执行以下命令,创建文件上传组件和编辑器集成组件:
ng g c spire/uploadFile
ng g c spire/officeJS
执行完成后,src/app/spire/ 目录下会生成 upload-file/ 和 office-js/ 两个组件文件夹。

(2)文件上传组件(upload-file)配置
上传组件负责接收用户拖放或选择的文件,转换为二进制格式后存储到全局状态,再跳转至编辑器页面。
- 定义上传界面样式(
upload-file.css):
:host {
display: block;
min-height: 100vh;
}
.upload-main {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
}
.upload-container {
width: 80%;
max-width: 600px;
padding: 40px;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
text-align: center;
}
.drop-area {
border: 2px dashed #ccc;
border-radius: 6px;
padding: 40px;
margin-bottom: 20px;
transition: all 0.3s;
}
.drop-area.highlight {
border-color: #4CAF50;
background-color: #f0fff0;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-top: 10px;
}
button:hover {
background-color: #45a049;
}
#fileInput {
display: none; /* 隐藏原生文件选择框 */
}
- 设置上传界面结构(
upload-file.html),支持拖放上传和点击选择文件两种方式:
<main class="upload-main">
<div class="upload-container">
<h2>拖放文件上传</h2>
<div class="drop-area" id="dropArea">
<p>拖放文件到浏览器</p>
<p>或</p>
<button id="browseBtn" #browseBtn (click)="handleButtonClick($event)">选择文件</button>
<input type="file" id="fileInput" #fileInput (change)="handleDrop($event)">
</div>
</div>
</main>
- 上传逻辑实现(
upload-file.ts),处理文件拖放、选择、二进制转换与状态储存:
import { ViewChild, ElementRef, Component, AfterViewInit, inject } from '@angular/core';
import { Router } from '@angular/router';
import { fileStore } from '../../store/index';
@Component({
selector: 'app-upload-file',
imports: [],
templateUrl: './upload-file.html',
styleUrl: './upload-file.css',
})
export class UploadFile implements AfterViewInit {
constructor(private router: Router) { }
// 注入状态管理 Store
store = inject(fileStore);
// 绑定 HTML 元素
@ViewChild('browseBtn') browseBtn!: ElementRef<HTMLButtonElement>;
@ViewChild('fileInput') fileInput!: ElementRef<HTMLInputElement>;
file: File | null = null; // 上传的文件对象
fileUint8Data: Uint8Array | null = null; // 文件二进制数据
// 组件视图初始化完成后执行
ngAfterViewInit() {
this.init();
}
// 初始化拖放事件监听
init() {
// 阻止浏览器默认拖放行为
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
document.addEventListener(eventName, this.preventDefaults, false);
});
// 监听文件拖放事件
document.addEventListener('drop', (e) => {
this.handleDrop.call(this, e);
}, false);
}
// 阻止默认事件
preventDefaults(e: Event) {
e.preventDefault();
e.stopPropagation();
}
// 点击“选择文件”按钮触发原生文件选择框
handleButtonClick(e: Event) {
e.preventDefault();
this.fileInput.nativeElement.click();
}
// 处理文件拖放/选择事件
async handleDrop(e: any) {
// 获取文件对象(拖放或点击选择)
if (e.target && e.target.files) {
this.file = e.target.files[0];
} else if (e.dataTransfer && e.dataTransfer.files) {
this.file = e.dataTransfer.files[0];
}
// 转换文件为 Uint8Array 二进制格式
this.fileUint8Data = await this.handleFile(this.file) as Uint8Array;
// 更新状态到 Store(供编辑器组件使用)
this.store.setFileData(this.file);
this.store.setFileUint8Data(this.fileUint8Data);
// 跳转到编辑器页面
this.openDocument();
}
// 将文件转换为 Uint8Array 二进制数据
handleFile(file: any) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const arrayBuffer = reader.result as ArrayBuffer;
const uint8Array = new Uint8Array(arrayBuffer);
resolve(uint8Array);
};
reader.onerror = (error) => reject(error);
reader.readAsArrayBuffer(file); // 以 ArrayBuffer 格式读取文件
});
}
// 跳转到编辑器页面(路由导航)
openDocument() {
this.router.navigate(['spire']);
}
}
(3) 编辑器组件(office-js)配置
编辑器组件负责加载 Spire.OfficeJS 脚本、初始化编辑器实例、配置编辑权限与功能,是核心交互模块。
- 定义编辑器容器 (
office-js.html):
<div class="form">
<div id="iframeEditor">
</div>
</div>
- 实现编辑器脚本加载、配置初始化、文件解析与事件监听等核心逻辑(
office-js.ts):
import { Component, AfterViewInit, inject } from '@angular/core';
import { Router } from '@angular/router';
import { fileStore } from '../../store/index';
// 声明 SpireCloudEditor 全局变量(来自产品脚本)
declare const SpireCloudEditor: any;
@Component({
selector: 'app-office-js',
imports: [],
templateUrl: './office-js.html',
styleUrl: './office-js.css',
})
export class OfficeJS implements AfterViewInit {
constructor(private router: Router) { };
// 注入状态管理 Store
store = inject(fileStore);
// 从 Store 获取文件数据
file = this.store.file() as File;
fileUint8Data = this.store.fileUint8Data() as Uint8Array;
originUrl = window.location.origin; // 当前项目域名
Editor: any; // 编辑器实例
config: any; // 编辑器配置
Api: any; // 编辑器 API
// 组件视图初始化完成后执行
ngAfterViewInit() {
this.init();
}
// 初始化校验(无文件则跳回上传页)
init() {
if (!this.file) {
this.router.navigate(['']); // 跳转到上传页面
return;
}
this.loadSrcipt(); // 加载编辑器脚本
}
// 动态加载 SpireCloudEditor.js 脚本
loadSrcipt() {
const script = document.createElement('script');
// 脚本路径需与静态资源部署路径一致
script.setAttribute('src', '/spire.cloud/web/editors/spireapi/SpireCloudEditor.js');
script.onload = () => this.initEditor(); // 脚本加载完成后初始化编辑器
document.head.appendChild(script);
}
// 初始化编辑器配置与实例
initEditor() {
const iframeId = 'iframeEditor'; // 与模板文件中容器 ID 一致
this.initConfig(); // 配置编辑器参数
// 创建编辑器实例
this.Editor = new SpireCloudEditor.OpenApi(iframeId, this.config);
this.Api = this.Editor.GetOpenApi(); // 获取编辑器 API (用于扩展功能)
this.OnWindowReSize(); // 适配窗口大小
}
// 编辑器核心配置(文件信息 + 用户权限 + 编辑器行为)
initConfig() {
this.config = {
"fileAttrs": {
"fileInfo": {
"name": this.file.name, // 文件名
"ext": this.getFileExtension(), // 文件后缀
"primary": String(new Date().getTime()), // 唯一标识(时间戳)
"creator": "",
"createTime": ""
},
"sourceUrl": `${this.originUrl}/files/__ffff_192.168.3.121/${this.file.name}`,
"createUrl": `${this.originUrl}/open`,
"mergeFolderUrl": "",
"fileChoiceUrl": "",
"templates": {}
},
"user": {
"id": "uid-1",
"name": "Jonn",
"canSave": true, // 允许保存文件
},
"editorAttrs": {
"editorMode": this.file.name.endsWith('.pdf') ? 'view' : "edit", // PDF 默认为预览模式
"editorWidth": "100%", // 编辑器宽度
"editorHeight": "100%", // 编辑器高度
"editorType": "document", // 编辑器类型(文档)
"platform": "desktop", // 平台类型(桌面端)
"viewLanguage": "zh", // 界面语言(中文)
"isReadOnly": false, // 非只读模式
"canChat": true, // 启用聊天功能
"canComment": true, // 启用批注功能
"canReview": true, // 启用审阅功能
"canDownload": true, // 允许下载文件
"canEdit": this.file.name.endsWith('.pdf') ? false : true, // PDF 禁止编辑
"canForcesave": true, // 允许强制保存
"embedded": {
"saveUrl": "",
"embedUrl": "",
"shareUrl": "",
"toolbarDocked": "top" // 工具栏置顶
},
// 启用 WebAssembly 加速(提升编辑和转换性能)
"useWebAssemblyDoc": true,
"useWebAssemblyExcel": true,
"useWebAssemblyPpt": true,
"useWebAssemblyPdf": true,
// 许可证配置(若有许可证,填写对应密钥)
"spireDocJsLicense": "",
"spireXlsJsLicense": "",
"spirePresentationJsLicense": "",
"spirePdfJsLicense": "",
"serverless": {
"useServerless": true,
"baseUrl": this.originUrl,
"fileData": this.fileUint8Data, // 核心:传入文件二进制数据
},
"events": {
"onSave": this.onFileSave // 保存回调事件
},
"plugins": {
"pluginsData": []
}
}
};
}
// 窗口大小适配
OnWindowReSize() {
const wrapEl = document.getElementsByClassName("form") as any;
if (wrapEl.length) {
wrapEl[0].style.height = window.innerHeight + "px";
window.scrollTo(0, -1);
}
}
// 获取文件后缀名
getFileExtension() {
const filename = this.file.name.split(/[\\/]/).pop() as String;
return filename.substring(filename.lastIndexOf('.') + 1).toLowerCase() || '';
}
// 自定义保存逻辑(可根据需求扩展)
onFileSave(data: any) {
console.log('保存的数据:', data);
}
}
4. 路由配置 (实现页面跳转)
为上传页面和编辑器页面配置路由,实现页面跳转。
- 修改路由规则(
app.routes.ts),配置上传页和编辑器页的路由:
import { Routes } from '@angular/router';
import { UploadFile } from './spire/upload-file/upload-file';
import { OfficeJS } from './spire/office-js/office-js';
export const routes: Routes = [
{ path: '', component: UploadFile }, // 默认路由:文件上传页
{ path: 'spire', component: OfficeJS } // 编辑器路由:/spire
];
- 注入路由配置(
app.config.ts),确保路由配置生效:
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes), // 注入路由
]
};
- 设置路由出口(
app.html),确保页面正确渲染:
<main class="app-main">
<router-outlet /> <!-- 路由出口:渲染当前路由对应的组件 -->
</main>
启动运行与功能验证
1. 启动项目
保存所有修改后,在项目根目录(F:\angular\spireOfficeJS)执行以下命令,重启开发服务器:
npm run start
2. 功能验证
- 访问上传页面:浏览器打开 http://localhost:4200/,显示 “拖放文件上传” 界面,支持两种上传方式:
- 拖放文件到浏览器指定区域;
- 点击 “选择文件” 按钮,从本地上传文档(支持 Word、Excel、PPT、PDF 等格式)。

- 在线编辑文档: 上传文件后,自动跳转到编辑器页面(http://localhost:4200/spire),可实现:
- 文档编辑(PDF 仅支持预览);
- 添加批注、评论、跟踪修订等功能;
- 界面语言为中文,工具栏布局清晰。

- 下载 / 转换文档:编辑完成后,点击编辑器顶部 “文件→“下载为”,可将文档导出为PDF、TXT、RTF、HTML等多种格式。

常见问题解答
Q1:编辑器加载失败,页面空白?
- 排查路径:确认
SpireCloudEditor.js路径与office-js.ts中script.src一致 - 浏览器兼容:升级至 Chrome 100+ 等支持 WebAssembly 的浏览器
Q2:安装 NgRx 依赖时提示 peer dependency 冲突?
- Angular 版本与 NgRx 版本存在依赖约束差异,可使用 --legacy-peer-deps 强制兼容安装:
npm install @ngrx/signals @ngrx/store --legacy-peer-deps
- 或根据 Angular 升级指南调整版本
Q3:启动项目时,浏览器控制台报错,提示找不到 zone.js 模块?
- 原因:初始化项目时误选 “zoneless” 模式(无
zone.js依赖),但 Spire.OfficeJS 依赖zone.js处理异步事件。 - 解决方案:
- 先安装 zone.js 依赖:
npm install zone.js --save - 再打开
src/main.ts,在文件顶部添加导入:import 'zone.js'; - 最后重新启动项目,确认报错消失。
- 先安装 zone.js 依赖:
完整示例下载
下载 Angular 集成 Spire.OfficeJS 的完整示例项目,包含所有配置文件与代码,直接运行即可体验功能。
临时许可证申请
如需去除水印或解锁全部功能,可联系我们获取 30 天临时许可证。







