基于WASM的纯前端Office解决方案:在线编辑/导入导出/权限切换(已开源)

效果展示

所有操作均在浏览器进行,先来看看最终效果:

🌐 在线演示 : 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集成需要:

  1. 搭建OnlyOffice Document Server
  2. 配置文档转换服务
  3. 处理文档上传下载
  4. 管理服务器资源

本方案通过WASM技术:

  1. 在浏览器中直接运行x2t转换引擎
  2. 使用虚拟文件系统处理文档
  3. 完全客户端化,无需服务器

参考项目

开源地址

🔗 GitHub仓库 : mvp-onlyoffice

总结

本项目提供了一个完整的纯前端OnlyOffice集成方案,通过WASM技术实现了文档格式转换的本地化,结合React和OnlyOffice SDK,打造了一个功能完善、性能优秀的文档编辑器。

核心亮点

  • 🚀 纯前端架构,无需后端服务
  • 🔒 数据完全本地化,保护隐私安全
  • ⚡ 基于WASM的高性能转换
  • 🌏 内置国际化支持
  • 📦 支持导入导出
  • 🔐 灵活的权限控制

欢迎Star和Fork,一起推动前端Office编辑技术的发展!


相关阅读

相关推荐
合作小小程序员小小店1 小时前
web网页开发,在线%医院诊断管理%系统,基于Idea,html,css,jQuery,java,jsp,ssh,mysql。
java·前端·css·数据库·jdk·html·intellij-idea
爱学习的程序媛2 小时前
【Web前端】Vue2与Vue3核心概览与优化对比
前端·javascript·vue.js·typescript
li@h2 小时前
如何在电脑端访问小程序时在胶囊添加一个全屏和缩放功能
前端
LFly_ice3 小时前
学习React-23-React-router
前端·学习·react.js
我叫张小白。3 小时前
TypeScript对象类型与接口:构建复杂数据结构
前端·javascript·typescript
墨客希3 小时前
如何快速掌握大型Vue项目
前端·javascript·vue.js
大福ya3 小时前
AI开源项目改造NextChat(ChatGPT-Next-Web)实现前端SSR改造打造一个初始框架
前端·chatgpt·前端框架·开源·aigc·reactjs·ai编程
n***33353 小时前
SpringBoot返回文件让前端下载的几种方式
前端·spring boot·后端
纯粹的热爱4 小时前
🌐 阿里云 Linux 服务器 Let's Encrypt 免费 SSL 证书完整部署指南
前端