希望有大佬能开发Onlyoffice+Nocobase插件 ![]()
官方有付费插件的,如果只是需要预览,可以看看我这个FileView 文件预览插件 v0.8.2 更新时间:2026.07.28 (内网0部署预览)
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 附件组件
- 数据表添加一个 attachment 字段。
- 打开字段设置 → Field component 下拉 → 选择
OnlyOffice attachment (editable)。
- 这是独立于内置
UploadFieldModel的编辑态模型(order 40,非默认)。 - 该模型在 CardUpload 基础上叠加「新建并在线编辑」「从模板新建」两个按钮。
- 添加/编辑记录时即可使用。
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 从模板新建(附件字段内按钮)
- 在
templateCollection指向的集合中上传 Word 模板文件(.docx最佳)。 - 在业务记录的表单里,附件字段下方点「从模板新建」→ 弹窗选择模板。
- 服务端拉取模板 → 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
必须替换的 5 个标识(按你的系统修改)
| 占位 | 当前示例值 | 说明 |
|---|---|---|
| 模板集合名 | onlyofficetemp |
ctx.request url: 'onlyofficetemp:get' 中的集合名 |
| 模板附件字段 | f_onlyofficetemp |
模板集合中指向 attachments 的字段名(appends 用它) |
| 业务集合名 | t_wya47twsc35 |
createFromTemplate 的 collectionName + 绑定 URL |
| 记录 id | recordId |
ctx.record.id,勿改 |
| 附件字段 | f_nr3ol7qxrec |
业务表附件字段名(add URL 用它) |
6.4 关键 API 要点
- 响应解包两层:
onlyoffice:createFromTemplate→res.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 createFromTemplate→res.data.data.attachmentcallback/builderScript→ 原始响应(withoutDataWrapping,勿动)
8. 安全须知(移植前必读)
onlyoffice:file是公开且无签名端点:GET /api/onlyoffice:file?attachmentId=N无需登录即可下载任意附件二进制。对附件有权限要求的系统必须加固(如加签名 token)。onlyoffice:get向所有登录用户返回jwtSecret明文;onlyoffice:set所有登录用户可改。生产环境建议收紧 ACL(仅 root/admin)。createFromTemplate按请求中的collectionName/recordId读取记录字段值,绕过 ACL/数据范围。任何登录用户可把任意表任意行的标量字段"打印"进 docx。有严格权限要求的系统需在 action 内校验。debugLog开启会把完整文档脚本(含记录明文值)打进日志。生产保持关闭。- 启用 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 双向可达:
file、callback、builderScript三个端点 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.tsx、client/plugin.tsx、locale.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 |
感谢大佬!!!
![]()
感谢 ![]()
这个厉害,功能挺复杂的,试了下新建编辑功能好像只针对附件字段类型有效,能否支持下附件url字段类型。



