Onlyoffice+Nocobase

希望有大佬能开发Onlyoffice+Nocobase插件 :rose:

官方有付费插件的,如果只是需要预览,可以看看我这个FileView 文件预览插件 v0.8.2 更新时间:2026.07.28 (内网0部署预览)

1 Like

my-project-plugin-onlyoffice-0.1.54.zip (271.0 KB)
试试看(v2页面)




@my-project/plugin-onlyoffice

NocoBase OnlyOffice 文档预览/编辑插件(v2 UI)。基于 OnlyOffice Document Server,支持在线预览、在线编辑、附件字段内「新建并在线编辑」、Word 模板占位符替换生成文档。

本插件面向 v2 (/v/admin) UI。v1 (/admin) 端仅保留设置页与路由,不维护新功能。


1. 功能一览

  • 在线预览 / 编辑 / 审阅:点击 Office 附件在 OnlyOffice 编辑器中打开(支持 doc/docx/xls/xlsx/ppt/pptx/pdf 等)。
  • 附件字段「新建并在线编辑」:在表单的附件字段下方提供按钮,创建空白 docx 并立即打开编辑器,保存后绑定到字段。
  • 附件字段「从模板新建」:选择 Word 模板(也是附件),服务端用 OnlyOffice Document Builder Service 把模板中 {{字段名}} 占位符替换为当前记录字段值,生成新文档并绑定。
  • 表格行「从模板生成」:通过 JS Action 在业务表操作列加按钮,读取行记录的模板字段,一键生成并绑定文档。
  • 保存机制:编辑器 forcesave → 服务端 callback → 覆盖原附件(nb_attachments 中同一条记录)。

已知限制

  • 不支持多人协同编辑:每次打开都是新的 OnlyOffice 会话(docKey 含时间戳),forcesave 流程依赖此设计。
  • 仅支持 docx 模板替换:Document Builder Service 只可靠处理 .docx/.dotx.doc/.dot 替换不可靠。
  • 模板占位符必须与字段真实 name 完全一致(见 §5.2)。
  • v1 客户端已废弃。

2. 环境要求

依赖 版本 说明
NocoBase 2.x 需 v2 UI(@nocobase/client-v2
Node.js >= 18
@nocobase/plugin-file-manager 2.x peerDependency,必装
OnlyOffice Document Server 社区版即可 需启用 Document Builder Service/docbuilder)与 JWT

网络拓扑

浏览器 ──api.js──> OnlyOffice Document Server (DS)
                 DS ──/onlyoffice:file ─────────────> NocoBase
                 DS ──/onlyoffice:callback ─────────> NocoBase
                 DS ──/onlyoffice:builderScript ────> NocoBase

DS 与 NocoBase 必须双向 HTTP 可达(局域网/内网通常天然满足)。


3. 安装与部署

3.1 首次启用

# 在 NocoBase 应用目录下
nb plugin enable @my-project/plugin-onlyoffice

启用后自动创建配置表 onlyofficeConfig(单例 id=1)。

3.2 源码插件部署(开发/二开)

cd <app>/source
# 导出数据库环境变量(Postgres)
nocobase-v1 build @my-project/plugin-onlyoffice

# 拷贝客户端产物到运行服务目录(重要!生产服务从 storage/dist-client 读取)
cp -r packages/plugins/@my-project/plugin-onlyoffice/dist/{client,client-v2,locale} \
      <app>/storage/dist-client/<version>/static/plugins/@my-project/plugin-onlyoffice/dist/

# 提升版本号(package.json)并重启
PM2_HOME=<app>/storage/.pm2 pm2 restart index

客户端 bundle 通过 ?v=<版本> 缓存,必须升版本号否则浏览器命中旧包。

3.3 设置页入口

/v/admin/settings/onlyoffice(v2),或通过左侧「OnlyOffice」菜单进入。


4. 设置项说明(onlyofficeConfig)

设置页分为 连接 / 编辑器 / 模板 / 高级 四个 Tab,保存时一次性提交全部。

4.1 连接 Connection

字段 默认值 作用
documentServerUrl (必填) OnlyOffice Document Server 地址
jwtSecret 与 DS 端 JWT_SECRET 保持一致;模板功能强制要求非空
serverOrigin DS 回调 NocoBase 的固定出口地址。反向代理/HTTPS 下推荐显式配置(优先级高于自动识别)
apiBasePath API_BASE_PATH env 或 /api/ NocoBase API 路径前缀,用于构造 file/callback/builderScript URL
callbackUrl 兜底地址(仅当请求域名不可用时使用),通常留空

4.2 编辑器 Editor

字段 默认值 作用
allowEdit true 全局是否允许编辑(关闭则只读)
defaultOpenMode view 打开文档的默认模式(view/edit/review);"在线编辑"按钮在非 view 时按此值进入
trackChanges false 为 true 时打开文档对所有用户强制开启修订跟踪
reviewDisplay markup 审阅显示模式(markup=标记和内容气球 / simple / final / original)
editorLang zh-CN 编辑器界面语言

4.3 模板 Template

字段 默认值 作用
templateCollection attachments 存放 Word 模板的集合(可用自定义文件集合)
templateExtensions docx,dotx 可选的模板扩展名(逗号分隔)
newDocTitleTemplate 未命名文档 新建空白文档的默认标题
docbuilderTimeoutMs 120000 Document Builder Service 调用超时(毫秒)
defaultStorageId 生成文档落到的存储 ID(空 = 默认存储)

4.4 高级 Advanced

字段 默认值 作用
supportedExtensions 内置 23 项 交由 OnlyOffice 处理的扩展名(逗号分隔)
overridePreviewer true 是否全局拦截 Office 文件点击并在 OnlyOffice 打开(false = 保留内置预览器)
debugLog false 输出详细 [onlyoffice] 日志(可能含文档明文,生产关闭)

服务端 getSettings() 对所有新字段都有默认值兜底,即使已有配置行某列 NULL 也能正常工作,无需数据迁移。


5. 使用指南

5.1 在表单中启用 OnlyOffice 附件组件

  1. 数据表添加一个 attachment 字段。
  2. 打开字段设置 → Field component 下拉 → 选择 OnlyOffice attachment (editable)
  • 这是独立于内置 UploadFieldModel 的编辑态模型(order 40,非默认)。
  • 该模型在 CardUpload 基础上叠加「新建并在线编辑」「从模板新建」两个按钮。
  1. 添加/编辑记录时即可使用。

5.2 Word 模板编写规范

  • 占位符语法:{{字段名}}
  • 必须与集合字段的真实 name 完全一致(如 {{f_1hydl4g39k9}}),不是字段显示标题。
    • 例如 t_wya47twsc35 中字段 f_1hydl4g39k9 的标题可能是 name,但模板里必须写 {{f_1hydl4g39k9}}
  • 仅支持标量字段替换(string/text/number/boolean/date 等);关系字段、附件字段、多选字段不支持。
  • 占位符建议在单个 Word 运行段(run)内(记事本写好再粘贴,或用查找替换生成),避免被 Word 拆分成多个 run 导致匹配失败。
  • 布尔值输出「是/否」;日期输出 ISO 字符串;下拉/单选输出选项值(非显示文本)。

5.3 从模板新建(附件字段内按钮)

  1. templateCollection 指向的集合中上传 Word 模板文件(.docx 最佳)。
  2. 在业务记录的表单里,附件字段下方点「从模板新建」→ 弹窗选择模板。
  3. 服务端拉取模板 → Document Builder 替换 {{字段}}当前记录字段值 → 生成新附件 → 绑定到附件字段。

新建记录时用表单已填值;编辑已有记录时用库里字段值。


6. 进阶:表格行「从模板生成」JS Action 示例

在业务表(如 t_wya47twsc35)的操作列加一个 JS Action 按钮,一键用模板生成文档并绑定到该行的附件字段。

6.1 需要的表结构

角色 示例 说明
业务表 t_wya47twsc35 含一个到模板表的 belongsTo 字段(如 f_temp)和一个附件字段(如 f_nr3ol7qxrec
模板表 onlyofficetemp 含标题字段(f_title)和模板附件字段(f_onlyofficetemp,指向 attachments)
模板附件 实际 .docx 文件(存于模板表附件字段,独立 storage)

6.2 完整 runJs 代码(version: v2)

将以下代码写入 JS Action 的「Write JavaScript」配置(options.stepParams.clickSettings.runJs):

async function main() {
  const record = ctx.record;
  if (!record) { ctx.message.error('未获取到当前记录'); return; }
  const recordId = record.id;

  // 1. 读取当前记录已选模板(兼容展开对象 / 仅外键)
  const tempId = record?.f_temp?.id ?? record?.f_temp_id;
  if (tempId == null || tempId === '') { ctx.message.error('请先选择模板'); return; }

  // 2. 二次确认
  const ok = await ctx.modal?.confirm?.({
    title: '从模板生成',
    content: '将按所选模板生成文档,并添加到本条记录的附件中,是否继续?',
    okText: '生成', cancelText: '取消',
  });
  if (!ok) return;

  // 3. 取模板记录 + 模板附件
  let templateAttachmentId;
  try {
    const res = await ctx.request({
      url: 'onlyofficetemp:get',
      method: 'get',
      params: { filterByTk: tempId, appends: ['f_onlyofficetemp'] },
      skipNotify: true,
    });
    const template = res?.data?.data; // dataWrapping 一层
    const atts = template?.f_onlyofficetemp;
    if (!Array.isArray(atts) || atts.length === 0) {
      ctx.message.error('该模板未绑定附件文件'); return;
    }
    templateAttachmentId = atts[0]?.id ?? atts[0];
  } catch (e) {
    ctx.message.error('读取模板失败:' + (e?.response?.data?.message || e?.message || '未知错误'));
    return;
  }

  // 4. 生成文档
  let attachment;
  const gen = ctx.message.loading('正在生成文档...', 0);
  try {
    const res = await ctx.request({
      url: 'onlyoffice:createFromTemplate',
      method: 'post',
      data: { templateId: templateAttachmentId, collectionName: 't_wya47twsc35', recordId },
      skipNotify: true,
    });
    attachment = res?.data?.data?.attachment; // 两层解包
    if (!attachment?.id) throw new Error('接口未返回附件');
  } catch (e) {
    ctx.message.error('生成文档失败:' + (e?.response?.data?.message || e?.message || '未知错误'));
    return;
  } finally { gen(); }

  // 5. 绑定附件到当前记录附件字段(belongsToMany add:URL 斜杠 + body 主键数组)
  const add = ctx.message.loading('正在绑定附件...', 0);
  try {
    await ctx.request({
      url: 't_wya47twsc35/' + recordId + '/f_nr3ol7qxrec:add',
      method: 'post',
      data: [attachment.id],
      skipNotify: true,
    });
  } catch (e) {
    ctx.message.error('绑定附件失败:' + (e?.response?.data?.message || e?.message || '未知错误'));
    return;
  } finally { add(); }

  // 6. 刷新表格 + 提示
  try { await ctx.resource?.refresh?.(); } catch (e) {}
  ctx.message.success('已生成并绑定文档:' + (attachment?.title || attachment?.filename || ''));
}
await main();

6.3 :warning: 必须替换的 5 个标识(按你的系统修改)

占位 当前示例值 说明
模板集合名 onlyofficetemp ctx.request url: 'onlyofficetemp:get' 中的集合名
模板附件字段 f_onlyofficetemp 模板集合中指向 attachments 的字段名(appends 用它)
业务集合名 t_wya47twsc35 createFromTemplatecollectionName + 绑定 URL
记录 id recordId ctx.record.id,勿改
附件字段 f_nr3ol7qxrec 业务表附件字段名(add URL 用它)

6.4 关键 API 要点

  • 响应解包两层onlyoffice:createFromTemplateres.data.data.attachment
  • belongsToMany add:URL 用斜杠 t_wya47twsc35/{id}/f_nr3ol7qxrec:add,body 用主键数组 [attachmentId](不要用冒号 :id 写法,会 404)。
  • 所有 ctx.request 建议带 skipNotify: true,错误自行用 ctx.message.error 提示。
  • ctx.message.loading(msg, 0) 返回关闭函数。

7. API 参考

资源名:onlyoffice。URL 均带 camelCase action 名(如 builderScript 不是 builder-script)。

Action 方法 参数 ACL
get GET loggedIn(返回全部设置,含 jwtSecret——注意安全)
set POST 全部设置字段 loggedIn
config GET attachmentId, mode loggedIn
callback POST OnlyOffice 回调 public(JWT 保护)
file GET attachmentId, collection? public(无签名)
forcesave POST attachmentId, key loggedIn
createBlank POST title?, storageId? loggedIn
createFromTemplate POST templateId, collectionName, recordId?, recordValues?, title?, storageId? loggedIn
builderScript GET token public(JWT 保护)

响应解包

  • 普通 action → res.data.data
  • createFromTemplateres.data.data.attachment
  • callback / builderScript → 原始响应(withoutDataWrapping,勿动)

8. 安全须知(移植前必读)

  1. onlyoffice:file 是公开且无签名端点GET /api/onlyoffice:file?attachmentId=N 无需登录即可下载任意附件二进制。对附件有权限要求的系统必须加固(如加签名 token)。
  2. onlyoffice:get 向所有登录用户返回 jwtSecret 明文onlyoffice:set 所有登录用户可改。生产环境建议收紧 ACL(仅 root/admin)。
  3. createFromTemplate 按请求中的 collectionName/recordId 读取记录字段值,绕过 ACL/数据范围。任何登录用户可把任意表任意行的标量字段"打印"进 docx。有严格权限要求的系统需在 action 内校验。
  4. debugLog 开启会把完整文档脚本(含记录明文值)打进日志。生产保持关闭。
  5. 启用 JWT 时,插件 jwtSecret 必须与 DS 端 JWT_SECRET 一致;builderScript 一次性 token 60 秒有效,DS 拉取慢时会 403。

9. 移植到其他系统的检查清单

  • API_BASE_PATH:目标系统若改了 API_BASE_PATH,插件设置 apiBasePath 需同步。
  • serverOrigin:反向代理 / HTTPS / Docker 部署时,设置 serverOrigin 为 DS 可达的 NocoBase 地址。
  • DS ⇄ NocoBase 双向可达filecallbackbuilderScript 三个端点 DS 必须能访问。
  • 存储类型onlyoffice:file 已支持任意存储(local/S3/OSS 走统一 stream API);模板读取同理。
  • 模板集合与扩展名:按你的系统设置 templateCollection / templateExtensions
  • attachments 集合名固定:NocoBase file-manager 自身硬编码 attachments不要改名(仅模板来源集合可用自定义文件集合)。
  • 端口/域名:确认 DS 与 NocoBase 使用相同协议(HTTPS 站点必须配 HTTPS 的 DS,否则混合内容被拦)。
  • 资源名/路由冲突:插件 resource 名固定 onlyoffice、路由 /onlyoffice;若与目标系统冲突需改代码。
  • 包名:若改名 @my-project/plugin-onlyoffice,需同步 client-v2/plugin.tsxclient/plugin.tsxlocale.ts 的 i18n namespace。

10. 常见问题

现象 排查
docbuilder error -3 脚本错误 / 响应被 dataWrapping 包裹(builderScript 必须 withoutDataWrapping
docbuilder error -4 DS 下载源文件(模板/脚本 URL)失败——检查 DS 能否访问 NocoBase、serverOrigin 是否正确
docbuilder error -8 token 无效——jwtSecret 与 DS 不一致
生成的文档占位符没替换 ① 占位符与字段真实 name 不一致(见 §5.2);② 字段值为空(跳过替换);③ 占位符被 Word 拆到多个 run
保存后文件没变 forcesave 回调没到——检查 DS 回调地址、JWT、callback 端点日志(开 debugLog)
前端仍是旧 bundle 升版本号后强制刷新(?v= 缓存)
每次打开都是新会话、无法协同 设计如此(docKey 含时间戳),forcesave 流程依赖
在线编辑按钮进入只读 defaultOpenMode 设为了 view;设 edit/review 可改

11. 变更日志

版本 内容
0.1.45 弹窗内嵌编辑器;附件值回传修复
0.1.46 全面配置化:14 个新配置项 + 设置页 4 Tab + 支持任意存储 + debugLog 门控 + README
2 Likes

感谢大佬!!! :rose: :rose: :rose:

感谢 :smiling_face_with_three_hearts:

这个厉害,功能挺复杂的,试了下新建编辑功能好像只针对附件字段类型有效,能否支持下附件url字段类型。