@tmagic/editor 1.8.0-beta.7 → 1.8.0-manmanyu.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +13 -3
  2. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +6 -5
  3. package/dist/es/components/ContentMenu.vue_vue_type_script_setup_true_lang.js +11 -4
  4. package/dist/es/components/FloatingBox.vue_vue_type_script_setup_true_lang.js +9 -4
  5. package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +4 -1
  6. package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +4 -1
  7. package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +68 -43
  8. package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +5 -1
  9. package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +4 -1
  10. package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +10 -7
  11. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +4 -1
  12. package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +4 -1
  13. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +4 -1
  14. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +4 -1
  15. package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +4 -1
  16. package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +4 -1
  17. package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +8 -2
  18. package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +101 -64
  19. package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +4 -1
  20. package/dist/es/fields/PageFragmentSelect.vue_vue_type_script_setup_true_lang.js +4 -1
  21. package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +5 -4
  22. package/dist/es/fields/StyleSetter/components/BackgroundPosition.vue_vue_type_script_setup_true_lang.js +4 -1
  23. package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +1 -1
  24. package/dist/es/fields/StyleSetter/icons/align-items/Center.js +31 -0
  25. package/dist/es/fields/StyleSetter/icons/align-items/FlexEnd.js +31 -0
  26. package/dist/es/fields/StyleSetter/icons/align-items/FlexStart.js +31 -0
  27. package/dist/es/fields/StyleSetter/icons/align-items/SpaceAround.js +50 -0
  28. package/dist/es/fields/StyleSetter/icons/align-items/SpaceBetween.js +50 -0
  29. package/dist/es/fields/StyleSetter/icons/background-repeat/NoRepeat.js +6 -6
  30. package/dist/es/fields/StyleSetter/icons/background-repeat/Repeat.js +5 -2
  31. package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatX.js +28 -5
  32. package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatY.js +31 -5
  33. package/dist/es/fields/StyleSetter/icons/flex-direction/Column.js +10 -2
  34. package/dist/es/fields/StyleSetter/icons/flex-direction/ColumnReverse.js +10 -2
  35. package/dist/es/fields/StyleSetter/icons/flex-direction/Row.js +10 -2
  36. package/dist/es/fields/StyleSetter/icons/flex-direction/RowReverse.js +10 -2
  37. package/dist/es/fields/StyleSetter/icons/justify-content/Center.js +16 -2
  38. package/dist/es/fields/StyleSetter/icons/justify-content/FlexEnd.js +18 -2
  39. package/dist/es/fields/StyleSetter/icons/justify-content/FlexStart.js +18 -2
  40. package/dist/es/fields/StyleSetter/icons/justify-content/SpaceAround.js +36 -2
  41. package/dist/es/fields/StyleSetter/icons/justify-content/SpaceBetween.js +32 -2
  42. package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +11 -6
  43. package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +6 -3
  44. package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +82 -46
  45. package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +4 -1
  46. package/dist/es/icons/DatasourceIcon.js +17 -0
  47. package/dist/es/index.js +5 -3
  48. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +14 -10
  49. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +38 -26
  50. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +5 -1
  51. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +135 -288
  52. package/dist/es/layouts/history-list/composables.js +23 -55
  53. package/dist/es/layouts/history-list/useHistoryList.js +56 -0
  54. package/dist/es/layouts/history-list/useHistoryRevert.js +304 -0
  55. package/dist/es/services/history.js +18 -1
  56. package/dist/es/style.css +306 -37
  57. package/dist/es/utils/const.js +1 -1
  58. package/dist/es/utils/props.js +137 -31
  59. package/dist/style.css +306 -37
  60. package/dist/themes/magic-admin.css +2316 -0
  61. package/dist/tmagic-editor.umd.cjs +4427 -2871
  62. package/package.json +7 -7
  63. package/src/Editor.vue +13 -1
  64. package/src/components/CompareForm.vue +14 -6
  65. package/src/components/ContentMenu.vue +10 -2
  66. package/src/components/FloatingBox.vue +13 -2
  67. package/src/editorProps.ts +9 -0
  68. package/src/fields/CodeSelect.vue +51 -40
  69. package/src/fields/CodeSelectCol.vue +1 -0
  70. package/src/fields/DataSourceFieldSelect/Index.vue +14 -5
  71. package/src/fields/DisplayConds.vue +4 -1
  72. package/src/fields/EventSelect.vue +58 -32
  73. package/src/fields/StyleSetter/Index.vue +1 -4
  74. package/src/fields/StyleSetter/components/Border.vue +1 -1
  75. package/src/fields/StyleSetter/icons/align-items/Center.vue +19 -0
  76. package/src/fields/StyleSetter/icons/align-items/FlexEnd.vue +19 -0
  77. package/src/fields/StyleSetter/icons/align-items/FlexStart.vue +19 -0
  78. package/src/fields/StyleSetter/icons/align-items/SpaceAround.vue +34 -0
  79. package/src/fields/StyleSetter/icons/align-items/SpaceBetween.vue +34 -0
  80. package/src/fields/StyleSetter/icons/align-items/index copy.ts +5 -0
  81. package/src/fields/StyleSetter/icons/align-items/index.ts +5 -0
  82. package/src/fields/StyleSetter/icons/background-repeat/NoRepeat.vue +20 -4
  83. package/src/fields/StyleSetter/icons/background-repeat/Repeat.vue +10 -28
  84. package/src/fields/StyleSetter/icons/background-repeat/RepeatX.vue +4 -5
  85. package/src/fields/StyleSetter/icons/background-repeat/RepeatY.vue +4 -5
  86. package/src/fields/StyleSetter/icons/flex-direction/Column.vue +4 -3
  87. package/src/fields/StyleSetter/icons/flex-direction/ColumnReverse.vue +4 -3
  88. package/src/fields/StyleSetter/icons/flex-direction/Row.vue +4 -3
  89. package/src/fields/StyleSetter/icons/flex-direction/RowReverse.vue +4 -3
  90. package/src/fields/StyleSetter/icons/justify-content/Center.vue +5 -2
  91. package/src/fields/StyleSetter/icons/justify-content/FlexEnd.vue +15 -2
  92. package/src/fields/StyleSetter/icons/justify-content/FlexStart.vue +15 -2
  93. package/src/fields/StyleSetter/icons/justify-content/SpaceAround.vue +28 -3
  94. package/src/fields/StyleSetter/icons/justify-content/SpaceBetween.vue +28 -2
  95. package/src/fields/StyleSetter/pro/Background.vue +11 -5
  96. package/src/fields/StyleSetter/pro/Font.vue +3 -3
  97. package/src/fields/StyleSetter/pro/Layout.vue +105 -28
  98. package/src/fields/UISelect.vue +1 -1
  99. package/src/icons/DatasourceIcon.vue +7 -0
  100. package/src/index.ts +2 -0
  101. package/src/layouts/Framework.vue +7 -1
  102. package/src/layouts/NavMenu.vue +2 -2
  103. package/src/layouts/history-list/GroupRow.vue +10 -0
  104. package/src/layouts/history-list/HistoryDiffDialog.vue +6 -1
  105. package/src/layouts/history-list/HistoryListPanel.vue +53 -250
  106. package/src/layouts/history-list/PageTab.vue +4 -4
  107. package/src/layouts/history-list/composables.ts +52 -90
  108. package/src/layouts/history-list/useHistoryList.ts +60 -0
  109. package/src/layouts/history-list/useHistoryRevert.ts +400 -0
  110. package/src/services/codeBlock.ts +30 -29
  111. package/src/services/dataSource.ts +37 -37
  112. package/src/services/editor.ts +32 -29
  113. package/src/services/history.ts +340 -430
  114. package/src/theme/code-block.scss +37 -0
  115. package/src/theme/common/var.scss +1 -1
  116. package/src/theme/component-list-panel.scss +2 -2
  117. package/src/theme/data-source-field.scss +4 -0
  118. package/src/theme/display-conds.scss +11 -0
  119. package/src/theme/event.scss +55 -1
  120. package/src/theme/history-list-panel.scss +23 -9
  121. package/src/theme/props-panel.scss +2 -1
  122. package/src/theme/style-setter/border.scss +14 -5
  123. package/src/theme/theme.scss +1 -0
  124. package/src/theme/themes/magic-admin/index.scss +125 -0
  125. package/src/type.ts +174 -92
  126. package/src/utils/const.ts +2 -1
  127. package/src/utils/history.ts +52 -67
  128. package/src/utils/props.ts +138 -9
  129. package/types/index.d.ts +156 -13
@@ -18,7 +18,7 @@
18
18
  <TMagicTabs v-model="activeTab" class="m-editor-history-list-tabs">
19
19
  <component
20
20
  :is="tabPaneComponent?.component || 'el-tab-pane'"
21
- v-bind="tabPaneComponent?.props({ name: 'page', label: `页面 (${pageGroups.length})` }) || {}"
21
+ v-bind="tabPaneComponent?.props({ name: 'page', label: `${pageName} (${pageGroups.length})` }) || {}"
22
22
  >
23
23
  <PageTab
24
24
  :list="pageGroupsDisplay"
@@ -37,7 +37,10 @@
37
37
  <component
38
38
  v-if="!disabledDataSource"
39
39
  :is="tabPaneComponent?.component || 'el-tab-pane'"
40
- v-bind="tabPaneComponent?.props({ name: 'data-source', label: `数据源 (${dataSourceGroups.length})` }) || {}"
40
+ v-bind="
41
+ tabPaneComponent?.props({ name: 'data-source', label: `${dataSourceName} (${dataSourceGroups.length})` }) ||
42
+ {}
43
+ "
41
44
  >
42
45
  <BucketTab
43
46
  :config="dataSourceConfig"
@@ -55,7 +58,9 @@
55
58
  <component
56
59
  v-if="!disabledCodeBlock"
57
60
  :is="tabPaneComponent?.component || 'el-tab-pane'"
58
- v-bind="tabPaneComponent?.props({ name: 'code-block', label: `代码块 (${codeBlockGroups.length})` }) || {}"
61
+ v-bind="
62
+ tabPaneComponent?.props({ name: 'code-block', label: `${codeBlockName} (${codeBlockGroups.length})` }) || {}
63
+ "
59
64
  >
60
65
  <BucketTab
61
66
  :config="codeBlockConfig"
@@ -91,9 +96,6 @@
91
96
  </TMagicTooltip>
92
97
  </template>
93
98
  </TMagicPopover>
94
-
95
- <HistoryDiffDialog ref="diffDialog" :extend-state="extendFormState" />
96
- <HistoryDiffDialog ref="confirmDialog" :is-confirm="true" :extend-state="extendFormState" />
97
99
  </template>
98
100
 
99
101
  <script lang="ts" setup>
@@ -118,18 +120,10 @@
118
120
  * (通过 title / prefix / describe* / isStepDiffable 注入差异)。
119
121
  * 共享的描述生成与折叠状态在 composables.ts 中维护。
120
122
  */
121
- import { computed, inject, markRaw, ref, useTemplateRef, watch } from 'vue';
123
+ import { computed, inject, markRaw, ref, watch } from 'vue';
122
124
  import { Clock, Close } from '@element-plus/icons-vue';
123
125
 
124
- import {
125
- getDesignConfig,
126
- TMagicButton,
127
- tMagicMessage,
128
- tMagicMessageBox,
129
- TMagicPopover,
130
- TMagicTabs,
131
- TMagicTooltip,
132
- } from '@tmagic/design';
126
+ import { getDesignConfig, TMagicButton, tMagicMessage, TMagicPopover, TMagicTabs, TMagicTooltip } from '@tmagic/design';
133
127
  import type { FormState } from '@tmagic/form';
134
128
 
135
129
  import MIcon from '@editor/components/Icon.vue';
@@ -138,15 +132,15 @@ import type {
138
132
  BaseStepValue,
139
133
  CodeBlockStepValue,
140
134
  DataSourceStepValue,
141
- DiffDialogPayload,
142
135
  HistoryBucketConfig,
143
136
  HistoryListExtraTab,
144
137
  } from '@editor/type';
145
138
 
146
139
  import BucketTab from './BucketTab.vue';
147
- import { describeStep, isSingleDiffStepRevertable, useHistoryList } from './composables';
148
- import HistoryDiffDialog from './HistoryDiffDialog.vue';
140
+ import { confirmHistoryAction, describeStep, isSingleDiffStepRevertable } from './composables';
149
141
  import PageTab from './PageTab.vue';
142
+ import { useHistoryList } from './useHistoryList';
143
+ import { useHistoryRevert } from './useHistoryRevert';
150
144
 
151
145
  defineOptions({
152
146
  name: 'MEditorHistoryListPanel',
@@ -173,8 +167,9 @@ const extraTabs = inject<HistoryListExtraTab[]>('historyListExtraTabs', []);
173
167
  /** label 支持字符串或函数,函数形式便于展示动态数量等内容。 */
174
168
  const resolveTabLabel = (tab: HistoryListExtraTab) => (typeof tab.label === 'function' ? tab.label() : tab.label);
175
169
 
170
+ const services = useServices();
176
171
  const { editorService, dataSourceService, codeBlockService, historyService, propsService, stageOverlayService } =
177
- useServices();
172
+ services;
178
173
 
179
174
  /**
180
175
  * 数据源 / 代码块功能可被业务方通过 `disabledDataSource` / `disabledCodeBlock` 禁用,
@@ -214,7 +209,15 @@ const {
214
209
  * 当前活动页的「加载/初始」标记记录(设置 root 时生成),透传给 PageTab 的底部初始行展示。
215
210
  * 基于 historyService 的 reactive state 派生,活动页切换或标记写入后自动刷新。
216
211
  */
217
- const pageMarker = computed(() => historyService.getPageMarker());
212
+ const pageMarker = computed(() => historyService.getMarker('page', editorService.get('page')?.id));
213
+
214
+ /**
215
+ * 各历史类型的展示名称(页面 / 数据源 / 代码块),由 historyService.state.stepNames 配置,
216
+ * 业务方可通过 historyService.setStepName / registerStepType 覆盖。基于 reactive state 派生,配置变更后自动刷新。
217
+ */
218
+ const pageName = computed(() => historyService.getStepName('page'));
219
+ const dataSourceName = computed(() => historyService.getStepName('dataSource'));
220
+ const codeBlockName = computed(() => historyService.getStepName('codeBlock'));
218
221
 
219
222
  /** 代码块 step 仅 update(前后 content 都存在)时可查看差异。 */
220
223
  const isStepDiffable = (step: BaseStepValue) => Boolean(step.diff?.[0]?.oldSchema && step.diff?.[0]?.newSchema);
@@ -222,23 +225,26 @@ const isStepDiffable = (step: BaseStepValue) => Boolean(step.diff?.[0]?.oldSchem
222
225
  /**
223
226
  * 数据源 / 代码块两类 bucket 历史的整体渲染配置:把 title / prefix 与各自的描述、
224
227
  * 可差异、可回滚判定收敛为单一对象整体注入 BucketTab,组件内部按需读取。
228
+ * title / 描述回退名取自可配置的展示名称,故用 computed 使其随 stepNames 变更刷新。
225
229
  */
226
230
  // 数据源/代码块不做相邻合并,每组恒为单步,省略 describeGroup,由 toRowGroup 回退到 describeStep。
227
- const dataSourceConfig: HistoryBucketConfig<DataSourceStepValue> = {
228
- title: '数据源',
231
+ const dataSourceConfig = computed<HistoryBucketConfig<DataSourceStepValue>>(() => ({
232
+ title: dataSourceName.value,
229
233
  prefix: 'ds',
230
- describeStep: (step: DataSourceStepValue): string => describeStep(step, (schema) => schema?.title, '数据源'),
234
+ describeStep: (step: DataSourceStepValue): string =>
235
+ describeStep(step, (schema) => schema?.title, dataSourceName.value),
231
236
  isStepDiffable,
232
237
  isStepRevertable: isSingleDiffStepRevertable,
233
- };
238
+ }));
234
239
 
235
- const codeBlockConfig: HistoryBucketConfig<CodeBlockStepValue> = {
236
- title: '代码块',
240
+ const codeBlockConfig = computed<HistoryBucketConfig<CodeBlockStepValue>>(() => ({
241
+ title: codeBlockName.value,
237
242
  prefix: 'cb',
238
- describeStep: (step: CodeBlockStepValue): string => describeStep(step, (content) => content?.name, '代码块'),
243
+ describeStep: (step: CodeBlockStepValue): string =>
244
+ describeStep(step, (content) => content?.name, codeBlockName.value),
239
245
  isStepDiffable,
240
246
  isStepRevertable: isSingleDiffStepRevertable,
241
- };
247
+ }));
242
248
 
243
249
  /** 把"目标 step 索引"翻译成"目标 cursor"(已应用步骤数量)。 */
244
250
  const indexToCursor = (index: number) => index + 1;
@@ -254,7 +260,7 @@ const onPageGoto = (index: number) => {
254
260
  * - 该 step 涉及的节点都已不存在(如删除记录、被撤销的新增)时给出提示,不做选中。
255
261
  */
256
262
  const onPageSelect = async (index: number) => {
257
- const step = historyService.getPageStepList()[index]?.step;
263
+ const step = historyService.getStepList('page', editorService.get('page')?.id)[index]?.step;
258
264
  if (!step) return;
259
265
  const targetId = (step.diff ?? [])
260
266
  .map((item) => item.newSchema?.id ?? item.oldSchema?.id)
@@ -294,222 +300,13 @@ const onCodeBlockGotoInitial = (id: string | number) => {
294
300
  codeBlockService.goto(id, 0);
295
301
  };
296
302
 
297
- const diffDialogRef = useTemplateRef<InstanceType<typeof HistoryDiffDialog>>('diffDialog');
298
- const confirmDialogRef = useTemplateRef<InstanceType<typeof HistoryDiffDialog>>('confirmDialog');
299
-
300
- /**
301
- * 三类历史(页面 / 数据源 / 代码块)差异弹窗入参的构造差异,收敛为一份配置:
302
- * 仅「分组来源、当前值读取、类型 / 展示名提取」不同,定位 step、校验前后值、组装 payload 的流程共用。
303
- */
304
- interface DiffPayloadSource {
305
- /** 表单类别:节点 / 数据源 / 代码块。 */
306
- category: DiffDialogPayload['category'];
307
- /** 该类别按时间正序的历史分组列表(含已撤销)。 */
308
- groups: () => { id?: string | number; steps: { index: number; step: { diff?: any[] } }[] }[];
309
- /** 读取目标当前实际值,用于「与当前对比」;不存在时返回空即禁用对比。 */
310
- getCurrent: (_id: string | number) => Record<string, any> | null | undefined;
311
- /** 由新/旧快照提取展示名(含各自的兜底,如节点回退 type、数据源 / 代码块回退 id)。 */
312
- resolveLabel: (_newSchema: Record<string, any>, _oldSchema: Record<string, any>, _id: string | number) => string;
313
- /** 由新/旧快照提取类型;代码块无 type 字段则不传。 */
314
- resolveType?: (_newSchema: Record<string, any>, _oldSchema: Record<string, any>) => string;
315
- }
316
-
317
- /**
318
- * 构造差异弹窗入参:仅 update(前后值都存在)可对比。
319
- * - 页面(无 id):在全部分组中按 index 定位 step,目标 id 取自快照;
320
- * - 数据源 / 代码块(带 id):先匹配分组 id 再按 index 定位。
321
- * 无可对比内容(多节点 / add / remove)或定位不到时返回 null。
322
- */
323
- const buildDiffPayload = (source: DiffPayloadSource, index: number, id?: string | number): DiffDialogPayload | null => {
324
- for (const group of source.groups()) {
325
- if (id !== undefined && group.id !== id) continue;
326
- const step = group.steps.find((s) => s.index === index)?.step;
327
- if (!step) continue;
328
- const oldSchema = step.diff?.[0]?.oldSchema as Record<string, any> | undefined;
329
- const newSchema = step.diff?.[0]?.newSchema as Record<string, any> | undefined;
330
- if (!oldSchema || !newSchema) return null;
331
- const targetId = id ?? newSchema.id ?? oldSchema.id;
332
- const type = source.resolveType?.(newSchema, oldSchema);
333
- return {
334
- category: source.category,
335
- ...(type !== undefined ? { type } : {}),
336
- lastValue: oldSchema,
337
- value: newSchema,
338
- currentValue: (targetId !== undefined ? source.getCurrent(targetId) : null) || null,
339
- targetLabel: source.resolveLabel(newSchema, oldSchema, targetId),
340
- id: targetId,
341
- };
342
- }
343
- return null;
344
- };
345
-
346
- const buildPageDiffPayload = (index: number): DiffDialogPayload | null =>
347
- buildDiffPayload(
348
- {
349
- category: 'node',
350
- groups: () => historyService.getPageHistoryGroups(),
351
- getCurrent: (id) => editorService.getNodeById(id) as Record<string, any> | null,
352
- resolveType: (n, o) => n.type || o.type || '',
353
- resolveLabel: (n, o) => n.name || o.name || n.type || o.type || '',
354
- },
355
- index,
356
- );
357
-
358
- const buildDataSourceDiffPayload = (id: string | number, index: number): DiffDialogPayload | null =>
359
- buildDiffPayload(
360
- {
361
- category: 'data-source',
362
- groups: () => historyService.getDataSourceHistoryGroups(),
363
- getCurrent: (id) => dataSourceService.getDataSourceById(`${id}`) as Record<string, any> | null,
364
- resolveType: (n, o) => n.type || o.type || 'base',
365
- resolveLabel: (n, o, id) => n.title || o.title || `${id}`,
366
- },
367
- index,
368
- id,
369
- );
370
-
371
- const buildCodeBlockDiffPayload = (id: string | number, index: number): DiffDialogPayload | null =>
372
- buildDiffPayload(
373
- {
374
- category: 'code-block',
375
- groups: () => historyService.getCodeBlockHistoryGroups(),
376
- getCurrent: (id) => codeBlockService.getCodeContentById(id) as Record<string, any> | null,
377
- resolveLabel: (n, o, id) => n.name || o.name || `${id}`,
378
- },
379
- index,
380
- id,
381
- );
382
-
383
- const onPageDiff = (index: number) => {
384
- const payload = buildPageDiffPayload(index);
385
- if (payload) diffDialogRef.value?.open(payload);
386
- };
387
-
388
- const onDataSourceDiff = (id: string | number, index: number) => {
389
- const payload = buildDataSourceDiffPayload(id, index);
390
- if (payload) diffDialogRef.value?.open(payload);
391
- };
392
-
393
- const onCodeBlockDiff = (id: string | number, index: number) => {
394
- const payload = buildCodeBlockDiffPayload(id, index);
395
- if (payload) diffDialogRef.value?.open(payload);
396
- };
397
-
398
- /**
399
- * 「回滚」统一入口:把目标历史步骤的修改作为一次新操作反向应用(类 git revert),
400
- * 不破坏原有栈结构。各 service 内部完成反向 + 入栈,并自带描述用于面板展示。
401
- *
402
- * 交互:
403
- * - 可差异对比的步骤(单节点 / 单实体 update):弹出差异弹窗供用户确认,点「确定回滚」再执行;
404
- * - 无法对比的步骤(add / remove / 多节点更新,payload 为 null):弹出普通二次确认框,确认后执行。
405
- *
406
- * 页面 / 数据源 / 代码块三类回滚仅「差异入参构造」与「实际 revert 调用」不同,
407
- * 由调用方分别传入 payload 与 revert,公共的弹窗 / 确认流程在此收敛。
408
- */
409
- const runRevert = (payload: DiffDialogPayload | null): Promise<boolean> => {
410
- if (payload && confirmDialogRef.value) {
411
- return confirmDialogRef.value.confirm(payload);
412
- }
413
- return confirmRevert();
414
- };
415
-
416
- /**
417
- * 回滚前置校验:若该历史步骤回滚所依赖的目标数据已被删除,则无法回滚。
418
- * - update(把旧值写回):被修改的目标必须仍存在;
419
- * - 页面 remove(还原被删节点):被删节点的原父容器必须仍存在,否则无处插回;
420
- * add(回滚即删除)即使目标已不在,也已达成「删除」目的,不视为失败。
421
- *
422
- * 命中时弹出「回滚失败」提示并返回 true,调用方据此中止本次回滚。
423
- */
424
- const isPageRevertTargetMissing = (index: number): boolean => {
425
- const step = historyService.getPageStepList()[index]?.step;
426
- if (!step) return false;
427
- if (step.opType === 'update') {
428
- return (step.diff ?? []).some((item) => {
429
- const id = item.newSchema?.id ?? item.oldSchema?.id;
430
- return id !== undefined && !editorService.getNodeById(id, false);
431
- });
432
- }
433
- if (step.opType === 'remove') {
434
- return (step.diff ?? []).some(
435
- (item) => item.parentId !== undefined && !editorService.getNodeById(item.parentId, false),
436
- );
437
- }
438
- return false;
439
- };
440
-
441
- /** 数据源 update 步骤回滚时,对应数据源必须仍存在(已删除则无处写回旧值)。 */
442
- const isDataSourceRevertTargetMissing = (id: string | number, index: number): boolean => {
443
- const step = historyService.getDataSourceStepList(id)[index]?.step;
444
- return Boolean(step && step.opType === 'update' && !dataSourceService.getDataSourceById(`${id}`));
445
- };
446
-
447
- /** 代码块 update 步骤回滚时,对应代码块必须仍存在(已删除则无处写回旧值)。 */
448
- const isCodeBlockRevertTargetMissing = (id: string | number, index: number): boolean => {
449
- const step = historyService.getCodeBlockStepList(id)[index]?.step;
450
- return Boolean(step && step.opType === 'update' && !codeBlockService.getCodeContentById(id));
451
- };
452
-
453
- /** 目标数据已被删除、无法回滚时的统一提示。 */
454
- const showRevertTargetMissing = () => {
455
- tMagicMessage.error('回滚失败:该记录对应的数据已被删除');
456
- };
457
-
458
- const onPageRevert = (index: number) => {
459
- if (isPageRevertTargetMissing(index)) {
460
- showRevertTargetMissing();
461
- return Promise.resolve(null);
462
- }
463
- return runRevert(buildPageDiffPayload(index)).then((result) => (result ? editorService.revertPageStep(index) : null));
464
- };
465
-
466
- const onDataSourceRevert = (id: string | number, index: number) => {
467
- if (isDataSourceRevertTargetMissing(id, index)) {
468
- showRevertTargetMissing();
469
- return Promise.resolve(null);
470
- }
471
- return runRevert(buildDataSourceDiffPayload(id, index)).then((result) =>
472
- result ? dataSourceService.revert(id, index) : null,
473
- );
474
- };
475
-
476
- const onCodeBlockRevert = (id: string | number, index: number) => {
477
- if (isCodeBlockRevertTargetMissing(id, index)) {
478
- showRevertTargetMissing();
479
- return Promise.resolve(null);
480
- }
481
- return runRevert(buildCodeBlockDiffPayload(id, index)).then((result) =>
482
- result ? codeBlockService.revert(id, index) : null,
483
- );
484
- };
485
-
486
- /**
487
- * 「回滚」二次确认:新增 / 删除 / 多节点更新等无法做差异对比的步骤,
488
- * 不弹差异弹窗,改用一个普通确认框替代「确定回滚」按钮,避免点击后无任何提示直接执行。
489
- * 用户取消时返回 false,调用方据此中止回滚。
490
- */
491
- const confirmRevert = (): Promise<boolean> =>
492
- confirmDialog(
493
- '确定回滚该步骤吗?回滚会将该操作作为一条新记录反向应用(新增将被删除、删除将被还原),不影响后续历史记录。',
494
- );
495
-
496
303
  /**
497
- * 通用二次确认弹窗:清空历史 / 无法差异对比的回滚等会改变状态的操作,先弹出确认框,
498
- * 用户点击「确定」返回 true,取消(confirm reject)时返回 false 并静默忽略。
304
+ * 「单步回滚」与「查看差异」的完整逻辑收敛到 useHistoryRevert,面板与业务方共用:
305
+ * 二者均由 useHistoryRevert 内部按需动态挂载 HistoryDiffDialog,
306
+ * 业务方亦可直接 import useHistoryRevert(services) 调用,无需自行挂载任何弹窗。
499
307
  */
500
- const confirmDialog = async (message: string): Promise<boolean> => {
501
- try {
502
- await tMagicMessageBox.confirm(message, '提示', {
503
- confirmButtonText: '确定',
504
- cancelButtonText: '取消',
505
- type: 'warning',
506
- });
507
- return true;
508
- // eslint-disable-next-line no-unused-vars
509
- } catch (e) {
510
- return false;
511
- }
512
- };
308
+ const { onPageRevert, onDataSourceRevert, onCodeBlockRevert, onPageDiff, onDataSourceDiff, onCodeBlockDiff } =
309
+ useHistoryRevert(services, { extendState: extendFormState });
513
310
 
514
311
  /**
515
312
  * 把内存中(已清空对应类别后的)历史状态重新写回 IndexedDB,
@@ -527,27 +324,33 @@ const syncIndexedDB = async () => {
527
324
 
528
325
  const onPageClear = async () => {
529
326
  if (
530
- await confirmDialog('确定清空当前页面的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。')
327
+ await confirmHistoryAction(
328
+ '确定清空当前页面的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。',
329
+ )
531
330
  ) {
532
- historyService.clearPage();
331
+ historyService.clear('page', editorService.get('page')?.id);
533
332
  await syncIndexedDB();
534
333
  }
535
334
  };
536
335
 
537
336
  const onDataSourceClear = async () => {
538
337
  if (
539
- await confirmDialog('确定清空数据源的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。')
338
+ await confirmHistoryAction(
339
+ '确定清空数据源的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。',
340
+ )
540
341
  ) {
541
- historyService.clearDataSource();
342
+ historyService.clear('dataSource');
542
343
  await syncIndexedDB();
543
344
  }
544
345
  };
545
346
 
546
347
  const onCodeBlockClear = async () => {
547
348
  if (
548
- await confirmDialog('确定清空代码块的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。')
349
+ await confirmHistoryAction(
350
+ '确定清空代码块的历史记录吗?清空后将无法撤销/重做之前的操作,本地保存的记录也会一并删除。',
351
+ )
549
352
  ) {
550
- historyService.clearCodeBlock();
353
+ historyService.clear('codeBlock');
551
354
  await syncIndexedDB();
552
355
  }
553
356
  };
@@ -34,7 +34,7 @@ import { computed } from 'vue';
34
34
 
35
35
  import { TMagicScrollbar } from '@tmagic/design';
36
36
 
37
- import type { HistoryRowDescriptor, PageHistoryGroup, StepValue } from '@editor/type';
37
+ import type { HistoryGroup, HistoryRowDescriptor, StepValue } from '@editor/type';
38
38
 
39
39
  import type { HistoryRowGroup } from './composables';
40
40
  import {
@@ -53,7 +53,7 @@ defineOptions({
53
53
 
54
54
  const props = defineProps<{
55
55
  /** 当前活动页面的历史分组列表,已按时间倒序排好(最新一组在最前)。空数组时显示空态。 */
56
- list: PageHistoryGroup[];
56
+ list: HistoryGroup<StepValue>[];
57
57
  /**
58
58
  * 共享的折叠状态表(key -> 是否展开,缺省或 true 为展开、false 为收起),由顶层 panel 统一维护。
59
59
  * 本 tab 使用 `pg-${组内首步 index}` 作为 key——以稳定的 step 索引而非展示位置标识分组,
@@ -105,9 +105,9 @@ const descriptor: HistoryRowDescriptor<StepValue> = {
105
105
  isStepRevertable: isPageStepRevertable,
106
106
  };
107
107
 
108
- const rowKey = (group: PageHistoryGroup) => `pg-${group.steps[0]?.index}`;
108
+ const rowKey = (group: HistoryGroup<StepValue>) => `pg-${group.steps[0]?.index}`;
109
109
 
110
- const toRow = (group: PageHistoryGroup): HistoryRowGroup => toRowGroup(group, rowKey(group), descriptor);
110
+ const toRow = (group: HistoryGroup<StepValue>): HistoryRowGroup => toRowGroup(group, rowKey(group), descriptor);
111
111
 
112
112
  /**
113
113
  * 是否处于"初始状态"——即对应页面历史栈 cursor===0:
@@ -1,14 +1,12 @@
1
- import { computed, reactive } from 'vue';
2
-
1
+ import { tMagicMessageBox } from '@tmagic/design';
3
2
  import { datetimeFormatter } from '@tmagic/form';
4
3
 
5
- import { useServices } from '@editor/hooks/use-services';
6
4
  import type {
7
5
  BaseStepValue,
6
+ HistoryGroup,
8
7
  HistoryOpSource,
9
8
  HistoryOpType,
10
9
  HistoryRowDescriptor,
11
- PageHistoryGroup,
12
10
  StepValue,
13
11
  } from '@editor/type';
14
12
 
@@ -27,51 +25,49 @@ export interface HistoryBucketGroup<T extends BaseStepValue = BaseStepValue> {
27
25
  steps: { index: number; applied: boolean; isCurrent?: boolean; step: T }[];
28
26
  }
29
27
 
30
- /** GroupRow 渲染所需的单个子步视图模型(已由 {@link toRowGroup} 预先派生,组件内部不再触碰原始 step)。 */
31
- export interface HistoryRowStep {
32
- /** 该子步在所属栈中的稳定索引。 */
33
- index: number;
28
+ /**
29
+ * GroupRow 的组头部与子步共用的展示字段(均由 {@link toRowGroup} 预先派生)。
30
+ * 抽出公共部分避免 {@link HistoryRowStep} / {@link HistoryRowGroup} 重复声明,
31
+ * 也便于消费方用本类型收窄「组头 / 子步」通用渲染逻辑的入参。
32
+ */
33
+ export interface HistoryRowDisplay {
34
34
  /** 是否已应用(false 表示已被 undo,UI 灰态)。 */
35
35
  applied: boolean;
36
- /** 是否为当前所在步骤。 */
37
- isCurrent?: boolean;
38
- /** 是否为最近一次保存的记录。 */
39
- saved?: boolean;
40
- /** 子步描述文案。 */
36
+ /** 是否为当前所在步骤 / 分组。 */
37
+ isCurrent: boolean;
38
+ /** 描述文案。 */
41
39
  desc: string;
42
- /** 是否可查看差异。 */
43
- diffable?: boolean;
44
- /** 是否可回滚。 */
45
- revertable?: boolean;
46
40
  /** 操作途径。 */
47
41
  source?: HistoryOpSource;
42
+ /** 操作人。 */
43
+ operator?: string;
48
44
  /** 时间文案。 */
49
45
  time?: string;
50
46
  /** 时间的完整 title 提示。 */
51
47
  timeTitle?: string;
52
48
  }
53
49
 
50
+ /** GroupRow 渲染所需的单个子步视图模型(已由 {@link toRowGroup} 预先派生,组件内部不再触碰原始 step)。 */
51
+ export interface HistoryRowStep extends HistoryRowDisplay {
52
+ /** 该子步在所属栈中的稳定索引。 */
53
+ index: number;
54
+ /** 是否为最近一次保存的记录。 */
55
+ saved?: boolean;
56
+ /** 是否可查看差异。 */
57
+ diffable?: boolean;
58
+ /** 是否可回滚。 */
59
+ revertable?: boolean;
60
+ }
61
+
54
62
  /**
55
63
  * GroupRow 渲染所需的整组视图模型(由 {@link toRowGroup} 统一派生)。
56
64
  * 把原先 GroupRow 上十多个扁平 props 收敛为单一对象,header 信息与子步列表一并携带。
57
65
  */
58
- export interface HistoryRowGroup {
66
+ export interface HistoryRowGroup extends HistoryRowDisplay {
59
67
  /** 分组的稳定 key,作为 toggle 事件 payload 与折叠状态的索引。 */
60
68
  key: string;
61
- /** 组内最后一步是否已应用。 */
62
- applied: boolean;
63
- /** 是否为当前所在分组。 */
64
- isCurrent: boolean;
65
69
  /** 操作类型,用于徽标颜色与文案。 */
66
70
  opType: HistoryOpType;
67
- /** 组整体描述文案。 */
68
- desc: string;
69
- /** 组的操作途径(取组内最近一步)。 */
70
- source?: HistoryOpSource;
71
- /** 组头部时间文案(取组内最近一步)。 */
72
- time?: string;
73
- /** 组头部时间的完整 title 提示。 */
74
- timeTitle?: string;
75
71
  /** 子步列表(时间正序);其长度即合并步数,length > 1 即为合并组。 */
76
72
  subSteps: HistoryRowStep[];
77
73
  }
@@ -79,63 +75,6 @@ export interface HistoryRowGroup {
79
75
  /** 合并组默认展开;仅当 expanded[key] === false 时为收起。 */
80
76
  export const isHistoryGroupExpanded = (expanded: Record<string, boolean>, key: string) => expanded[key] !== false;
81
77
 
82
- /**
83
- * 历史记录面板共享逻辑:
84
- * - 暴露三类历史的聚合数据(页面 / 数据源 / 代码块);
85
- * - 提供折叠状态管理;
86
- * - 提供操作描述文案生成器。
87
- *
88
- * 所有数据基于 historyService 的 reactive state 派生,自动跟随历史变化刷新。
89
- */
90
- export const useHistoryList = () => {
91
- const { historyService } = useServices();
92
-
93
- /**
94
- * 折叠状态:key 形如 `pg-${组内首步 index}` / `ds-${id}-${组内首步 index}` / `cb-${id}-${组内首步 index}`。
95
- * 用组内首步的稳定 index(而非展示位置)作为 key,确保历史数据更新后已展开的分组状态保持不变。
96
- * 合并组默认展开;仅当值为 `false` 时表示收起。
97
- */
98
- const expanded = reactive<Record<string, boolean>>({});
99
- const toggleGroup = (key: string) => {
100
- expanded[key] = expanded[key] === false;
101
- };
102
-
103
- const pageGroups = computed(() => historyService.getPageHistoryGroups());
104
- const dataSourceGroups = computed(() => historyService.getDataSourceHistoryGroups());
105
- const codeBlockGroups = computed(() => historyService.getCodeBlockHistoryGroups());
106
-
107
- /** 页面 tab 倒序展示(最新一组在最上面)。 */
108
- const pageGroupsDisplay = computed(() => pageGroups.value.slice().reverse());
109
-
110
- /**
111
- * 把按时间正序的 group 列表,再按 id 聚拢成 bucket(同 id 的所有分组放一起)。
112
- * 每个 bucket 内部仍然按时间倒序展示(最近的操作最先看到)。
113
- */
114
- const groupByTarget = <G extends { id: string | number }>(groups: G[]) => {
115
- const map = new Map<string | number, G[]>();
116
- groups.forEach((g) => {
117
- const list = map.get(g.id) ?? [];
118
- list.push(g);
119
- map.set(g.id, list);
120
- });
121
- return Array.from(map.entries()).map(([id, gs]) => ({ id, groups: gs.slice().reverse() }));
122
- };
123
-
124
- const dataSourceGroupsByTarget = computed(() => groupByTarget(dataSourceGroups.value));
125
- const codeBlockGroupsByTarget = computed(() => groupByTarget(codeBlockGroups.value));
126
-
127
- return {
128
- expanded,
129
- toggleGroup,
130
- pageGroups,
131
- dataSourceGroups,
132
- codeBlockGroups,
133
- pageGroupsDisplay,
134
- dataSourceGroupsByTarget,
135
- codeBlockGroupsByTarget,
136
- };
137
- };
138
-
139
78
  /**
140
79
  * 历史面板的时间展示:
141
80
  * - 当天的记录只显示 `HH:mm:ss`;
@@ -203,7 +142,11 @@ export const sourceLabel = (source: HistoryOpSource = 'unknown'): string => {
203
142
  export const groupSource = (group: { steps: { step: { source?: HistoryOpSource } }[] }): HistoryOpSource | undefined =>
204
143
  group.steps[group.steps.length - 1]?.step.source;
205
144
 
206
- /** {@link toRowGroup} 接受的最小分组结构,PageHistoryGroup 与 HistoryBucketGroup 均满足。 */
145
+ /** 取一组历史步骤里最后一步(最近一次)的操作人,用于组头部展示。 */
146
+ export const groupOperator = (group: { steps: { step: { operator?: string } }[] }): string | undefined =>
147
+ group.steps[group.steps.length - 1]?.step.operator;
148
+
149
+ /** {@link toRowGroup} 接受的最小分组结构 */
207
150
  interface RowGroupInput<T extends BaseStepValue = BaseStepValue> {
208
151
  applied: boolean;
209
152
  isCurrent?: boolean;
@@ -232,17 +175,19 @@ export const toRowGroup = <T extends BaseStepValue = BaseStepValue>(
232
175
  opType: group.opType,
233
176
  desc: describeGroup ? describeGroup(group) : describeStep(lastStep),
234
177
  source: groupSource(group),
178
+ operator: groupOperator(group),
235
179
  time: formatHistoryTime(timestamp),
236
180
  timeTitle: formatHistoryFullTime(timestamp),
237
181
  subSteps: group.steps.map((s) => ({
238
182
  index: s.index,
239
183
  applied: s.applied,
240
- isCurrent: s.isCurrent,
184
+ isCurrent: Boolean(s.isCurrent),
241
185
  saved: s.step.saved,
242
186
  desc: describeStep(s.step),
243
187
  diffable: isStepDiffable ? isStepDiffable(s.step) : false,
244
188
  revertable: s.applied && (isStepRevertable ? isStepRevertable(s.step) : true),
245
189
  source: s.step.source,
190
+ operator: s.step.operator,
246
191
  time: formatHistoryTime(s.step.timestamp),
247
192
  timeTitle: formatHistoryFullTime(s.step.timestamp),
248
193
  })),
@@ -314,7 +259,7 @@ export const describePageStep = (step: StepValue): string => describeStep(step,
314
259
  * - 单步组:复用 describePageStep;
315
260
  * - 多步组(连续修改同一节点):展示节点名 + 涉及的前几个 propPath。
316
261
  */
317
- export const describePageGroup = (group: PageHistoryGroup) => {
262
+ export const describePageGroup = (group: HistoryGroup<StepValue>) => {
318
263
  const lastDesc = pickLastDescription(group.steps.map((s) => s.step.historyDescription));
319
264
  if (lastDesc) return lastDesc;
320
265
  if (group.steps.length === 1) return describePageStep(group.steps[0].step);
@@ -345,3 +290,20 @@ export const isSingleDiffStepRevertable = (step: BaseStepValue): boolean => {
345
290
  if (!item?.oldSchema || !item?.newSchema) return true;
346
291
  return Boolean(item.changeRecords?.length);
347
292
  };
293
+
294
+ /**
295
+ * 通用二次确认弹窗:清空历史 / 无法差异对比的回滚等会改变状态的操作,先弹出确认框,
296
+ * 用户点击「确定」返回 true,取消(confirm reject)时返回 false 并静默忽略。
297
+ */
298
+ export const confirmHistoryAction = async (message: string): Promise<boolean> => {
299
+ try {
300
+ await tMagicMessageBox.confirm(message, '提示', {
301
+ confirmButtonText: '确定',
302
+ cancelButtonText: '取消',
303
+ type: 'warning',
304
+ });
305
+ return true;
306
+ } catch {
307
+ return false;
308
+ }
309
+ };