@tmagic/editor 1.8.0-beta.2 → 1.8.0-beta.3

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 (45) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +9 -0
  2. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +32 -28
  3. package/dist/es/editorProps.js +2 -0
  4. package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +2 -5
  5. package/dist/es/hooks/use-stage.js +2 -1
  6. package/dist/es/index.js +3 -1
  7. package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +2 -5
  8. package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +33 -14
  9. package/dist/es/layouts/history-list/BucketTab.js +5 -0
  10. package/dist/es/layouts/history-list/{CodeBlockTab.vue_vue_type_script_setup_true_lang.js → BucketTab.vue_vue_type_script_setup_true_lang.js} +27 -16
  11. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +71 -53
  12. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +78 -28
  13. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +136 -66
  14. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +15 -9
  15. package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +12 -6
  16. package/dist/es/layouts/history-list/composables.js +20 -2
  17. package/dist/es/services/history.js +5 -2
  18. package/dist/es/style.css +44 -12
  19. package/dist/style.css +44 -12
  20. package/dist/tmagic-editor.umd.cjs +479 -326
  21. package/package.json +7 -7
  22. package/src/Editor.vue +8 -0
  23. package/src/components/CompareForm.vue +32 -19
  24. package/src/editorProps.ts +7 -0
  25. package/src/fields/CodeLink.vue +2 -5
  26. package/src/hooks/use-stage.ts +1 -0
  27. package/src/index.ts +2 -0
  28. package/src/layouts/CodeEditor.vue +2 -5
  29. package/src/layouts/history-list/Bucket.vue +53 -28
  30. package/src/layouts/history-list/BucketTab.vue +77 -0
  31. package/src/layouts/history-list/GroupRow.vue +95 -60
  32. package/src/layouts/history-list/HistoryDiffDialog.vue +51 -32
  33. package/src/layouts/history-list/HistoryListPanel.vue +161 -52
  34. package/src/layouts/history-list/InitialRow.vue +17 -6
  35. package/src/layouts/history-list/PageTab.vue +20 -6
  36. package/src/layouts/history-list/composables.ts +32 -1
  37. package/src/services/history.ts +3 -0
  38. package/src/theme/history-list-panel.scss +52 -13
  39. package/src/type.ts +90 -0
  40. package/types/index.d.ts +295 -135
  41. package/dist/es/layouts/history-list/CodeBlockTab.js +0 -5
  42. package/dist/es/layouts/history-list/DataSourceTab.js +0 -5
  43. package/dist/es/layouts/history-list/DataSourceTab.vue_vue_type_script_setup_true_lang.js +0 -62
  44. package/src/layouts/history-list/CodeBlockTab.vue +0 -61
  45. package/src/layouts/history-list/DataSourceTab.vue +0 -61
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.8.0-beta.2",
2
+ "version": "1.8.0-beta.3",
3
3
  "name": "@tmagic/editor",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -58,11 +58,11 @@
58
58
  "moveable": "^0.53.0",
59
59
  "serialize-javascript": "^7.0.0",
60
60
  "sortablejs": "^1.15.6",
61
- "@tmagic/design": "1.8.0-beta.2",
62
- "@tmagic/table": "1.8.0-beta.2",
63
- "@tmagic/utils": "1.8.0-beta.2",
64
- "@tmagic/form": "1.8.0-beta.2",
65
- "@tmagic/stage": "1.8.0-beta.2"
61
+ "@tmagic/design": "1.8.0-beta.3",
62
+ "@tmagic/form": "1.8.0-beta.3",
63
+ "@tmagic/stage": "1.8.0-beta.3",
64
+ "@tmagic/utils": "1.8.0-beta.3",
65
+ "@tmagic/table": "1.8.0-beta.3"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/events": "^3.0.3",
@@ -76,7 +76,7 @@
76
76
  "type-fest": "^5.2.0",
77
77
  "typescript": "^6.0.3",
78
78
  "vue": "^3.5.34",
79
- "@tmagic/core": "1.8.0-beta.2"
79
+ "@tmagic/core": "1.8.0-beta.3"
80
80
  },
81
81
  "peerDependenciesMeta": {
82
82
  "typescript": {
package/src/Editor.vue CHANGED
@@ -221,6 +221,7 @@ const stageOptions: StageOptions = {
221
221
  guidesOptions: props.guidesOptions,
222
222
  disabledMultiSelect: props.disabledMultiSelect,
223
223
  alwaysMultiSelect: props.alwaysMultiSelect,
224
+ disabledFlashTip: props.disabledFlashTip,
224
225
  beforeDblclick: props.beforeDblclick,
225
226
  };
226
227
 
@@ -237,6 +238,13 @@ provide('stageOptions', stageOptions);
237
238
  */
238
239
  provide('extendFormState', props.extendFormState);
239
240
 
241
+ /**
242
+ * 把历史记录面板的自定义扩展 tab 提供给深层的 HistoryListPanel(它挂在 NavMenu 中,
243
+ * 以 markRaw component 形式渲染,无法直接通过 props 透传)。业务方可借此在历史记录
244
+ * 面板内追加自定义模块的历史 tab。
245
+ */
246
+ provide('historyListExtraTabs', props.historyListExtraTabs);
247
+
240
248
  provide<EventBus>('eventBus', new EventEmitter());
241
249
 
242
250
  const propsPanelMountedHandler = (e: InstanceType<typeof FormPanel>) => {
@@ -24,20 +24,13 @@ import { type CodeBlockContent, type DataSourceSchema, HookType, type MNode } fr
24
24
  import { type FormConfig, type FormState, type FormValue, MForm } from '@tmagic/form';
25
25
 
26
26
  import { useServices } from '@editor/hooks/use-services';
27
+ import type { CompareCategory, CompareFormLoadConfig } from '@editor/type';
27
28
  import { getCodeBlockFormConfig } from '@editor/utils/code-block';
28
29
 
29
30
  defineOptions({
30
31
  name: 'MEditorCompareForm',
31
32
  });
32
33
 
33
- /**
34
- * 对比类型:
35
- * - node: 节点组件,按 `type` 从 propsService 获取属性表单配置
36
- * - data-source: 数据源,按 `type`(base/http/...) 从 dataSourceService 获取数据源表单配置
37
- * - code-block: 数据源代码块,使用内置的代码块表单配置
38
- */
39
- export type CompareCategory = 'node' | 'data-source' | 'code-block';
40
-
41
34
  const props = withDefaults(
42
35
  defineProps<{
43
36
  /** 当前值(修改后的值) */
@@ -68,6 +61,12 @@ const props = withDefaults(
68
61
  * 因此在差异对比场景下也需要透传,避免出现 `formState.xxx is undefined` 的运行时错误。
69
62
  */
70
63
  extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
64
+ /**
65
+ * 自定义 FormConfig 加载逻辑。传入后将接管内置的按 `category`(node/data-source/code-block)
66
+ * 取配置逻辑,调用方可根据业务自行返回(或异步返回)表单配置。可通过
67
+ * `ctx.defaultLoadConfig()` 复用默认结果再做二次加工。返回的 config 直接用于对比展示。
68
+ */
69
+ loadConfig?: CompareFormLoadConfig;
71
70
  }>(),
72
71
  {
73
72
  category: 'node',
@@ -153,22 +152,23 @@ const showDiff = ({ curValue, lastValue, config }: { curValue: any; lastValue: a
153
152
  return !isEqual(curValue, lastValue);
154
153
  };
155
154
 
156
- const loadConfig = async () => {
155
+ /**
156
+ * 内置的默认 FormConfig 加载逻辑:按 `category` 从对应 service / 工具取配置。
157
+ * 作为 ctx.defaultLoadConfig 透传给自定义 `loadConfig`,方便复用与二次加工。
158
+ */
159
+ const defaultLoadConfig = async (): Promise<FormConfig> => {
157
160
  switch (props.category) {
158
161
  case 'node': {
159
162
  if (!props.type) {
160
- config.value = [];
161
- return;
163
+ return [];
162
164
  }
163
- config.value = await propsService.getPropsConfig(props.type);
164
- break;
165
+ return await propsService.getPropsConfig(props.type);
165
166
  }
166
167
  case 'data-source': {
167
- config.value = dataSourceService.getFormConfig(props.type || 'base');
168
- break;
168
+ return dataSourceService.getFormConfig(props.type || 'base');
169
169
  }
170
170
  case 'code-block': {
171
- config.value = getCodeBlockFormConfig({
171
+ return getCodeBlockFormConfig({
172
172
  paramColConfig: codeBlockService.getParamsColConfig(),
173
173
  // 通过传入 dataSourceType 间接表达"是数据源代码块"——在对比场景下 props.dataSourceType
174
174
  // 由调用方按 step 上下文显式传入,未传则视为普通代码块,「执行时机」字段隐藏。
@@ -178,15 +178,28 @@ const loadConfig = async () => {
178
178
  // 对比模式只读,不需要校验/语法检查
179
179
  editable: false,
180
180
  });
181
- break;
182
181
  }
183
182
  default:
184
- config.value = [];
183
+ return [];
184
+ }
185
+ };
186
+
187
+ const loadConfig = async () => {
188
+ if (props.loadConfig) {
189
+ config.value = await props.loadConfig({
190
+ category: props.category,
191
+ type: props.type,
192
+ dataSourceType: props.dataSourceType,
193
+ defaultLoadConfig,
194
+ });
195
+ return;
185
196
  }
197
+
198
+ config.value = await defaultLoadConfig();
186
199
  };
187
200
 
188
201
  watch(
189
- [() => props.category, () => props.type, () => props.dataSourceType],
202
+ [() => props.category, () => props.type, () => props.dataSourceType, () => props.loadConfig],
190
203
  () => {
191
204
  loadConfig();
192
205
  },
@@ -15,6 +15,7 @@ import type {
15
15
  ComponentGroup,
16
16
  CustomContentMenuFunction,
17
17
  DatasourceTypeOption,
18
+ HistoryListExtraTab,
18
19
  IsExpandableFunction,
19
20
  MenuBarData,
20
21
  MenuButton,
@@ -86,6 +87,8 @@ export interface EditorProps {
86
87
  alwaysMultiSelect?: boolean;
87
88
  /** 禁用页面片 */
88
89
  disabledPageFragment?: boolean;
90
+ /** 禁用「非点击画布选中组件时(如从图层树、面包屑等外部选中),对选中区域做高亮闪烁提示」,默认 false(即默认开启闪烁) */
91
+ disabledFlashTip?: boolean;
89
92
  /** 禁用双击在浮层中单独编辑选中组件 */
90
93
  disabledStageOverlay?: boolean;
91
94
  /** 禁用属性配置面板右下角显示源码的按钮 */
@@ -125,6 +128,8 @@ export interface EditorProps {
125
128
  /** 组件树节点双击前的钩子函数,返回 false 则阻止默认的双击行为 */
126
129
  beforeLayerNodeDblclick?: (event: MouseEvent, data: TreeNodeData) => Promise<boolean | void> | boolean | void;
127
130
  extendFormState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
131
+ /** 历史记录面板的自定义扩展 tab,追加在内置的页面/数据源/代码块 tab 之后 */
132
+ historyListExtraTabs?: HistoryListExtraTab[];
128
133
  /** 页面顺序拖拽配置参数 */
129
134
  pageBarSortOptions?: PageBarSortOptions;
130
135
  /** 页面搜索函数 */
@@ -136,6 +141,7 @@ export const defaultEditorProps = {
136
141
  disabledMultiSelect: false,
137
142
  alwaysMultiSelect: false,
138
143
  disabledPageFragment: false,
144
+ disabledFlashTip: false,
139
145
  disabledStageOverlay: false,
140
146
  containerHighlightClassName: CONTAINER_HIGHLIGHT_CLASS_NAME,
141
147
  containerHighlightDuration: 800,
@@ -145,6 +151,7 @@ export const defaultEditorProps = {
145
151
  disabledCodeBlock: false,
146
152
  componentGroupList: () => [],
147
153
  datasourceList: () => [],
154
+ historyListExtraTabs: () => [],
148
155
  menu: () => ({ left: [], right: [] }),
149
156
  layerContentMenu: () => [],
150
157
  stageContentMenu: () => [],
@@ -4,11 +4,11 @@
4
4
 
5
5
  <script lang="ts" setup>
6
6
  import { computed, reactive, watch } from 'vue';
7
- import serialize from 'serialize-javascript';
8
7
 
9
8
  import type { CodeLinkConfig, FieldProps, MLink } from '@tmagic/form';
10
9
 
11
10
  import { getEditorConfig } from '@editor/utils/config';
11
+ import { serializeConfig } from '@editor/utils/editor';
12
12
 
13
13
  defineOptions({
14
14
  name: 'MFieldsCodeLink',
@@ -47,10 +47,7 @@ watch(
47
47
  () => props.model[props.name],
48
48
  (value) => {
49
49
  modelValue.form = {
50
- [props.name]: serialize(value, {
51
- space: 2,
52
- unsafe: true,
53
- }).replace(/"(\w+)":\s/g, '$1: '),
50
+ [props.name]: serializeConfig(value),
54
51
  };
55
52
  },
56
53
  {
@@ -48,6 +48,7 @@ export const useStage = (stageOptions: StageOptions) => {
48
48
  disabledMultiSelect: stageOptions.disabledMultiSelect,
49
49
  alwaysMultiSelect: stageOptions.alwaysMultiSelect,
50
50
  disabledRule: stageOptions.disabledRule,
51
+ disabledFlashTip: stageOptions.disabledFlashTip,
51
52
  });
52
53
 
53
54
  watch(
package/src/index.ts CHANGED
@@ -71,6 +71,8 @@ export { default as SplitView } from './components/SplitView.vue';
71
71
  export { default as Resizer } from './components/Resizer.vue';
72
72
  export { default as CodeBlockEditor } from './components/CodeBlockEditor.vue';
73
73
  export { default as CompareForm } from './components/CompareForm.vue';
74
+ export { default as HistoryListBucket } from './layouts/history-list/Bucket.vue';
75
+ export { default as HistoryDiffDialog } from './layouts/history-list/HistoryDiffDialog.vue';
74
76
  export { default as FloatingBox } from './components/FloatingBox.vue';
75
77
  export { default as Tree } from './components/Tree.vue';
76
78
  export { default as TreeNode } from './components/TreeNode.vue';
@@ -21,12 +21,12 @@ import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, useTe
21
21
  import { FullScreen } from '@element-plus/icons-vue';
22
22
  import { throttle } from 'lodash-es';
23
23
  import type * as Monaco from 'monaco-editor';
24
- import serialize from 'serialize-javascript';
25
24
 
26
25
  import { TMagicButton } from '@tmagic/design';
27
26
 
28
27
  import MIcon from '@editor/components/Icon.vue';
29
28
  import { getEditorConfig } from '@editor/utils/config';
29
+ import { serializeConfig } from '@editor/utils/editor';
30
30
  import loadMonaco from '@editor/utils/monaco-editor';
31
31
 
32
32
  defineOptions({
@@ -163,10 +163,7 @@ const toString = (v: string | any, language: string): string => {
163
163
  if (language === 'json') {
164
164
  value = JSON.stringify(v, null, 2);
165
165
  } else {
166
- value = serialize(v, {
167
- space: 2,
168
- unsafe: true,
169
- }).replace(/"(\w+)":\s/g, '$1: ');
166
+ value = serializeConfig(v);
170
167
  }
171
168
  } else {
172
169
  value = v;
@@ -8,13 +8,15 @@
8
8
 
9
9
  <ul class="m-editor-history-list-ul">
10
10
  <GroupRow
11
- v-for="(group, gIdx) in groups"
12
- :key="`${prefix}-${bucketId}-${gIdx}`"
13
- :group-key="`${prefix}-${bucketId}-${gIdx}`"
11
+ v-for="group in groups"
12
+ :key="`${prefix}-${bucketId}-${group.steps[0]?.index}`"
13
+ :group-key="`${prefix}-${bucketId}-${group.steps[0]?.index}`"
14
14
  :applied="group.applied"
15
15
  :merged="group.steps.length > 1"
16
16
  :op-type="group.opType"
17
17
  :desc="describeGroup(group)"
18
+ :time="formatHistoryTime(groupTimestamp(group))"
19
+ :time-title="formatHistoryFullTime(groupTimestamp(group))"
18
20
  :step-count="group.steps.length"
19
21
  :sub-steps="
20
22
  group.steps.map((s: any) => ({
@@ -24,10 +26,13 @@
24
26
  desc: describeStep(s.step),
25
27
  diffable: isStepDiffable ? isStepDiffable(s.step) : false,
26
28
  revertable: s.applied,
29
+ time: formatHistoryTime(s.step.timestamp),
30
+ timeTitle: formatHistoryFullTime(s.step.timestamp),
27
31
  }))
28
32
  "
29
33
  :is-current="group.isCurrent"
30
- :expanded="!!expanded[`${prefix}-${bucketId}-${gIdx}`]"
34
+ :expanded="!!expanded[`${prefix}-${bucketId}-${group.steps[0]?.index}`]"
35
+ :goto-enabled="gotoEnabled"
31
36
  @toggle="(key: string) => $emit('toggle', key)"
32
37
  @goto="(index: number) => $emit('goto', bucketId, index)"
33
38
  @diff-step="(index: number) => $emit('diff-step', bucketId, index)"
@@ -36,8 +41,14 @@
36
41
  <!--
37
42
  初始状态项:永远位于该 bucket 列表底部(同样按倒序展示,最底部 = 最早状态)。
38
43
  当 bucket 内所有 group 都未 applied 时即为当前位置。
44
+ showInitial=false 时不展示(用于没有"撤销到初始状态"语义的自定义历史,如业务模块历史)。
39
45
  -->
40
- <InitialRow :is-current="isInitial" @goto-initial="$emit('goto-initial', bucketId)" />
46
+ <InitialRow
47
+ v-if="showInitial !== false"
48
+ :is-current="isInitial"
49
+ :goto-enabled="gotoEnabled"
50
+ @goto-initial="$emit('goto-initial', bucketId)"
51
+ />
41
52
  </ul>
42
53
  </div>
43
54
  </template>
@@ -47,6 +58,7 @@ import { computed } from 'vue';
47
58
 
48
59
  import type { HistoryOpType } from '@editor/type';
49
60
 
61
+ import { formatHistoryFullTime, formatHistoryTime, groupTimestamp } from './composables';
50
62
  import GroupRow from './GroupRow.vue';
51
63
  import InitialRow from './InitialRow.vue';
52
64
 
@@ -54,29 +66,42 @@ defineOptions({
54
66
  name: 'MEditorHistoryListBucket',
55
67
  });
56
68
 
57
- const props = defineProps<{
58
- /** Bucket 标题,例如 "数据源" / "代码块",渲染在 bucket 头部。 */
59
- title: string;
60
- /** 当前 bucket 对应的目标 id(dataSource.id 或 codeBlock.id),同时用于组装子项的 key。 */
61
- bucketId: string | number;
62
- /** 子项 key 的命名空间前缀:`ds` 表示数据源,`cb` 表示代码块。与上层折叠状态 key 保持一致。 */
63
- prefix: 'ds' | 'cb';
64
- /** 当前 bucket 下的所有历史分组,按时间倒序展示(最近的操作在前)。 */
65
- groups: {
66
- applied: boolean;
67
- isCurrent?: boolean;
68
- opType: HistoryOpType;
69
- steps: { index: number; applied: boolean; isCurrent?: boolean; step: any }[];
70
- }[];
71
- /** 组级描述文案生成器,接收一个 group,返回展示文本。由父组件按业务类型注入。 */
72
- describeGroup: (_group: any) => string;
73
- /** 单步描述文案生成器,接收一个 step,返回展示文本。用于合并组展开后的子步列表。 */
74
- describeStep: (_step: any) => string;
75
- /** 判断某个 step 是否可查看差异(前后值都存在)。由父组件按业务类型注入;不传则一律不展示差异入口。 */
76
- isStepDiffable?: (_step: any) => boolean;
77
- /** 共享的折叠状态表(key -> 是否展开),由顶层 panel 统一维护以便跨 tab 复用。 */
78
- expanded: Record<string, boolean>;
79
- }>();
69
+ const props = withDefaults(
70
+ defineProps<{
71
+ /** Bucket 标题,例如 "数据源" / "代码块",渲染在 bucket 头部。 */
72
+ title: string;
73
+ /** 当前 bucket 对应的目标 id(dataSource.id 或 codeBlock.id),同时用于组装子项的 key。 */
74
+ bucketId: string | number;
75
+ /**
76
+ * 子项 key 的命名空间前缀:内置 `ds` 表示数据源,`cb` 表示代码块;
77
+ * 业务方复用 Bucket 时可传入自定义前缀(如 `mod`)。与上层折叠状态 key 保持一致。
78
+ */
79
+ prefix: string;
80
+ /** 是否展示底部「回到初始状态」入口,默认 true。无 undo cursor 语义的自定义历史可传 false 关闭。 */
81
+ showInitial?: boolean;
82
+ /** 当前 bucket 下的所有历史分组,按时间倒序展示(最近的操作在前)。 */
83
+ groups: {
84
+ applied: boolean;
85
+ isCurrent?: boolean;
86
+ opType: HistoryOpType;
87
+ steps: { index: number; applied: boolean; isCurrent?: boolean; step: any }[];
88
+ }[];
89
+ /** 组级描述文案生成器,接收一个 group,返回展示文本。由父组件按业务类型注入。 */
90
+ describeGroup: (_group: any) => string;
91
+ /** 单步描述文案生成器,接收一个 step,返回展示文本。用于合并组展开后的子步列表。 */
92
+ describeStep: (_step: any) => string;
93
+ /** 判断某个 step 是否可查看差异(前后值都存在)。由父组件按业务类型注入;不传则一律不展示差异入口。 */
94
+ isStepDiffable?: (_step: any) => boolean;
95
+ /** 共享的折叠状态表(key -> 是否展开),由顶层 panel 统一维护以便跨 tab 复用。 */
96
+ expanded: Record<string, boolean>;
97
+ /** 是否支持「跳转到该记录」(goto)。默认 true。 */
98
+ gotoEnabled?: boolean;
99
+ }>(),
100
+ {
101
+ showInitial: true,
102
+ gotoEnabled: true,
103
+ },
104
+ );
80
105
 
81
106
  defineEmits<{
82
107
  /** 透传子组件 GroupRow 的 toggle,由上层 panel 更新 expanded。 */
@@ -0,0 +1,77 @@
1
+ <template>
2
+ <div v-if="!buckets.length" class="m-editor-history-list-empty">暂无操作记录</div>
3
+ <TMagicScrollbar v-else max-height="360px">
4
+ <Bucket
5
+ v-for="bucket in buckets"
6
+ :key="`${prefix}-${bucket.id}`"
7
+ :title="title"
8
+ :bucket-id="bucket.id"
9
+ :prefix="prefix"
10
+ :groups="bucket.groups"
11
+ :describe-group="describeGroup"
12
+ :describe-step="describeStep"
13
+ :is-step-diffable="isStepDiffable"
14
+ :expanded="expanded"
15
+ :goto-enabled="gotoEnabled"
16
+ @toggle="(key: string) => $emit('toggle', key)"
17
+ @goto="(id: string | number, index: number) => $emit('goto', id, index)"
18
+ @goto-initial="(id: string | number) => $emit('goto-initial', id)"
19
+ @diff-step="(id: string | number, index: number) => $emit('diff-step', id, index)"
20
+ @revert-step="(id: string | number, index: number) => $emit('revert-step', id, index)"
21
+ />
22
+ </TMagicScrollbar>
23
+ </template>
24
+
25
+ <script lang="ts" setup>
26
+ import { TMagicScrollbar } from '@tmagic/design';
27
+
28
+ import Bucket from './Bucket.vue';
29
+
30
+ defineOptions({
31
+ name: 'MEditorHistoryListBucketTab',
32
+ });
33
+
34
+ withDefaults(
35
+ defineProps<{
36
+ /** bucket 头部展示的标题,例如 "数据源" / "代码块"。 */
37
+ title: string;
38
+ /** 子项 key 的命名空间前缀(`ds` 数据源 / `cb` 代码块),与上层折叠状态 key 保持一致。 */
39
+ prefix: string;
40
+ /**
41
+ * 已按目标 id 聚拢成的 bucket 列表,每个 bucket 内部的 groups 已按时间倒序排好。
42
+ * 空数组时显示空态。
43
+ */
44
+ buckets: { id: string | number; groups: any[] }[];
45
+ /** 组级描述文案生成器,由父组件按业务类型注入。 */
46
+ describeGroup: (_group: any) => string;
47
+ /** 单步描述文案生成器,由父组件按业务类型注入。 */
48
+ describeStep: (_step: any) => string;
49
+ /** 判断某个 step 是否可查看差异(前后值都存在)。由父组件按业务类型注入。 */
50
+ isStepDiffable: (_step: any) => boolean;
51
+ /**
52
+ * 共享的折叠状态表(key -> 是否展开),由顶层 panel 统一维护。
53
+ * 本 tab 使用 `${prefix}-${id}-${组内首步 index}` 作为 key——以稳定的 step 索引而非展示位置标识分组,
54
+ * 这样历史数据更新后已展开的分组状态仍能正确保持。
55
+ */
56
+ expanded: Record<string, boolean>;
57
+ /** 是否支持「跳转到该记录」(goto),透传给 Bucket。默认 true。 */
58
+ gotoEnabled?: boolean;
59
+ }>(),
60
+ {
61
+ gotoEnabled: true,
62
+ },
63
+ );
64
+
65
+ defineEmits<{
66
+ /** 透传子组件 Bucket 的 toggle 事件给上层 panel,由其更新 expanded。 */
67
+ (_e: 'toggle', _key: string): void;
68
+ /** 透传 Bucket 的 goto 事件,携带目标 id 与目标 step 索引。 */
69
+ (_e: 'goto', _targetId: string | number, _index: number): void;
70
+ /** 透传 Bucket 的 goto-initial 事件,携带目标 id(回到该目标未修改时的状态)。 */
71
+ (_e: 'goto-initial', _targetId: string | number): void;
72
+ /** 透传 Bucket 的 diff-step 事件,携带目标 id 与 step 索引。 */
73
+ (_e: 'diff-step', _targetId: string | number, _index: number): void;
74
+ /** 透传 Bucket 的 revert-step 事件,携带目标 id 与 step 索引(类 git revert)。 */
75
+ (_e: 'revert-step', _targetId: string | number, _index: number): void;
76
+ }>();
77
+ </script>