单实例示例
单实例场景使用独立 Subframe:父页面保留工具栏和状态,OnlyOffice Embed SDK 运行在固定的 rat.onlyoffice.* 子域名中。这样即使只有一个编辑器,也不会把 SDK 全局对象、事件总线和文档状态放进宿主页面。
在线演示:站点路由 /docs/demos/single
完整源码:src/features/demo/office-preview-page.tsx
在线演示
下方为可交互的单实例编辑器。
单实例场景使用独立 Subframe:父页面保留工具栏和状态,OnlyOffice Embed SDK 运行在固定的 rat.onlyoffice.* 子域名中。这样即使只有一个编辑器,也不会把 SDK 全局对象、事件总线和文档状态放进宿主页面。
在线演示:站点路由 /docs/demos/single
完整源码:src/features/demo/office-preview-page.tsx
下方为可交互的单实例编辑器。
rat.onlyoffice.localhost(生产环境为 rat.onlyoffice.agent-bridges.com)的 /subframe iframeSubframeManager 通过精确 targetOrigin 和 postMessage RPC 请求子框架打开文档openDocument / downloadExport / setReadOnly / setTheme / toggleLanguage / printLogsdestroy,再释放父页面管理器业务应用不要复制演示协议,也不要在宿主窗口直接挂载兼容编辑器。安装
@agentbridges-ai/onlyoffice-embed-sdk 后使用正式子路径:
import {
createOfficeEditor,
getOfficeSubframeOrigin,
} from "@agentbridges-ai/onlyoffice-embed-sdk/compat/subframe";
const editor = await createOfficeEditor(container, {
hostUrl: getOfficeSubframeOrigin("rat"),
file,
fileName: file.name,
onSave: async (output) => {
await persist(output);
return true;
},
});
该入口自动创建 /subframe?runtime=compat,并用精确 iframe window、origin、
实例 ID 和随机 session token 绑定 RPC。父子边界只传 File、Blob、字节、状态、
错误和插件结果,不传 EditorManager、DocsAPI 或 WindowProxy。
"use client";
import { memo } from "react";
const OnlyOfficeSubframeHost = memo(function OnlyOfficeSubframeHost({
src,
onLoad,
}: {
src: string;
onLoad: () => void;
}) {
return (
<iframe
src={src}
title="OnlyOffice Embed SDK · 🐀 鼠"
data-onlyoffice-subframe="true"
className="absolute inset-0 h-full w-full border-0"
onLoad={onLoad}
/>
);
});
useEffect(() => {
const manager = new SubframeManager({
containerId: "onlyoffice-subframe-editor",
fileType: FILE_TYPE.DOCX,
defaultFileName: "New_Document.docx",
readOnly,
theme: currentTheme,
instanceId: "single-editor",
targetOrigin: getSubframeOrigin("rat"),
frame: frameRef.current,
});
managerRef.current = manager;
void manager.openDocument({
fileName: "New_Document.docx",
isNew: true,
});
return () => {
manager.destroy();
managerRef.current = null;
};
}, []);
const handleOpenDocument = (fileName: string, file?: File) =>
managerRef.current?.openDocument({ fileName, file, readOnly });
const handleExport = () => managerRef.current?.downloadExport();
const handleToggleReadOnly = async () => {
const manager = managerRef.current;
await manager?.setReadOnly(!manager.getReadOnly());
setReadOnly(managerRef.current?.getReadOnly() ?? false);
};
const handleThemeChange = async (theme: OfficeTheme) => {
await managerRef.current?.setTheme(theme);
setCurrentThemeState(managerRef.current?.getTheme() ?? theme);
};
const handleLanguageSwitch = async () => {
const nextLang = await managerRef.current?.toggleLanguage();
setCurrentLangState(nextLang);
};
export function OfficePreviewPage({ embedded = false, ...props }) {
return (
<div className={`flex flex-col bg-white ${embedded ? "h-full min-h-0" : "h-screen"}`}>
<header>{/* 语言 / 主题 / 上传 / 新建 / 导出 / 只读 */}</header>
<div className="relative min-h-0 flex-1">
<OnlyOfficeSubframeHost src={subframeSrc} onLoad={handleFrameLoad} />
{loading && <LoadingOverlay />}
</div>
<input ref={fileInputRef} type="file" className="hidden" onChange={...} />
</div>
);
}
| 能力 | API |
|---|---|
| 上传文件 | manager.openDocument({ fileName, file }) |
| 新建空白 | manager.openDocument({ fileName: defaultFileName }) |
| 导出 | manager.downloadExport() |
| 只读切换 | manager.setReadOnly(!manager.getReadOnly()) |
| 主题 | manager.setTheme(theme) |
| 语言 | manager.toggleLanguage() |
| 打印日志 | manager.printLogs() |