@tmagic/editor 1.8.0-beta.1 → 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 (119) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +20 -2
  2. package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +18 -110
  3. package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +6 -0
  4. package/dist/es/components/CompareForm.js +5 -0
  5. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +171 -0
  6. package/dist/es/editorProps.js +2 -0
  7. package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +19 -3
  8. package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +2 -5
  9. package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +18 -2
  10. package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +37 -6
  11. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +3 -1
  12. package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +2 -2
  13. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +14 -6
  14. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +3 -1
  15. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +15 -7
  16. package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +14 -6
  17. package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +3 -1
  18. package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +58 -11
  19. package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +11 -6
  20. package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +8 -5
  21. package/dist/es/hooks/use-code-block-edit.js +2 -2
  22. package/dist/es/hooks/use-stage.js +17 -8
  23. package/dist/es/index.js +5 -1
  24. package/dist/es/initService.js +6 -6
  25. package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +6 -5
  26. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +12 -7
  27. package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +8 -0
  28. package/dist/es/layouts/history-list/Bucket.js +5 -0
  29. package/dist/es/layouts/history-list/Bucket.vue_vue_type_script_setup_true_lang.js +103 -0
  30. package/dist/es/layouts/history-list/BucketTab.js +5 -0
  31. package/dist/es/layouts/history-list/BucketTab.vue_vue_type_script_setup_true_lang.js +73 -0
  32. package/dist/es/layouts/history-list/GroupRow.js +5 -0
  33. package/dist/es/layouts/history-list/GroupRow.vue_vue_type_script_setup_true_lang.js +198 -0
  34. package/dist/es/layouts/history-list/HistoryDiffDialog.js +5 -0
  35. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +245 -0
  36. package/dist/es/layouts/history-list/HistoryListPanel.js +5 -0
  37. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +340 -0
  38. package/dist/es/layouts/history-list/InitialRow.js +5 -0
  39. package/dist/es/layouts/history-list/InitialRow.vue_vue_type_script_setup_true_lang.js +54 -0
  40. package/dist/es/layouts/history-list/PageTab.js +5 -0
  41. package/dist/es/layouts/history-list/PageTab.vue_vue_type_script_setup_true_lang.js +105 -0
  42. package/dist/es/layouts/history-list/composables.js +172 -0
  43. package/dist/es/layouts/sidebar/layer/use-node-status.js +1 -1
  44. package/dist/es/services/BaseService.js +31 -68
  45. package/dist/es/services/codeBlock.js +205 -8
  46. package/dist/es/services/componentList.js +2 -2
  47. package/dist/es/services/dataSource.js +217 -8
  48. package/dist/es/services/dep.js +21 -9
  49. package/dist/es/services/editor.js +350 -160
  50. package/dist/es/services/events.js +2 -2
  51. package/dist/es/services/history.js +410 -12
  52. package/dist/es/services/keybinding.js +2 -2
  53. package/dist/es/services/props.js +8 -8
  54. package/dist/es/services/stageOverlay.js +2 -2
  55. package/dist/es/services/storage.js +2 -2
  56. package/dist/es/services/ui.js +2 -2
  57. package/dist/es/style.css +379 -1
  58. package/dist/es/utils/code-block.js +122 -0
  59. package/dist/es/utils/content-menu.js +4 -5
  60. package/dist/es/utils/dep/worker.js +1 -1
  61. package/dist/es/utils/editor.js +2 -2
  62. package/dist/es/utils/undo-redo.js +20 -0
  63. package/dist/style.css +379 -1
  64. package/dist/tmagic-editor.umd.cjs +3331 -787
  65. package/package.json +7 -7
  66. package/src/Editor.vue +15 -0
  67. package/src/components/CodeBlockEditor.vue +20 -109
  68. package/src/components/CodeParams.vue +6 -0
  69. package/src/components/CompareForm.vue +238 -0
  70. package/src/editorProps.ts +9 -0
  71. package/src/fields/Code.vue +19 -3
  72. package/src/fields/CodeLink.vue +2 -5
  73. package/src/fields/CodeSelect.vue +17 -1
  74. package/src/fields/CodeSelectCol.vue +30 -2
  75. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +7 -1
  76. package/src/fields/DataSourceFieldSelect/Index.vue +2 -2
  77. package/src/fields/DataSourceFields.vue +12 -3
  78. package/src/fields/DataSourceMethodSelect.vue +4 -1
  79. package/src/fields/DataSourceMethods.vue +14 -4
  80. package/src/fields/DataSourceMocks.vue +13 -3
  81. package/src/fields/DataSourceSelect.vue +4 -1
  82. package/src/fields/EventSelect.vue +57 -8
  83. package/src/fields/KeyValue.vue +17 -4
  84. package/src/fields/UISelect.vue +5 -2
  85. package/src/hooks/use-code-block-edit.ts +5 -2
  86. package/src/hooks/use-stage.ts +24 -7
  87. package/src/index.ts +3 -0
  88. package/src/layouts/CodeEditor.vue +17 -5
  89. package/src/layouts/Framework.vue +10 -5
  90. package/src/layouts/NavMenu.vue +9 -0
  91. package/src/layouts/history-list/Bucket.vue +124 -0
  92. package/src/layouts/history-list/BucketTab.vue +77 -0
  93. package/src/layouts/history-list/GroupRow.vue +242 -0
  94. package/src/layouts/history-list/HistoryDiffDialog.vue +231 -0
  95. package/src/layouts/history-list/HistoryListPanel.vue +380 -0
  96. package/src/layouts/history-list/InitialRow.vue +52 -0
  97. package/src/layouts/history-list/PageTab.vue +108 -0
  98. package/src/layouts/history-list/composables.ts +226 -0
  99. package/src/layouts/sidebar/layer/use-node-status.ts +9 -6
  100. package/src/services/BaseService.ts +35 -86
  101. package/src/services/codeBlock.ts +256 -5
  102. package/src/services/dataSource.ts +257 -6
  103. package/src/services/dep.ts +32 -9
  104. package/src/services/editor.ts +454 -171
  105. package/src/services/history.ts +455 -8
  106. package/src/services/props.ts +2 -2
  107. package/src/theme/history-list-panel.scss +462 -0
  108. package/src/theme/theme.scss +1 -0
  109. package/src/type.ts +269 -6
  110. package/src/utils/code-block.ts +150 -0
  111. package/src/utils/content-menu.ts +8 -5
  112. package/src/utils/dep/worker.ts +5 -3
  113. package/src/utils/index.ts +1 -0
  114. package/src/utils/undo-redo.ts +23 -0
  115. package/types/index.d.ts +2274 -819
  116. package/dist/es/utils/compose.js +0 -44
  117. package/dist/es/utils/editor-history.js +0 -100
  118. package/src/utils/compose.ts +0 -52
  119. package/src/utils/editor-history.ts +0 -138
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.8.0-beta.1",
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.1",
62
- "@tmagic/form": "1.8.0-beta.1",
63
- "@tmagic/stage": "1.8.0-beta.1",
64
- "@tmagic/table": "1.8.0-beta.1",
65
- "@tmagic/utils": "1.8.0-beta.1"
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.1"
79
+ "@tmagic/core": "1.8.0-beta.3"
80
80
  },
81
81
  "peerDependenciesMeta": {
82
82
  "typescript": {
package/src/Editor.vue CHANGED
@@ -3,6 +3,7 @@
3
3
  :disabled-page-fragment="disabledPageFragment"
4
4
  :page-bar-sort-options="pageBarSortOptions"
5
5
  :page-filter-function="pageFilterFunction"
6
+ :hide-sidebar="hideSidebar"
6
7
  >
7
8
  <template #header>
8
9
  <slot name="header"></slot>
@@ -220,6 +221,7 @@ const stageOptions: StageOptions = {
220
221
  guidesOptions: props.guidesOptions,
221
222
  disabledMultiSelect: props.disabledMultiSelect,
222
223
  alwaysMultiSelect: props.alwaysMultiSelect,
224
+ disabledFlashTip: props.disabledFlashTip,
223
225
  beforeDblclick: props.beforeDblclick,
224
226
  };
225
227
 
@@ -229,6 +231,19 @@ provide('services', services);
229
231
 
230
232
  provide('codeOptions', props.codeOptions);
231
233
  provide('stageOptions', stageOptions);
234
+ /**
235
+ * 把顶层 `extendFormState` 提供给非 PropsPanel 链路上的组件使用(例如历史差异对话框 HistoryDiffDialog
236
+ * 内部的 CompareForm)。这样所有依赖业务上下文的表单 filterFunction 都能拿到一致的扩展状态,
237
+ * 与 PropsPanel 通过 `:extend-state` 显式传入的方式保持等价。
238
+ */
239
+ provide('extendFormState', props.extendFormState);
240
+
241
+ /**
242
+ * 把历史记录面板的自定义扩展 tab 提供给深层的 HistoryListPanel(它挂在 NavMenu 中,
243
+ * 以 markRaw component 形式渲染,无法直接通过 props 透传)。业务方可借此在历史记录
244
+ * 面板内追加自定义模块的历史 tab。
245
+ */
246
+ provide('historyListExtraTabs', props.historyListExtraTabs);
232
247
 
233
248
  provide<EventBus>('eventBus', new EventEmitter());
234
249
 
@@ -34,7 +34,7 @@
34
34
  <Teleport to="body">
35
35
  <TMagicDialog title="查看修改" v-model="difVisible" fullscreen destroy-on-close>
36
36
  <div style="display: flex; margin-bottom: 10px">
37
- <div style="flex: 1"><TMagicTag size="small" type="info">修改前</TMagicTag></div>
37
+ <div style="flex: 1"><TMagicTag size="small" type="danger">修改前</TMagicTag></div>
38
38
  <div style="flex: 1"><TMagicTag size="small" type="success">修改后</TMagicTag></div>
39
39
  </div>
40
40
 
@@ -63,14 +63,7 @@ import { computed, inject, nextTick, Ref, ref, useTemplateRef, watch } from 'vue
63
63
 
64
64
  import type { CodeBlockContent } from '@tmagic/core';
65
65
  import { TMagicButton, TMagicDialog, tMagicMessage, tMagicMessageBox, TMagicTag } from '@tmagic/design';
66
- import {
67
- type ContainerChangeEventData,
68
- defineFormConfig,
69
- defineFormItem,
70
- type FormConfig,
71
- MFormBox,
72
- type TableColumnConfig,
73
- } from '@tmagic/form';
66
+ import { type ContainerChangeEventData, type FormConfig, MFormBox } from '@tmagic/form';
74
67
 
75
68
  import FloatingBox from '@editor/components/FloatingBox.vue';
76
69
  import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
@@ -78,6 +71,7 @@ import { useNextFloatBoxPosition } from '@editor/hooks/use-next-float-box-positi
78
71
  import { useServices } from '@editor/hooks/use-services';
79
72
  import { useWindowRect } from '@editor/hooks/use-window-rect';
80
73
  import CodeEditor from '@editor/layouts/CodeEditor.vue';
74
+ import { getCodeBlockFormConfig } from '@editor/utils/code-block';
81
75
  import { getEditorConfig } from '@editor/utils/config';
82
76
 
83
77
  defineOptions({
@@ -119,106 +113,23 @@ const diffChange = () => {
119
113
  difVisible.value = false;
120
114
  };
121
115
 
122
- const defaultParamColConfig = defineFormItem<TableColumnConfig>({
123
- type: 'row',
124
- label: '参数类型',
125
- items: [
126
- {
127
- text: '参数类型',
128
- labelWidth: '70px',
129
- type: 'select',
130
- name: 'type',
131
- options: [
132
- {
133
- text: '数字',
134
- label: '数字',
135
- value: 'number',
136
- },
137
- {
138
- text: '字符串',
139
- label: '字符串',
140
- value: 'text',
141
- },
142
- {
143
- text: '组件',
144
- label: '组件',
145
- value: 'ui-select',
146
- },
147
- ],
148
- },
149
- ],
150
- });
151
-
152
- const functionConfig = computed(
153
- () =>
154
- defineFormConfig([
155
- {
156
- text: '名称',
157
- name: 'name',
158
- rules: [{ required: true, message: '请输入名称', trigger: 'blur' }],
159
- },
160
- {
161
- text: '描述',
162
- name: 'desc',
163
- },
164
- {
165
- text: '执行时机',
166
- name: 'timing',
167
- type: 'select',
168
- options: () => {
169
- const options = [
170
- { text: '初始化前', value: 'beforeInit' },
171
- { text: '初始化后', value: 'afterInit' },
172
- ];
173
- if (props.dataSourceType !== 'base') {
174
- options.push({ text: '请求前', value: 'beforeRequest' });
175
- options.push({ text: '请求后', value: 'afterRequest' });
176
- }
177
- return options;
178
- },
179
- display: () => props.isDataSource,
180
- },
181
- {
182
- type: 'table',
183
- border: true,
184
- text: '参数',
185
- enableFullscreen: false,
186
- enableToggleMode: false,
187
- name: 'params',
188
- dropSort: false,
189
- items: [
190
- {
191
- type: 'text',
192
- label: '参数名',
193
- name: 'name',
194
- },
195
- {
196
- type: 'text',
197
- label: '描述',
198
- name: 'extra',
199
- },
200
- codeBlockService.getParamsColConfig() || defaultParamColConfig,
201
- ],
202
- },
203
- {
204
- name: 'content',
205
- type: 'vs-code',
206
- options: inject('codeOptions', {}),
207
- autosize: { minRows: 10, maxRows: 30 },
208
- onChange: (_formState, code: string) => {
209
- try {
210
- // 检测js代码是否存在语法错误
211
- getEditorConfig('parseDSL')(code);
212
-
213
- return code;
214
- } catch (error: any) {
215
- tMagicMessage.error(error.message);
216
-
217
- throw error;
218
- }
219
- },
220
- },
221
- ]) as FormConfig,
116
+ const codeOptions = inject<Record<string, any>>('codeOptions', {});
117
+
118
+ /**
119
+ * 代码块编辑表单配置。统一委托到 utils/code-block 的 `getCodeBlockFormConfig`,
120
+ * 与 CompareForm 等其它使用方共享同一份 schema,避免双份维护。
121
+ *
122
+ * 这里以 computed 包裹是为了让 `props.isDataSource` / `props.dataSourceType` 变化时
123
+ * "执行时机"字段的可见性与可选项实时刷新。
124
+ */
125
+ const functionConfig = computed<FormConfig>(() =>
126
+ getCodeBlockFormConfig({
127
+ paramColConfig: codeBlockService.getParamsColConfig(),
128
+ isDataSource: () => Boolean(props.isDataSource),
129
+ dataSourceType: () => props.dataSourceType,
130
+ codeOptions,
131
+ editable: true,
132
+ }),
222
133
  );
223
134
 
224
135
  const parseContent = (content: any) => {
@@ -3,6 +3,8 @@
3
3
  ref="form"
4
4
  :config="codeParamsConfig"
5
5
  :init-values="model"
6
+ :last-values="lastValues"
7
+ :is-compare="isCompare"
6
8
  :disabled="disabled"
7
9
  :size="size"
8
10
  :watch-props="false"
@@ -24,6 +26,10 @@ defineOptions({
24
26
 
25
27
  const props = defineProps<{
26
28
  model: any;
29
+ /** 对比模式下的历史值,透传给内部 MForm 用于逐项展示参数差异 */
30
+ lastValues?: any;
31
+ /** 是否开启对比模式 */
32
+ isCompare?: boolean;
27
33
  size?: 'small' | 'default' | 'large';
28
34
  disabled?: boolean;
29
35
  name: string;
@@ -0,0 +1,238 @@
1
+ <template>
2
+ <div class="m-editor-compare-form-wrapper" :style="wrapperStyle">
3
+ <MForm
4
+ v-if="config.length"
5
+ ref="form"
6
+ class="m-editor-compare-form"
7
+ :config="config"
8
+ :init-values="currentValues"
9
+ :last-values="lastValuesProcessed"
10
+ :is-compare="true"
11
+ :disabled="true"
12
+ :label-width="labelWidth"
13
+ :extend-state="extendState"
14
+ :show-diff="showDiff"
15
+ ></MForm>
16
+ </div>
17
+ </template>
18
+
19
+ <script lang="ts" setup>
20
+ import { computed, inject, type Ref, ref, type ShallowRef, useTemplateRef, watch, watchEffect } from 'vue';
21
+ import { isEqual } from 'lodash-es';
22
+
23
+ import { type CodeBlockContent, type DataSourceSchema, HookType, type MNode } from '@tmagic/core';
24
+ import { type FormConfig, type FormState, type FormValue, MForm } from '@tmagic/form';
25
+
26
+ import { useServices } from '@editor/hooks/use-services';
27
+ import type { CompareCategory, CompareFormLoadConfig } from '@editor/type';
28
+ import { getCodeBlockFormConfig } from '@editor/utils/code-block';
29
+
30
+ defineOptions({
31
+ name: 'MEditorCompareForm',
32
+ });
33
+
34
+ const props = withDefaults(
35
+ defineProps<{
36
+ /** 当前值(修改后的值) */
37
+ value: Partial<MNode> | Partial<DataSourceSchema> | Partial<CodeBlockContent> | Record<string, any>;
38
+ /** 用于对比的旧值(修改前的值) */
39
+ lastValue?: Partial<MNode> | Partial<DataSourceSchema> | Partial<CodeBlockContent> | Record<string, any>;
40
+ /**
41
+ * 类型说明:
42
+ * - `category` 为 `node` 时,`type` 为节点组件的类型,例如 'text'、'button'、'page'、'container' 等
43
+ * - `category` 为 `data-source` 时,`type` 为数据源类型,例如 'base'、'http'
44
+ * - `category` 为 `code-block` 时,`type` 可不传
45
+ */
46
+ type?: string;
47
+ /** 表单配置类别,决定从哪里取 FormConfig */
48
+ category?: CompareCategory;
49
+ /** 数据源代码块场景下的数据源类型(base/http),用于代码块表单中"执行时机"展示 */
50
+ dataSourceType?: string;
51
+ labelWidth?: string;
52
+ /**
53
+ * 外层容器高度。设置后表单内容超出时会在 CompareForm 内部出现滚动条,
54
+ * 避免 dialog / 面板使用方需要自行处理滚动。可传任意 CSS 长度,例如 `60vh` / `400px` / `100%`。
55
+ */
56
+ height?: string;
57
+ /**
58
+ * 用户自定义注入到 MForm.formState 的扩展字段,与 Editor 顶层的 `extendFormState`、
59
+ * PropsPanel 的 `extend-state` 语义一致。表单 item 的 `display` / `disabled` 等
60
+ * filterFunction 经常依赖这里注入的字段(如 stage、自定义业务上下文等),
61
+ * 因此在差异对比场景下也需要透传,避免出现 `formState.xxx is undefined` 的运行时错误。
62
+ */
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;
70
+ }>(),
71
+ {
72
+ category: 'node',
73
+ labelWidth: '120px',
74
+ },
75
+ );
76
+
77
+ const { propsService, dataSourceService, codeBlockService, editorService } = useServices();
78
+ const services = useServices();
79
+
80
+ const config = ref<FormConfig>([]);
81
+
82
+ /** vs-code 编辑器的 monaco 配置项,沿用 Editor 顶层 provide('codeOptions', ...) 的注入。 */
83
+ const codeOptions = inject<Record<string, any>>('codeOptions', {});
84
+
85
+ /** 将代码块的 content 字段统一成字符串,便于在表单/对比中展示 */
86
+ const normalizeCodeBlockValue = (
87
+ v: Partial<CodeBlockContent> | Record<string, any> | undefined,
88
+ ): Record<string, any> => {
89
+ if (!v) return {};
90
+ const next: Record<string, any> = { ...v };
91
+ if (next.content && typeof next.content !== 'string') {
92
+ try {
93
+ next.content = next.content.toString();
94
+ } catch {
95
+ next.content = '';
96
+ }
97
+ }
98
+ return next;
99
+ };
100
+
101
+ const currentValues = computed<FormValue>(() => {
102
+ if (props.category === 'code-block') {
103
+ return normalizeCodeBlockValue(props.value as Partial<CodeBlockContent>);
104
+ }
105
+ return (props.value || {}) as FormValue;
106
+ });
107
+
108
+ const lastValuesProcessed = computed<FormValue>(() => {
109
+ if (props.category === 'code-block') {
110
+ return normalizeCodeBlockValue(props.lastValue as Partial<CodeBlockContent>);
111
+ }
112
+ return (props.lastValue || {}) as FormValue;
113
+ });
114
+
115
+ /**
116
+ * 外层包裹层的样式:当传入 `height` 时启用固定高度 + 内部滚动,
117
+ * 这样滚动条会出现在 CompareForm 内部,避免父容器(如 Dialog)自身也产生滚动。
118
+ */
119
+ const wrapperStyle = computed(() => {
120
+ if (!props.height) return undefined;
121
+ return {
122
+ height: props.height,
123
+ overflow: 'auto',
124
+ } as Record<string, string>;
125
+ });
126
+
127
+ /**
128
+ * `code-select` 字段在历史数据中存在两种"语义为空"的形态:
129
+ * - 字符串 `''`(旧数据 / 用户从未配置过钩子);
130
+ * - `{ hookType: HookType.CODE, hookData: [] }`(CodeSelect.vue 在挂载时
131
+ * 写入的默认结构,参见 packages/editor/src/fields/CodeSelect.vue 中
132
+ * `props.model[props.name] = { hookType: HookType.CODE, hookData: [] }`)。
133
+ *
134
+ * 直接 `isEqual` 会把两者判为不等,从而在历史对比里对每个未配置过钩子的组件
135
+ * 都展示一份"差异",体验很糟糕。这里把它们视为相等,跳过对比。
136
+ *
137
+ * 其它类型字段沿用 MForm/Container 的默认 `!isEqual` 判断逻辑。
138
+ */
139
+ const isEmptyCodeSelectValue = (v: any): boolean => {
140
+ if (v === '' || v === undefined || v === null) return true;
141
+ if (Array.isArray(v) && v.length === 0) return true;
142
+ return typeof v === 'object' && v.hookType === HookType.CODE && Array.isArray(v.hookData) && v.hookData.length === 0;
143
+ };
144
+
145
+ const showDiff = ({ curValue, lastValue, config }: { curValue: any; lastValue: any; config: any }) => {
146
+ if (config?.type === 'code-select') {
147
+ // 双方都是"空形态",视为相等,不展示对比
148
+ if (isEmptyCodeSelectValue(curValue) && isEmptyCodeSelectValue(lastValue)) {
149
+ return false;
150
+ }
151
+ }
152
+ return !isEqual(curValue, lastValue);
153
+ };
154
+
155
+ /**
156
+ * 内置的默认 FormConfig 加载逻辑:按 `category` 从对应 service / 工具取配置。
157
+ * 作为 ctx.defaultLoadConfig 透传给自定义 `loadConfig`,方便复用与二次加工。
158
+ */
159
+ const defaultLoadConfig = async (): Promise<FormConfig> => {
160
+ switch (props.category) {
161
+ case 'node': {
162
+ if (!props.type) {
163
+ return [];
164
+ }
165
+ return await propsService.getPropsConfig(props.type);
166
+ }
167
+ case 'data-source': {
168
+ return dataSourceService.getFormConfig(props.type || 'base');
169
+ }
170
+ case 'code-block': {
171
+ return getCodeBlockFormConfig({
172
+ paramColConfig: codeBlockService.getParamsColConfig(),
173
+ // 通过传入 dataSourceType 间接表达"是数据源代码块"——在对比场景下 props.dataSourceType
174
+ // 由调用方按 step 上下文显式传入,未传则视为普通代码块,「执行时机」字段隐藏。
175
+ isDataSource: () => Boolean(props.dataSourceType),
176
+ dataSourceType: () => props.dataSourceType,
177
+ codeOptions,
178
+ // 对比模式只读,不需要校验/语法检查
179
+ editable: false,
180
+ });
181
+ }
182
+ default:
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;
196
+ }
197
+
198
+ config.value = await defaultLoadConfig();
199
+ };
200
+
201
+ watch(
202
+ [() => props.category, () => props.type, () => props.dataSourceType, () => props.loadConfig],
203
+ () => {
204
+ loadConfig();
205
+ },
206
+ { immediate: true },
207
+ );
208
+
209
+ const formRef = useTemplateRef<InstanceType<typeof MForm>>('form');
210
+
211
+ /**
212
+ * 把 services / stage 注入 MForm 的 formState,避免 propsService 注入的表单配置中
213
+ * 形如 `display: ({ services }) => services.uiService.get(...)` 的 filterFunction
214
+ * 在执行时拿不到 `formState.services` 而报错。
215
+ *
216
+ * 与 props-panel/FormPanel.vue 中的注入方式保持一致:
217
+ * - services:整个 useServices() 返回的服务集合;
218
+ * - stage:当前 editorService.get('stage') 的最新值。
219
+ */
220
+ const stage = computed(() => editorService.get('stage'));
221
+
222
+ watchEffect(() => {
223
+ if (formRef.value) {
224
+ formRef.value.formState.stage = stage.value;
225
+ formRef.value.formState.services = services;
226
+ }
227
+ });
228
+
229
+ defineExpose<{
230
+ form: ShallowRef<InstanceType<typeof MForm> | null>;
231
+ config: Ref<FormConfig>;
232
+ reload: () => Promise<void>;
233
+ }>({
234
+ form: formRef,
235
+ config,
236
+ reload: loadConfig,
237
+ });
238
+ </script>
@@ -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,
@@ -34,6 +35,8 @@ export interface EditorProps {
34
35
  datasourceList?: DatasourceTypeOption[];
35
36
  /** 左侧面板配置 */
36
37
  sidebar?: SideBarData;
38
+ /** 是否隐藏左侧面板 */
39
+ hideSidebar?: boolean;
37
40
  /** 顶部工具栏配置 */
38
41
  menu?: MenuBarData;
39
42
  /** 组件树右键菜单 */
@@ -84,6 +87,8 @@ export interface EditorProps {
84
87
  alwaysMultiSelect?: boolean;
85
88
  /** 禁用页面片 */
86
89
  disabledPageFragment?: boolean;
90
+ /** 禁用「非点击画布选中组件时(如从图层树、面包屑等外部选中),对选中区域做高亮闪烁提示」,默认 false(即默认开启闪烁) */
91
+ disabledFlashTip?: boolean;
87
92
  /** 禁用双击在浮层中单独编辑选中组件 */
88
93
  disabledStageOverlay?: boolean;
89
94
  /** 禁用属性配置面板右下角显示源码的按钮 */
@@ -123,6 +128,8 @@ export interface EditorProps {
123
128
  /** 组件树节点双击前的钩子函数,返回 false 则阻止默认的双击行为 */
124
129
  beforeLayerNodeDblclick?: (event: MouseEvent, data: TreeNodeData) => Promise<boolean | void> | boolean | void;
125
130
  extendFormState?: (state: FormState) => Record<string, any> | Promise<Record<string, any>>;
131
+ /** 历史记录面板的自定义扩展 tab,追加在内置的页面/数据源/代码块 tab 之后 */
132
+ historyListExtraTabs?: HistoryListExtraTab[];
126
133
  /** 页面顺序拖拽配置参数 */
127
134
  pageBarSortOptions?: PageBarSortOptions;
128
135
  /** 页面搜索函数 */
@@ -134,6 +141,7 @@ export const defaultEditorProps = {
134
141
  disabledMultiSelect: false,
135
142
  alwaysMultiSelect: false,
136
143
  disabledPageFragment: false,
144
+ disabledFlashTip: false,
137
145
  disabledStageOverlay: false,
138
146
  containerHighlightClassName: CONTAINER_HIGHLIGHT_CLASS_NAME,
139
147
  containerHighlightDuration: 800,
@@ -143,6 +151,7 @@ export const defaultEditorProps = {
143
151
  disabledCodeBlock: false,
144
152
  componentGroupList: () => [],
145
153
  datasourceList: () => [],
154
+ historyListExtraTabs: () => [],
146
155
  menu: () => ({ left: [], right: [] }),
147
156
  layerContentMenu: () => [],
148
157
  stageContentMenu: () => [],
@@ -1,11 +1,13 @@
1
1
  <template>
2
2
  <MagicCodeEditor
3
3
  :height="config.height"
4
- :init-values="model[name]"
4
+ :type="diffMode ? 'diff' : undefined"
5
+ :init-values="diffMode ? (lastValues || {})[name] : model[name]"
6
+ :modified-values="diffMode ? model[name] : undefined"
5
7
  :language="config.language"
6
8
  :options="{
7
9
  ...config.options,
8
- readOnly: disabled,
10
+ readOnly: diffMode ? true : disabled,
9
11
  }"
10
12
  :autosize="config.autosize"
11
13
  :parse="config.parse"
@@ -15,6 +17,8 @@
15
17
  </template>
16
18
 
17
19
  <script lang="ts" setup>
20
+ import { computed } from 'vue';
21
+
18
22
  import type { CodeConfig, FieldProps } from '@tmagic/form';
19
23
 
20
24
  import MagicCodeEditor from '@editor/layouts/CodeEditor.vue';
@@ -27,10 +31,22 @@ const emit = defineEmits<{
27
31
  change: [value: string | any];
28
32
  }>();
29
33
 
30
- withDefaults(defineProps<FieldProps<CodeConfig>>(), {
34
+ const props = withDefaults(defineProps<FieldProps<CodeConfig>>(), {
31
35
  disabled: false,
32
36
  });
33
37
 
38
+ /**
39
+ * 对比模式判定:
40
+ *
41
+ * - 当 `isCompare === true` 时,由父级 `MFormContainer` 统一渲染一次本字段(不再渲染前后两份独立组件),
42
+ * 并把 `model`(当前值)与 `lastValues`(历史值)一并传入;
43
+ * - 此时本字段切换到 monaco 自带的 diff 编辑器(左侧旧、右侧新),相比"两个独立 monaco 实例"更直观,
44
+ * 也避免了同一表单内重复实例化重型编辑器带来的开销。
45
+ *
46
+ * 仅当存在历史值(lastValues)且开启了对比模式时启用 diff,避免在 lastValues 缺失时退化为空对比。
47
+ */
48
+ const diffMode = computed(() => Boolean(props.isCompare && props.lastValues));
49
+
34
50
  const save = (v: string | any) => {
35
51
  emit('change', v);
36
52
  };
@@ -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
  {
@@ -6,7 +6,8 @@
6
6
  :size="size"
7
7
  :prop="prop"
8
8
  :disabled="disabled"
9
- :lastValues="lastValues"
9
+ :is-compare="isCompareMode"
10
+ :last-values="lastValues?.[name]"
10
11
  :model="model[name]"
11
12
  @change="changeHandler"
12
13
  >
@@ -38,6 +39,21 @@ const { dataSourceService, codeBlockService } = useServices();
38
39
 
39
40
  const props = withDefaults(defineProps<FieldProps<CodeSelectConfig>>(), {});
40
41
 
42
+ /**
43
+ * 对比模式判定:
44
+ *
45
+ * code-select 仅是对内部「钩子列表」group-list 的包裹,本身不渲染叶子字段。父级 `MFormContainer`
46
+ * 已将其归入「自接管对比字段」(见 Container.vue 的 `SELF_DIFF_FIELD_TYPES`),即对比时只渲染一次
47
+ * 本组件,并把当前值 `model` 与历史值 `lastValues` 一并传入,由本组件把 `is-compare`/`lastValues`
48
+ * 透传给内部 MContainer,再由 group-list / code-select-col 等子级逐项展示前后差异。
49
+ *
50
+ * 注意:`model` 传入的是 `model[name]`(钩子值本身),因此 `lastValues` 也必须同层取 `lastValues[name]`,
51
+ * 否则前后值的嵌套层级不一致会导致对比错位。
52
+ *
53
+ * 仅当存在历史值时才启用对比,避免 lastValues 缺失时退化为「全部新增」的空对比。
54
+ */
55
+ const isCompareMode = computed(() => Boolean(props.isCompare && props.lastValues));
56
+
41
57
  const codeConfig = computed<GroupListConfig>(() => ({
42
58
  type: 'group-list',
43
59
  name: 'hookData',