diff --git a/packages/editor/src/layouts/history-list/GroupRow.vue b/packages/editor/src/layouts/history-list/GroupRow.vue
index cfc6f0c0..cf661e8d 100644
--- a/packages/editor/src/layouts/history-list/GroupRow.vue
+++ b/packages/editor/src/layouts/history-list/GroupRow.vue
@@ -64,8 +64,9 @@
#{{ s.index + 1 }}
{{ s.desc }}
@@ -133,9 +134,16 @@ const props = withDefaults(
* 仅保留合并组头部的展开 / 收起能力,以及查看差异、回滚等其它入口。
*/
gotoEnabled?: boolean;
+ /**
+ * 是否支持「点击记录选中对应节点」。默认 false(仅页面 tab 启用,数据源 / 代码块无节点概念)。
+ * 为 true 时:点击单步组头部、子步条目或合并组头部都会发出 `select` 事件(携带对应 step 索引),
+ * 由上层解析出节点 id 并在画布选中;合并组头部同时保留展开 / 收起能力。
+ */
+ selectEnabled?: boolean;
}>(),
{
gotoEnabled: true,
+ selectEnabled: false,
},
);
@@ -165,6 +173,11 @@ const emit = defineEmits<{
* payload 为该 step 在所属栈中的索引。仅在单步组头部(headRevertable)或合并组的可回滚子步上触发。
*/
(_e: 'revert-step', _index: number): void;
+ /**
+ * 用户希望「选中该记录对应的节点」。payload 为该 step 在所属栈中的索引,
+ * 由上层据 index 取出 step、解析出节点 id 并在画布选中。仅在 `selectEnabled` 为 true 时触发。
+ */
+ (_e: 'select', _index: number): void;
}>();
/** 子步数大于 1 即为合并组:决定是否展示合并标记与可展开的子步列表。 */
@@ -174,21 +187,32 @@ const merged = computed(() => props.group.subSteps.length > 1);
const stepCount = computed(() => props.group.subSteps.length);
/**
- * 仅合并组头部可点击(切换展开 / 收起);
- * 单步组的跳转改由头部的「回到」按钮触发,整行不再可点击。
+ * 头部可点击的场景:
+ * - 合并组:点击切换展开 / 收起;
+ * - 开启 `selectEnabled`(页面 tab):点击选中对应节点。
+ * 单步组的跳转仍由头部的「回到」按钮触发。
*/
-const isHeadClickable = computed(() => merged.value);
+const isHeadClickable = computed(() => merged.value || props.selectEnabled);
const headTitle = computed(() => {
- if (merged.value) return props.expanded ? '点击收起子步' : '点击展开子步';
+ if (merged.value) {
+ const expandHint = props.expanded ? '点击收起子步' : '点击展开子步';
+ return props.selectEnabled ? `${expandHint}(并选中该节点)` : expandHint;
+ }
+ if (props.selectEnabled) return '点击选中该节点';
if (props.group.isCurrent) return '当前所在记录';
return '';
});
/**
- * 头部点击行为:仅合并组切换展开 / 收起;单步组不再响应整行点击。
+ * 头部点击行为:
+ * - 开启 selectEnabled 时,发出 select(携带组内首步 index,上层据此选中节点);
+ * - 合并组同时切换展开 / 收起。
*/
const onHeadClick = () => {
+ if (props.selectEnabled && props.group.subSteps.length) {
+ emit('select', props.group.subSteps[0].index);
+ }
if (merged.value) {
emit('toggle', props.group.key);
}
@@ -199,7 +223,14 @@ const onGotoClick = (index: number) => {
emit('goto', index);
};
+/** 点击子步行:开启 selectEnabled 时选中该子步对应的节点。 */
+const onSubStepClick = (index: number) => {
+ if (!props.selectEnabled) return;
+ emit('select', index);
+};
+
const subStepTitle = (s: { isCurrent?: boolean }) => {
+ if (props.selectEnabled) return '点击选中该节点';
if (s.isCurrent) return '当前所在记录';
return '';
};
diff --git a/packages/editor/src/layouts/history-list/HistoryListPanel.vue b/packages/editor/src/layouts/history-list/HistoryListPanel.vue
index 846b2c62..f9eaf535 100644
--- a/packages/editor/src/layouts/history-list/HistoryListPanel.vue
+++ b/packages/editor/src/layouts/history-list/HistoryListPanel.vue
@@ -29,6 +29,7 @@
@goto-initial="onPageGotoInitial"
@diff-step="onPageDiff"
@revert-step="onPageRevert"
+ @select="onPageSelect"
@clear="onPageClear"
/>
@@ -176,7 +177,8 @@ const extraTabs = inject('historyListExtraTabs', []);
/** label 支持字符串或函数,函数形式便于展示动态数量等内容。 */
const resolveTabLabel = (tab: HistoryListExtraTab) => (typeof tab.label === 'function' ? tab.label() : tab.label);
-const { editorService, dataSourceService, codeBlockService, historyService, propsService } = useServices();
+const { editorService, dataSourceService, codeBlockService, historyService, propsService, stageOverlayService } =
+ useServices();
/**
* 数据源 / 代码块功能可被业务方通过 `disabledDataSource` / `disabledCodeBlock` 禁用,
@@ -255,6 +257,29 @@ const onPageGoto = (index: number) => {
editorService.gotoPageStep(indexToCursor(index));
};
+/**
+ * 点击页面历史记录行:选中该记录对应的画布节点。
+ * - 从目标 step 的 diff 中取节点 id(优先 newSchema,回退 oldSchema),按出现顺序找到第一个当前仍存在的节点;
+ * - 与图层树点击选中一致:editorService.select + 画布 / overlay 画布 select 三者联动;
+ * - 该 step 涉及的节点都已不存在(如删除记录、被撤销的新增)时给出提示,不做选中。
+ */
+const onPageSelect = async (index: number) => {
+ const step = historyService.getPageStepList()[index]?.step;
+ if (!step) return;
+ const targetId = (step.diff ?? [])
+ .map((item) => item.newSchema?.id ?? item.oldSchema?.id)
+ .find((id) => id !== undefined && id !== null && editorService.getNodeById(id, false));
+ if (targetId === undefined || targetId === null) {
+ tMagicMessage.warning('该记录对应的节点已不存在,无法选中');
+ return;
+ }
+ const node = editorService.getNodeById(targetId, false);
+ if (!node) return;
+ await editorService.select(node);
+ editorService.get('stage')?.select(targetId);
+ stageOverlayService.get('stage')?.select(targetId);
+};
+
const onDataSourceGoto = (id: string | number, index: number) => {
dataSourceService.goto(id, indexToCursor(index));
};
diff --git a/packages/editor/src/layouts/history-list/PageTab.vue b/packages/editor/src/layouts/history-list/PageTab.vue
index 73e6c9fc..70335eb6 100644
--- a/packages/editor/src/layouts/history-list/PageTab.vue
+++ b/packages/editor/src/layouts/history-list/PageTab.vue
@@ -11,10 +11,12 @@
:key="rowKey(group)"
:group="toRow(group)"
:expanded="isHistoryGroupExpanded(expanded, rowKey(group))"
+ :select-enabled="true"
@toggle="(key: string) => $emit('toggle', key)"
@goto="(index: number) => $emit('goto', index)"
@diff-step="(index: number) => $emit('diff-step', index)"
@revert-step="(index: number) => $emit('revert-step', index)"
+ @select="(index: number) => $emit('select', index)"
/>