|
79 | 79 |
|
80 | 80 | 📚 **预览组件文档**: [https://chaxus.github.io/ran/src/ranui/preview/](https://chaxus.github.io/ran/src/ranui/preview/) |
81 | 81 |
|
| 82 | +## 🧩 iframe 嵌入使用方式 |
| 83 | + |
| 84 | +本项目支持通过 iframe 嵌入到其他业务系统中。推荐架构是:**父系统负责鉴权、下载文件和上传保存结果;iframe 只负责文档编辑**。这样 token、cookie、业务接口都留在父系统内,编辑器不需要知道业务系统的授权细节。 |
| 85 | + |
| 86 | +项目内置了一个示例页面: |
| 87 | + |
| 88 | +```text |
| 89 | +/embed-demo.html |
| 90 | +``` |
| 91 | + |
| 92 | +本地启动后可以访问: |
| 93 | + |
| 94 | +```text |
| 95 | +http://127.0.0.1:8082/embed-demo.html |
| 96 | +``` |
| 97 | + |
| 98 | +### 1. 嵌入编辑器 |
| 99 | + |
| 100 | +```html |
| 101 | +<iframe id="documentEditor" src="http://127.0.0.1:8082/?embed=1" style="width: 100%; height: 720px; border: 0"></iframe> |
| 102 | +``` |
| 103 | + |
| 104 | +如果需要限制只接收指定父页面来源,可以增加 `embedOrigin`: |
| 105 | + |
| 106 | +```html |
| 107 | +<iframe id="documentEditor" src="http://127.0.0.1:8082/?embed=1&embedOrigin=https://your-system.example.com"></iframe> |
| 108 | +``` |
| 109 | + |
| 110 | +### 2. 发送命令 |
| 111 | + |
| 112 | +建议每条命令带上 `id`,便于父页面匹配响应: |
| 113 | + |
| 114 | +```js |
| 115 | +const iframe = document.getElementById('documentEditor'); |
| 116 | +const editorOrigin = 'http://127.0.0.1:8082'; |
| 117 | + |
| 118 | +function sendEditorCommand(type, payload = {}) { |
| 119 | + const id = `${Date.now()}-${Math.random().toString(16).slice(2)}`; |
| 120 | + iframe.contentWindow.postMessage({ id, type, payload }, editorOrigin); |
| 121 | + return id; |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +监听 iframe 响应: |
| 126 | + |
| 127 | +```js |
| 128 | +window.addEventListener('message', (event) => { |
| 129 | + if (event.origin !== editorOrigin) return; |
| 130 | + |
| 131 | + const { id, type, payload } = event.data || {}; |
| 132 | + if (!type || !type.startsWith('document:')) return; |
| 133 | + |
| 134 | + if (type === 'document:ready') { |
| 135 | + console.log('编辑器已就绪'); |
| 136 | + } |
| 137 | + |
| 138 | + if (type === 'document:opened') { |
| 139 | + console.log('文档已打开', id, payload); |
| 140 | + } |
| 141 | + |
| 142 | + if (type === 'document:saved') { |
| 143 | + console.log('保存完成', payload.fileName, payload.file); |
| 144 | + } |
| 145 | + |
| 146 | + if (type === 'document:error') { |
| 147 | + console.error('编辑器错误', payload.message); |
| 148 | + } |
| 149 | +}); |
| 150 | +``` |
| 151 | + |
| 152 | +### 3. 打开文档 |
| 153 | + |
| 154 | +通过 URL 打开: |
| 155 | + |
| 156 | +```js |
| 157 | +sendEditorCommand('document:open-url', { |
| 158 | + url: 'https://example.com/files/demo.xlsx', |
| 159 | + fileName: 'demo.xlsx', |
| 160 | + readonly: false, |
| 161 | +}); |
| 162 | +``` |
| 163 | + |
| 164 | +通过本地文件对话框打开: |
| 165 | + |
| 166 | +```js |
| 167 | +const input = document.createElement('input'); |
| 168 | +input.type = 'file'; |
| 169 | +input.accept = '.xlsx,.xls,.csv,.docx,.doc,.pptx,.ppt'; |
| 170 | +input.onchange = () => { |
| 171 | + const file = input.files[0]; |
| 172 | + sendEditorCommand('document:open-file', { file, readonly: false }); |
| 173 | +}; |
| 174 | +input.click(); |
| 175 | +``` |
| 176 | + |
| 177 | +通过父系统授权请求后传入二进制数据: |
| 178 | + |
| 179 | +```js |
| 180 | +const response = await fetch('/api/files/1', { |
| 181 | + headers: { Authorization: `Bearer ${token}` }, |
| 182 | +}); |
| 183 | +const buffer = await response.arrayBuffer(); |
| 184 | +sendEditorCommand('document:open-buffer', { |
| 185 | + fileName: 'demo.xlsx', |
| 186 | + buffer, |
| 187 | + readonly: false, |
| 188 | +}); |
| 189 | +``` |
| 190 | + |
| 191 | +### 4. 设置只读 |
| 192 | + |
| 193 | +```js |
| 194 | +sendEditorCommand('document:set-readonly', { readonly: true }); |
| 195 | +``` |
| 196 | + |
| 197 | +### 5. 保存并上传到服务端 |
| 198 | + |
| 199 | +```js |
| 200 | +sendEditorCommand('document:save', { targetExt: 'XLSX' }); |
| 201 | + |
| 202 | +window.addEventListener('message', async (event) => { |
| 203 | + if (event.origin !== editorOrigin) return; |
| 204 | + const { type, payload } = event.data || {}; |
| 205 | + if (type !== 'document:saved') return; |
| 206 | + |
| 207 | + await fetch('/api/files/1', { |
| 208 | + method: 'POST', |
| 209 | + headers: { Authorization: `Bearer ${token}` }, |
| 210 | + body: payload.file, |
| 211 | + }); |
| 212 | +}); |
| 213 | +``` |
| 214 | + |
| 215 | +### 6. 支持的消息 |
| 216 | + |
| 217 | +| 方向 | 类型 | 说明 | |
| 218 | +| --- | --- | --- | |
| 219 | +| 父页面 → iframe | `document:open-url` | 通过 URL 打开文档 | |
| 220 | +| 父页面 → iframe | `document:open-file` | 通过 `File` / `Blob` 打开文档 | |
| 221 | +| 父页面 → iframe | `document:open-buffer` | 通过 `ArrayBuffer` / `Uint8Array` 打开文档 | |
| 222 | +| 父页面 → iframe | `document:set-readonly` | 设置只读或可编辑 | |
| 223 | +| 父页面 → iframe | `document:save` | 保存并返回 `File` | |
| 224 | +| 父页面 → iframe | `document:get-state` | 获取当前状态 | |
| 225 | +| iframe → 父页面 | `document:ready` | iframe 初始化完成 | |
| 226 | +| iframe → 父页面 | `document:opened` | 文档打开完成 | |
| 227 | +| iframe → 父页面 | `document:readonly-changed` | 只读状态已切换 | |
| 228 | +| iframe → 父页面 | `document:saved` | 保存完成,返回文件 | |
| 229 | +| iframe → 父页面 | `document:state` | 返回当前状态 | |
| 230 | +| iframe → 父页面 | `document:error` | 操作失败 | |
| 231 | + |
82 | 232 | ## 🛠️ 技术架构 |
83 | 233 |
|
84 | 234 | - **OnlyOffice SDK**: 提供强大的文档编辑能力 |
|
0 commit comments