[]
在协同场景中,撤销(Undo)和重做(Redo)操作同样得到支持。本文档将详细介绍相关机制。
由于协同中的所有修改都需要与其他客户端和服务器同步,撤销操作的结果也必须同步。这意味着撤销操作的结果以操作(Ops)的形式存储。因此,撤销栈中存储的是操作,而无法用操作表示的行为(如选择状态)则无法被撤销。
在协同环境中,每个用户可以进行修改并接收他人的修改。撤销操作仅应撤销用户自己的操作,而不包括他人的操作。
由于撤销仅影响用户自己的操作,如果在用户的操作被撤销前接收到了其他客户端的操作,则用户的撤销可能需要与这些操作进行转换。
例如:
Alice 将单元格 (1,1) 的值从 "old" 改为 "new",即 sheet.setValue(1,1,'new');
Bob 在位置 0 插入一行,即 sheet.addRows(0,1);
Alice 执行撤销操作,此时修改的是单元格 (2,1) 而非 (1,1),即 sheet.setValue(2,1, 'old');
Alice 执行重做操作,同样修改的是 (2,1) 而非 (1,1),即 sheet.setValue(2,1,'new');
从这个例子可以看出,撤销和重做操作的结果可能与初始执行不同。撤销和重做操作执行的是与其他客户端操作转换后的结果。
协同撤销是协同场景特有的功能。在非协同场景中,撤销仍使用非协同的撤销方法。
撤销的使用方式没有变化,仍需通过命令执行,但存在一些差异。以下面的颜色修改命令为例:

命令中的代码分为两部分:
非协同场景:
do:执行代码段 1
undo:执行代码段 2
redo:执行代码段 1
协同场景:
由于撤销/重做操作执行的是操作(Ops),而非命令代码,因此:
do:执行代码段 1
undo:执行其他代码
redo:执行其他代码
虽然命令的执行逻辑发生了变化,但协同撤销与命令兼容。用户可以直接使用上述方式编写的命令支持协同撤销,无需修改。
由于撤销和重做操作不执行命令内部的逻辑,因此之前在命令中触发的事件在协同撤销时将不再触发。为了给用户提供处理其场景的适当时机(例如更新自己的 UI),新增了四个事件来提供协同撤销的时机:
interface IChangeSet {
ops: IOpComponent[];
}
workbook.bind(GC.Spread.Sheets.Events.CollaborationStartUndo, (sender, args: { changeSet: IChangeSet }) => {
const changeSet = args.changeSet;
console.log('undo changeSet is:', changeSet);
});
workbook.bind(GC.Spread.Sheets.Events.CollaborationEndUndo, (sender, args: { changeSet: IChangeSet }) => {
const changeSet = args.changeSet;
console.log('undo changeSet is:', changeSet);
});
workbook.bind(GC.Spread.Sheets.Events.CollaborationStartRedo, (sender, args: { changeSet: IChangeSet }) => {
const changeSet = args.changeSet;
console.log('redo changeSet is:', changeSet);
});
workbook.bind(GC.Spread.Sheets.Events.CollaborationEndRedo, (sender, args: { changeSet: IChangeSet }) => {
const changeSet = args.changeSet;
console.log('redo changeSet is:', changeSet);
});协同撤销栈存储的是操作(Ops),Op 的 src 与 seq 字段用于区分不同客户端提交的操作,是"仅撤销自己操作"这一行为的基础。关于 Op 的字段定义与版本管理机制,参见 底层协同框架 中的 操作(Op)。