Skip to content

x-office-u Office 文档编辑

基于 LibreOffice 内核(Collabora 的 COKit)的 Office 文档编辑组件。doc/docx/xls/xlsx/ppt/pptx/odf 完整编辑,三端同一套编辑前端,文档解析与编辑全部在设备本地完成。

兼容性

HarmonyiOSAndroidWEB小程序
支持支持支持需自建部署不支持

App 三端能力对等:编辑 UI 是同一套 Collabora Online 的 JS 前端,跑在各端 WebView 里 (Android WebView / iOS WKWebView / HarmonyOS ArkUI Web),各端只提供 WebView 宿主 与引擎桥。因此三端不存在功能差集。

Web 端走 wasm,见文末「Web 端」。小程序不支持:既没有 native 扩展也起不了 wasm 线程。

调用

vue
<x-office-u
  ref="office"
  :src="docPath"
  style="width:100%;height:100%;"
  @ready="onReady"
  @load="onLoad"
  @statechange="onStateChange"
  @error="onError"
  @back="onBack"
></x-office-u>
ts
import type { XOfficeDocumentInfo, XOfficeFail, XOfficeStateChange } from "@/uni_modules/x-office-u"

const office = ref<XOfficeUComponentPublicInstance | null>(null)
const docPath = ref<string>("")

function onReady() : void {
  // 引擎桥已连通,可以 open。src 非空时组件会自动打开,不必再手动调
}

function onLoad(info : XOfficeDocumentInfo) : void {
  // 只读判断的字段叫 isReadonly 不叫 readonly:readonly 是类成员修饰符关键字,
  // 作为必填字段编到鸿蒙 ArkTS 会直接语法报错
  console.log(info.kind, info.partCount, info.isReadonly)
}

function onStateChange(change : XOfficeStateChange) : void {
  // 工具栏的 撤销/重做/保存 按钮按这个置灰
  console.log(change.modified, change.canUndo, change.canRedo)
}

function onError(fail : XOfficeFail) : void {
  console.error(fail.errCode, fail.errMsg)
}

function onBack() : void {
  // 前端顶栏返回键(含系统返回手势)被点击。前端不会自行切只读或关文档,
  // 怎么返回由页面决定:典型做法是有未保存修改先 save() 再 navigateBack
  uni.navigateBack()
}

// 原地保存
office.value?.save()

// 另存为 docx
office.value?.saveAs({
  path: "/storage/emulated/0/Download/out.docx",
  success: (res) => { console.log(res.path, res.byteSize) }
})

// 导出 PDF
office.value?.exportPdf({ path: "/storage/emulated/0/Download/out.pdf" })

// OCR / 纯文本落成 Word(只支持 doc / docx;Web 端不支持)
office.value?.createFromTextByDoc({
  text: "识别结果\n第二段",
  path: uni.env.CACHE_PATH + "ocr.docx",
  format: "docx"
})

// 没被组件包装的能力直接下 UNO 命令
office.value?.postUnoCommand(".uno:Bold", "")

// 移动端打开后先是只读展示;右下角铅笔已隐藏,用这个进入编辑
office.value?.enterEditMode()

参数

字段说明默认
src文档路径。非空时 @ready 后自动打开""
password打开密码,加密文档需要""
readonly只读打开,屏蔽全部编辑入口false

事件

事件负载说明
ready引擎桥连通、可以 open
loadXOfficeDocumentInfo文档打开成功
statechangeXOfficeStateChangemodified / canUndo / canRedo / currentPart 变化
errorXOfficeFail引擎初始化、打开或保存失败
back前端顶栏返回键(含系统返回手势)被点击。前端不自行切只读 / 关文档,由外层页面决定如何返回(App 三端;web 端无顶栏,不会触发)

顶栏右上角的汉堡主菜单按钮已隐藏(宿主自绘菜单);返回键行为见 back 事件。 右下角进入编辑的铅笔悬浮按钮也已隐藏,改由 ref.enterEditMode() 进入编辑态 (移动端打开后仍先是只读展示,只是不再露出那颗 FAB)。 编辑态左上角的打勾按钮 = 退出编辑回到只读展示 + 宿主自动原地保存(写回 open() 传入的原始路径,见下文「工作副本」;失败走 error 事件,成功后 statechangemodified 变 false)。 这些都是前端(browser/src)与宿主改动,重建前端产物后三端一起生效。 enterEditMode() 本身走 WebView 注入,不重建前端也能用。

方法

方法说明
open(options)打开文档。options.path 必填
save(options?)原地保存回原路径原格式(写回 open() 传入的原始文件,见「工作副本」)
saveAs(options)另存为其他路径 / 格式。options.path 必填
exportPdf(options)导出 PDF,等价于 saveAs({ format: "pdf" })
createFromTextByDoc(options)把纯文本建成 Word 并打开。只支持 doc / docx。内部先写 UTF-8 txt,引擎打开后再另存为 Word。适合 OCR 识别结果。App 三端可用,Web 端会 fail
close()关闭当前文档,组件可复用
undo() / redo()撤销 / 重做
enterEditMode()从移动端只读展示进入编辑态(原右下角铅笔 FAB)。readonly 打开时无效
setPart(index)切页 / 切工作表 / 切幻灯片
getFormatState()光标处格式状态,供业务自绘工具栏
postUnoCommand(command, args?)直连 LibreOffice 的 UNO 命令
destroy()释放。组件卸载时自动调用

open() 收什么路径

引擎只能做裸 POSIX open,所以 options.path 最终必须是应用沙箱里的真实路径。系统选择 器给的不是路径而是授权 URI,两者不能混:

来源给出的东西插件怎么处理
鸿蒙 DocumentViewPickerfile://docs/storage/Users/...,仅临时授权、只能按 fd 读自动拷进 <cacheDir>/x-office-u/inbox/<handle>/ 再打开
Android x-chooseFile-s已是 cache 副本的真实路径直接用
iOS UIDocumentPicker真实路径直接用

判据是 file:// 后的 authority 段必须为空:file:///a 的第三个斜杠后就是路径, 而 file://docs/adocs 是 authority。后者直接丢给引擎会得到 Unsupported URL <...>: "type detection failed" —— 字面意思是格式识别失败, 实际是路径不指向任何文件。

由此带来一个语义:这种情况下编辑的是副本,save() 也写回副本,用户原始文件不变。 这不是实现取巧 —— select() 只给读权限,写回原文件在鸿蒙上本来就做不到(要么用保存 选择器,要么申请 ohos.permission.FILE_ACCESS_PERSIST 做持久化授权)。要落到用户可见 位置得走 saveAs() / exportPdf()

工作副本与原地保存

编辑链路打开时,插件会先把文档复制到应用缓存 (<cacheDir 或 tmp>/x-office-u/work/<handle>/<时间戳-序号>/),交给引擎的是这份 工作副本:引擎的「原地保存」写回的是它自己的会话文件,而外部传入的原始路径未必 可写(只读目录、受限作用域路径等),副本则必在可写沙箱内。每次打开都是全新目录 (COOLWSD 的 docKey 就是文件路径,复用会撞上上一篇的 DocumentBroker)。

save()(不带参数)的完整链路 = 引擎写回工作副本 → 成功后宿主把副本覆盖回 原始路径 → 回调里的 path / byteSize 报告的是原始文件。写回失败会报 save:fail,不会让改动悄悄只留在缓存里。编辑态顶栏的打勾按钮触发的就是这同一条 原地保存。close() / destroy() 清理工作副本目录。

鸿蒙选择器场景例外:此时「原始路径」本身就是 inbox 副本(见上表),不再二次拷贝, 保存写回的就是该副本。

只读属性

statemodifiedcanUndocanRedocurrentPart

支持的另存格式

odt ott docx doc rtf txt ods xlsx xls csv odp pptx ppt odgpdf html epub

saveAs 传了 format 但路径扩展名不匹配时,组件会按 format 补正扩展名,避免写出 扩展名与内容不符的文件。

Web 端

Web 端没有 native 引擎,用的是 LibreOffice 的 wasm 产物:把 COOL 前端整包(含 soffice.wasm)部署到一个基址下,组件用 iframe 装进来。默认基址 /x-office-u/, 可用 window.X_OFFICE_WEB_BASE 覆盖。产物来源二选一:

bash
# 自己编:单仓已有 wasm/ 目录,走上游的 LibreOfficeWASM32.conf + EMSCRIPTEN_INTEL_GCC.mk
# 或者直接用 ZetaOffice 的预编译产物,省掉一次 emscripten 全量构建

必须给承载页和该基址同时下发这两个响应头,否则 SharedArrayBuffer 不可用、 wasm 线程起不来,组件会直接报 errCode: 1801 而不是等到超时:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

代价明确:约 32MB wasm + 15MB data,brotli 后约 52MB 首次下载。Safari 对 SharedArrayBuffer / wasm 线程支持较弱,实测体验明显差于 Chromium,故 package.json 里 safari 标 ×

Web 端与 App 端的语义差异:save / saveAs / exportPdf 落点由浏览器下载决定, path 退化为建议文件名,返回的 XOfficeSaveResult.path 是文件名而非绝对路径; getFormatState() 拿不到同步状态(wasm 前端只在变化时推消息),返回默认值, 业务请改用 @statechangecreateFromTextByDoc 需要可写本地路径,Web 端会 直接 fail。

许可证

本插件的桥接层代码与构建脚本按仓库许可证分发。引擎侧(LibreOffice / Collabora Online) 是 MPL-2.0,对 MPL 文件的修改必须公开——third_party/collabora/online/engine 下 的 OHOS 移植补丁属于这类修改,建议直接推 Gerrit 上游,既合规又能减少长期维护负担。

更新日志

1.0.0(2026-09-01)

  • 初始版本
最近更新