@tmagic/editor 1.8.0-beta.11 → 1.8.0-beta.12

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 (111) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +7 -4
  2. package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +4 -0
  3. package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +2 -1
  4. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +5 -2
  5. package/dist/es/components/FloatingBox.vue_vue_type_script_setup_true_lang.js +60 -14
  6. package/dist/es/components/ScrollBar.js +0 -1
  7. package/dist/es/editorProps.js +6 -1
  8. package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +15 -3
  9. package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +2 -17
  10. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +2 -1
  11. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +7 -0
  12. package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +5 -17
  13. package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +4 -0
  14. package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +29 -3
  15. package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +50 -88
  16. package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +2 -5
  17. package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +48 -23
  18. package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +6 -3
  19. package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +28 -22
  20. package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +28 -22
  21. package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +28 -22
  22. package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +6 -3
  23. package/dist/es/fields/UISelect.js +0 -1
  24. package/dist/es/index.js +9 -6
  25. package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +21 -6
  26. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +9 -6
  27. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +4 -1
  28. package/dist/es/layouts/history-list/composables.js +1 -1
  29. package/dist/es/layouts/history-list/useHistoryRevert.js +7 -3
  30. package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +44 -5
  31. package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +15 -8
  32. package/dist/es/layouts/props-panel/use-style-panel.js +3 -2
  33. package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +15 -31
  34. package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
  35. package/dist/es/layouts/sidebar/code-block/useContentMenu.js +18 -3
  36. package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +4 -0
  37. package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
  38. package/dist/es/layouts/sidebar/data-source/useContentMenu.js +18 -3
  39. package/dist/es/layouts/sidebar/layer/LayerNodeContent.js +5 -0
  40. package/dist/es/layouts/sidebar/layer/LayerNodeContent.vue_vue_type_script_setup_true_lang.js +46 -0
  41. package/dist/es/layouts/sidebar/layer/LayerPanel.vue_vue_type_script_setup_true_lang.js +2 -1
  42. package/dist/es/layouts/workspace/viewer/NodeListMenu.vue_vue_type_script_setup_true_lang.js +8 -2
  43. package/dist/es/plugin.js +3 -1
  44. package/dist/es/services/editor.js +104 -5
  45. package/dist/es/services/props.js +2 -1
  46. package/dist/es/services/storage.js +1 -1
  47. package/dist/es/services/ui.js +3 -0
  48. package/dist/es/style.css +104 -55
  49. package/dist/es/utils/data-source/index.js +30 -11
  50. package/dist/es/utils/event.js +123 -0
  51. package/dist/es/utils/monaco-editor.js +22 -6
  52. package/dist/es/utils/props.js +94 -8
  53. package/dist/es/utils/type-match-rules.js +360 -0
  54. package/dist/style.css +104 -55
  55. package/dist/tmagic-editor.umd.cjs +16895 -15757
  56. package/package.json +8 -8
  57. package/src/Editor.vue +3 -1
  58. package/src/components/CodeBlockEditor.vue +3 -0
  59. package/src/components/CompareForm.vue +7 -0
  60. package/src/components/FloatingBox.vue +77 -8
  61. package/src/components/ScrollBar.vue +0 -67
  62. package/src/editorProps.ts +13 -0
  63. package/src/fields/CodeSelect.vue +4 -1
  64. package/src/fields/CondOpSelect.vue +2 -24
  65. package/src/fields/DataSourceFields.vue +5 -0
  66. package/src/fields/DataSourceInput.vue +7 -25
  67. package/src/fields/DataSourceMocks.vue +3 -0
  68. package/src/fields/DisplayConds.vue +14 -0
  69. package/src/fields/EventSelect.vue +84 -129
  70. package/src/fields/StyleSetter/Index.vue +2 -8
  71. package/src/fields/StyleSetter/pro/Background.vue +94 -55
  72. package/src/fields/StyleSetter/pro/Border.vue +9 -11
  73. package/src/fields/StyleSetter/pro/Font.vue +66 -64
  74. package/src/fields/StyleSetter/pro/Layout.vue +140 -138
  75. package/src/fields/StyleSetter/pro/Position.vue +69 -67
  76. package/src/fields/StyleSetter/pro/Transform.vue +23 -25
  77. package/src/fields/UISelect.vue +0 -12
  78. package/src/index.ts +1 -0
  79. package/src/layouts/CodeEditor.vue +32 -6
  80. package/src/layouts/Framework.vue +7 -6
  81. package/src/layouts/history-list/HistoryDiffDialog.vue +11 -1
  82. package/src/layouts/history-list/useHistoryRevert.ts +4 -0
  83. package/src/layouts/props-panel/FormPanel.vue +66 -5
  84. package/src/layouts/props-panel/PropsPanel.vue +20 -6
  85. package/src/layouts/props-panel/use-style-panel.ts +4 -3
  86. package/src/layouts/sidebar/Sidebar.vue +2 -0
  87. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +4 -1
  88. package/src/layouts/sidebar/code-block/useContentMenu.ts +13 -1
  89. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +3 -0
  90. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +2 -2
  91. package/src/layouts/sidebar/data-source/useContentMenu.ts +13 -1
  92. package/src/layouts/sidebar/layer/LayerNodeContent.vue +57 -0
  93. package/src/layouts/sidebar/layer/LayerPanel.vue +4 -1
  94. package/src/layouts/workspace/viewer/NodeListMenu.vue +4 -1
  95. package/src/plugin.ts +3 -1
  96. package/src/services/editor.ts +168 -3
  97. package/src/services/ui.ts +6 -0
  98. package/src/theme/event.scss +8 -0
  99. package/src/theme/floating-box.scss +9 -1
  100. package/src/theme/layer-node-content.scss +14 -0
  101. package/src/theme/scroll-bar.scss +64 -0
  102. package/src/theme/theme.scss +3 -0
  103. package/src/theme/ui-select.scss +9 -0
  104. package/src/type.ts +53 -2
  105. package/src/utils/data-source/index.ts +30 -12
  106. package/src/utils/event.ts +224 -0
  107. package/src/utils/index.ts +2 -0
  108. package/src/utils/monaco-editor.ts +27 -0
  109. package/src/utils/props.ts +160 -2
  110. package/src/utils/type-match-rules.ts +678 -0
  111. package/types/index.d.ts +372 -85
@@ -25,9 +25,9 @@
25
25
  right-class="m-editor-framework-right"
26
26
  :left="hideSidebar ? undefined : columnWidth.left"
27
27
  :right="columnWidth.right"
28
- :min-left="hideSidebar ? 0 : MIN_LEFT_COLUMN_WIDTH"
29
- :min-right="MIN_RIGHT_COLUMN_WIDTH"
30
- :min-center="MIN_CENTER_COLUMN_WIDTH"
28
+ :min-left="hideSidebar ? 0 : minLeftColumnWidth"
29
+ :min-right="minRightColumnWidth"
30
+ :min-center="minCenterColumnWidth"
31
31
  :width="frameworkRect.width"
32
32
  @change="columnWidthChange"
33
33
  >
@@ -78,9 +78,6 @@ import { getEditorConfig } from '@editor/utils/config';
78
78
  import {
79
79
  DEFAULT_LEFT_COLUMN_WIDTH,
80
80
  LEFT_COLUMN_WIDTH_STORAGE_KEY,
81
- MIN_CENTER_COLUMN_WIDTH,
82
- MIN_LEFT_COLUMN_WIDTH,
83
- MIN_RIGHT_COLUMN_WIDTH,
84
81
  RIGHT_COLUMN_WIDTH_STORAGE_KEY,
85
82
  } from '@editor/utils/const';
86
83
 
@@ -116,6 +113,10 @@ const showSrc = computed(() => uiService.get('showSrc'));
116
113
 
117
114
  const columnWidth = computed(() => uiService.get('columnWidth'));
118
115
 
116
+ const minLeftColumnWidth = computed(() => uiService.get('minLeftColumnWidth'));
117
+ const minCenterColumnWidth = computed(() => uiService.get('minCenterColumnWidth'));
118
+ const minRightColumnWidth = computed(() => uiService.get('minRightColumnWidth'));
119
+
119
120
  watch(pageLength, () => {
120
121
  splitViewRef.value?.updateWidth();
121
122
  });
@@ -48,6 +48,7 @@
48
48
  :load-config="loadConfig"
49
49
  :self-diff-field-types="selfDiffFieldTypes"
50
50
  :services="props.services"
51
+ :size="props.size"
51
52
  height="70vh"
52
53
  />
53
54
 
@@ -77,7 +78,14 @@
77
78
  import { computed, ref, watch } from 'vue';
78
79
  import { isEqual } from 'lodash-es';
79
80
 
80
- import { TMagicButton, TMagicDialog, TMagicRadioButton, TMagicRadioGroup, TMagicTag } from '@tmagic/design';
81
+ import {
82
+ type FieldSize,
83
+ TMagicButton,
84
+ TMagicDialog,
85
+ TMagicRadioButton,
86
+ TMagicRadioGroup,
87
+ TMagicTag,
88
+ } from '@tmagic/design';
81
89
  import type { FormState } from '@tmagic/form';
82
90
 
83
91
  import CompareForm from '@editor/components/CompareForm.vue';
@@ -104,6 +112,8 @@ const props = withDefaults(
104
112
  */
105
113
  loadConfig?: CompareFormLoadConfig;
106
114
  width?: string;
115
+ /** 差异对比表单内组件的尺寸(透传给 CompareForm 的 `size`),可选 'large' | 'default' | 'small'。 */
116
+ size?: FieldSize;
107
117
  isConfirm?: boolean;
108
118
  onConfirm?: () => void;
109
119
  selfDiffFieldTypes?: string[];
@@ -95,6 +95,7 @@ const mountHistoryDiffDialog = async (
95
95
  selfDiffFieldTypes: options.selfDiffFieldTypes,
96
96
  compareFormState: options.compareFormState,
97
97
  width: options.width,
98
+ size: options.size ?? options.services?.uiService?.get('propsPanelSize'),
98
99
  onClose: options.onClose,
99
100
  });
100
101
  if (options.appContext) {
@@ -223,6 +224,7 @@ export const useHistoryRevert = (options: UseHistoryRevertOptions = {}, services
223
224
  services,
224
225
  ...extra,
225
226
  width: extra?.width ?? dialogWidth,
227
+ size: extra?.size,
226
228
  });
227
229
  }
228
230
  return confirmRevert();
@@ -382,6 +384,7 @@ export const useHistoryRevert = (options: UseHistoryRevertOptions = {}, services
382
384
  loadConfig: revertOptions.loadConfig,
383
385
  selfDiffFieldTypes: revertOptions.selfDiffFieldTypes,
384
386
  width: revertOptions.width,
387
+ size: revertOptions.size,
385
388
  });
386
389
  if (!confirmed) return null;
387
390
  return await revertOptions.revert();
@@ -399,6 +402,7 @@ export const useHistoryRevert = (options: UseHistoryRevertOptions = {}, services
399
402
  services,
400
403
  ...extra,
401
404
  width: extra?.width ?? dialogWidth,
405
+ size: extra?.size,
402
406
  });
403
407
  };
404
408
 
@@ -12,6 +12,7 @@
12
12
  :size="propsPanelSize"
13
13
  :init-values="values"
14
14
  :config="config"
15
+ :type-match-valid="true"
15
16
  :extend-state="extendState"
16
17
  @change="submit"
17
18
  @error="errorHandler"
@@ -46,13 +47,16 @@
46
47
  import { computed, getCurrentInstance, inject, onMounted, onUnmounted, ref, useTemplateRef, watchEffect } from 'vue';
47
48
  import { Document as DocumentIcon } from '@element-plus/icons-vue';
48
49
 
49
- import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
50
+ import { TMagicButton, tMagicMessage, TMagicScrollbar } from '@tmagic/design';
50
51
  import type { ContainerChangeEventData, FormConfig, FormState, FormValue } from '@tmagic/form';
51
52
  import { MForm } from '@tmagic/form';
53
+ import { filterXSS } from '@tmagic/utils';
52
54
 
53
55
  import MIcon from '@editor/components/Icon.vue';
56
+ import { ENABLE_PROPS_FORM_VALIDATE } from '@editor/editorProps';
54
57
  import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
55
58
  import { useServices } from '@editor/hooks/use-services';
59
+ import { validatePropsForm } from '@editor/utils/props';
56
60
 
57
61
  import CodeEditor from '../CodeEditor.vue';
58
62
 
@@ -75,13 +79,15 @@ const props = defineProps<{
75
79
  }>();
76
80
 
77
81
  const emit = defineEmits<{
78
- submit: [values: any, eventData?: ContainerChangeEventData];
82
+ submit: [values: any, eventData?: ContainerChangeEventData, error?: any];
79
83
  'submit-error': [e: any];
80
84
  'form-error': [e: any];
81
85
  mounted: [internalInstance: any];
82
86
  unmounted: [];
83
87
  }>();
84
88
 
89
+ const enablePropsFormValidate = inject(ENABLE_PROPS_FORM_VALIDATE, false);
90
+
85
91
  const services = useServices();
86
92
  const { editorService, uiService } = services;
87
93
 
@@ -113,9 +119,16 @@ onUnmounted(() => {
113
119
  const submit = async (v: FormValue, eventData: ContainerChangeEventData) => {
114
120
  try {
115
121
  const values = await configFormRef.value?.submitForm();
122
+ // 校验成功:正常更新节点(第三个参数 error 为空,表示清除该来源的错误记录)
116
123
  emit('submit', values, eventData);
117
124
  } catch (e: any) {
118
- emit('submit-error', e);
125
+ if (enablePropsFormValidate) {
126
+ // 启用校验联动:校验失败时仍以当前表单值更新节点,并把错误信息一并抛给上层记录
127
+ emit('submit', v, eventData, e);
128
+ } else {
129
+ // 未启用:保持原行为,校验失败丢弃本次改动
130
+ emit('submit-error', e);
131
+ }
119
132
  }
120
133
  };
121
134
 
@@ -123,8 +136,56 @@ const errorHandler = (e: any) => {
123
136
  emit('form-error', e);
124
137
  };
125
138
 
126
- const saveCode = (values: any) => {
127
- emit('submit', props.codeValueKey ? { [props.codeValueKey]: values } : values);
139
+ /**
140
+ * 将校验错误文案安全地转为用于弹窗展示的 HTML。
141
+ *
142
+ * 文案形如 `字段 -> 主文案\n\n建议`,多条以结构性 `<br>` 拼接。为在保留换行排版的同时
143
+ * 避免 `${value}`/字段名中可能夹带的 HTML 造成 XSS:先按结构性 `<br>` 拆分,对每段做 HTML
144
+ * 转义后再把段内换行 `\n` 替换为 `<br>`,最后用 `<br>` 拼回。
145
+ */
146
+ const formatValidateErrorHtml = (error: string): string =>
147
+ error
148
+ .split(/<br\s*\/?>/i)
149
+ .map((segment) => filterXSS(segment).replaceAll('\n', '<br>'))
150
+ .join('<br>');
151
+
152
+ const saveCode = async (values: any) => {
153
+ const newValues = props.codeValueKey ? { [props.codeValueKey]: values } : values;
154
+
155
+ if (!enablePropsFormValidate) {
156
+ // 未启用校验联动:保持原行为,直接提交源码保存的值
157
+ emit('submit', newValues);
158
+ return;
159
+ }
160
+
161
+ // 启用校验联动:源码编辑器保存的值未经过表单交互,这里另建一个独立的 MForm 实例对最新配置
162
+ // 做一次静默校验(不复用、也不污染页面上正在展示的表单),并将校验结果(错误信息)随提交
163
+ // 一并抛给上层记录,使源码保存的错误状态与表单编辑保持一致。
164
+ try {
165
+ const error = await validatePropsForm({
166
+ config: props.config,
167
+ values: newValues,
168
+ appContext: internalInstance?.appContext ?? null,
169
+ services,
170
+ stage: stage.value,
171
+ typeMatchValid: true,
172
+ extendState: props.extendState,
173
+ });
174
+
175
+ if (error) {
176
+ tMagicMessage({
177
+ type: 'error',
178
+ message: formatValidateErrorHtml(error),
179
+ dangerouslyUseHTMLString: true,
180
+ });
181
+ }
182
+
183
+ emit('submit', newValues, undefined, error ? new Error(error) : undefined);
184
+ } catch (e: any) {
185
+ console.log('validateForm error', e);
186
+ // 静默校验本身出现异常(如初始化超时)时,退回到不携带错误信息的提交,避免阻塞源码保存
187
+ emit('submit', newValues);
188
+ }
128
189
  };
129
190
 
130
191
  defineExpose({ configForm: configFormRef, submit });
@@ -9,7 +9,7 @@
9
9
  :values="values"
10
10
  :disabledShowSrc="disabledShowSrc"
11
11
  :extendState="extendState"
12
- @submit="submit"
12
+ @submit="(v, eventData, error) => submit(v, eventData, error, 'props')"
13
13
  @submit-error="errorHandler"
14
14
  @form-error="errorHandler"
15
15
  @mounted="mountedHandler"
@@ -27,7 +27,7 @@
27
27
  :values="values"
28
28
  :disabledShowSrc="disabledShowSrc"
29
29
  :extendState="extendState"
30
- @submit="submit"
30
+ @submit="(v, eventData, error) => submit(v, eventData, error, 'style')"
31
31
  @submit-error="errorHandler"
32
32
  @form-error="errorHandler"
33
33
  >
@@ -55,7 +55,7 @@
55
55
  </template>
56
56
 
57
57
  <script lang="ts" setup>
58
- import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch, watchEffect } from 'vue';
58
+ import { computed, inject, onBeforeUnmount, onMounted, ref, useTemplateRef, watch, watchEffect } from 'vue';
59
59
  import { Close, Sugar } from '@element-plus/icons-vue';
60
60
  import type { OnDrag } from 'gesto';
61
61
 
@@ -66,9 +66,10 @@ import { setValueByKeyPath } from '@tmagic/utils';
66
66
 
67
67
  import MIcon from '@editor/components/Icon.vue';
68
68
  import Resizer from '@editor/components/Resizer.vue';
69
+ import { ENABLE_PROPS_FORM_VALIDATE } from '@editor/editorProps';
69
70
  import { useServices } from '@editor/hooks/use-services';
70
71
  import { Protocol } from '@editor/services/storage';
71
- import type { PropsPanelSlots } from '@editor/type';
72
+ import type { NodeInvalidSource, PropsPanelSlots } from '@editor/type';
72
73
  import { styleTabConfig } from '@editor/utils';
73
74
  import { PROPS_PANEL_WIDTH_STORAGE_KEY } from '@editor/utils/const';
74
75
 
@@ -95,6 +96,8 @@ const emit = defineEmits<{
95
96
 
96
97
  const { editorService, uiService, propsService, storageService } = useServices();
97
98
 
99
+ const enablePropsFormValidate = inject(ENABLE_PROPS_FORM_VALIDATE, false);
100
+
98
101
  const values = ref<FormValue>({});
99
102
  // ts类型应该是FormConfig, 但是打包时会出错,所以暂时用any
100
103
  const curFormConfig = ref<any>([]);
@@ -126,7 +129,12 @@ onBeforeUnmount(() => {
126
129
  propsService.off('props-configs-change', init);
127
130
  });
128
131
 
129
- const submit = async (v: MNode, eventData?: ContainerChangeEventData) => {
132
+ const submit = async (
133
+ v: MNode,
134
+ eventData?: ContainerChangeEventData,
135
+ error?: any,
136
+ source: NodeInvalidSource = 'props',
137
+ ) => {
130
138
  try {
131
139
  if (!v.id) {
132
140
  v.id = values.value.id;
@@ -155,7 +163,13 @@ const submit = async (v: MNode, eventData?: ContainerChangeEventData) => {
155
163
  // 源码编辑器(CodeEditor @save → saveCode)保存时不带 eventData,据此标记为「源码编辑器」。
156
164
  const historySource = eventData ? 'props' : 'code';
157
165
 
158
- editorService.update(newValue, { changeRecords: eventData?.changeRecords, historySource });
166
+ editorService.update(newValue, {
167
+ changeRecords: eventData?.changeRecords,
168
+ historySource,
169
+ // 启用校验联动时,仅校验失败(error 存在)才把错误信息随更新传入 editorService 记录;
170
+ // 其余情况(含表单校验成功、CodeEditor 源码保存)不携带 invalidInfo,由 editorService 在执行 update 时统一清除该节点错误。
171
+ ...(enablePropsFormValidate && error ? { invalidInfo: { id: newValue.id, source, error: error?.message } } : {}),
172
+ });
159
173
  } catch (e: any) {
160
174
  emit('submit-error', e);
161
175
  }
@@ -2,7 +2,7 @@ import { computed, type Ref, watch } from 'vue';
2
2
 
3
3
  import { Protocol } from '@editor/services/storage';
4
4
  import { Services } from '@editor/type';
5
- import { MIN_CENTER_COLUMN_WIDTH, RIGHT_COLUMN_WIDTH_STORAGE_KEY } from '@editor/utils/const';
5
+ import { RIGHT_COLUMN_WIDTH_STORAGE_KEY } from '@editor/utils/const';
6
6
 
7
7
  export const useStylePanel = (
8
8
  { uiService, storageService }: Pick<Services, 'uiService' | 'storageService'>,
@@ -47,8 +47,9 @@ export const useStylePanel = (
47
47
  }
48
48
 
49
49
  if (columnWidth.center < 0) {
50
- columnWidth.right = columnWidth.right + columnWidth.center - MIN_CENTER_COLUMN_WIDTH;
51
- columnWidth.center = MIN_CENTER_COLUMN_WIDTH;
50
+ const minCenterColumnWidth = uiService.get('minCenterColumnWidth');
51
+ columnWidth.right = columnWidth.right + columnWidth.center - minCenterColumnWidth;
52
+ columnWidth.center = minCenterColumnWidth;
52
53
 
53
54
  propsPanelWidth.value = columnWidth.right / 2;
54
55
  }
@@ -132,6 +132,7 @@
132
132
  v-model:height="columnLeftHeight"
133
133
  :width="columnLeftWidth"
134
134
  :title="config.text"
135
+ :framework-width="frameworkWidth"
135
136
  :position="{
136
137
  left: floatBoxStates[config.$key].left,
137
138
  top: floatBoxStates[config.$key].top,
@@ -221,6 +222,7 @@ const taskLength = computed(() => depService.get('taskLength'));
221
222
  const tipsBarVisible = ref(true);
222
223
 
223
224
  const columnLeftWidth = computed(() => uiService.get('columnWidth')[ColumnLayout.LEFT]);
225
+ const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
224
226
  const { height: editorContentHeight } = useEditorContentHeight();
225
227
  const columnLeftHeight = ref(0);
226
228
 
@@ -121,8 +121,11 @@ const {
121
121
  nodeContentMenuHandler,
122
122
  menuData: contentMenuData,
123
123
  contentMenuHideHandler,
124
+ getTarget,
124
125
  } = useContentMenu((id: string) => {
125
126
  codeBlockListRef.value?.deleteCode(id, { historySource: 'tree-contextmenu' });
126
127
  });
127
- const menuData = computed<(MenuButton | MenuComponent)[]>(() => props.customContentMenu(contentMenuData, 'code-block'));
128
+ const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
129
+ props.customContentMenu(contentMenuData, 'code-block', getTarget),
130
+ );
128
131
  </script>
@@ -3,13 +3,21 @@ import { CopyDocument, Delete, Edit } from '@element-plus/icons-vue';
3
3
  import { cloneDeep } from 'lodash-es';
4
4
 
5
5
  import ContentMenu from '@editor/components/ContentMenu.vue';
6
- import type { EventBus, MenuButton, MenuComponent, TreeNodeData } from '@editor/type';
6
+ import type { ContentMenuTarget, EventBus, MenuButton, MenuComponent, TreeNodeData } from '@editor/type';
7
7
 
8
8
  export const useContentMenu = (deleteCode: (id: string) => void) => {
9
9
  const eventBus = inject<EventBus>('eventBus');
10
10
  const menuRef = useTemplateRef<InstanceType<typeof ContentMenu>>('menu');
11
11
 
12
12
  let selectId = '';
13
+ let selectData: TreeNodeData | null = null;
14
+
15
+ const getTarget = (): ContentMenuTarget | null => {
16
+ if (!selectId) {
17
+ return null;
18
+ }
19
+ return { id: selectId, data: selectData ?? undefined };
20
+ };
13
21
 
14
22
  const menuData: (MenuButton | MenuComponent)[] = [
15
23
  {
@@ -65,19 +73,23 @@ export const useContentMenu = (deleteCode: (id: string) => void) => {
65
73
  menuRef.value?.show(event);
66
74
  if (data.id) {
67
75
  selectId = `${data.id}`;
76
+ selectData = data;
68
77
  } else {
69
78
  selectId = '';
79
+ selectData = null;
70
80
  }
71
81
  }
72
82
  };
73
83
 
74
84
  const contentMenuHideHandler = () => {
75
85
  selectId = '';
86
+ selectData = null;
76
87
  };
77
88
 
78
89
  return {
79
90
  menuData,
80
91
  nodeContentMenuHandler,
81
92
  contentMenuHideHandler,
93
+ getTarget,
82
94
  };
83
95
  };
@@ -1,9 +1,11 @@
1
1
  <template>
2
2
  <FloatingBox
3
+ :body-style="{ padding: '0 16px' }"
3
4
  v-model:visible="boxVisible"
4
5
  v-model:width="width"
5
6
  v-model:height="editorHeight"
6
7
  :title="title"
8
+ :framework-width="frameworkWidth"
7
9
  :position="boxPosition"
8
10
  >
9
11
  <template #body>
@@ -65,6 +67,7 @@ const { height: editorHeight } = useEditorContentHeight();
65
67
 
66
68
  const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
67
69
  const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
70
+ const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
68
71
 
69
72
  /** 供「方法定义」tab 内的字段消费,用于打开数据源详情后自动打开指定方法 */
70
73
  provide(
@@ -171,8 +171,8 @@ eventBus?.on('remove-data-source', (id: string) => {
171
171
  removeHandler(id);
172
172
  });
173
173
 
174
- const { nodeContentMenuHandler, menuData: contentMenuData, contentMenuHideHandler } = useContentMenu();
174
+ const { nodeContentMenuHandler, menuData: contentMenuData, contentMenuHideHandler, getTarget } = useContentMenu();
175
175
  const menuData = computed<(MenuButton | MenuComponent)[]>(() =>
176
- props.customContentMenu(contentMenuData, 'data-source'),
176
+ props.customContentMenu(contentMenuData, 'data-source', getTarget),
177
177
  );
178
178
  </script>
@@ -3,13 +3,21 @@ import { CopyDocument, Delete, Edit } from '@element-plus/icons-vue';
3
3
  import { cloneDeep } from 'lodash-es';
4
4
 
5
5
  import ContentMenu from '@editor/components/ContentMenu.vue';
6
- import type { EventBus, MenuButton, MenuComponent, TreeNodeData } from '@editor/type';
6
+ import type { ContentMenuTarget, EventBus, MenuButton, MenuComponent, TreeNodeData } from '@editor/type';
7
7
 
8
8
  export const useContentMenu = () => {
9
9
  const eventBus = inject<EventBus>('eventBus');
10
10
  const menuRef = useTemplateRef<InstanceType<typeof ContentMenu>>('menu');
11
11
 
12
12
  let selectId = '';
13
+ let selectData: TreeNodeData | null = null;
14
+
15
+ const getTarget = (): ContentMenuTarget | null => {
16
+ if (!selectId) {
17
+ return null;
18
+ }
19
+ return { id: selectId, data: selectData ?? undefined };
20
+ };
13
21
 
14
22
  const menuData: (MenuButton | MenuComponent)[] = [
15
23
  {
@@ -63,19 +71,23 @@ export const useContentMenu = () => {
63
71
  menuRef.value?.show(event);
64
72
  if (data.id) {
65
73
  selectId = `${data.id}`;
74
+ selectData = data;
66
75
  } else {
67
76
  selectId = '';
77
+ selectData = null;
68
78
  }
69
79
  }
70
80
  };
71
81
 
72
82
  const contentMenuHideHandler = () => {
73
83
  selectId = '';
84
+ selectData = null;
74
85
  };
75
86
 
76
87
  return {
77
88
  menuData,
78
89
  nodeContentMenuHandler,
79
90
  contentMenuHideHandler,
91
+ getTarget,
80
92
  };
81
93
  };
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <span class="m-editor-layer-node-content" :class="{ 'is-invalid': hasError }">
3
+ <span class="m-editor-layer-node-label">{{ label }}</span>
4
+ <TMagicTooltip v-if="hasError" placement="top">
5
+ <template #content>
6
+ <span v-html="errorMessage"></span>
7
+ </template>
8
+ <MIcon class="m-editor-layer-node-error-icon" :icon="WarningFilled"></MIcon>
9
+ </TMagicTooltip>
10
+ </span>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { computed } from 'vue';
15
+ import { WarningFilled } from '@element-plus/icons-vue';
16
+
17
+ import { stripValidateSuggestion, TMagicTooltip } from '@tmagic/design';
18
+
19
+ import MIcon from '@editor/components/Icon.vue';
20
+ import { useServices } from '@editor/hooks/use-services';
21
+ import type { TreeNodeData } from '@editor/type';
22
+
23
+ defineOptions({
24
+ name: 'MEditorLayerNodeContent',
25
+ });
26
+
27
+ const props = defineProps<{
28
+ data: TreeNodeData;
29
+ }>();
30
+
31
+ const { editorService } = useServices();
32
+
33
+ const label = computed(() => `${props.data.name} (${props.data.id})`);
34
+
35
+ /** 读取集中存储的校验错误状态,建立响应式依赖 */
36
+ const invalidInfo = computed(() => editorService.get('invalidNodeIds').get(props.data.id));
37
+
38
+ const hasError = computed(() => Boolean(invalidInfo.value?.props || invalidInfo.value?.style));
39
+
40
+ /**
41
+ * 去掉单条校验文案中的「修改建议」部分。
42
+ *
43
+ * 错误文案可能是多条错误以 `<br>` 拼接的 HTML,每条本身形如 `主文案\n\n建议`。
44
+ * 组件树 tooltip 仅展示错误描述,不展示修改建议(建议仅在错误汇总面板展示),
45
+ * 故先按 `<br>` 拆分,每段用 `stripValidateSuggestion` 截断建议后再拼回。
46
+ */
47
+ const stripSuggestion = (text?: string): string =>
48
+ String(text ?? '')
49
+ .split(/<br\s*\/?>/i)
50
+ .map((segment) => stripValidateSuggestion(segment))
51
+ .join('<br>');
52
+
53
+ /** 合并属性表单与样式表单的错误文案(去掉建议,本身可能仍是含 <br> 的 HTML) */
54
+ const errorMessage = computed(() =>
55
+ [stripSuggestion(invalidInfo.value?.props), stripSuggestion(invalidInfo.value?.style)].filter(Boolean).join('<br>'),
56
+ );
57
+ </script>
@@ -33,7 +33,9 @@
33
33
  </template>
34
34
 
35
35
  <template #tree-node-label="{ data: nodeData }">
36
- <slot name="layer-node-label" :data="nodeData"></slot>
36
+ <slot name="layer-node-label" :data="nodeData">
37
+ <LayerNodeContent :data="nodeData"></LayerNodeContent>
38
+ </slot>
37
39
  </template>
38
40
  </Tree>
39
41
 
@@ -69,6 +71,7 @@ import type {
69
71
  } from '@editor/type';
70
72
 
71
73
  import LayerMenu from './LayerMenu.vue';
74
+ import LayerNodeContent from './LayerNodeContent.vue';
72
75
  import LayerNodeTool from './LayerNodeTool.vue';
73
76
  import { useClick } from './use-click';
74
77
  import { useDrag } from './use-drag';
@@ -8,6 +8,7 @@
8
8
  ref="box"
9
9
  v-model:visible="visible"
10
10
  title="当前位置下的组件"
11
+ :framework-width="frameworkWidth"
11
12
  :position="menuPosition"
12
13
  >
13
14
  <template #body>
@@ -36,7 +37,9 @@ import { useNodeStatus } from '@editor/layouts/sidebar/layer/use-node-status';
36
37
  import type { TreeNodeData } from '@editor/type';
37
38
 
38
39
  const services = useServices();
39
- const { editorService } = services;
40
+ const { editorService, uiService } = services;
41
+
42
+ const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
40
43
 
41
44
  const visible = ref(false);
42
45
  const buttonVisible = ref(false);
package/src/plugin.ts CHANGED
@@ -21,7 +21,7 @@ import type { App } from 'vue';
21
21
  import type { DesignPluginOptions } from '@tmagic/design';
22
22
  import designPlugin from '@tmagic/design';
23
23
  import type { FormInstallOptions } from '@tmagic/form';
24
- import formPlugin from '@tmagic/form';
24
+ import formPlugin, { registerTypeMatchRules } from '@tmagic/form';
25
25
  import tablePlugin from '@tmagic/table';
26
26
 
27
27
  import Code from './fields/Code.vue';
@@ -44,6 +44,7 @@ import StyleSetter from './fields/StyleSetter/Index.vue';
44
44
  import uiSelect from './fields/UISelect.vue';
45
45
  import CodeEditor from './layouts/CodeEditor.vue';
46
46
  import { setEditorConfig } from './utils/config';
47
+ import { editorTypeMatchRules } from './utils/type-match-rules';
47
48
  import Editor from './Editor.vue';
48
49
  import type { EditorInstallOptions } from './type';
49
50
 
@@ -64,6 +65,7 @@ export default {
64
65
  app.use(designPlugin, opt || {});
65
66
  app.use(formPlugin, opt || {});
66
67
  app.use(tablePlugin);
68
+ registerTypeMatchRules(editorTypeMatchRules);
67
69
 
68
70
  app.config.globalProperties.$TMAGIC_EDITOR = option;
69
71
  setEditorConfig(option);