historyService事件
page-change
详情: 页面切换
事件回调函数:
(undoRedo: UndoRedo) => void查看 UndoRedo 类定义
tsexport class UndoRedo<T = any> { /** * 由 {@link UndoRedo.serialize} 产出的快照重建一个 UndoRedo 实例。 * 游标会被夹紧到 [0, length] 区间,避免脏数据导致越界。 * * @param options.isSavedStep 可选谓词:若提供,则把游标定位到「最近一条满足该谓词的记录」之后 * (即恢复到最近一个已保存点);找不到匹配记录时退回快照中的原游标。 */ public static fromSerialized<T = any>( data: SerializedUndoRedo<T>, options: { isSavedStep?: (element: T) => boolean } = {}, ): UndoRedo<T> { const undoRedo = new UndoRedo<T>(data.listMaxSize); const list = Array.isArray(data.elementList) ? data.elementList.map((item) => cloneDeep(item)) : []; let cursor = Number.isFinite(data.listCursor) ? data.listCursor : list.length; // 本地数据同样遵循容量上限:超出时裁掉最旧的记录(与 pushElement 的 shift 行为一致),并同步回退游标。 const overflow = list.length - undoRedo.listMaxSize; if (overflow > 0) { list.splice(0, overflow); cursor -= overflow; } // 若指定了「已保存」谓词,则把游标移动到最近一条已保存记录之后;在裁剪后的 list 上查找以保证索引正确。 if (options.isSavedStep) { for (let i = list.length - 1; i >= 0; i--) { if (options.isSavedStep(list[i])) { cursor = i + 1; break; } } } undoRedo.elementList = list; undoRedo.listCursor = Math.max(0, Math.min(cursor, list.length)); return undoRedo; } private elementList: T[]; private listCursor: number; private listMaxSize: number; constructor(listMaxSize = 100) { const minListMaxSize = 2; this.elementList = []; this.listCursor = 0; this.listMaxSize = listMaxSize > minListMaxSize ? listMaxSize : minListMaxSize; } /** * 导出当前栈的可序列化快照(深克隆,避免外部改动污染内部状态)。 * 配合 {@link UndoRedo.fromSerialized} 可在持久化后完整还原撤销/重做栈。 */ public serialize(): SerializedUndoRedo<T> { return { elementList: this.elementList.map((item) => cloneDeep(item)), listCursor: this.listCursor, listMaxSize: this.listMaxSize, }; } public pushElement(element: T): void { // 新元素进来时,把游标之外的元素全部丢弃,并把新元素放进来 this.elementList.splice(this.listCursor, this.elementList.length - this.listCursor, cloneDeep(element)); this.listCursor += 1; // 如果list中的元素超过maxSize,则移除第一个元素 if (this.elementList.length > this.listMaxSize) { this.elementList.shift(); this.listCursor -= 1; } } public canUndo(): boolean { return this.listCursor > 0; } /** 返回被撤销的操作 */ public undo(): T | null { if (!this.canUndo()) { return null; } this.listCursor -= 1; return cloneDeep(this.elementList[this.listCursor]); } public canRedo() { return this.elementList.length > this.listCursor; } /** 返回被重做的操作 */ public redo(): T | null { if (!this.canRedo()) { return null; } const element = cloneDeep(this.elementList[this.listCursor]); this.listCursor += 1; return element; } public getCurrentElement(): T | null { if (this.listCursor < 1) { return null; } return cloneDeep(this.elementList[this.listCursor - 1]); } /** * 用 `element` 替换当前游标所在元素(cursor - 1),并丢弃其后的重做尾部(与 {@link pushElement} 的丢尾一致), * 游标位置保持不变(元素数量不增)。cursor 为 0(无已应用元素)时不做任何操作并返回 false。 * 用于「连续同类记录合并」等就地替换最新一条的场景。 */ public replaceCurrentElement(element: T): boolean { if (this.listCursor < 1) return false; this.elementList.splice(this.listCursor - 1, this.elementList.length - (this.listCursor - 1), cloneDeep(element)); return true; } /** * 对当前游标所在元素(cursor - 1)做就地更新;cursor 为 0(全部已撤销)时不做任何操作。 * 用于给「当前步骤」打标记(如标记为已保存)等元数据写入场景。 */ public updateCurrentElement(updater: (element: T) => void): void { if (this.listCursor < 1) return; updater(this.elementList[this.listCursor - 1]); } /** 对栈内全部元素做就地更新。用于批量清理元数据(如清空所有元素的已保存标记)。 */ public updateElements(updater: (element: T, index: number) => void): void { this.elementList.forEach(updater); } /** * 返回栈内全部元素的浅克隆数组(按时间顺序,索引 0 为最早一步)。 * 仅用于历史面板等只读展示场景,不应直接修改返回值。 */ public getElementList(): T[] { return this.elementList.slice(); } /** * 当前游标位置:表示已应用的步骤数量。 * - cursor === 0 表示全部已撤销 * - cursor === length 表示已重做到末尾 * 历史面板用于区分"已应用 / 已撤销"两段。 */ public getCursor(): number { return this.listCursor; } /** 栈内总步数。 */ public getLength(): number { return this.elementList.length; } }
change
详情: 历史记录发生变化
事件回调函数:
(state: StepValue | null) => void查看 StepValue 及关联类型定义
tsexport interface StepValue extends BaseStepValue<MNode> { /** 页面信息 */ data: { name: string; id: Id }; /** 操作前选中的节点 ID,用于撤销后恢复选择状态 */ selectedBefore: Id[]; /** 操作后选中的节点 ID,用于重做后恢复选择状态 */ selectedAfter: Id[]; modifiedNodeIds: Map<Id, Id>; }ts/** * 历史记录操作类型: * - `add` / `remove` / `update`:普通可撤销/重做的节点变更; * - `initial`:页面「未修改的初始状态」基线(设置 root 时生成),作为页面栈 index 0 的固定底线 step。 * 该 step 不可被撤销/回滚(cursor 不会低于它),仅用于历史面板底部的初始行展示。 */ export type HistoryOpType = 'add' | 'remove' | 'update' | 'initial';ts/** * 历史记录的「操作途径」——标记本次变更由哪条交互入口触发,仅用于历史面板展示 / 业务埋点, * 不影响 undo/redo 行为。缺省(未传)时 UI 视为「未知」。 * * - `stage`:画布(拖拽 / 缩放 / 排序等舞台直接操作) * - `tree`:树形面板(图层 / 数据源 / 代码块等树形结构里的拖拽 / 菜单操作) * - `component-panel`:组件面板(左侧组件列表点击 / 拖拽新增组件) * - `props`:配置面板表单(属性表单字段编辑) * - `code`:源码编辑器(配置面板「源码」面板里直接编辑 JSON/代码后保存) * - `stage-contextmenu`:画布右键菜单(舞台上节点的右键上下文菜单) * - `tree-contextmenu`:树面板右键菜单(图层 / 数据源 / 代码块等树形列表上的右键上下文菜单) * - `toolbar`:工具栏菜单(顶部导航工具栏按钮) * - `shortcut`:键盘快捷键 * - `rollback`:历史回滚(历史面板里对某条历史「回滚」,反向应用为一条新记录,类 git revert) * - `api`:代码 / 接口调用(程序化触发) * - `ai`:AI 生成 / 智能助手触发的变更 * - `unknown`:未知来源 * * 通过 `(string & {})` 允许业务侧扩展自定义途径字符串,同时保留内置值的自动补全。 */ export type HistoryOpSource = | 'initial' | 'stage' | 'tree' | 'component-panel' | 'props' | 'code' | 'root-code' | 'stage-contextmenu' | 'tree-contextmenu' | 'toolbar' | 'shortcut' | 'rollback' | 'api' | 'ai' // 同步 | 'sync' | 'unknown' | (string & {});tsexport type Id = string | number;tsexport type MNode = MComponent | MContainer | MIteratorContainer | MPage | MApp | MPageFragment;TIP
当游标处于历史栈边界(已经无法继续撤销或重做)时,
UndoRedo.undo()/redo()返回null,对应change回调收到的state为null
code-block-history-change
详情: 代码块历史记录发生变化(
pushCodeBlock/undoCodeBlock/redoCodeBlock成功时触发)事件回调函数:
(codeBlockId: Id, step: CodeBlockStepValue) => void查看 CodeBlockStepValue 及关联类型定义
ts/** * 代码块历史记录条目。按 codeBlock.id 分组保存到 historyState.codeBlockState。 * 变更内容统一由 `diff`(单项)表达,每项见 {@link StepDiffItem}: * - 新增(opType 'add'):仅 `newSchema`(新内容); * - 更新(opType 'update'):`oldSchema` + `newSchema`,并可带 `changeRecords` 做局部更新; * - 删除(opType 'remove'):仅 `oldSchema`(删除前内容)。 */ export interface CodeBlockStepValue extends BaseStepValue<CodeBlockContent> { /** 关联的代码块 id */ id: Id; }ts/** * 历史记录的「操作途径」——标记本次变更由哪条交互入口触发,仅用于历史面板展示 / 业务埋点, * 不影响 undo/redo 行为。缺省(未传)时 UI 视为「未知」。 * * - `stage`:画布(拖拽 / 缩放 / 排序等舞台直接操作) * - `tree`:树形面板(图层 / 数据源 / 代码块等树形结构里的拖拽 / 菜单操作) * - `component-panel`:组件面板(左侧组件列表点击 / 拖拽新增组件) * - `props`:配置面板表单(属性表单字段编辑) * - `code`:源码编辑器(配置面板「源码」面板里直接编辑 JSON/代码后保存) * - `stage-contextmenu`:画布右键菜单(舞台上节点的右键上下文菜单) * - `tree-contextmenu`:树面板右键菜单(图层 / 数据源 / 代码块等树形列表上的右键上下文菜单) * - `toolbar`:工具栏菜单(顶部导航工具栏按钮) * - `shortcut`:键盘快捷键 * - `rollback`:历史回滚(历史面板里对某条历史「回滚」,反向应用为一条新记录,类 git revert) * - `api`:代码 / 接口调用(程序化触发) * - `ai`:AI 生成 / 智能助手触发的变更 * - `unknown`:未知来源 * * 通过 `(string & {})` 允许业务侧扩展自定义途径字符串,同时保留内置值的自动补全。 */ export type HistoryOpSource = | 'initial' | 'stage' | 'tree' | 'component-panel' | 'props' | 'code' | 'root-code' | 'stage-contextmenu' | 'tree-contextmenu' | 'toolbar' | 'shortcut' | 'rollback' | 'api' | 'ai' // 同步 | 'sync' | 'unknown' | (string & {});tsexport interface CodeBlockContent { /** 代码块名称 */ name: string; /** 代码块内容 */ content: ((...args: any[]) => any) | Function; /** 参数定义 */ params: CodeParam[] | []; /** 注释 */ desc?: string; /** 扩展字段 */ [propName: string]: any; }tsexport type Id = string | number;TIP
- 新增触发的 step 中
oldContent为null - 删除触发的 step 中
newContent为null undo/redo返回null(边界状态)时不会触发该事件
- 新增触发的 step 中
data-source-history-change
详情: 数据源历史记录发生变化(
pushDataSource/undoDataSource/redoDataSource成功时触发)事件回调函数:
(dataSourceId: Id, step: DataSourceStepValue) => void查看 DataSourceStepValue 及关联类型定义
ts/** * 数据源历史记录条目。按 dataSource.id 分组保存到 historyState.dataSourceState。 * 变更内容统一由 `diff`(单项)表达,每项见 {@link StepDiffItem}: * - 新增(opType 'add'):仅 `newSchema`(新 schema); * - 更新(opType 'update'):`oldSchema` + `newSchema`,并可带 `changeRecords` 做局部更新; * - 删除(opType 'remove'):仅 `oldSchema`(删除前 schema)。 */ export interface DataSourceStepValue extends BaseStepValue<DataSourceSchema> { /** 关联的数据源 id */ id: Id; }ts/** * 历史记录的「操作途径」——标记本次变更由哪条交互入口触发,仅用于历史面板展示 / 业务埋点, * 不影响 undo/redo 行为。缺省(未传)时 UI 视为「未知」。 * * - `stage`:画布(拖拽 / 缩放 / 排序等舞台直接操作) * - `tree`:树形面板(图层 / 数据源 / 代码块等树形结构里的拖拽 / 菜单操作) * - `component-panel`:组件面板(左侧组件列表点击 / 拖拽新增组件) * - `props`:配置面板表单(属性表单字段编辑) * - `code`:源码编辑器(配置面板「源码」面板里直接编辑 JSON/代码后保存) * - `stage-contextmenu`:画布右键菜单(舞台上节点的右键上下文菜单) * - `tree-contextmenu`:树面板右键菜单(图层 / 数据源 / 代码块等树形列表上的右键上下文菜单) * - `toolbar`:工具栏菜单(顶部导航工具栏按钮) * - `shortcut`:键盘快捷键 * - `rollback`:历史回滚(历史面板里对某条历史「回滚」,反向应用为一条新记录,类 git revert) * - `api`:代码 / 接口调用(程序化触发) * - `ai`:AI 生成 / 智能助手触发的变更 * - `unknown`:未知来源 * * 通过 `(string & {})` 允许业务侧扩展自定义途径字符串,同时保留内置值的自动补全。 */ export type HistoryOpSource = | 'initial' | 'stage' | 'tree' | 'component-panel' | 'props' | 'code' | 'root-code' | 'stage-contextmenu' | 'tree-contextmenu' | 'toolbar' | 'shortcut' | 'rollback' | 'api' | 'ai' // 同步 | 'sync' | 'unknown' | (string & {});tsexport type Id = string | number;TIP
- 新增触发的 step 中
oldSchema为null - 删除触发的 step 中
newSchema为null undo/redo返回null(边界状态)时不会触发该事件
- 新增触发的 step 中
mark-saved
详情: 调用
markSaved/markPageSaved/markCodeBlockSaved/markDataSourceSaved标记「已保存」记录时触发事件回调函数:
(payload: { kind: 'all' | 'page' | 'code-block' | 'data-source'; id?: Id }) => voidTIP
markSaved触发时kind为all,无id- 细粒度方法触发时
kind对应类别,id为目标页面 / 代码块 / 数据源 id
save-to-indexed-db
详情:
saveToIndexedDB把历史记录写入本地 IndexedDB 成功时触发事件回调函数:
(snapshot: PersistedHistoryState) => void查看 PersistedHistoryState 类型定义
ts/** * 历史记录的可持久化快照。由 historyService.saveToIndexedDB 写入 IndexedDB, * 再由 historyService.restoreFromIndexedDB 读出并重建各 UndoRedo 栈。 */ export interface PersistedHistoryState { /** 快照结构版本号,便于后续兼容升级。 */ version: number; /** 保存时的活动页 id。 */ pageId?: Id; /** 各页面历史栈的序列化快照,按 pageId 分组。 */ pageSteps: Record<Id, SerializedUndoRedo<StepValue>>; /** 各代码块历史栈的序列化快照,按 codeBlockId 分组。 */ codeBlockState: Record<Id, SerializedUndoRedo<CodeBlockStepValue>>; /** 各数据源历史栈的序列化快照,按 dataSourceId 分组。 */ dataSourceState: Record<Id, SerializedUndoRedo<DataSourceStepValue>>; /** 保存时间戳(毫秒)。 */ savedAt: number; }ts/** * UndoRedo 栈的可序列化快照,用于持久化(如写入 IndexedDB)后再还原。 */ export interface SerializedUndoRedo<T = any> { /** 栈内全部元素(按时间正序,索引 0 为最早一步)。 */ elementList: T[]; /** 游标位置(已应用步骤数量)。 */ listCursor: number; /** 栈容量上限。 */ listMaxSize: number; }
restore-from-indexed-db
详情:
restoreFromIndexedDB从本地 IndexedDB 读取并重建历史记录成功时触发(找不到记录时不触发)事件回调函数:
(snapshot: PersistedHistoryState) => void查看 PersistedHistoryState 类型定义
ts/** * 历史记录的可持久化快照。由 historyService.saveToIndexedDB 写入 IndexedDB, * 再由 historyService.restoreFromIndexedDB 读出并重建各 UndoRedo 栈。 */ export interface PersistedHistoryState { /** 快照结构版本号,便于后续兼容升级。 */ version: number; /** 保存时的活动页 id。 */ pageId?: Id; /** 各页面历史栈的序列化快照,按 pageId 分组。 */ pageSteps: Record<Id, SerializedUndoRedo<StepValue>>; /** 各代码块历史栈的序列化快照,按 codeBlockId 分组。 */ codeBlockState: Record<Id, SerializedUndoRedo<CodeBlockStepValue>>; /** 各数据源历史栈的序列化快照,按 dataSourceId 分组。 */ dataSourceState: Record<Id, SerializedUndoRedo<DataSourceStepValue>>; /** 保存时间戳(毫秒)。 */ savedAt: number; }