historyService方法
reset
详情:
重置全部历史记录:清空
state.steps下的页面 / 代码块 / 数据源 / 扩展类型全部栈(保留已注册的扩展类型键)。
resetState
详情:
同
reset,清空state.steps下全部栈。
registerStepType
参数:
{string} stepType自定义历史类型标识(勿与内置page/codeBlock/dataSource重名){Object} options可选{string} eventpush / undo / redo 后派发的事件名;缺省为${stepType}-history-change{string} name历史面板中的展示名称(tab / 分组标题等);缺省回退到 stepType 本身
详情:
注册一个扩展历史类型,使其可与内置
page/codeBlock/dataSource一样走统一的push/undo/redo(按 id 分栈、独立 undo/redo)。 注册后该类型的栈存放在historyService.state.steps[stepType],展示名称存放在historyService.state.stepNames[stepType]。
getStepName
参数:
{HistoryStepType} stepType历史类型
返回:
{string}该类型的展示名称(用于历史面板 tab / 分组标题等);未登记时回退到 stepType 本身
详情:
读取指定历史类型的展示名称。内置
page/codeBlock/dataSource默认分别为「页面 / 代码块 / 数据源」。
setStepName
参数:
{HistoryStepType} stepType历史类型{string} name展示名称
详情:
设置指定历史类型的展示名称(写入
historyService.state.stepNames,历史面板会响应式刷新)。 内置page/codeBlock/dataSource也可在此覆盖默认中文名。
push
参数:
{HistoryStepType} stepType历史类型,内置'page'/'codeBlock'/'dataSource',并支持通过registerStepType扩展{StepValue | BaseStepValue} step已构造好的历史记录(缺省自动补全uuid/timestamp){Id} id必填;目标栈 id(page为 pageId,其余类型为对应资源 id)
查看 StepValue 及关联类型定义
ts/** * 页面节点历史记录条目(`diff` 内容为 {@link MNode})。结构已与代码块 / 数据源统一收敛到 * {@link BaseStepValue}:关联 id 见 `data.id`,选区等上下文见 `extra`。 */ export type StepValue = BaseStepValue<MNode, StepExtra>;ts/** * 历史记录条目公共字段,被 {@link StepValue} / {@link CodeBlockStepValue} / {@link DataSourceStepValue} 复用。 * * 泛型 `T` 为 `diff` 中变化内容的快照类型(页面节点 `MNode` / 代码块 `CodeBlockContent` / 数据源 `DataSourceSchema`)。 */ export interface BaseStepValue<T = unknown, U extends Record<string, any> = {}> { /** * 历史记录唯一标识(uuid)。入栈时自动写入(若调用方未指定), * 用于精确定位 / 引用某一条历史记录(如 revert、埋点、跨端同步等)。 * 注意与 `data.id`(关联的页面 / 代码块 / 数据源 id)区分。 */ uuid: string; /** * 关联目标信息:`id` 为关联的页面 / 代码块 / 数据源等资源 id(也是历史栈的分组 key), * `name` 为展示名。所有历史类型统一携带。 */ data: { name: string; id: Id }; /** 操作类型:新增 / 删除 / 更新(三类历史记录统一携带)。 */ opType: HistoryOpType; /** * 本次变更的内容(统一 diff 表达),每项见 {@link StepDiffItem}。 * 页面节点(add/remove 多节点、update 多节点)会有多项,代码块 / 数据源通常只有一项。 */ diff: StepDiffItem<T>[]; /** * 调用方可选传入的人类可读描述(如「调整按钮颜色」),用于历史面板展示。 * 不影响 undo/redo 行为;缺省时面板会根据节点 / propPath 自动生成描述。 */ historyDescription?: string; /** * 操作途径:标记本次变更由哪条交互入口触发,取值见 {@link HistoryOpSource} * (画布 / 树面板 / 组件面板 / 配置面板 / 源码编辑器 / 右键菜单 / 工具栏 / 快捷键 / 回滚 / 接口 等)。 * 仅用于历史面板展示与业务埋点,不影响 undo/redo 行为;缺省时面板视为「未知」。 */ source?: HistoryOpSource; /** * 入栈时间戳(毫秒)。入栈时自动写入(若调用方未指定),仅用于历史面板展示。 */ timestamp?: number; /** * 是否为「已保存」记录:DSL 落库(如保存到后端 / 本地)时由 historyService.markSaved 标记。 * 同一栈内任意时刻最多只有一条记录为 true;从 IndexedDB 恢复时游标会被定位到最近一条已保存记录之后。 */ saved?: boolean; /** * 是否为「整体设置 root」(set root)产生的记录(由 {@link Editor.pushRootDiffHistory} 写入)。 * 用于「连续 set root 合并」:当某页栈最新一条已是 root 记录时,下一条 set root 会替换它而非新增, * 避免源码反复保存 / 外部重设 DSL 时堆积多条 root 记录。 */ rootStep?: boolean; /** 操作人 */ operator?: string; /** 扩展信息 */ extra?: U; }ts/** * 历史记录的扩展上下文({@link BaseStepValue.extra})。 * 内置字段供 `page` 类型在撤销 / 重做时恢复选区与受影响节点;扩展类型可自由附加其它键。 */ export interface StepExtra { /** 操作前选中的节点 ID,用于撤销后恢复选择状态(page 类型) */ selectedBefore?: Id[]; /** 操作后选中的节点 ID,用于重做后恢复选择状态(page 类型) */ selectedAfter?: Id[]; /** 本次操作涉及的节点 id 集合(page 类型) */ modifiedNodeIds?: Map<Id, Id>; [key: string]: any; }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;tsexport interface ChangeRecord { propPath?: string; value: any; }返回:
{StepValue | BaseStepValue | null}入栈失败(未传 / 无效 id)时返回null
详情:
添加一条历史记录。统一入口,所有类型(
page/codeBlock/dataSource/ 扩展)行为完全一致:按stepType选择目标栈类型、按id(必填)选择具体栈,按需建栈后入栈,并派发对应的历史变更事件(page为change,其余如code-block-history-change/data-source-history-change),回调签名统一为(id, step)。跨页 / 跨资源操作(如把节点搬到其它页)必须显式传入目标 id。
codeBlock/dataSource的 step 通常由createStackStep等工具按oldValue/newValue构造后传入。TIP
opType: 'update'的每个 diff 项上可携带changeRecords,用于撤销 / 重做时仅按propPath局部更新对应字段,避免整节点替换冲掉同节点上的其它无关变更;不带changeRecords时退化为整节点替换(如sort/moveLayer/ 拖动等纯快照场景)。step上的historyDescription/source仅用于历史面板展示与埋点,不影响 undo/redo 行为。入栈时会为每条记录自动生成唯一标识
uuid(调用方未指定时),可用于精确引用 / 定位某一条历史记录。 若需要在执行 DSL 操作后拿到本次写入记录的uuid,可使用 editorService / dataSourceService / codeBlockService 提供的*AndGetHistoryId方法,参见 editorService 历史记录 uuid 与 *AndGetHistoryId。
undo
参数:
{HistoryStepType} stepType历史类型{Id} id必填;目标栈 id(page为 pageId,其余类型为对应资源 id)
返回:
{StepValue | BaseStepValue | null}
详情:
撤销指定历史栈的最近一次变更。所有类型行为一致:按
stepType+id定位栈,不会越过 index 0 的 initial 基线(所有类型同等适用,见setMarker),仅在确有可撤销 step 时派发对应的历史变更事件(page为change,回调签名(id, step))。page类型opType: 'update'时,若 diff 项的changeRecords存在,会按propPath从oldSchema取值做局部回滚;否则用oldSchema整节点替换。codeBlock/dataSource拿到 step 后由调用方写回对应 service(本方法不会自动回放)。
redo
参数:
{HistoryStepType} stepType历史类型{Id} id必填;目标栈 id(page为 pageId,其余类型为对应资源 id)
返回:
{StepValue | BaseStepValue | null}
详情:
恢复指定历史栈到下一步,语义与
undo对称。page类型opType: 'update'时,若 diff 项的changeRecords存在,会按propPath从newSchema取值做局部重做;否则用newSchema整节点替换。
canUndo
参数:
{HistoryStepType} stepType历史类型{Id} id可选;目标栈 id;缺省 / 无效时返回false
返回:
{boolean}
详情:
指定历史栈当前是否可撤销(游标高于 index 0 的 initial 基线底线)。适用于所有类型(
page/codeBlock/dataSource/ 扩展)。
canRedo
参数:
{HistoryStepType} stepType历史类型{Id} id可选;目标栈 id;缺省 / 无效时返回false
返回:
{boolean}
详情:
指定历史栈当前是否可重做。适用于所有类型(
page/codeBlock/dataSource/ 扩展)。
setMarker
参数:
{HistoryStepType} stepType历史类型{Id} id目标栈 id(page为 pageId,其余类型为对应资源 id){Object} options可选:name/description/source,用于基线的展示信息
返回:
{StepValue | null}已存在基线时返回原基线;栈非空(无基线)或 id 无效时返回null
详情:
为指定历史栈种入一条
opType: 'initial'的「初始基线」记录,作为该栈 index 0 的固定底线:它是真实入栈并随栈持久化的 step,但被钉为撤销 / 回滚的下限,undo/goto/revert都不会越过它。所有类型(含扩展类型)均可设置基线,仅当目标栈为空时种入。
getMarker
参数:
{HistoryStepType} stepType历史类型{Id} id可选;目标栈 id;缺省 / 无效时返回undefined
返回:
{StepValue | undefined}
详情:
读取指定历史栈的初始基线 step(栈 index 0 且
opType: 'initial'),不存在时返回undefined。
markSaved
参数:
{HistoryStepType} stepType历史类型,内置另有'codeBlock'/'dataSource',并支持扩展(仅在传入id时生效){Id} id可选;目标栈 id。缺省表示标记全部类型、全部栈
详情:
标记历史记录为「已保存」(把对应栈当前游标所在的记录标记为
saved = true)。统一入口:- 缺省
id:标记「整份 DSL 已保存」——把所有类型、所有栈当前游标所在的记录都标记为已保存(此时stepType不生效),触发mark-saved事件且{ kind: 'all' }。通常在 DSL 整体落库成功后调用。 - 传入
id:仅标记stepType下该 id 对应的栈,触发mark-saved事件且{ kind: stepType, id }(如{ kind: 'page', id }/{ kind: 'codeBlock', id }/{ kind: 'dataSource', id })。
同一栈内任意时刻最多保留一条已保存记录(标记前会清除该栈内全部旧标记);某个栈处于「全部已撤销」(cursor 为 0)时不会留下已保存记录,从 IndexedDB 恢复时其游标会回到 0。配合
restoreFromIndexedDB把游标恢复到此处。- 缺省
clear
参数:
{HistoryStepType} stepType历史类型,内置另有'codeBlock'/'dataSource',并支持扩展{Id} id可选;目标栈 id。缺省表示清空stepType下的全部栈
详情:
清空历史记录栈。统一入口,所有类型(page / codeBlock / dataSource / 扩展)行为一致:
- 传入
id:仅清空stepType下该 id 对应的栈; - 缺省
id:清空stepType下的全部栈。
仅删除撤销/重做记录,不会改动 DSL / 代码块 / 数据源本身。清空时会保留各栈原有的 initial 基线(文案 / 来源,见
setMarker),无基线时清空成空栈。清空后触发clear事件(签名(id, stepType))。- 传入
saveToIndexedDB
参数:
{HistoryPersistOptions} options可选
查看 HistoryPersistOptions / PersistedHistoryState 类型定义
ts/** historyService 持久化相关 API 的可选配置。 */ export interface HistoryPersistOptions { /** IndexedDB 数据库名,默认 `tmagic-editor`(最终库名会拼上当前 DSL app id)。 */ dbName?: string; /** objectStore 名,默认 `history`。 */ storeName?: string; /** 记录 key,用于区分不同活动页 / 项目,默认 `default`。 */ key?: IDBValidKey; /** * 显式指定用于库名隔离的 DSL app id。 * 缺省时回退到当前 editorService 的 `root.id`;在「先恢复历史再 set root」场景下 root 尚未设置, * 需由调用方(如从待加载 DSL 取 id)显式传入,否则会读 / 写到未按 app 隔离的默认库。 */ appId?: Id; }ts/** * 历史记录的可持久化快照。由 historyService.saveToIndexedDB 写入 IndexedDB, * 再由 historyService.restoreFromIndexedDB 读出并重建各 UndoRedo 栈。 */ export interface PersistedHistoryState { /** 快照结构版本号,便于后续兼容升级。 */ version: number; /** * 全部历史栈的序列化快照,按「类型 -> id」两级分组,与 {@link HistorySteps} 对应。 * 内置 `page` / `codeBlock` / `dataSource`,并包含业务注册的扩展类型。 */ steps: { page: Record<Id, SerializedUndoRedo<StepValue>>; codeBlock: Record<Id, SerializedUndoRedo<CodeBlockStepValue>>; dataSource: Record<Id, SerializedUndoRedo<DataSourceStepValue>>; [stepType: string]: Record<Id, SerializedUndoRedo<any>>; }; /** 保存时间戳(毫秒)。 */ savedAt: number; }ts/** * UndoRedo 栈的可序列化快照,用于持久化(如写入 IndexedDB)后再还原。 */ export interface SerializedUndoRedo<T = any> { /** 栈内全部元素(按时间正序,索引 0 为最早一步)。 */ elementList: T[]; /** 游标位置(已应用步骤数量)。 */ listCursor: number; /** 栈容量上限。 */ listMaxSize: number; }返回:
{Promise<PersistedHistoryState>}写入成功的快照对象
详情:
把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)连同各自游标、容量序列化后写入本地 IndexedDB。
- 最终库名为
${dbName}-${当前 DSL app id},按应用隔离; key用于在同一 store 下区分不同记录,缺省为default;- 历史记录里可能包含函数(代码块内容 / 节点事件等),内部使用
serialize-javascript序列化为字符串后写入,恢复时再用parseDSL还原,因此可安全持久化函数 /Map等; - 不支持 IndexedDB 的环境(如 SSR)会 reject。
写入成功后触发
save-to-indexed-db事件。WARNING
beforeunload/pagehide阶段浏览器不会等待异步 IndexedDB 事务提交,单纯依赖卸载时写入可能丢失最近一次编辑。建议在历史变更时(防抖)即调用本方法持久化,确保刷新后能完整恢复。- 最终库名为
restoreFromIndexedDB
参数:
{HistoryPersistOptions} options可选
返回:
{Promise<PersistedHistoryState | null>}找不到记录时返回null
详情:
从本地 IndexedDB 读取此前保存的历史快照并重建全部撤销/重做栈。
- 每个栈都会按
listMaxSize裁剪并还原游标; - 若某个栈存在已保存记录(见
markSaved),其游标会被定位到「最近一条已保存记录」之后,使恢复后的状态与落库的 DSL 对齐; - 会整体覆盖当前内存中的历史状态(活动页由
editorService维护,不在此恢复); - 找不到对应记录时返回
null且不改动当前状态;不支持 IndexedDB 的环境会 reject。
成功后触发
restore-from-indexed-db事件。- 每个栈都会按
findStepLocationByUuid
参数:
{HistoryStepType} stepType历史类型{string} uuid目标历史记录的 uuid{Id} id可选;目标栈 id
返回:
{ { id: Id; index: number } | null }找到时返回所属栈 id 与步骤索引;找不到时返回null
详情:
按历史记录 uuid 在指定历史类型的栈中查找其所属 id 与索引,统一入口。
- 传入
id:仅在该 id 对应的单个栈中查找(如页面历史按活动页查看,传入 pageId); - 缺省
id:遍历该类型下全部栈查找(代码块 / 数据源等按全部资源分桶的场景)。
供「按 uuid 回滚」等需要把 uuid 映射回
(id, index)的场景使用,如 editorService.revertPageStepById / codeBlockService.revertById / dataSourceService.revertById 内部均通过本方法定位步骤。- 传入
destroy
详情:
销毁