
在大多数企业系统中,文档编辑器只是一个"嵌入式组件"——用户打开、编辑、保存,仅此而已。但真实业务场景中,我们往往需要从外部系统控制文档内容:
这些需求的共同特点是:操作文档的主体不是用户,而是外部系统。
OnlyOffice 提供了 连接器(Connector) 机制来满足这类需求。中国版完整实现了官方连接器的全部功能,兼容官方 JSAPI,并可与用户只读模式、动态权限切换等增强功能配合使用,构建出更强大的业务集成方案。
中国版连接器增强能力:兼容官方 Automation API,支持 Word/Excel/PPT 全文档类型操作 可与用户只读模式配合:用户无法手动编辑,但连接器可操作文档 支持动态权限切换:运行时通过连接器修改用户权限 支持细粒度文档操作:段落、Run、样式、图表等均可操控
连接器是 OnlyOffice 文档编辑器提供的 JavaScript API 接口,允许外部代码(宿主页面)对正在编辑的文档执行操作。它与插件(Plugin)拥有相同的底层接口,但使用方式更灵活:
对于业务系统集成来说,连接器是更合适的选择。
在初始化编辑器后,通过 createConnector 方法获取连接器实例:
// 初始化编辑器
const docEditor = new DocsAPI.DocEditor("placeholder", config);
// 创建连接器
const connector = docEditor.createConnector();连接器提供两个核心方法:
callCommand —— 在文档上下文中执行代码:
connector.callCommand(function () {
// 这里的代码运行在文档编辑器内部
// 可以使用 Api 对象操作文档
var oDocument = Api.GetDocument();
// ...
});executeMethod —— 调用编辑器提供的方法:
connector.executeMethod("InsertTextToCursor", ["Hello World"]);两者的区别在于:callCommand 内的函数运行在编辑器沙箱中,可以调用完整的文档操作 API;executeMethod 是对常用操作的封装,调用更简洁。
注意:
callCommand中的函数是序列化后传递到编辑器内部执行的,因此不能引用外部变量。需要传递数据时,可以通过函数返回值或事件机制。
某企业合同管理系统的需求:
在 Word 模板中,使用特定格式的占位符标记可变内容,例如:
甲方:{{partyA}}
乙方:{{partyB}}
合同金额:人民币 {{amount}} 元整
合同期限:{{startDate}} 至 {{endDate}}第一步:配置编辑器
使用用户只读模式,确保用户不能手动编辑,但连接器可以操作文档:
const config = {
document: {
fileType: "docx",
key: contractKey,
title: "采购合同-2026-0412",
url: templateDownloadUrl,
permissions: {
edit: true,
copy: true,
copyOut: false,
print: true
}
},
editorConfig: {
mode: "edit",
customization: {
readOnly: true, // 用户只读模式
waterMark: {
value: `${currentUser.name}\\n合同预览`,
fillstyle: "rgba(192, 192, 192, 0.2)",
font: "14px SimHei",
rotate: -30,
opacity: 0.2
}
}
}
};第二步:获取业务数据并填充
当用户在业务表单中填写完合同要素后,通过连接器将数据写入文档:
// 业务数据
const contractData = {
partyA: "北京某某科技有限公司",
partyB: "上海某某信息技术有限公司",
amount: "壹佰贰拾叁万肆仟伍佰陆拾柒",
amountNum: "1,234,567.00",
startDate: "2026年04月12日",
endDate: "2027年04月11日",
signDate: "2026年04月12日"
};
// 通过连接器填充数据
function fillContract(data) {
const connector = docEditor.createConnector();
// 将数据序列化后传入
const jsonData = JSON.stringify(data);
connector.callCommand(function () {
// 在文档上下文中执行
var oDocument = Api.GetDocument();
var aElements = oDocument.GetAllContentControls();
// 如果使用内容控件方式
for (var i = 0; i < aElements.length; i++) {
var tag = aElements[i].GetTag();
// 根据 tag 匹配字段并替换
}
});
// 也可以使用搜索替换方式
connector.callCommand(function () {
var oDocument = Api.GetDocument();
// 使用 SearchAndReplace 方法
var oSearchData = {
searchString: "{{partyA}}",
replaceString: "北京某某科技有限公司",
matchCase: true
};
oDocument.SearchAndReplace(oSearchData);
});
}第三步:逐字段替换的完整实现
实际项目中,建议封装一个通用的模板填充方法:
function fillTemplate(connector, fieldMap) {
const entries = Object.entries(fieldMap);
// 由于 callCommand 内部不能引用外部变量
// 需要逐个字段调用,或者将数据编码到函数体中
entries.forEach(([placeholder, value]) => {
// 动态构造函数字符串
const script = `
var oDocument = Api.GetDocument();
oDocument.SearchAndReplace({
searchString: "{{${placeholder}}}",
replaceString: "${value.replace(/"/g, '\\"')}",
matchCase: true
});
`;
connector.callCommand(new Function(script));
});
}
// 使用
fillTemplate(connector, {
partyA: contractData.partyA,
partyB: contractData.partyB,
amount: contractData.amount,
amountNum: contractData.amountNum,
startDate: contractData.startDate,
endDate: contractData.endDate
});用户只读模式是中国版特有的功能,可以实现"用户不可编辑,但连接器可操作文档"的效果。
与普通只读模式的区别:
模式 | 用户能否编辑 | 连接器能否操作 | 适用场景 |
|---|---|---|---|
普通只读(mode: view) | 否 | 否 | 纯预览场景 |
用户只读(readOnly: true) | 否 | 是 | 合同生成、公文套打等 |
配置要点:
{
"editorConfig": {
"customization": {
"readOnly": true
},
"permissions": {
"edit": true
},
"mode": "edit"
}
}三个字段必须同时配置:mode 设为 edit、permissions.edit 设为 true、customization.readOnly 设为 true。
注意:用户只读模式为高级版功能,目前仅支持 Word/Excel/PPT 的 PC 模式
{{fieldName}})某内容管理平台需要集成 AI 写作能力:

获取选中文本,发送给AI处理:
// 获取当前选中的文本
function getSelectedText(connector) {
return new Promise((resolve) => {
connector.callCommand(
function () {
var oDocument = Api.GetDocument();
var selectedText = oDocument.GetSelectedText();
return selectedText;
},
false, // isNoCalc
function (result) {
resolve(result);
}
);
});
}
// AI润色流程
async function aiPolish() {
const selectedText = await getSelectedText(connector);
if (!selectedText) {
alert("请先选中需要润色的文本");
return;
}
// 调用后端AI接口
const response = await fetch("/api/ai/polish", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: selectedText })
});
const { result } = await response.json();
// 将AI结果替换选中内容
connector.callCommand(function () {
var oDocument = Api.GetDocument();
// 在当前选区位置插入新文本
var oParagraph = Api.CreateParagraph();
oParagraph.AddText(result);
oDocument.InsertContent([oParagraph], true); // true 表示替换选区
});
}在光标位置插入AI生成的内容:
async function aiGenerate(prompt) {
const response = await fetch("/api/ai/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt })
});
const { result } = await response.json();
// 将生成的内容插入光标位置
connector.callCommand(function () {
var oParagraph = Api.CreateParagraph();
var oRun = Api.CreateRun();
// 设置字体样式与文档保持一致
oRun.AddText(result);
oRun.SetFontFamily("SimSun");
oRun.SetFontSize(24); // 单位是半磅,24 = 12pt
oParagraph.AddElement(oRun);
var oDocument = Api.GetDocument();
oDocument.InsertContent([oParagraph]);
});
}如果AI接口支持流式输出(SSE),可以实现逐字显示效果。但需要注意,频繁调用 callCommand 会有性能开销。建议的处理方式:
// 推荐:在侧边栏展示完整结果后,一次性插入
function insertAiResult(text) {
const paragraphs = text.split("\n").filter(p => p.trim());
connector.callCommand(function () {
var aContent = [];
for (var i = 0; i < paragraphs.length; i++) {
var oParagraph = Api.CreateParagraph();
oParagraph.AddText(paragraphs[i]);
aContent.push(oParagraph);
}
var oDocument = Api.GetDocument();
oDocument.InsertContent(aContent);
});
}某数据分析平台需要将统计数据自动填入Excel模板并生成图表:
// 销售数据
const salesData = [
{ month: "1月", revenue: 125000, cost: 89000, profit: 36000 },
{ month: "2月", revenue: 138000, cost: 92000, profit: 46000 },
{ month: "3月", revenue: 156000, cost: 98000, profit: 58000 },
// ...
];
function fillExcelReport(connector, data) {
// 将数据转为JSON字符串,嵌入到函数中
const jsonStr = JSON.stringify(data);
connector.callCommand(function () {
var data = JSON.parse(jsonStr);
var oWorksheet = Api.GetActiveSheet();
// 写入表头
oWorksheet.GetRange("A1").SetValue("月份");
oWorksheet.GetRange("B1").SetValue("收入(元)");
oWorksheet.GetRange("C1").SetValue("成本(元)");
oWorksheet.GetRange("D1").SetValue("利润(元)");
// 设置表头样式
var headerRange = oWorksheet.GetRange("A1:D1");
headerRange.SetBold(true);
headerRange.SetFillColor(Api.CreateColorFromRGB(68, 114, 196));
headerRange.SetFontColor(Api.CreateColorFromRGB(255, 255, 255));
// 写入数据
for (var i = 0; i < data.length; i++) {
var row = i + 2;
oWorksheet.GetRange("A" + row).SetValue(data[i].month);
oWorksheet.GetRange("B" + row).SetValue(data[i].revenue);
oWorksheet.GetRange("C" + row).SetValue(data[i].cost);
oWorksheet.GetRange("D" + row).SetValue(data[i].profit);
}
// 设置数字格式
var dataRows = data.length;
oWorksheet.GetRange("B2:D" + (dataRows + 1)).SetNumberFormat("#,##0.00");
// 自动调整列宽
oWorksheet.GetRange("A1:D1").SetColumnWidth(15);
});
}function createChart(connector, dataRowCount) {
connector.callCommand(function () {
var oWorksheet = Api.GetActiveSheet();
// 创建柱状图
var oChart = oWorksheet.AddChart(
"'" + oWorksheet.GetName() + "'!$A$1:$D$" + (dataRowCount + 1),
true, // 按行
"bar", // 图表类型
2, // 样式
200 * 36000, // 宽度(EMU)
150 * 36000 // 高度(EMU)
);
oChart.SetTitle("月度销售报表", 12);
oChart.SetLegendPos("bottom");
// 将图表放置在数据下方
oChart.SetPosition(oWorksheet, dataRowCount + 3, 0, 0, 0);
});
}async function generateMonthlyReport() {
// 1. 从后端获取数据
const response = await fetch("/api/reports/monthly-sales");
const salesData = await response.json();
// 2. 创建连接器
const connector = docEditor.createConnector();
// 3. 填充数据
fillExcelReport(connector, salesData);
// 4. 生成图表
createChart(connector, salesData.length);
// 5. 通知用户
showNotification("报表生成完成");
}由于 callCommand 中的函数在编辑器沙箱中执行,不能直接引用外部变量。推荐的数据传递方式:
// 方式一:将数据序列化后拼接到函数体中
function setValueByConnector(connector, cellRef, value) {
const safeValue = JSON.stringify(value);
connector.callCommand(
new Function(`
var oSheet = Api.GetActiveSheet();
oSheet.GetRange("${cellRef}").SetValue(${safeValue});
`)
);
}
// 方式二:使用 callCommand 的回调获取返回值
connector.callCommand(
function () {
return Api.GetDocument().GetStatistics();
},
false,
function (stats) {
console.log("文档统计:", stats);
}
);function safeCallCommand(connector, fn, callback) {
try {
connector.callCommand(fn, false, function (result) {
if (callback) callback(null, result);
});
} catch (error) {
console.error("连接器调用失败:", error);
if (callback) callback(error, null);
}
}callCommand 调用中,减少通信开销callCommand,应在单次调用中完成所有操作callCommand 是异步的,注意操作顺序的控制// 不推荐:逐行调用
for (let i = 0; i < 1000; i++) {
connector.callCommand(function () {
// 写入一行数据
});
}
// 推荐:一次性写入所有数据
connector.callCommand(function () {
var oSheet = Api.GetActiveSheet();
for (var i = 0; i < 1000; i++) {
oSheet.GetRange("A" + (i + 1)).SetValue("data" + i);
}
});中国版自 9.3.0 版本开始支持通过连接器动态修改用户权限,无需重新打开文档即可实时生效。
使用场景:
实现示例:
// 创建连接器
const connector = docEditor.createConnector();
// 审批人点击"开始审批"按钮时,切换为只读+可评论
function onStartReview() {
connector.callCommand(function () {
Api.changePermissions({
edit: false,
comment: true,
copy: true,
copyOut: false,
print: false
});
});
}
// 审批通过后,进入签署阶段,完全禁止操作
function onApproved() {
connector.callCommand(function () {
Api.changePermissions({
edit: false,
comment: false,
copy: false,
copyOut: false,
print: false
});
});
}
// 审批驳回,退回给起草人编辑
function onRejected() {
connector.callCommand(function () {
Api.changePermissions({
edit: true,
comment: true,
copy: true,
copyOut: true,
print: true
});
});
}支持的权限字段:
字段 | 说明 | 类型 |
|---|---|---|
comment | 是否允许评论 | Boolean |
copy | 是否允许复制 | Boolean |
copyOut | 是否允许复制到外部(中国版特有) | Boolean |
edit | 是否允许编辑 | Boolean |
是否允许打印 | Boolean |
注意:动态权限切换为高级版功能,目前仅支持 Word/Excel/PPT 的 PC 模式
连接器可以与中国版的多个增强功能组合使用,构建更强大的业务场景:
组合方式 | 典型场景 | 关键配置 |
|---|---|---|
连接器 + 用户只读模式 | 合同制作、公文套打 |
|
连接器 + 动态权限切换 | 审批流程中的权限流转 |
|
连接器 + 防截图水印 | 安全环境下的自动文档生成 |
|
连接器 + 内部剪切板 | 敏感数据填充后防止用户复制到外部 |
|
连接器 + 迷你工具栏 | 简化用户编辑体验 |
|
对于有国内办公套件集成经验的开发者,可能更熟悉 WPS 的 JSSDK。以下是两者的关键差异:
对比维度 | OnlyOffice 连接器 | WPS JSSDK |
|---|---|---|
API丰富度 | 与插件接口相同,覆盖面广 | 提供标准化接口,覆盖常用场景 |
文档操作深度 | 可操作到段落、Run、样式等细粒度 | 以高层封装为主 |
私有化部署 | 完全支持 | 需要商业授权 |
学习成本 | 需了解 OOXML 模型 | 接口设计更面向业务 |
扩展性 | 插件 + 连接器双通道 | SDK标准接口 |
OnlyOffice 连接器的优势在于更深的文档操作能力和完全的私有化支持,适合需要深度定制的企业级场景。
OnlyOffice 中国版的连接器为业务系统与文档编辑器之间架起了一座桥梁。通过 JSAPI,外部系统可以像操作数据库一样操作文档内容——读取、写入、格式化、生成图表,一切都可以通过代码完成。
核心价值:
连接器让 OnlyOffice 不再只是一个编辑器,而是业务系统中可编程的文档引擎。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。