diff --git a/docs/guide/advanced/history-list.md b/docs/guide/advanced/history-list.md index 68330156..ce6ce72e 100644 --- a/docs/guide/advanced/history-list.md +++ b/docs/guide/advanced/history-list.md @@ -82,7 +82,7 @@ const { onPageDiff, onDataSourceDiff, onCodeBlockDiff, -} = useHistoryRevert(editorRef.value); +} = useHistoryRevert({}, editorRef.value); // 回滚:可差异步骤弹出差异确认弹窗、其余步骤弹普通二次确认框;用户点「确定」后回滚第 index 步, // 命中前置校验或用户取消时不执行,返回 null diff --git a/packages/editor/src/components/CompareForm.vue b/packages/editor/src/components/CompareForm.vue index 7bbbddbb..56569926 100644 --- a/packages/editor/src/components/CompareForm.vue +++ b/packages/editor/src/components/CompareForm.vue @@ -24,7 +24,6 @@ import { isEqual } from 'lodash-es'; import { type CodeBlockContent, type DataSourceSchema, HookType, type MNode } from '@tmagic/core'; import { type FormConfig, type FormState, type FormValue, MForm } from '@tmagic/form'; -import { useServices } from '@editor/hooks/use-services'; import type { CompareCategory, CompareFormLoadConfig, Services } from '@editor/type'; import { getCodeBlockFormConfig } from '@editor/utils/code-block'; @@ -76,18 +75,15 @@ const props = withDefaults( */ loadConfig?: CompareFormLoadConfig; /** 编辑器服务集合,由调用方传入(不再通过 inject('services') 获取)。 */ - services: Services; + services?: Services; }>(), { category: 'node', labelWidth: '120px', - services: () => useServices(), extendState: (state: FormState) => state, }, ); -const { propsService, dataSourceService, codeBlockService, editorService } = props.services; - provide('services', props.services); const config = ref([]); @@ -192,17 +188,21 @@ const mergedExtendState = (state: FormState) => { * 作为 ctx.defaultLoadConfig 透传给自定义 `loadConfig`,方便复用与二次加工。 */ const defaultLoadConfig = async (): Promise => { + if (!props.services) { + return []; + } + switch (props.category) { case 'node': { if (!props.type) { return []; } return removeStyleDisplayConfig( - await propsService.getPropsConfig(props.type, { node: props.value as unknown as MNode }), + await props.services.propsService.getPropsConfig(props.type, { node: props.value as unknown as MNode }), ); } case 'data-source': { - const config = dataSourceService.getFormConfig(props.type || 'base'); + const config = props.services.dataSourceService.getFormConfig(props.type || 'base'); // 数据源表单外层 tab 的「数据定义」项 status 为 'fields',tab-pane name 随之为 'fields'。 // 未显式设置 active 时,Tabs 默认取 '0',与 'fields' 不匹配会导致打开弹窗时无默认激活项, // 这里与 DataSourceConfigPanel 保持一致,默认激活「数据定义」tab。 @@ -210,7 +210,7 @@ const defaultLoadConfig = async (): Promise => { } case 'code-block': { return getCodeBlockFormConfig({ - paramColConfig: codeBlockService.getParamsColConfig(), + paramColConfig: props.services.codeBlockService.getParamsColConfig(), // 通过传入 dataSourceType 间接表达"是数据源代码块"——在对比场景下 props.dataSourceType // 由调用方按 step 上下文显式传入,未传则视为普通代码块,「执行时机」字段隐藏。 isDataSource: () => Boolean(props.dataSourceType), @@ -258,11 +258,9 @@ const formRef = useTemplateRef>('form'); * - services:整个 useServices() 返回的服务集合; * - stage:当前 editorService.get('stage') 的最新值。 */ -const stage = computed(() => editorService.get('stage')); - watchEffect(() => { - if (formRef.value) { - formRef.value.formState.stage = stage.value; + if (formRef.value && props.services) { + formRef.value.formState.stage = props.services.editorService.get('stage'); formRef.value.formState.services = props.services; } }); diff --git a/packages/editor/src/layouts/history-list/HistoryDiffDialog.vue b/packages/editor/src/layouts/history-list/HistoryDiffDialog.vue index f617ecd8..041ad6b0 100644 --- a/packages/editor/src/layouts/history-list/HistoryDiffDialog.vue +++ b/packages/editor/src/layouts/history-list/HistoryDiffDialog.vue @@ -81,7 +81,6 @@ import { TMagicButton, TMagicDialog, TMagicRadioButton, TMagicRadioGroup, TMagic import type { FormState } from '@tmagic/form'; import CompareForm from '@editor/components/CompareForm.vue'; -import { useServices } from '@editor/hooks/use-services'; import CodeEditor from '@editor/layouts/CodeEditor.vue'; import type { CompareCategory, CompareFormLoadConfig, DiffDialogPayload, Services } from '@editor/type'; @@ -92,7 +91,7 @@ defineOptions({ const props = withDefaults( defineProps<{ /** 编辑器服务集合,由调用方传入(不再通过 inject('services') 获取)。 */ - services: Services; + services?: Services; /** * 来自 Editor 顶层的 `extendFormState`,用于扩展 MForm.formState。 * 透传给 CompareForm,从而让差异对比时表单 item 中依赖业务上下文的 @@ -111,7 +110,6 @@ const props = withDefaults( compareFormState?: FormState; }>(), { - services: () => useServices(), width: '900px', }, ); diff --git a/packages/editor/src/layouts/history-list/HistoryListPanel.vue b/packages/editor/src/layouts/history-list/HistoryListPanel.vue index 22ef9ab0..9061573a 100644 --- a/packages/editor/src/layouts/history-list/HistoryListPanel.vue +++ b/packages/editor/src/layouts/history-list/HistoryListPanel.vue @@ -313,10 +313,10 @@ const onCodeBlockGotoInitial = (id: string | number) => { /** * 「单步回滚」与「查看差异」的完整逻辑收敛到 useHistoryRevert,面板与业务方共用: * 二者均由 useHistoryRevert 内部按需动态挂载 HistoryDiffDialog, - * 业务方亦可直接 import useHistoryRevert(services) 调用,无需自行挂载任何弹窗。 + * 业务方亦可直接 import useHistoryRevert(options, services) 调用,无需自行挂载任何弹窗。 */ const { onPageRevert, onDataSourceRevert, onCodeBlockRevert, onPageDiff, onDataSourceDiff, onCodeBlockDiff } = - useHistoryRevert(services, { extendState: extendFormState, getPropsPanelFormState }); + useHistoryRevert({ extendState: extendFormState, getPropsPanelFormState }, services); /** * 把内存中(已清空对应类别后的)历史状态重新写回 IndexedDB, diff --git a/packages/editor/src/layouts/history-list/useHistoryRevert.ts b/packages/editor/src/layouts/history-list/useHistoryRevert.ts index e9ed2793..54bd071d 100644 --- a/packages/editor/src/layouts/history-list/useHistoryRevert.ts +++ b/packages/editor/src/layouts/history-list/useHistoryRevert.ts @@ -76,7 +76,7 @@ interface MountedDiffDialog { const mountHistoryDiffDialog = async ( options: Pick & CustomDiffFormOptions & { - services: Services; + services?: Services; isConfirm?: boolean; onClose?: () => void; }, @@ -125,7 +125,7 @@ const confirmRevertWithDiffDialog = async ( payload: DiffDialogPayload, options: Pick & CustomDiffFormOptions & { - services: Services; + services?: Services; }, ): Promise => { const { instance, destroy } = await mountHistoryDiffDialog({ @@ -147,7 +147,7 @@ const viewHistoryDiffDialog = async ( payload: DiffDialogPayload, options: Pick & CustomDiffFormOptions & { - services: Services; + services?: Services; }, ): Promise => { // onClose 在用户关闭弹窗时才触发,此时 handle.destroy 早已赋值。 @@ -184,13 +184,12 @@ const viewHistoryDiffDialog = async ( * ```ts * import { useHistoryRevert } from '@tmagic/editor'; * - * const { onPageRevert, onPageDiff } = useHistoryRevert(editorRef.value); // editorRef.value 即 Editor 暴露的 services + * const { onPageRevert, onPageDiff } = useHistoryRevert({}, editorRef.value); // editorRef.value 即 Editor 暴露的 services * await onPageRevert(index); // 弹出差异 / 二次确认弹窗后回滚 * onPageDiff(index); // 弹出只读差异弹窗查看前后值差异 * ``` */ -export const useHistoryRevert = (services: Services, options: UseHistoryRevertOptions = {}) => { - const { editorService, dataSourceService, codeBlockService, historyService } = services; +export const useHistoryRevert = (options: UseHistoryRevertOptions = {}, services?: Services) => { // 自动捕获调用方所在组件的 appContext(在 setup 中调用时),业务方亦可显式覆盖。 const appContext = options.appContext ?? getCurrentInstance()?.appContext ?? null; const { extendState, getPropsPanelFormState } = options; @@ -226,8 +225,8 @@ export const useHistoryRevert = (services: Services, options: UseHistoryRevertOp buildDiffPayload( { category: 'node', - groups: () => historyService.getHistoryGroups('page', editorService.get('page')?.id), - getCurrent: (id) => editorService.getNodeById(id) as Record | null, + groups: () => services?.historyService.getHistoryGroups('page', services?.editorService.get('page')?.id) ?? [], + getCurrent: (id) => services?.editorService.getNodeById(id) as Record | null, resolveType: (n, o) => n.type || o.type || '', resolveLabel: (n, o) => n.name || o.name || n.type || o.type || '', }, @@ -238,8 +237,8 @@ export const useHistoryRevert = (services: Services, options: UseHistoryRevertOp buildDiffPayload( { category: 'data-source', - groups: () => historyService.getHistoryGroups('dataSource'), - getCurrent: (id) => dataSourceService.getDataSourceById(`${id}`) as Record | null, + groups: () => services?.historyService.getHistoryGroups('dataSource') ?? [], + getCurrent: (id) => services?.dataSourceService.getDataSourceById(`${id}`) as Record | null, resolveType: (n, o) => n.type || o.type || 'base', resolveLabel: (n, o, id) => n.title || o.title || `${id}`, }, @@ -251,8 +250,8 @@ export const useHistoryRevert = (services: Services, options: UseHistoryRevertOp buildDiffPayload( { category: 'code-block', - groups: () => historyService.getHistoryGroups('codeBlock'), - getCurrent: (id) => codeBlockService.getCodeContentById(id) as Record | null, + groups: () => services?.historyService.getHistoryGroups('codeBlock') ?? [], + getCurrent: (id) => services?.codeBlockService.getCodeContentById(id) as Record | null, resolveLabel: (n, o, id) => n.name || o.name || `${id}`, }, index, @@ -266,17 +265,17 @@ export const useHistoryRevert = (services: Services, options: UseHistoryRevertOp * add(回滚即删除)即使目标已不在,也已达成「删除」目的,不视为失败。 */ const isPageRevertTargetMissing = (index: number): boolean => { - const step = historyService.getStepList('page', editorService.get('page')?.id)[index]?.step; + const step = services?.historyService.getStepList('page', services?.editorService.get('page')?.id)[index]?.step; if (!step) return false; if (step.opType === 'update') { return (step.diff ?? []).some((item) => { const id = item.newSchema?.id ?? item.oldSchema?.id; - return id !== undefined && !editorService.getNodeById(id, false); + return id !== undefined && !services?.editorService.getNodeById(id, false); }); } if (step.opType === 'remove') { return (step.diff ?? []).some( - (item) => item.parentId !== undefined && !editorService.getNodeById(item.parentId, false), + (item) => item.parentId !== undefined && !services?.editorService.getNodeById(item.parentId, false), ); } return false; @@ -284,14 +283,14 @@ export const useHistoryRevert = (services: Services, options: UseHistoryRevertOp /** 数据源 update 步骤回滚时,对应数据源必须仍存在(已删除则无处写回旧值)。 */ const isDataSourceRevertTargetMissing = (id: string | number, index: number): boolean => { - const step = historyService.getStepList('dataSource', id)[index]?.step; - return Boolean(step?.opType === 'update' && !dataSourceService.getDataSourceById(`${id}`)); + const step = services?.historyService.getStepList('dataSource', id)[index]?.step; + return Boolean(step?.opType === 'update' && !services?.dataSourceService.getDataSourceById(`${id}`)); }; /** 代码块 update 步骤回滚时,对应代码块必须仍存在(已删除则无处写回旧值)。 */ const isCodeBlockRevertTargetMissing = (id: string | number, index: number): boolean => { - const step = historyService.getStepList('codeBlock', id)[index]?.step; - return Boolean(step?.opType === 'update' && !codeBlockService.getCodeContentById(id)); + const step = services?.historyService.getStepList('codeBlock', id)[index]?.step; + return Boolean(step?.opType === 'update' && !services?.codeBlockService.getCodeContentById(id)); }; const onPageRevert = (index: number) => { @@ -300,7 +299,7 @@ export const useHistoryRevert = (services: Services, options: UseHistoryRevertOp return Promise.resolve(null); } return runRevert(buildPageDiffPayload(index), { compareFormState: getPropsPanelFormState?.() }).then((result) => - result ? editorService.revertPageStep(index) : null, + result ? services?.editorService.revertPageStep(index) : null, ); }; @@ -310,7 +309,7 @@ export const useHistoryRevert = (services: Services, options: UseHistoryRevertOp return Promise.resolve(null); } return runRevert(buildDataSourceDiffPayload(id, index)).then((result) => - result ? dataSourceService.revert(id, index) : null, + result ? services?.dataSourceService.revert(id, index) : null, ); }; @@ -320,7 +319,7 @@ export const useHistoryRevert = (services: Services, options: UseHistoryRevertOp return Promise.resolve(null); } return runRevert(buildCodeBlockDiffPayload(id, index)).then((result) => - result ? codeBlockService.revert(id, index) : null, + result ? services?.codeBlockService.revert(id, index) : null, ); }; diff --git a/packages/editor/tests/unit/layouts/history-list/HistoryDiffDialog.spec.ts b/packages/editor/tests/unit/layouts/history-list/HistoryDiffDialog.spec.ts index 0bc53a06..436de10f 100644 --- a/packages/editor/tests/unit/layouts/history-list/HistoryDiffDialog.spec.ts +++ b/packages/editor/tests/unit/layouts/history-list/HistoryDiffDialog.spec.ts @@ -87,7 +87,7 @@ const services = {} as any; const factory = () => mount(HistoryDiffDialog, { // 让 Teleport 内容内联渲染,便于通过 wrapper 查询 - global: { stubs: { teleport: true }, provide: { services } }, + global: { stubs: { teleport: true } }, }); const basePayload = (extra: any = {}) => ({ diff --git a/packages/editor/tests/unit/layouts/history-list/useHistoryRevert.spec.ts b/packages/editor/tests/unit/layouts/history-list/useHistoryRevert.spec.ts index 68f464a6..02b0ab52 100644 --- a/packages/editor/tests/unit/layouts/history-list/useHistoryRevert.spec.ts +++ b/packages/editor/tests/unit/layouts/history-list/useHistoryRevert.spec.ts @@ -62,7 +62,7 @@ describe('useHistoryRevert', () => { ]); services.editorService.getNodeById.mockReturnValue(null); - const { onPageRevert } = useHistoryRevert(services); + const { onPageRevert } = useHistoryRevert({}, services); await onPageRevert(0); expect(tMagicMessage.error).toHaveBeenCalledWith('回滚失败:该记录对应的数据已被删除'); @@ -80,7 +80,7 @@ describe('useHistoryRevert', () => { }, ]); - const { onPageRevert } = useHistoryRevert(services); + const { onPageRevert } = useHistoryRevert({}, services); await onPageRevert(0); expect(confirmHistoryAction).toHaveBeenCalled(); @@ -99,7 +99,7 @@ describe('useHistoryRevert', () => { ]); services.dataSourceService.getDataSourceById.mockReturnValue(null); - const { onDataSourceRevert } = useHistoryRevert(services); + const { onDataSourceRevert } = useHistoryRevert({}, services); await onDataSourceRevert('ds_1', 0); expect(tMagicMessage.error).toHaveBeenCalledWith('回滚失败:该记录对应的数据已被删除');