通用 API
在 Univer 中,根据文档的不同类型,所能调用的 Facade API 也有所不同。本章节将介绍在所有类型文档都适用的通用 Facade API。
概念
命令
Univer 中大多数的操作都会注册到命令系统,并通过命令系统来触发。这种统一的操作方式使得 Univer 可以很容易的实现撤销、重做、协同等功能。
如需了解更多设计细节请阅读 Univer 命令系统 。
命令系统
Univer 为用户提供了统一的命令系统,通过命令系统用户可以实现各种定制化的功能。
每一个命令对应了一个唯一的 ID。如果你正在寻找某个特定的命令 ID,可以参考 如何查找命令 ID。
监听命令
Univer 设计了两种监听命令的方式,分别是在命令执行前和在命令执行后:
onBeforeCommandExecute
:在命令执行前执行自定义逻辑。onCommandExecuted
:在命令执行后执行自定义逻辑。
监听命令执行前
在命令执行之前,我们向 FUniver.onBeforeCommandExecute
API 传入一个回调函数来注册自定义的预处理监听器。
当命令执行前,预处理监听器内逻辑会被执行。
const univerAPI = FUniver.newAPI(univer);
univerAPI.onBeforeCommandExecute((command) => {
const { id, type, params } = command;
// 在命令执行前执行自定义逻辑
})
如果你想在命令执行前阻止命令执行,可以在监听器回调中 throw
一个异常。
const univerAPI = FUniver.newAPI(univer);
univerAPI.onBeforeCommandExecute((command) => {
throw new Error('禁止编辑')
})
监听命令执行后
在命令执行之后,我们也可以向 FUniver.onCommandExecuted
API 传入一个回调函数来注册自定义的后处理监听器。
当命令执行后,后处理监听器内逻辑会被执行。
const univerAPI = FUniver.newAPI(univer);
univerAPI.onCommandExecuted((command) => {
const { id, type, params } = command;
// 在命令执行后执行自定义逻辑
})
取消监听
注册命令监听器的方法会返回一个 IDisposable
对象,调用 IDisposable.dispose
可以销毁该监听器。
建议你及时销毁不再使用的监听器,有助于提高程序的健壮性。
const univerAPI = FUniver.newAPI(univer);
// 注册监听器
const disposable = univerAPI.onBeforeCommandExecute((command) => {
// 在命令执行前执行自定义逻辑
})
// 示例:1 秒后取消监听
setTimeout(() => {
// 取消监听
disposable.dispose();
}, 1000);
执行命令
如果你知道命令 ID 和所需要传递的参数,也可以通过 FUniver.executeCommand
方法来执行命令。
例如,我们可以通过 sheet.command.set-range-values
命令来设置单元格 A1 的值:
const univerAPI = FUniver.newAPI(univer);
// 执行命令
univerAPI.executeCommand('sheet.command.set-range-values', {
value: { v: "Hello, Univer!" },
range: { startRow: 0, startColumn: 0, endRow: 0, endColumn: 0 }
});
事件系统
Univer 提供了一个完整的事件系统,允许你监听和响应电子表格中的各种变化。事件可以分为多个类别,包括剪贴板操作、选区变化、单元格交互、表格修改等。
事件类别
完整事件列表请参考:https://reference.univer.ai/zh-CN/classes/FEventName#%E5%B1%9E%E6%80%A7
-
剪贴板事件
BeforeClipboardChange
: 在剪贴板内容改变前触发,可以通过设置params.cancel = true
来阻止改变BeforeClipboardPaste
: 在粘贴内容前触发,可以通过设置params.cancel = true
来阻止粘贴ClipboardChanged
: 在剪贴板内容改变后触发,可以获取新的剪贴板内容ClipboardPasted
: 在内容粘贴后触发,可以获取粘贴的内容
-
选区事件
SelectionChanged
: 在选区改变时触发,提供新的选区范围信息SelectionMoveStart
: 在选区开始移动时触发,可用于实现自定义选区效果SelectionMoveEnd
: 在选区结束移动时触发,提供最终的选区范围SelectionMoving
: 在选区移动过程中触发,可用于实时更新UIDragOver
: 在拖动经过单元格时触发Drop
: 在放置选区内容时触发
-
单元格事件
CellClicked
: 在单元格被点击时触发,提供单元格的位置和内容信息CellHover
: 在鼠标悬停在单元格上时触发,可用于显示提示信息CellPointerDown
: 在单元格上按下指针时触发,用于开始拖动或选择操作CellPointerUp
: 在单元格上释放指针时触发,用于完成操作CellPointerMove
: 在指针在单元格上移动时触发
-
表格事件
SheetValueChanged
: 在表格值改变时触发,提供变更的范围和新值SheetZoomChanged
: 在缩放级别改变时触发,可获取新的缩放比例SheetSkeletonChanged
: 在表格结构改变时触发,如行列的插入删除BeforeSheetEditStart
: 在单元格开始编辑前触发,可用于控制是否允许编辑SheetEditStarted
: 在单元格开始编辑时触发,可用于自定义编辑器行为BeforeSheetEditEnd
: 在单元格结束编辑前触发,可用于验证编辑内容SheetEditEnded
: 在单元格结束编辑时触发,提供编辑后的值SheetEditChanging
: 在单元格编辑过程中触发,可用于实时验证或格式化Scroll
: 在表格滚动时触发,提供滚动位置信息CrosshairHighlightColorChanged
: 在十字光标高亮颜色改变时触发CrosshairHighlightEnabledChanged
: 在启用或禁用十字光标高亮时触发
-
表头事件
RowHeaderClick
: 在行表头被点击时触发RowHeaderHover
: 在鼠标悬停在行表头时触发ColumnHeaderClick
: 在列表头被点击时触发ColumnHeaderHover
: 在鼠标悬停在列表头时触发
-
数据验证事件
BeforeSheetDataValidationAdd
: 在添加数据验证规则前触发SheetDataValidationChanged
: 在数据验证规则改变时触发SheetDataValidatorStatusChanged
: 在数据验证状态改变时触发
-
评论事件
BeforeCommentAdd
: 在添加评论前触发CommentAdded
: 在评论添加后触发BeforeCommentUpdate
: 在更新评论前触发CommentUpdated
: 在评论更新后触发BeforeCommentDeleted
: 在删除评论前触发CommentDeleted
: 在评论删除后触发
-
数据处理事件
SheetBeforeRangeSort
: 在范围排序前触发SheetRangeSorted
: 在范围排序后触发SheetBeforeRangeFilter
: 在范围筛选前触发SheetRangeFiltered
: 在范围筛选后触发BeforePivotTableAdd
: 在添加数据透视表前触发PivotTableAdded
: 在数据透视表添加后触发
使用事件
你可以使用 univerAPI
的 addEvent
方法来监听事件。以下是基本模式:
univerAPI.addEvent(univerAPI.Event.事件名称, (params) => {
// 处理事件参数
});
示例:监听单元格点击
univerAPI.addEvent(univerAPI.Event.CellClicked, (params) => {
const { worksheet, workbook, row, column, value } = params;
console.log(`单元格被点击,行:${row},列:${column}`);
});
示例:监控剪贴板操作
univerAPI.addEvent(univerAPI.Event.BeforeClipboardPaste, (params) => {
const { text, html } = params;
// 如果要取消粘贴操作
params.cancel = true;
});
示例:跟踪选区变化
univerAPI.addEvent(univerAPI.Event.SelectionChanged, (params) => {
const { worksheet, workbook, selections } = params;
console.log('选区已改变:', selections);
});
取消事件监听
事件监听器会返回一个 IDisposable
对象,可以用它来在不再需要时移除监听器:
const disposable = univerAPI.addEvent(univerAPI.Event.SheetValueChanged, (params) => {
// 处理值变化
});
// 之后,当你想要移除监听器时:
disposable.dispose();
最佳实践
- 当不再需要时,务必销毁事件监听器以防止内存泄漏
- 根据具体用例选择合适的事件 - 优先使用特定事件而不是通用事件
- 保持事件处理程序轻量化以维持性能
- 当需要阻止默认行为时,考虑使用
BeforeClipboardChange
等”前置”事件
撤销和重做
撤销
await univerAPI.undo();
重做
await univerAPI.redo();
系统剪切板
从 0.2.12
开始,使用 FUniver.copy()
和 FUniver.paste()
方法读取和写入系统剪贴板。
复制与粘贴依赖浏览器原生 API ,当环境条件或者权限 不满足时复制与粘贴功能将无法工作。
示例: Sheet 区域的复制与粘贴 0.2.12+
// 防止在控制台中执行复制粘贴代码时因失去焦点而失败,
// 此示例监听单元格点击事件并执行复制粘贴代码。
univerAPI.addEvent(univerAPI.Event.CellClicked, async (params) => {
const fWorkbook = univerAPI.getActiveWorkbook();
const fWorksheet = fWorkbook.getActiveSheet();
// 将范围 A1:B2 复制到剪贴板
const fRange = fWorksheet.getRange('A1:B2');
fRange.activate().setValues([
[1, 2],
[3, 4]
]);
await univerAPI.copy();
// 将复制的内容粘贴到范围 C1:D2
const fRange2 = fWorksheet.getRange('C1');
fRange2.activate();
await univerAPI.paste();
// 检查粘贴的内容
console.log(fWorksheet.getRange('C1:D2').getValues()); // [[1, 2], [3, 4]]
});
0.2.12
之前的版本,可以使用命令系统实现调用剪贴板功能:
import { CopyCommand, PasteCommand } from '@univerjs/ui';
// 复制
univerAPI.executeCommand(CopyCommand.id)
// 粘贴
univerAPI.executeCommand(PasteCommand.id)
UI
请参考下面文档来拓展 Univer 的 UI :
WebSocket 0.6.5+
Facade 提供了一个便捷的 API createSocket
来创建 WebSocket,传入一个 URL 即可。
然后可以监听 open、message、close、error 事件,以及主动发送消息 send 方法和主动关闭 close 方法。
使用 Presets 安装,可以直接使用 univerAPI.createSocket(url)
。
使用手动组合安装,需要安装 @univerjs/network
依赖包,并注册 UniverNetworkPlugin
插件。
// ... 省略其他插件引入
import { UniverNetworkPlugin } from '@univerjs/network';
// ... 省略其他 facade 引入
import '@univerjs/network/facade';
// ... 省略其他插件注册
univer.registerPlugin(UniverNetworkPlugin);
以下是一个简单的示例:
// URL 换成你自己 WebSocket 服务的地址
const ws = univerAPI.createSocket("ws://47.100.177.253:8449/ws");
ws.open$.subscribe(() => {
console.log('websocket opened')
ws.send('hello')
})
ws.message$.subscribe((message) => {
console.log('websocket message', message)
const content = JSON.parse(message.data).content
if (!content.includes('command')) {
return
}
const commandInfo = JSON.parse(content);
const { command, options } = commandInfo;
const { id, params } = command;
// 接受到协同数据,本地落盘
univerAPI.executeCommand(id, params, options)
});
ws.close$.subscribe(() => {
console.log("websocket closed");
});
ws.error$.subscribe((error) => {
console.log("websocket error", error);
});
univerAPI.onCommandExecuted((command, options) => {
// 仅同步本地 mutation
if (command.type !== 2 || options?.fromCollab || options?.onlyLocal || command.id === 'doc.mutation.rich-text-editing') {
return;
}
const commandInfo = JSON.stringify({ command, options: { fromCollab: true } })
ws.send(commandInfo);
})
注意:启动 Univer 的时候要确保有 unitID ,不指定 unitID 的话无法协同。
注册公式
使用 Facade API,可以方便快速的在当前 Univer 实例中注册自定义公式。
如下案例所示,使用 registerFunction
将一个 CUSTOMSUM
公式所需要的算法、名称、描述一次性注册到公式插件,执行之后就可以使用公式了。在任一空白单元格输入 =CUSTOMSUM
可以看到提示。
// 注册公式支持 lambda 函数
const formulaEngine = univerAPI.getFormula();
formulaEngine.registerFunction(
'CUSTOMSUM',
(...variants) => {
let sum = 0;
const last = variants[variants.length - 1];
if (last.isLambda && last.isLambda()) {
variants.pop();
const variantsList = variants.map((variant) => Array.isArray(variant) ? variant[0][0]: variant);
sum += last.executeCustom(...variantsList).getValue();
}
for (const variant of variants) {
sum += Number(variant) || 0;
}
return sum;
},
'求参数的和'
);
// Use the function in a cell
const fWorkbook = univerAPI.getActiveWorkbook();
const fWorksheet = fWorkbook.getActiveSheet();
const cellA1 = fWorksheet.getRange('A1');
cellA1.setValue(1);
const cellA2 = fWorksheet.getRange('A2');
cellA2.setValue(2);
const cellA3 = fWorksheet.getRange('A3');
cellA3.setValue({ f: '=CUSTOMSUM(A1,A2,LAMBDA(x,y,x*y))' });
// A3 will display: 5
formulaEngine.calculationEnd((functionsExecutedState) => {
if (functionsExecutedState === 3) {
console.log(cellA3.getValue()); // 5
}
})
如果需要卸载所注册的公式,可以调用 dispose
方法。
const functionDisposable = formulaEngine.registerFunction(
// calculate
);
// 卸载所注册的公式
functionDisposable.dispose();
如果想要提供更完善的国际化内容和描述,还可以配置 locales
和 description
字段。如下所示。
formulaEngine.registerFunction(
'CUSTOMSUM',
(...variants) => {
let sum = 0;
const last = variants[variants.length - 1];
if (last.isLambda && last.isLambda()) {
variants.pop();
const variantsList = variants.map((variant) => Array.isArray(variant) ? variant[0][0]: variant);
sum += last.executeCustom(...variantsList).getValue();
}
for (const variant of variants) {
sum += Number(variant) || 0;
}
return sum;
},
{
description: {
functionName: 'CUSTOMSUM',
description: 'formulaCustom.CUSTOMSUM.description',
abstract: 'formulaCustom.CUSTOMSUM.abstract',
functionParameter: [
{
name: 'formulaCustom.CUSTOMSUM.functionParameter.number1.name',
detail: 'formulaCustom.CUSTOMSUM.functionParameter.number1.detail',
example: 'A1:A20',
require: 1,
repeat: 0,
},
{
name: 'formulaCustom.CUSTOMSUM.functionParameter.number2.name',
detail: 'formulaCustom.CUSTOMSUM.functionParameter.number2.detail',
example: 'B2:B10',
require: 0,
repeat: 1,
},
],
},
locales: {
'zhCN': {
formulaCustom: {
CUSTOMSUM: {
description: '将单个值、单元格引用或是区域相加,或者将三者的组合相加。',
abstract: '求参数的和',
functionParameter: {
number1: {
name: '数值1',
detail: '要相加的第一个数字。 该数字可以是 4 之类的数字,B6 之类的单元格引用或 B2:B8 之类的单元格范围。',
},
number2: {
name: '数值2',
detail: '这是要相加的第二个数字。 可以按照这种方式最多指定 255 个数字。',
},
},
},
},
},
'enUS': {
formulaCustom: {
CUSTOMSUM: {
description: `You can add individual values, cell references or ranges or a mix of all three.`,
abstract: `Adds its arguments`,
functionParameter: {
number1: {
name: 'number1',
detail: 'The first number you want to add. The number can be like 4, a cell reference like B6, or a cell range like B2:B8.',
},
number2: {
name: 'number2',
detail: 'This is the second number you want to add. You can specify up to 255 numbers in this way.',
},
},
},
},
},
},
},
);
说明
func
编写计算公式的具体算法和名称映射。入参为使用公式时用户输入的内容,可能为数字、字符串、布尔值,或者一个数组。description
设置自定义公式的描述。locales
下可以设置多种语言,命名规则参考 LocaleType 。可以在functionList
下添加多个公式的翻译。详细的字段说明请参考 如何在 Formula Engine 中添加公式 的部分。
如果想复用公式系统提供的算法,增强公式算法的能力,可以通过插件配置的方式注册自定义公式,详细教程请参考 自定义公式。
枚举类型 API
Facade API 提供了一些常用的枚举类型,可以在开发过程中使用。例如:
console.log(univerAPI.Enum);
console.log(univerAPI.Enum.UniverInstanceType.UNIVER_SHEET);
console.log(univerAPI.Enum.LifecycleStages.Rendered);
工具方法 API
Facade API 提供了一些常用的工具方法,可以在开发过程中使用。例如:
console.log(univerAPI.Util);
console.log(univerAPI.Util.tools.chatAtABC(10));
console.log(univerAPI.Util.tools.ABCatNum('K'));