From 745f44939605528e8e2ccd0639478db98293a441 Mon Sep 17 00:00:00 2001 From: roymondchen Date: Thu, 23 Jul 2026 19:00:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20page-structure-cha?= =?UTF-8?q?nge=20=E4=BA=8B=E4=BB=B6=EF=BC=8C=E6=94=AF=E6=8C=81=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E5=A2=9E=E5=88=A0=E7=BB=9F=E4=B8=80=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/editor/src/services/editor.ts | 22 ++++++++++++++++++++++ packages/editor/src/services/history.ts | 23 ++++++++++++++++++++++- packages/editor/src/type.ts | 6 ++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/services/editor.ts b/packages/editor/src/services/editor.ts index 8d95d75d..481f2961 100644 --- a/packages/editor/src/services/editor.ts +++ b/packages/editor/src/services/editor.ts @@ -554,6 +554,12 @@ class Editor extends BaseService { doNotPushHistory, }); + // 页面 / 页面片新增不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知 + const addedPages = newNodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[]; + if (addedPages.length) { + historyService.notifyPageStructureChange({ add: addedPages, remove: [] }); + } + return Array.isArray(addNode) ? newNodes : newNodes[0]; } @@ -695,6 +701,12 @@ class Editor extends BaseService { this.emit('remove', nodes); this.emit('change', { type: 'remove', data: changeItems, historySource, doNotPushHistory }); + + // 页面 / 页面片删除不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知 + const removedPages = nodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[]; + if (removedPages.length) { + historyService.notifyPageStructureChange({ add: [], remove: removedPages }); + } } public async doUpdate( @@ -1776,6 +1788,10 @@ class Editor extends BaseService { const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p])); const indexInItems = (root: MApp, id: Id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`); + // 收集本次整体替换中增删的页面,循环结束后合并为一次结构变更通知(避免逐页派发多个事件) + const addedPages: (MPage | MPageFragment)[] = []; + const removedPages: (MPage | MPageFragment)[] = []; + nextPages.forEach((nextPage) => { const prevPage = prevMap.get(`${nextPage.id}`); if (!prevPage) { @@ -1785,6 +1801,7 @@ class Editor extends BaseService { { newSchema: cloneDeep(toRaw(nextPage)), parentId: nextRoot.id, index: indexInItems(nextRoot, nextPage.id) }, source, ); + addedPages.push(nextPage); } else if (!isEqual(toRaw(prevPage), toRaw(nextPage))) { this.pushPageDiffStep( 'update', @@ -1803,8 +1820,13 @@ class Editor extends BaseService { { oldSchema: cloneDeep(toRaw(prevPage)), parentId: preRoot.id, index: indexInItems(preRoot, prevPage.id) }, source, ); + removedPages.push(prevPage); } }); + + if (addedPages.length || removedPages.length) { + historyService.notifyPageStructureChange({ add: addedPages, remove: removedPages }); + } } /** diff --git a/packages/editor/src/services/history.ts b/packages/editor/src/services/history.ts index 8b1c3d1a..401f465c 100644 --- a/packages/editor/src/services/history.ts +++ b/packages/editor/src/services/history.ts @@ -19,7 +19,7 @@ import { reactive } from 'vue'; import type { Writable } from 'type-fest'; -import type { Id } from '@tmagic/core'; +import type { Id, MPage, MPageFragment } from '@tmagic/core'; import { guid } from '@tmagic/utils'; import type { @@ -353,6 +353,27 @@ class History extends BaseService { this.emit('mark-saved', { kind: stepType, id }); } + /** + * 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。 + * + * 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中 + * 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些 + * 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。 + * + * 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**, + * 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。 + * + * @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 } + */ + public notifyPageStructureChange(change: { + add: (MPage | MPageFragment)[]; + remove: (MPage | MPageFragment)[]; + }): void { + // 增删均为空时不派发,避免无意义通知 + if (!change.add.length && !change.remove.length) return; + this.emit('page-structure-change', change); + } + /** * 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。 * diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts index 3b346bac..c90f3aea 100644 --- a/packages/editor/src/type.ts +++ b/packages/editor/src/type.ts @@ -1385,6 +1385,12 @@ export interface HistoryEvents { 'mark-saved': [{ kind: HistoryStepType; id?: Id }]; clear: [{ id: Id; stepType: HistoryStepType }]; 'marker-change': [{ id: Id; marker: StepValue; stepType: HistoryStepType }]; + /** + * 页面 / 页面片结构变更(新增 / 删除)时派发,见 {@link HistoryService.notifyPageStructureChange}。 + * 一次操作(add / remove / setRoot 整体替换)涉及多个页面时合并为**一个**事件; + * `add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。 + */ + 'page-structure-change': [change: { add: (MPage | MPageFragment)[]; remove: (MPage | MPageFragment)[] }]; } export const canUsePluginMethods = {