效果展示
所有操作均在浏览器进行,先来看看最终效果:
🌐 在线演示 : mvp-onlyoffice.vercel.app/

核心功能演示
- ✅ 文档上传:支持本地文件直接上传
- ✅ 实时编辑:流畅的文档编辑体验
- ✅ 格式转换:基于WASM的文档格式转换
- ✅ 导出保存:一键导出编辑后的文档
- ✅ 模式切换:只读/可编辑模式自由切换
- ✅ 多语言支持:中英文界面无缝切换
技术架构
核心技术栈
- React 19 + Next.js 15:现代化前端框架
- OnlyOffice SDK:官方JavaScript SDK,提供文档编辑核心能力
- WebAssembly (x2t-wasm):文档格式转换引擎
- TypeScript:类型安全的开发体验
- EventBus:事件驱动的架构设计
- IndexedDB:WASM文件缓存优化
tip: 事实上不依赖于 react,你可以拿到 项目中的 src/onlyoffice-comp ,然后接入到任何系统中去,接入层可以参考 src/app/excel/page.tsx等应用层文件
架构流程图
scss
用户上传文档
↓
React组件层
↓
EditorManager (编辑器管理器)
↓
X2T Converter (WASM转换器)
↓
OnlyOffice SDK (文档编辑器)
↓
EventBus (事件总线)
↓
导出/保存文档
WASM文档转换核心流程
转换流程图解
markdown
用户选择文件
↓
浏览器读取文件
↓
WASM虚拟文件系统
↓
X2T引擎执行转换
↓
生成二进制数据 + 媒体资源
↓
OnlyOffice编辑器加载
核心代码实现
typescript
// src/onlyoffice-comp/lib/x2t.ts
/**
* X2T 工具类 - 负责文档转换功能
*/
class X2TConverter {
private x2tModule: EmscriptenModule | null = null;
// 支持的文件类型映射
private readonly DOCUMENT_TYPE_MAP: Record<string, DocumentType> = {
docx: 'word',
doc: 'word',
odt: 'word',
rtf: 'word',
txt: 'word',
xlsx: 'cell',
xls: 'cell',
ods: 'cell',
csv: 'cell',
pptx: 'slide',
ppt: 'slide',
odp: 'slide',
};
/**
* 转换文档格式
*/
async convertDocument(file: File): Promise<ConversionResult> {
// 初始化WASM模块
await this.ensureReady();
// 写入虚拟文件系统
const data = await file.arrayBuffer();
this.x2tModule!.FS.writeFile('/working/origin', new Uint8Array(data));
// 执行C++编译的转换模块
this.executeConversion('/working/params.xml');
// 提取转换结果和媒体文件
return {
bin: this.x2tModule!.FS.readFile('/working/output.bin'),
media: this.collectMediaFiles() // 提取图片等资源
};
}
}
编辑器管理器:Proxy模式的安全封装
项目采用Proxy模式对OnlyOffice编辑器实例进行安全封装,提供统一的API接口:
typescript
// src/onlyoffice-comp/lib/editor-manager.ts
class EditorManager {
private editor: DocEditor | null = null;
// 使用 Proxy 提供安全的访问接口
private createProxy(): DocEditor {
return new Proxy({} as DocEditor, {
get: (_target, prop) => {
if (prop === 'destroyEditor') {
return () => this.destroy();
}
if (prop === 'sendCommand') {
return (params) => {
if (this.editor) {
this.editor.sendCommand(params);
}
};
}
return this.editor ? (this.editor as any)[prop] : undefined;
},
});
}
// 导出文档(事件驱动)
async export(): Promise<SaveDocumentData> {
const editor = this.get();
if (!editor) {
throw new Error('Editor not available');
}
// 触发保存
(editor as any).downloadAs();
// 等待保存事件
const result = await onlyofficeEventbus.waitFor(
ONLYOFFICE_EVENT_KEYS.SAVE_DOCUMENT,
10000
);
return result;
}
}
事件驱动架构:EventBus解耦设计
项目采用事件总线机制,实现组件间的松耦合通信:
typescript
// src/onlyoffice-comp/lib/eventbus.ts
class EventBus {
private listeners: Map<EventKey, Array<(data: any) => void>> = new Map();
// 监听事件
on<K extends EventKey>(key: K, callback: (data: EventDataMap[K]) => void): void {
if (!this.listeners.has(key)) {
this.listeners.set(key, []);
}
this.listeners.get(key)!.push(callback);
}
// 等待事件触发(返回 Promise)
waitFor<K extends EventKey>(key: K, timeout?: number): Promise<EventDataMap[K]> {
return new Promise((resolve, reject) => {
const timeoutId = timeout
? setTimeout(() => {
this.off(key, handleEvent);
reject(new Error(`Event ${key} timeout after ${timeout}ms`));
}, timeout)
: null;
const handleEvent = (data: EventDataMap[K]) => {
if (timeoutId) clearTimeout(timeoutId);
this.off(key, handleEvent);
resolve(data);
};
this.on(key, handleEvent);
});
}
}
支持的事件类型
saveDocument- 文档保存完成事件documentReady- 文档加载就绪事件loadingChange- 加载状态变化事件
核心功能特性
1. 国际化支持
项目内置多语言支持,可自由切换中英文界面:
typescript
// 切换语言
const handleLanguageSwitch = async () => {
const newLang = currentLang === 'zh' ? 'en' : 'zh';
setCurrentLang(newLang);
// 如果编辑器已存在,重新创建以应用新语言
if (editorManager.exists()) {
await handleView(fileName, file);
}
};
2. 导入导出功能
完整的文档导入导出能力:
typescript
// 导出文档
const result = await editorManager.export();
// result 包含: { fileName, fileType, binData, media }
// 转换并下载
const buffer = await convertBinToDocument(
result.binData,
result.fileName,
FILE_TYPE.XLSX,
result.media
);
const blob = new Blob([buffer.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
// 执行下载操作
3. 只读/可编辑模式切换
灵活的权限控制,支持动态切换编辑模式:
typescript
// 设置为只读模式
await editorManager.setReadOnly(true);
// 切换为可编辑模式
await editorManager.setReadOnly(false);
// 查询当前模式
const isReadOnly = editorManager.getReadOnly();
实现原理:
- 从只读切换到可编辑:重新创建编辑器实例
- 从可编辑切换到只读:使用
processRightsChange命令
4. IndexedDB缓存优化
使用IndexedDB缓存WASM文件,大幅提升二次加载速度:
typescript
// 拦截 fetch,缓存 WASM 文件到 IndexedDB
private interceptFetch(): void {
const originalFetch = window.fetch;
window.fetch = async function(input: RequestInfo | URL): Promise<Response> {
// 先尝试从缓存读取
const cached = await this.getCachedWasm(url);
if (cached) {
return new Response(cached, {
headers: { 'Content-Type': 'application/wasm' }
});
}
// 缓存未命中,从网络加载并缓存
const response = await originalFetch(input);
const arrayBuffer = await response.arrayBuffer();
await this.cacheWasm(url, arrayBuffer);
return response;
};
}
使用示例
基本使用
typescript
import { createEditorView } from '@/onlyoffice-comp/lib/x2t';
import { editorManager } from '@/onlyoffice-comp/lib/editor-manager';
// 创建编辑器视图
await createEditorView({
file: fileObject, // File 对象(可选)
fileName: 'document.xlsx', // 文件名
isNew: false, // 是否新建文档
readOnly: false, // 是否只读
lang: 'zh', // 界面语言
});
// 导出文档
const result = await editorManager.export();
console.log('导出成功:', result);
React组件集成
typescript
// src/app/excel/page.tsx
function ExcelPageContent() {
const [readOnly, setReadOnly] = useState(false);
const [currentLang, setCurrentLang] = useState<'zh' | 'en'>('zh');
// 上传文档
const handleView = async (fileName: string, file?: File) => {
await initializeOnlyOffice();
await createEditorView({
file,
fileName,
isNew: !file,
readOnly,
lang: currentLang,
});
};
// 导出文档
const handleExport = async () => {
const result = await editorManager.export();
const buffer = await convertBinToDocument(
result.binData,
result.fileName,
FILE_TYPE.XLSX,
result.media
);
// 下载文件...
};
// 切换只读模式
const toggleReadOnly = async () => {
const newReadOnly = !readOnly;
setReadOnly(newReadOnly);
await editorManager.setReadOnly(newReadOnly);
};
return (
<div>
{/* UI组件 */}
</div>
);
}
项目结构
csharp
mvp-onlyoffice/
├── src/
│ ├── app/ # Next.js 应用页面
│ │ ├── excel/ # Excel 编辑器页面
│ │ ├── docs/ # Word 编辑器页面
│ │ └── ppt/ # PowerPoint 编辑器页面
│ ├── onlyoffice-comp/ # OnlyOffice 组件库
│ │ └── lib/
│ │ ├── editor-manager.ts # 编辑器管理器
│ │ ├── x2t.ts # 文档转换模块
│ │ ├── eventbus.ts # 事件总线
│ │ └── utils.ts # 工具函数
│ └── components/ # 通用组件
├── public/ # 静态资源
│ ├── web-apps/ # OnlyOffice Web 应用资源
│ ├── sdkjs/ # OnlyOffice SDK 资源
│ └── wasm/ # WebAssembly 转换器
└── onlyoffice-x2t-wasm/ # x2t-wasm 源码
部署方案
Vercel一键部署
项目已配置静态导出,可直接部署到Vercel:
bash
# 安装依赖
npm install
# 构建项目
npm run build
# Vercel 会自动检测并部署
🌐 在线演示 : mvp-onlyoffice.vercel.app/
静态文件部署
项目支持静态导出,构建后的文件可部署到任何静态托管服务:
bash
# 构建静态文件
npm run build
# 输出目录: out/
# 可直接部署到 GitHub Pages、Netlify、Nginx 等
技术优势总结
| 特性 | 传统方案 | 本方案 |
|---|---|---|
| 数据安全 | ❌ 需要上传服务器 | ✅ 完全本地处理 |
| 部署成本 | ❌ 需要后端服务 | ✅ 纯静态部署 |
| 格式支持 | ⚠️ 有限格式 | ✅ 30+种格式 |
| 离线使用 | ❌ 需要网络 | ✅ 完全离线 |
| 性能优化 | ⚠️ 依赖网络 | ✅ IndexedDB缓存 |
| 国际化 | ⚠️ 需额外配置 | ✅ 内置支持 |
| 权限控制 | ⚠️ 复杂实现 | ✅ 简单API |
技术原理
使用x2t-wasm替代OnlyOffice服务
传统OnlyOffice集成需要:
- 搭建OnlyOffice Document Server
- 配置文档转换服务
- 处理文档上传下载
- 管理服务器资源
本方案通过WASM技术:
- 在浏览器中直接运行x2t转换引擎
- 使用虚拟文件系统处理文档
- 完全客户端化,无需服务器
参考项目
- Qihoo360/se-office - se-office扩展,提供基于开放标准的全功能办公生产力套件
- cryptpad/onlyoffice-x2t-wasm - CryptPad WebAssembly文件转换工具
- ranuts/document - 参考静态资源实现
开源地址
🔗 GitHub仓库 : mvp-onlyoffice
总结
本项目提供了一个完整的纯前端OnlyOffice集成方案,通过WASM技术实现了文档格式转换的本地化,结合React和OnlyOffice SDK,打造了一个功能完善、性能优秀的文档编辑器。
核心亮点:
- 🚀 纯前端架构,无需后端服务
- 🔒 数据完全本地化,保护隐私安全
- ⚡ 基于WASM的高性能转换
- 🌏 内置国际化支持
- 📦 支持导入导出
- 🔐 灵活的权限控制
欢迎Star和Fork,一起推动前端Office编辑技术的发展!
相关阅读: