@koi-br/office-provider-sdk
Version:
Universal Office Provider SDK supporting WPS, OnlyOffice and more. Framework-agnostic library for React, Vue, Angular, and vanilla JavaScript / 通用 Office 提供商 SDK,支持 WPS、OnlyOffice 等多种办公套件。框架无关库,支持 React、Vue、Angular 和原生 JavaScript
962 lines (775 loc) • 27.6 kB
Markdown
一个**通用的 Office 提供商 SDK**,支持 WPS、OnlyOffice 等多种办公套件。**框架无关**设计,完美适配 React、Vue、Angular 和原生 JavaScript。
A **universal Office Provider SDK** supporting WPS, OnlyOffice and more office suites. **Framework-agnostic** design, perfectly compatible with React, Vue, Angular, and vanilla JavaScript.
---
- 🌐 **多Provider支持** - 统一 API 支持 WPS、OnlyOffice 等多种办公套件
- 🚀 **框架无关** - 可与 React、Vue、Angular 或原生 JavaScript 无缝集成
- 🤖 **智能配置** - 支持自动配置和手动配置两种模式
- 📝 **TypeScript 支持** - 完整的类型定义,提供出色的开发体验
- ⚡ **高性能** - 基于现代 JavaScript 模式,支持 Tree Shaking
- 🛡️ **类型安全** - 严格的类型检查和运行时验证
- 🔧 **易于扩展** - 工厂模式设计,轻松添加新的 Provider
---
```bash
npm install @koi-br/office-provider-sdk
```
```typescript
// 主要初始化函数
import { initOfficeSDK } from '@koi-br/office-provider-sdk';
// 类型定义
import {
OfficeProviderType,
DocumentType,
OfficeConfig,
ReadyEventDetails,
ErrorEventDetails
} from '@koi-br/office-provider-sdk';
// SDK 实例创建
import { createOfficeSDK, getGlobalOfficeSDK } from '@koi-br/office-provider-sdk';
// Provider 类(高级用法)
import { WPSProvider, OnlyOfficeProvider } from '@koi-br/office-provider-sdk';
// 工具函数
import {
createErrorHandler,
createReadyHandler,
getSupportedProviders,
isProviderSupported
} from '@koi-br/office-provider-sdk';
```
---
```typescript
import { initOfficeSDK } from '@koi-br/office-provider-sdk';
// 通过 API 自动获取配置
// 注意:configApiUrl 会自动拼接 '/api/office/online-office/config' 路径
const sdk = await initOfficeSDK({
fileId: 'your-document-id',
containerSelector: '#office-container',
configApiUrl: 'https://api.example.com', // 基础URL,会自动拼接完整路径
token: 'your-auth-token', // 可选:API认证令牌
permissionToken: 'your-permission-token', // 可选:权限认证令牌
isReadOnly: false,
onReady: (provider, details) => {
console.log('编辑器已就绪:', details);
},
onError: (error) => {
console.error('编辑器错误:', error);
}
});
// 使用统一的 API
await sdk.saveDocument();
await sdk.searchAndLocateText('Hello World!', true);
```
```typescript
import { initOfficeSDK, OfficeProviderType, DocumentType } from '@koi-br/office-provider-sdk';
// WPS 手动配置
const wpsSDK = await initOfficeSDK({
fileId: 'your-document-id',
containerSelector: '#office-container',
configApiUrl: '', // 手动模式下可以为空
autoConfig: false,
providerType: OfficeProviderType.WPS,
manualConfig: {
wps: {
appId: 'your-wps-app-id',
token: 'your-auth-token',
refreshToken: 'your-refresh-token', // 可选
documentType: DocumentType.WRITER, // 可选
simple: false // 可选,是否使用简化模式
}
}
});
// OnlyOffice 手动配置
const onlyOfficeSDK = await initOfficeSDK({
fileId: 'your-document-id',
containerSelector: '#office-container',
configApiUrl: '', // 手动模式下可以为空
autoConfig: false,
providerType: OfficeProviderType.ONLYOFFICE,
manualConfig: {
onlyoffice: {
documentServerUrl: 'https://your-onlyoffice-server.com',
token: 'your-jwt-token', // 可选:JWT安全令牌
document: {
fileType: 'docx',
key: 'unique-document-key',
title: 'Document.docx',
url: 'https://example.com/document.docx',
token: 'document-level-token' // 可选:文档级JWT令牌
},
editorConfig: { // 可选
mode: 'edit', // 'edit' | 'view'
callbackUrl: 'https://example.com/callback',
user: {
id: 'user-id',
name: 'User Name'
}
},
permissions: { // 可选
edit: true,
download: true,
review: true,
comment: true
}
}
}
});
```
---
```typescript
interface OfficeConfig {
// 必需参数 / Required
fileId: string; // 文档ID
containerSelector: string; // 容器选择器(CSS选择器,如 '#office-container')
configApiUrl: string; // 配置API基础地址(自动模式)
// 实际请求URL为: configApiUrl + '/api/office/online-office/config?fileId=xxx&permissionToken=xxx'
// 可选参数 / Optional
isReadOnly?: boolean; // 只读模式,默认 false
autoConfig?: boolean; // 自动配置,默认 true
token?: string; // API认证令牌(用于Authorization头)
permissionToken?: string; // 权限认证令牌(用于API查询参数)
// 手动配置参数 / Manual Configuration(autoConfig=false 时必需)
providerType?: OfficeProviderType; // Provider类型('wps' | 'onlyoffice')
manualConfig?: ManualProviderConfig; // 具体配置
// 事件回调 / Event Callbacks
onReady?: (provider: any, details?: ReadyEventDetails) => void;
onError?: (error: ErrorEventDetails) => void;
}
```
```typescript
// ========== 文本搜索和定位 ==========
// 搜索并定位文本(所有Provider支持)
await sdk.searchAndLocateText(text: string, highlight?: boolean);
// 根据文本定位(所有Provider支持)
await sdk.locateByText({ text: string });
// 搜索并替换(所有Provider支持)
await sdk.searchAndReplace({ old: string, new: string });
// ========== WPS 特有文本操作 ==========
// ⚠️ 以下方法仅在 WPS Provider 下可用
await sdk.insertTextAtCursor(text: string); // 在光标位置插入文本
await sdk.replaceText(pos: number, length: number, newText: string); // 替换指定位置内容
await sdk.getDocumentLength(); // 获取文档长度
// ========== 高亮操作 ==========
// ⚠️ 以下方法仅在 WPS Provider 下可用
await sdk.highlightText(pos: number, length: number); // 高亮指定范围
await sdk.clearHighlight(); // 清除所有高亮
// ========== 内容控制(所有Provider支持) ==========
// 添加内容控制字段
await sdk.addContentControl({
fieldId: string,
placeholderText?: string
});
// 设置内容控制的值
await sdk.setContentControlValue(fieldId: string, value: string);
// 删除内容控制
await sdk.removeContentControl(fieldId: string);
// 设置内容控制高亮颜色 [R, G, B, A]
await sdk.setControlsHighlight([255, 0, 0, 1]);
// ========== 文档管理 ==========
// 保存文档(所有Provider支持)
await sdk.saveDocument();
// 设置只读状态(所有Provider支持)
await sdk.setReadOnly(isReadOnly: boolean);
// ========== 评论相关(所有Provider支持) ==========
// 获取评论列表
await sdk.getCommentsList(key: string);
// 跳转到指定评论
await sdk.jumpCommentDto(jumpId: string, documentKey: string);
// ========== 视图操作(所有Provider支持) ==========
// 滚动到指定位置
await sdk.viewScrollToY(y: number);
// 适应宽度
await sdk.zoomFitToWidth();
// ========== 修订管理(WPS 特有) ==========
// ⚠️ 以下方法仅在 WPS Provider 下可用
await sdk.getRevisions(date?: string); // 获取修订信息
await sdk.handleRevisions(revisionIds: number[], action: 'accept' | 'reject'); // 处理修订
await sdk.acceptReviewByText({ old: string, new: string }); // 根据文本接受修订
await sdk.rejectReviewByText({ old: string, new: string }); // 根据文本拒绝修订
// ========== 格式化(WPS 特有) ==========
// ⚠️ 此方法仅在 WPS Provider 下可用
await sdk.formatDocumentFont({
name?: string, // 字体名称
size?: number, // 字体大小
bold?: boolean, // 是否粗体
italic?: boolean, // 是否斜体
color?: string // 字体颜色(如 '#333333')
});
// ========== 高级用法 ==========
// 获取原生实例(所有Provider支持,但返回类型因Provider而异)
const nativeInstance = sdk.getNativeInstance();
```
```typescript
// 获取当前 Provider 信息
const providerType = sdk.getCurrentProviderType(); // 返回 'wps' | 'onlyoffice' | null
const providerName = sdk.getCurrentProviderName(); // 返回 Provider 名称
// 检查就绪状态
const isReady = sdk.isReady; // boolean
// 获取支持的 Provider 列表
const providers = sdk.getSupportedProviders(); // Array<{ name: string, type: OfficeProviderType }>
// 切换 Provider(高级用法)
await sdk.switchProvider(newConfig);
// 销毁实例
await sdk.destroy();
```
```typescript
import {
createErrorHandler,
createReadyHandler,
getSupportedProviders,
isProviderSupported,
createOfficeSDK,
getGlobalOfficeSDK
} from '@koi-br/office-provider-sdk';
// 创建标准化的错误处理回调
const errorHandler = createErrorHandler((error) => {
console.error('错误:', error.type, error.message);
});
// 创建标准化的就绪回调
const readyHandler = createReadyHandler((provider, details) => {
console.log('就绪:', details.providerName);
});
// 获取支持的 Provider 列表
const providers = await getSupportedProviders();
// 检查是否支持指定 Provider
const isWpsSupported = await isProviderSupported(OfficeProviderType.WPS);
// 创建独立的 SDK 实例
const sdk1 = createOfficeSDK();
const sdk2 = createOfficeSDK();
// 获取全局 SDK 实例(单例)
const globalSDK = getGlobalOfficeSDK();
```
---
```jsx
import React, { useEffect, useRef, useState } from 'react';
import { initOfficeSDK } from '@koi-br/office-provider-sdk';
function OfficeEditor() {
const [sdk, setSdk] = useState(null);
const [isReady, setIsReady] = useState(false);
useEffect(() => {
const initEditor = async () => {
try {
const officeSDK = await initOfficeSDK({
fileId: 'react-demo-doc',
containerSelector: '#office-container',
configApiUrl: 'https://api.example.com',
token: 'your-auth-token',
permissionToken: 'your-permission-token',
onReady: (provider, details) => {
setIsReady(true);
console.log('React: 编辑器已就绪', details);
},
onError: (error) => {
console.error('React: 编辑器错误', error);
}
});
setSdk(officeSDK);
} catch (error) {
console.error('初始化失败:', error);
}
};
initEditor();
return () => {
if (sdk) {
sdk.destroy();
}
};
}, []);
const handleSearch = async () => {
if (sdk && isReady) {
await sdk.searchAndLocateText('搜索文本', true);
}
};
const handleSave = async () => {
if (sdk && isReady) {
await sdk.saveDocument();
}
};
return (
<div>
<div className="controls">
<button onClick={handleSearch} disabled={!isReady}>
搜索文本
</button>
<button onClick={handleSave} disabled={!isReady}>
保存文档
</button>
</div>
<div id="office-container" style={{ height: '600px' }} />
</div>
);
}
```
```vue
<template>
<div>
<div class="controls">
<button @click="searchText" :disabled="!isReady">搜索文本</button>
<button @click="saveDoc" :disabled="!isReady">保存文档</button>
</div>
<div id="office-container" style="height: 600px"></div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { initOfficeSDK } from '@koi-br/office-provider-sdk';
const sdk = ref(null);
const isReady = ref(false);
onMounted(async () => {
try {
sdk.value = await initOfficeSDK({
fileId: 'vue-demo-doc',
containerSelector: '#office-container',
configApiUrl: 'https://api.example.com',
token: 'your-auth-token',
permissionToken: 'your-permission-token',
onReady: (provider, details) => {
isReady.value = true;
console.log('Vue: 编辑器已就绪', details);
},
onError: (error) => {
console.error('Vue: 编辑器错误', error);
}
});
} catch (error) {
console.error('初始化失败:', error);
}
});
onUnmounted(() => {
if (sdk.value) {
sdk.value.destroy();
}
});
const searchText = async () => {
if (sdk.value && isReady.value) {
await sdk.value.searchAndLocateText('搜索文本', true);
}
};
const saveDoc = async () => {
if (sdk.value && isReady.value) {
await sdk.value.saveDocument();
}
};
</script>
```
```typescript
// office.service.ts
import { Injectable } from '@angular/core';
import { initOfficeSDK, type OfficeSDK } from '@koi-br/office-provider-sdk';
@Injectable({
providedIn: 'root'
})
export class OfficeService {
private sdk: OfficeSDK | null = null;
private isReady = false;
async initialize(config: any): Promise<void> {
this.sdk = await initOfficeSDK({
...config,
onReady: () => {
this.isReady = true;
console.log('Angular: 编辑器已就绪');
},
onError: (error) => {
console.error('Angular: 编辑器错误', error);
}
});
}
async searchText(text: string): Promise<void> {
if (this.sdk && this.isReady) {
await this.sdk.searchAndLocateText(text, true);
}
}
async saveDocument(): Promise<void> {
if (this.sdk && this.isReady) {
await this.sdk.saveDocument();
}
}
destroy(): Promise<void> {
if (this.sdk) {
return this.sdk.destroy();
}
return Promise.resolve();
}
}
```
```typescript
// office.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { OfficeService } from './office.service';
@Component({
selector: 'app-office',
template: `
<div class="controls">
<button (click)="searchText()">搜索文本</button>
<button (click)="saveDocument()">保存文档</button>
</div>
<div id="office-container" style="height: 600px;"></div>
`
})
export class OfficeComponent implements OnInit, OnDestroy {
constructor(private officeService: OfficeService) {}
async ngOnInit() {
await this.officeService.initialize({
fileId: 'angular-demo-doc',
containerSelector: '#office-container',
configApiUrl: 'https://api.example.com',
token: 'your-auth-token',
permissionToken: 'your-permission-token'
});
}
async ngOnDestroy() {
await this.officeService.destroy();
}
async searchText() {
await this.officeService.searchText('搜索文本');
}
async saveDocument() {
await this.officeService.saveDocument();
}
}
```
```html
<!DOCTYPE html>
<html>
<head>
<title>Office Provider SDK Demo</title>
</head>
<body>
<div class="controls">
<button onclick="searchText()">搜索文本</button>
<button onclick="saveDocument()">保存文档</button>
</div>
<div id="office-container" style="height: 600px;"></div>
<script type="module">
import { initOfficeSDK } from '@koi-br/office-provider-sdk';
let sdk = null;
let isReady = false;
async function initializeOffice() {
try {
sdk = await initOfficeSDK({
fileId: 'vanilla-demo-doc',
containerSelector: '#office-container',
configApiUrl: 'https://api.example.com',
token: 'your-auth-token',
permissionToken: 'your-permission-token',
onReady: (provider, details) => {
isReady = true;
console.log('Vanilla JS: 编辑器已就绪', details);
},
onError: (error) => {
console.error('Vanilla JS: 编辑器错误', error);
}
});
} catch (error) {
console.error('初始化失败:', error);
}
}
window.searchText = async function() {
if (sdk && isReady) {
await sdk.searchAndLocateText('搜索文本', true);
}
};
window.saveDocument = async function() {
if (sdk && isReady) {
await sdk.saveDocument();
}
};
// 初始化
initializeOffice();
</script>
</body>
</html>
```
---
```typescript
// 获取当前 Provider 类型
const providerType = sdk.getCurrentProviderType();
// WPS 特定功能
if (providerType === OfficeProviderType.WPS) {
// WPS 特有的文本操作方法
await sdk.insertTextAtCursor('文本内容');
await sdk.replaceText(0, 5, '新文本');
await sdk.highlightText(0, 10);
await sdk.clearHighlight();
// 获取文档长度
const length = await sdk.getDocumentLength();
// 格式化文档
await sdk.formatDocumentFont({
name: '微软雅黑',
size: 14,
bold: true,
color: '#333333'
});
// 修订管理
const revisions = await sdk.getRevisions();
await sdk.handleRevisions([1, 2, 3], 'accept');
await sdk.acceptReviewByText({ old: '旧文本', new: '新文本' });
// 获取 WPS 原生实例(高级用法)
const nativeInstance = sdk.getNativeInstance();
}
// OnlyOffice 特定功能
if (providerType === OfficeProviderType.ONLYOFFICE) {
// 内容控制操作(所有Provider都支持,但OnlyOffice有更丰富的功能)
await sdk.addContentControl({
fieldId: 'custom-field',
placeholderText: '请输入内容'
});
await sdk.setContentControlValue('custom-field', '字段内容');
// 获取 OnlyOffice 原生实例(高级用法)
const nativeInstance = sdk.getNativeInstance();
}
```
```typescript
import {
initOfficeSDK,
createErrorHandler,
createReadyHandler
} from '@koi-br/office-provider-sdk';
const sdk = await initOfficeSDK({
fileId: 'your-document-id',
containerSelector: '#office-container',
configApiUrl: 'https://api.example.com/office/config',
// 使用辅助函数创建标准化的回调
onReady: createReadyHandler((provider, details) => {
console.log(`${details.providerName} 已就绪`, details);
// 自定义就绪逻辑
}),
onError: createErrorHandler((error) => {
// 统一的错误处理
switch (error.type) {
case 'initialization_error':
console.error('初始化失败:', error.message);
break;
case 'provider_switch_error':
console.error('Provider 切换失败:', error.message);
break;
default:
console.error('未知错误:', error);
}
// 可以添加错误上报、用户提示等逻辑
})
});
```
```typescript
import { createOfficeSDK } from '@koi-br/office-provider-sdk';
// 创建多个独立的 SDK 实例
const sdkInstances = [];
for (let i = 0; i < 3; i++) {
const sdk = createOfficeSDK();
await sdk.initialize({
fileId: `document-${i}`,
containerSelector: `
configApiUrl: 'https://api.example.com',
token: 'your-auth-token',
permissionToken: 'your-permission-token'
}, {
onReady: (provider, details) => {
console.log(`实例 ${i} 已就绪:`, details);
},
onError: (error) => {
console.error(`实例 ${i} 错误:`, error);
}
});
sdkInstances.push(sdk);
}
// 统一操作所有实例
for (const sdk of sdkInstances) {
await sdk.searchAndLocateText('批量操作文本', true);
await sdk.saveDocument();
}
```
---
```bash
npm install
npm run dev
npm run build
npm run build:example
npm run clean
```
---
```
office-provider-sdk/
├── src/
│ ├── core/
│ │ ├── OfficeSDK.ts
│ │ └── OfficeProviderFactory.ts
│ ├── providers/
│ │ ├── WPSProvider.ts
│ │ └── OnlyOfficeProvider.ts
│ ├── types/
│ │ └── index.ts
│ ├── utils/
│ │ └── index.ts
│ └── index.ts
├── examples/
│ ├── demo.vue
│ └── VueDemo.vue
├── dist/
├── sdk/
└── README.md
```
---
- **统一的 API 接口**:无论使用哪种 Office 套件,都使用相同的方法调用
- **Provider 模式**:可扩展的架构,轻松添加新的 Office 提供商
- **类型安全**:完整的 TypeScript 支持,编译时检查错误
- **自动检测**:通过 API 自动识别文档类型和 Provider
- **配置分离**:业务逻辑与具体 Provider 配置解耦
- **降级支持**:自动配置失败时可回退到手动配置
- **框架无关**:可在任何前端技术栈中使用
- **开箱即用**:最少配置即可开始使用
- **渐进增强**:从简单使用到高级定制的平滑过渡
- **错误处理**:完善的错误处理和回调机制
- **性能优化**:基于现代 JavaScript,支持 Tree Shaking
- **可维护性**:模块化设计,易于测试和维护
---
```typescript
// 搜索并高亮文本(所有Provider支持)
const result = await sdk.searchAndLocateText('目标文本', true);
if (result && typeof result === 'object' && result.found) {
console.log(`找到文本,位置: ${result.pos}, 长度: ${result.length}`);
}
// 搜索并替换(所有Provider支持)
await sdk.searchAndReplace({
old: '目标文本',
new: '新文本'
});
// 根据文本定位(所有Provider支持)
await sdk.locateByText({ text: '目标文本' });
// 清除所有高亮(仅WPS支持)
if (sdk.getCurrentProviderType() === OfficeProviderType.WPS) {
await sdk.clearHighlight();
}
```
```typescript
// 格式化整个文档(仅WPS支持)
if (sdk.getCurrentProviderType() === OfficeProviderType.WPS) {
await sdk.formatDocumentFont({
name: '微软雅黑',
size: 14,
bold: false,
italic: false,
color: '#333333'
});
}
// 设置文档为只读模式(所有Provider支持)
await sdk.setReadOnly(true);
```
```typescript
// 添加内容控制字段(所有Provider支持)
await sdk.addContentControl({
fieldId: 'field-1',
placeholderText: '请输入内容'
});
// 设置内容控制的值
await sdk.setContentControlValue('field-1', '字段内容');
// 设置内容控制高亮颜色 [R, G, B, A]
await sdk.setControlsHighlight([255, 0, 0, 1]); // 红色高亮
// 删除内容控制
await sdk.removeContentControl('field-1');
```
```typescript
// 获取评论列表(所有Provider支持)
const comments = await sdk.getCommentsList('document-key');
// 跳转到指定评论
await sdk.jumpCommentDto('comment-id', 'document-key');
// 滚动到指定位置(所有Provider支持)
await sdk.viewScrollToY(500);
// 适应宽度(所有Provider支持)
await sdk.zoomFitToWidth();
```
```typescript
// 获取所有修订
const revisions = await sdk.getRevisions();
console.log(`找到 ${revisions.length} 个修订`);
// 批量处理修订
const revisionIds = revisions.map(r => r.index);
await sdk.handleRevisions(revisionIds, 'accept'); // 或 'reject'
```
---
**A:** 推荐使用自动配置模式,它可以根据文档自动选择合适的 Provider。如果你需要精确控制或在离线环境中使用,可以选择手动配置模式。
**A:** SDK 会自动拼接完整路径。如果你传入 `configApiUrl: 'https://api.example.com'`,实际请求的URL是:
`https://api.example.com/api/office/online-office/config?fileId=xxx&permissionToken=xxx`
**A:** 是的,你可以使用 `createOfficeSDK()` 创建多个独立的 SDK 实例,每个实例管理一个文档。
**A:** WPS 特有的功能包括:`insertTextAtCursor`、`replaceText`、`highlightText`、`clearHighlight`、`getDocumentLength`、`formatDocumentFont`、`getRevisions`、`handleRevisions`、`acceptReviewByText`。其他功能(如内容控制、评论、搜索等)所有Provider都支持。
**A:** SDK 内置了性能优化,包括懒加载、事件节流等。对于特别大的文档,建议使用只读模式或分页加载。
**A:** 是的,你可以实现 `IOfficeProvider` 接口创建自定义 Provider,然后通过 `OfficeProviderFactory` 注册。
**A:** 启用详细的错误回调,检查控制台输出。确保:
- 容器元素存在且可见
- configApiUrl 正确(会自动拼接 `/api/office/online-office/config`)
- token 和 permissionToken 有效(如果使用)
- 网络连接正常
---
我们欢迎所有形式的贡献!请查看我们的贡献指南:
1. Fork 项目
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 开启 Pull Request
---
MIT License - 查看 [LICENSE](LICENSE) 文件了解详情。
---
- [WPS 开放平台](https://wwo.wps.cn/)
- [OnlyOffice API 文档](https://api.onlyoffice.com/)
- [TypeScript 官方文档](https://www.typescriptlang.org/)
- [Vue.js 官方文档](https://vuejs.org/)
- [React 官方文档](https://reactjs.org/)
- [Angular 官方文档](https://angular.io/)
---
如果你有任何问题或建议,请:
- 创建 [Issue](https://github.com/your-org/office-provider-sdk/issues)
- 发送邮件至:[support@example.com](mailto:support@example.com)
- 查看 [Wiki 文档](https://github.com/your-org/office-provider-sdk/wiki)
---
<div align="center">
**⭐ 如果这个项目对你有帮助,请给它一个 Star!**
Made with ❤️ by [Your Team]
</div>