mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-06-13 18:08:29 +08:00
fix(editor): 优化历史回滚确认流程
This commit is contained in:
parent
a965dfb06e
commit
48519b0155
@ -1,76 +1,74 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<TMagicDialog
|
||||
v-model="visible"
|
||||
class="m-editor-history-diff-dialog"
|
||||
:title="dialogTitle"
|
||||
top="5vh"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
:width="width"
|
||||
@close="onClose"
|
||||
>
|
||||
<div v-if="payload" class="m-editor-history-diff-dialog-body">
|
||||
<div v-if="onConfirm" class="m-editor-history-diff-dialog-notice">仅回滚有差异的字段</div>
|
||||
<TMagicDialog
|
||||
v-model="visible"
|
||||
class="m-editor-history-diff-dialog"
|
||||
:title="dialogTitle"
|
||||
top="5vh"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
:width="width"
|
||||
@close="onClose"
|
||||
>
|
||||
<div v-if="payload && visible" class="m-editor-history-diff-dialog-body">
|
||||
<div v-if="onConfirm" class="m-editor-history-diff-dialog-notice">仅回滚有差异的字段</div>
|
||||
|
||||
<div class="m-editor-history-diff-dialog-header">
|
||||
<span class="m-editor-history-diff-dialog-target">{{ targetText }}</span>
|
||||
<div class="m-editor-history-diff-dialog-controls">
|
||||
<TMagicRadioGroup v-model="viewMode" size="small" class="m-editor-history-diff-dialog-view">
|
||||
<TMagicRadioButton value="form">表单对比</TMagicRadioButton>
|
||||
<TMagicRadioButton value="code">源码对比</TMagicRadioButton>
|
||||
</TMagicRadioGroup>
|
||||
<div class="m-editor-history-diff-dialog-header">
|
||||
<span class="m-editor-history-diff-dialog-target">{{ targetText }}</span>
|
||||
<div class="m-editor-history-diff-dialog-controls">
|
||||
<TMagicRadioGroup v-model="viewMode" size="small" class="m-editor-history-diff-dialog-view">
|
||||
<TMagicRadioButton value="form">表单对比</TMagicRadioButton>
|
||||
<TMagicRadioButton value="code">源码对比</TMagicRadioButton>
|
||||
</TMagicRadioGroup>
|
||||
|
||||
<TMagicRadioGroup v-model="mode" size="small" class="m-editor-history-diff-dialog-mode">
|
||||
<TMagicRadioButton value="before">与修改前对比</TMagicRadioButton>
|
||||
<TMagicRadioButton value="current" :disabled="!hasCurrent">与当前对比</TMagicRadioButton>
|
||||
</TMagicRadioGroup>
|
||||
</div>
|
||||
<TMagicRadioGroup v-model="mode" size="small" class="m-editor-history-diff-dialog-mode">
|
||||
<TMagicRadioButton value="before">与修改前对比</TMagicRadioButton>
|
||||
<TMagicRadioButton value="current" :disabled="!hasCurrent">与当前对比</TMagicRadioButton>
|
||||
</TMagicRadioGroup>
|
||||
</div>
|
||||
|
||||
<div class="m-editor-history-diff-dialog-legend">
|
||||
<TMagicTag size="small" type="danger">{{ leftLabel }}</TMagicTag>
|
||||
<span class="m-editor-history-diff-dialog-arrow">→</span>
|
||||
<TMagicTag size="small" type="success">{{ rightLabel }}</TMagicTag>
|
||||
<span v-if="mode === 'current' && isSameAsCurrent" class="m-editor-history-diff-dialog-tip">
|
||||
当前值与该步修改后一致,无差异
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<CompareForm
|
||||
v-if="viewMode === 'form'"
|
||||
:category="payload.category"
|
||||
:type="payload.type"
|
||||
:data-source-type="payload.dataSourceType"
|
||||
:value="rightValue"
|
||||
:last-value="leftValue"
|
||||
:extend-state="extendState"
|
||||
:load-config="loadConfig"
|
||||
:self-diff-field-types="selfDiffFieldTypes"
|
||||
height="70vh"
|
||||
/>
|
||||
|
||||
<CodeEditor
|
||||
v-else
|
||||
type="diff"
|
||||
language="json"
|
||||
:init-values="leftValue"
|
||||
:modified-values="rightValue"
|
||||
:options="codeDiffOptions"
|
||||
disabled-full-screen
|
||||
height="70vh"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<template v-if="onConfirm">
|
||||
<TMagicButton size="small" @click="visible = false">取消</TMagicButton>
|
||||
<TMagicButton size="small" type="primary" @click="onConfirmClick">确定回滚</TMagicButton>
|
||||
</template>
|
||||
<TMagicButton v-else size="small" @click="visible = false">关闭</TMagicButton>
|
||||
<div class="m-editor-history-diff-dialog-legend">
|
||||
<TMagicTag size="small" type="danger">{{ leftLabel }}</TMagicTag>
|
||||
<span class="m-editor-history-diff-dialog-arrow">→</span>
|
||||
<TMagicTag size="small" type="success">{{ rightLabel }}</TMagicTag>
|
||||
<span v-if="mode === 'current' && isSameAsCurrent" class="m-editor-history-diff-dialog-tip">
|
||||
当前值与该步修改后一致,无差异
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<CompareForm
|
||||
v-if="viewMode === 'form'"
|
||||
:category="payload.category"
|
||||
:type="payload.type"
|
||||
:data-source-type="payload.dataSourceType"
|
||||
:value="rightValue"
|
||||
:last-value="leftValue"
|
||||
:extend-state="extendState"
|
||||
:load-config="loadConfig"
|
||||
:self-diff-field-types="selfDiffFieldTypes"
|
||||
height="70vh"
|
||||
/>
|
||||
|
||||
<CodeEditor
|
||||
v-else
|
||||
type="diff"
|
||||
language="json"
|
||||
:init-values="leftValue"
|
||||
:modified-values="rightValue"
|
||||
:options="codeDiffOptions"
|
||||
disabled-full-screen
|
||||
height="70vh"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<template v-if="isConfirm">
|
||||
<TMagicButton size="small" @click="visible = false">取消</TMagicButton>
|
||||
<TMagicButton size="small" type="primary" @click="onConfirmClick">确定回滚</TMagicButton>
|
||||
</template>
|
||||
</TMagicDialog>
|
||||
</Teleport>
|
||||
<TMagicButton v-else size="small" @click="visible = false">关闭</TMagicButton>
|
||||
</template>
|
||||
</TMagicDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -102,6 +100,7 @@ const props = withDefaults(
|
||||
*/
|
||||
loadConfig?: CompareFormLoadConfig;
|
||||
width?: string;
|
||||
isConfirm?: boolean;
|
||||
onConfirm?: () => void;
|
||||
selfDiffFieldTypes?: string[];
|
||||
}>(),
|
||||
@ -178,10 +177,15 @@ const isSameAsCurrent = computed(() => {
|
||||
return isEqual(payload.value.value, payload.value.currentValue);
|
||||
});
|
||||
|
||||
const onConfirmClick = () => {
|
||||
const cb = props.onConfirm;
|
||||
/** confirm() 的 resolve,仅在「等待用户确认回滚」期间存在 */
|
||||
let confirmResolve: ((_value: boolean) => void) | null = null;
|
||||
|
||||
cb?.();
|
||||
const onConfirmClick = () => {
|
||||
props.onConfirm?.();
|
||||
|
||||
// 用户确认回滚:resolve(true),并清空以避免随后 visible=false 再 resolve(false)
|
||||
confirmResolve?.(true);
|
||||
confirmResolve = null;
|
||||
|
||||
visible.value = false;
|
||||
};
|
||||
@ -210,6 +214,24 @@ const open = (p: DiffDialogPayload) => {
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 以 Promise 形式打开确认回滚弹窗:
|
||||
* - 用户点击「确定回滚」时 resolve(true);
|
||||
* - 取消 / 关闭 / 按 Esc 等其他方式关闭弹窗时 resolve(false)。
|
||||
*
|
||||
* 同一时刻只允许一个待确认流程,重复调用会先 resolve(false) 掉上一个。
|
||||
*/
|
||||
const confirm = (p: DiffDialogPayload): Promise<boolean> => {
|
||||
// 终止上一个未完成的确认流程,避免悬挂的 Promise
|
||||
confirmResolve?.(false);
|
||||
confirmResolve = null;
|
||||
|
||||
return new Promise<boolean>((resolve) => {
|
||||
confirmResolve = resolve;
|
||||
open(p);
|
||||
});
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
@ -218,6 +240,9 @@ const close = () => {
|
||||
watch(visible, (v) => {
|
||||
if (!v) {
|
||||
payload.value = null;
|
||||
// 非「确定回滚」方式关闭(取消 / Esc / 点遮罩等)时,resolve(false)
|
||||
confirmResolve?.(false);
|
||||
confirmResolve = null;
|
||||
}
|
||||
});
|
||||
|
||||
@ -227,6 +252,7 @@ const onClose = () => {
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
confirm,
|
||||
close,
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -90,12 +90,8 @@
|
||||
</template>
|
||||
</TMagicPopover>
|
||||
|
||||
<HistoryDiffDialog
|
||||
ref="diffDialog"
|
||||
:extend-state="extendFormState"
|
||||
:on-confirm="onConfirmRevert"
|
||||
@close="onDiffDialogClose"
|
||||
/>
|
||||
<HistoryDiffDialog ref="diffDialog" :extend-state="extendFormState" />
|
||||
<HistoryDiffDialog ref="confirmDialog" :is-confirm="true" :extend-state="extendFormState" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -120,12 +116,13 @@
|
||||
* (通过 title / prefix / describe* / isStepDiffable 注入差异)。
|
||||
* 共享的描述生成与折叠状态在 composables.ts 中维护。
|
||||
*/
|
||||
import { computed, inject, markRaw, ref, shallowRef, useTemplateRef, watch } from 'vue';
|
||||
import { computed, inject, markRaw, ref, useTemplateRef, watch } from 'vue';
|
||||
import { Clock, Close } from '@element-plus/icons-vue';
|
||||
|
||||
import {
|
||||
getDesignConfig,
|
||||
TMagicButton,
|
||||
tMagicMessage,
|
||||
tMagicMessageBox,
|
||||
TMagicPopover,
|
||||
TMagicTabs,
|
||||
@ -271,87 +268,90 @@ const onCodeBlockGotoInitial = (id: string | number) => {
|
||||
};
|
||||
|
||||
const diffDialogRef = useTemplateRef<InstanceType<typeof HistoryDiffDialog>>('diffDialog');
|
||||
const confirmDialogRef = useTemplateRef<InstanceType<typeof HistoryDiffDialog>>('confirmDialog');
|
||||
|
||||
/**
|
||||
* 构造页面 step 的差异弹窗入参:仅 update 单节点修改可对比,传入旧/新节点。
|
||||
* 节点类型 `type` 优先取 newSchema.type,再回退 oldSchema.type。
|
||||
* `currentValue` 取自 editorService 中该节点当前实际值,用于支持「与当前对比」。
|
||||
* 无可对比内容(如多节点 / add / remove)时返回 null。
|
||||
* 三类历史(页面 / 数据源 / 代码块)差异弹窗入参的构造差异,收敛为一份配置:
|
||||
* 仅「分组来源、当前值读取、类型 / 展示名提取」不同,定位 step、校验前后值、组装 payload 的流程共用。
|
||||
*/
|
||||
const buildPageDiffPayload = (index: number): DiffDialogPayload | null => {
|
||||
const groups = historyService.getPageHistoryGroups();
|
||||
for (const group of groups) {
|
||||
const entry = group.steps.find((s) => s.index === index);
|
||||
if (!entry) continue;
|
||||
const item = entry.step.diff?.[0];
|
||||
if (!item?.oldSchema || !item?.newSchema) return null;
|
||||
const type = (item.newSchema.type as string) || (item.oldSchema.type as string) || '';
|
||||
const nodeId = item.newSchema.id ?? item.oldSchema.id;
|
||||
const currentNode = nodeId !== undefined ? editorService.getNodeById(nodeId) : null;
|
||||
interface DiffPayloadSource {
|
||||
/** 表单类别:节点 / 数据源 / 代码块。 */
|
||||
category: DiffDialogPayload['category'];
|
||||
/** 该类别按时间正序的历史分组列表(含已撤销)。 */
|
||||
groups: () => { id?: string | number; steps: { index: number; step: { diff?: any[] } }[] }[];
|
||||
/** 读取目标当前实际值,用于「与当前对比」;不存在时返回空即禁用对比。 */
|
||||
getCurrent: (_id: string | number) => Record<string, any> | null | undefined;
|
||||
/** 由新/旧快照提取展示名(含各自的兜底,如节点回退 type、数据源 / 代码块回退 id)。 */
|
||||
resolveLabel: (_newSchema: Record<string, any>, _oldSchema: Record<string, any>, _id: string | number) => string;
|
||||
/** 由新/旧快照提取类型;代码块无 type 字段则不传。 */
|
||||
resolveType?: (_newSchema: Record<string, any>, _oldSchema: Record<string, any>) => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造差异弹窗入参:仅 update(前后值都存在)可对比。
|
||||
* - 页面(无 id):在全部分组中按 index 定位 step,目标 id 取自快照;
|
||||
* - 数据源 / 代码块(带 id):先匹配分组 id 再按 index 定位。
|
||||
* 无可对比内容(多节点 / add / remove)或定位不到时返回 null。
|
||||
*/
|
||||
const buildDiffPayload = (source: DiffPayloadSource, index: number, id?: string | number): DiffDialogPayload | null => {
|
||||
for (const group of source.groups()) {
|
||||
if (id !== undefined && group.id !== id) continue;
|
||||
const step = group.steps.find((s) => s.index === index)?.step;
|
||||
if (!step) continue;
|
||||
const oldSchema = step.diff?.[0]?.oldSchema as Record<string, any> | undefined;
|
||||
const newSchema = step.diff?.[0]?.newSchema as Record<string, any> | undefined;
|
||||
if (!oldSchema || !newSchema) return null;
|
||||
const targetId = id ?? newSchema.id ?? oldSchema.id;
|
||||
const type = source.resolveType?.(newSchema, oldSchema);
|
||||
return {
|
||||
category: 'node',
|
||||
type,
|
||||
lastValue: item.oldSchema as Record<string, any>,
|
||||
value: item.newSchema as Record<string, any>,
|
||||
currentValue: (currentNode as Record<string, any>) || null,
|
||||
targetLabel: (item.newSchema.name as string) || (item.oldSchema.name as string) || type,
|
||||
id: nodeId,
|
||||
category: source.category,
|
||||
...(type !== undefined ? { type } : {}),
|
||||
lastValue: oldSchema,
|
||||
value: newSchema,
|
||||
currentValue: (targetId !== undefined ? source.getCurrent(targetId) : null) || null,
|
||||
targetLabel: source.resolveLabel(newSchema, oldSchema, targetId),
|
||||
id: targetId,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 在指定分组列表中按 id / index 查找命中的 step,命中后交由 build 构造差异弹窗入参。
|
||||
* 用于统一数据源、代码块两类历史的查找逻辑。
|
||||
*/
|
||||
const findGroupStep = <G extends { id: string | number; steps: { index: number; step: any }[] }>(
|
||||
groups: G[],
|
||||
id: string | number,
|
||||
index: number,
|
||||
build: (_step: G['steps'][number]['step']) => DiffDialogPayload | null,
|
||||
): DiffDialogPayload | null => {
|
||||
for (const group of groups) {
|
||||
if (group.id !== id) continue;
|
||||
const entry = group.steps.find((s) => s.index === index);
|
||||
if (!entry) continue;
|
||||
return build(entry.step);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const buildPageDiffPayload = (index: number): DiffDialogPayload | null =>
|
||||
buildDiffPayload(
|
||||
{
|
||||
category: 'node',
|
||||
groups: () => historyService.getPageHistoryGroups(),
|
||||
getCurrent: (id) => editorService.getNodeById(id) as Record<string, any> | null,
|
||||
resolveType: (n, o) => n.type || o.type || '',
|
||||
resolveLabel: (n, o) => n.name || o.name || n.type || o.type || '',
|
||||
},
|
||||
index,
|
||||
);
|
||||
|
||||
const buildDataSourceDiffPayload = (id: string | number, index: number): DiffDialogPayload | null =>
|
||||
findGroupStep(historyService.getDataSourceHistoryGroups(), id, index, (step) => {
|
||||
const oldSchema = step.diff?.[0]?.oldSchema;
|
||||
const newSchema = step.diff?.[0]?.newSchema;
|
||||
if (!oldSchema || !newSchema) return null;
|
||||
const currentSchema = dataSourceService.getDataSourceById(`${id}`);
|
||||
return {
|
||||
buildDiffPayload(
|
||||
{
|
||||
category: 'data-source',
|
||||
type: newSchema.type || oldSchema.type || 'base',
|
||||
lastValue: oldSchema as Record<string, any>,
|
||||
value: newSchema as Record<string, any>,
|
||||
currentValue: (currentSchema as Record<string, any>) || null,
|
||||
targetLabel: newSchema.title || oldSchema.title || `${id}`,
|
||||
id,
|
||||
};
|
||||
});
|
||||
groups: () => historyService.getDataSourceHistoryGroups(),
|
||||
getCurrent: (id) => dataSourceService.getDataSourceById(`${id}`) as Record<string, any> | null,
|
||||
resolveType: (n, o) => n.type || o.type || 'base',
|
||||
resolveLabel: (n, o, id) => n.title || o.title || `${id}`,
|
||||
},
|
||||
index,
|
||||
id,
|
||||
);
|
||||
|
||||
const buildCodeBlockDiffPayload = (id: string | number, index: number): DiffDialogPayload | null =>
|
||||
findGroupStep(historyService.getCodeBlockHistoryGroups(), id, index, (step) => {
|
||||
const oldContent = step.diff?.[0]?.oldSchema;
|
||||
const newContent = step.diff?.[0]?.newSchema;
|
||||
if (!oldContent || !newContent) return null;
|
||||
const currentContent = codeBlockService.getCodeContentById(id);
|
||||
return {
|
||||
buildDiffPayload(
|
||||
{
|
||||
category: 'code-block',
|
||||
lastValue: oldContent as Record<string, any>,
|
||||
value: newContent as Record<string, any>,
|
||||
currentValue: (currentContent as Record<string, any>) || null,
|
||||
targetLabel: newContent.name || oldContent.name || `${id}`,
|
||||
id,
|
||||
};
|
||||
});
|
||||
groups: () => historyService.getCodeBlockHistoryGroups(),
|
||||
getCurrent: (id) => codeBlockService.getCodeContentById(id) as Record<string, any> | null,
|
||||
resolveLabel: (n, o, id) => n.name || o.name || `${id}`,
|
||||
},
|
||||
index,
|
||||
id,
|
||||
);
|
||||
|
||||
const onPageDiff = (index: number) => {
|
||||
const payload = buildPageDiffPayload(index);
|
||||
@ -368,50 +368,92 @@ const onCodeBlockDiff = (id: string | number, index: number) => {
|
||||
if (payload) diffDialogRef.value?.open(payload);
|
||||
};
|
||||
|
||||
const onConfirmRevert = shallowRef();
|
||||
|
||||
/**
|
||||
* 「回滚」入口:把目标历史步骤的修改作为一次新操作反向应用(类 git revert),
|
||||
* 「回滚」统一入口:把目标历史步骤的修改作为一次新操作反向应用(类 git revert),
|
||||
* 不破坏原有栈结构。各 service 内部完成反向 + 入栈,并自带描述用于面板展示。
|
||||
*
|
||||
* 交互:先弹出该步骤的差异弹窗供用户确认,点击「确定回滚」后再真正执行回滚;
|
||||
* 对没有可对比内容的步骤(如 add / remove / 多节点更新)则直接回滚。
|
||||
* 交互:
|
||||
* - 可差异对比的步骤(单节点 / 单实体 update):弹出差异弹窗供用户确认,点「确定回滚」再执行;
|
||||
* - 无法对比的步骤(add / remove / 多节点更新,payload 为 null):弹出普通二次确认框,确认后执行。
|
||||
*
|
||||
* 页面 / 数据源 / 代码块三类回滚仅「差异入参构造」与「实际 revert 调用」不同,
|
||||
* 由调用方分别传入 payload 与 revert,公共的弹窗 / 确认流程在此收敛。
|
||||
*/
|
||||
const onPageRevert = async (index: number) => {
|
||||
const payload = buildPageDiffPayload(index);
|
||||
const revert = () => editorService.revertPageStep(index);
|
||||
onConfirmRevert.value = revert;
|
||||
if (payload) {
|
||||
diffDialogRef.value?.open({ ...payload });
|
||||
} else if (await confirmRevert()) {
|
||||
revert();
|
||||
const runRevert = (payload: DiffDialogPayload | null): Promise<boolean> => {
|
||||
if (payload && confirmDialogRef.value) {
|
||||
return confirmDialogRef.value.confirm(payload);
|
||||
}
|
||||
return confirmRevert();
|
||||
};
|
||||
|
||||
const onDataSourceRevert = async (id: string | number, index: number) => {
|
||||
const payload = buildDataSourceDiffPayload(id, index);
|
||||
const revert = () => dataSourceService.revert(id, index);
|
||||
onConfirmRevert.value = revert;
|
||||
if (payload) {
|
||||
diffDialogRef.value?.open({ ...payload });
|
||||
} else if (await confirmRevert()) {
|
||||
revert();
|
||||
/**
|
||||
* 回滚前置校验:若该历史步骤回滚所依赖的目标数据已被删除,则无法回滚。
|
||||
* - update(把旧值写回):被修改的目标必须仍存在;
|
||||
* - 页面 remove(还原被删节点):被删节点的原父容器必须仍存在,否则无处插回;
|
||||
* add(回滚即删除)即使目标已不在,也已达成「删除」目的,不视为失败。
|
||||
*
|
||||
* 命中时弹出「回滚失败」提示并返回 true,调用方据此中止本次回滚。
|
||||
*/
|
||||
const isPageRevertTargetMissing = (index: number): boolean => {
|
||||
const step = historyService.getPageStepList()[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);
|
||||
});
|
||||
}
|
||||
if (step.opType === 'remove') {
|
||||
return (step.diff ?? []).some(
|
||||
(item) => item.parentId !== undefined && !editorService.getNodeById(item.parentId, false),
|
||||
);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const onCodeBlockRevert = async (id: string | number, index: number) => {
|
||||
const payload = buildCodeBlockDiffPayload(id, index);
|
||||
const revert = () => codeBlockService.revert(id, index);
|
||||
onConfirmRevert.value = revert;
|
||||
if (payload) {
|
||||
diffDialogRef.value?.open({ ...payload });
|
||||
} else if (await confirmRevert()) {
|
||||
revert();
|
||||
}
|
||||
/** 数据源 update 步骤回滚时,对应数据源必须仍存在(已删除则无处写回旧值)。 */
|
||||
const isDataSourceRevertTargetMissing = (id: string | number, index: number): boolean => {
|
||||
const step = historyService.getDataSourceStepList(id)[index]?.step;
|
||||
return Boolean(step && step.opType === 'update' && !dataSourceService.getDataSourceById(`${id}`));
|
||||
};
|
||||
|
||||
const onDiffDialogClose = () => {
|
||||
onConfirmRevert.value = undefined;
|
||||
/** 代码块 update 步骤回滚时,对应代码块必须仍存在(已删除则无处写回旧值)。 */
|
||||
const isCodeBlockRevertTargetMissing = (id: string | number, index: number): boolean => {
|
||||
const step = historyService.getCodeBlockStepList(id)[index]?.step;
|
||||
return Boolean(step && step.opType === 'update' && !codeBlockService.getCodeContentById(id));
|
||||
};
|
||||
|
||||
/** 目标数据已被删除、无法回滚时的统一提示。 */
|
||||
const showRevertTargetMissing = () => {
|
||||
tMagicMessage.error('回滚失败:该记录对应的数据已被删除');
|
||||
};
|
||||
|
||||
const onPageRevert = (index: number) => {
|
||||
if (isPageRevertTargetMissing(index)) {
|
||||
showRevertTargetMissing();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return runRevert(buildPageDiffPayload(index)).then((result) => (result ? editorService.revertPageStep(index) : null));
|
||||
};
|
||||
|
||||
const onDataSourceRevert = (id: string | number, index: number) => {
|
||||
if (isDataSourceRevertTargetMissing(id, index)) {
|
||||
showRevertTargetMissing();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return runRevert(buildDataSourceDiffPayload(id, index)).then((result) =>
|
||||
result ? dataSourceService.revert(id, index) : null,
|
||||
);
|
||||
};
|
||||
|
||||
const onCodeBlockRevert = (id: string | number, index: number) => {
|
||||
if (isCodeBlockRevertTargetMissing(id, index)) {
|
||||
showRevertTargetMissing();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return runRevert(buildCodeBlockDiffPayload(id, index)).then((result) =>
|
||||
result ? codeBlockService.revert(id, index) : null,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -420,14 +462,15 @@ const onDiffDialogClose = () => {
|
||||
* 用户取消时返回 false,调用方据此中止回滚。
|
||||
*/
|
||||
const confirmRevert = (): Promise<boolean> =>
|
||||
confirmClear('确定回滚该步骤吗?回滚会将该操作作为一条新记录反向应用(新增将被删除、删除将被还原),不影响后续历史记录。');
|
||||
confirmDialog(
|
||||
'确定回滚该步骤吗?回滚会将该操作作为一条新记录反向应用(新增将被删除、删除将被还原),不影响后续历史记录。',
|
||||
);
|
||||
|
||||
/**
|
||||
* 「清空历史记录」入口:先弹出二次确认,确认后清空对应类别的历史栈。
|
||||
* 仅删除撤销/重做记录,不会改动当前 DSL / 数据源 / 代码块本身。
|
||||
* 用户取消(confirm reject)时静默忽略。
|
||||
* 通用二次确认弹窗:清空历史 / 无法差异对比的回滚等会改变状态的操作,先弹出确认框,
|
||||
* 用户点击「确定」返回 true,取消(confirm reject)时返回 false 并静默忽略。
|
||||
*/
|
||||
const confirmClear = async (message: string): Promise<boolean> => {
|
||||
const confirmDialog = async (message: string): Promise<boolean> => {
|
||||
try {
|
||||
await tMagicMessageBox.confirm(message, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
@ -457,7 +500,7 @@ const syncIndexedDB = async () => {
|
||||
|
||||
const onPageClear = async () => {
|
||||
if (
|
||||
await confirmClear('确定清空当前页面的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。')
|
||||
await confirmDialog('确定清空当前页面的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。')
|
||||
) {
|
||||
historyService.clearPage();
|
||||
await syncIndexedDB();
|
||||
@ -465,14 +508,18 @@ const onPageClear = async () => {
|
||||
};
|
||||
|
||||
const onDataSourceClear = async () => {
|
||||
if (await confirmClear('确定清空数据源的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。')) {
|
||||
if (
|
||||
await confirmDialog('确定清空数据源的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。')
|
||||
) {
|
||||
historyService.clearDataSource();
|
||||
await syncIndexedDB();
|
||||
}
|
||||
};
|
||||
|
||||
const onCodeBlockClear = async () => {
|
||||
if (await confirmClear('确定清空代码块的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。')) {
|
||||
if (
|
||||
await confirmDialog('确定清空代码块的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。')
|
||||
) {
|
||||
historyService.clearCodeBlock();
|
||||
await syncIndexedDB();
|
||||
}
|
||||
|
||||
@ -82,25 +82,32 @@ type MoveItem = { node: MNode; parent: MContainer; pageForOp: { name: string; id
|
||||
*/
|
||||
const describeStepForRevert = (step: StepValue): string => {
|
||||
const items = step.diff ?? [];
|
||||
// 在可读名后拼接组件 id,便于在历史面板中精确定位被回滚的组件;id 缺失时退化为仅展示名称。
|
||||
const withId = (node: MNode | undefined, label: string): string => {
|
||||
const id = node?.id;
|
||||
if (id === undefined || id === null || `${id}` === '') return label;
|
||||
return label ? `${label}(id: ${id})` : `id: ${id}`;
|
||||
};
|
||||
switch (step.opType) {
|
||||
case 'add': {
|
||||
const count = items.length;
|
||||
const node = items[0]?.newSchema;
|
||||
const label = node?.name || node?.type || (node?.id !== undefined ? `${node.id}` : '');
|
||||
return `撤回新增 ${count} 个节点${count === 1 && label ? `(${label})` : ''}`;
|
||||
const label = node?.name || node?.type || '';
|
||||
return `撤回新增 ${count} 个节点${count === 1 ? `(${withId(node, label)})` : ''}`;
|
||||
}
|
||||
case 'remove': {
|
||||
const count = items.length;
|
||||
const node = items[0]?.oldSchema;
|
||||
const label = node?.name || node?.type || (node?.id !== undefined ? `${node.id}` : '');
|
||||
return `还原已删除的 ${count} 个节点${count === 1 && label ? `(${label})` : ''}`;
|
||||
const label = node?.name || node?.type || '';
|
||||
return `还原已删除的 ${count} 个节点${count === 1 ? `(${withId(node, label)})` : ''}`;
|
||||
}
|
||||
case 'update':
|
||||
default: {
|
||||
if (items.length === 1) {
|
||||
const { newSchema, oldSchema, changeRecords } = items[0];
|
||||
const target =
|
||||
newSchema?.name || newSchema?.type || oldSchema?.name || oldSchema?.type || `${newSchema?.id ?? ''}`;
|
||||
const node = newSchema || oldSchema;
|
||||
const label = newSchema?.name || newSchema?.type || oldSchema?.name || oldSchema?.type || '';
|
||||
const target = withId(node, label);
|
||||
const propPath = changeRecords?.[0]?.propPath;
|
||||
return propPath ? `还原 ${target} · ${propPath}` : `还原 ${target}`;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user