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

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 +7 -4
  2. package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +5 -1
  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 +18 -123
  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/components/ViewForm.js +5 -0
  8. package/dist/es/components/ViewForm.vue_vue_type_script_setup_true_lang.js +58 -0
  9. package/dist/es/editorProps.js +6 -1
  10. package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +15 -3
  11. package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +2 -17
  12. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +2 -1
  13. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +7 -0
  14. package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +5 -17
  15. package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +4 -0
  16. package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +29 -3
  17. package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +50 -88
  18. package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +2 -5
  19. package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +48 -23
  20. package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +6 -3
  21. package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +28 -22
  22. package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +28 -22
  23. package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +28 -22
  24. package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +6 -3
  25. package/dist/es/fields/UISelect.js +0 -1
  26. package/dist/es/hooks/use-compare-form.js +119 -0
  27. package/dist/es/index.js +13 -8
  28. package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +21 -6
  29. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +9 -6
  30. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +4 -1
  31. package/dist/es/layouts/history-list/composables.js +1 -1
  32. package/dist/es/layouts/history-list/useHistoryRevert.js +7 -3
  33. package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +44 -5
  34. package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +15 -8
  35. package/dist/es/layouts/props-panel/use-style-panel.js +3 -2
  36. package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +15 -31
  37. package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
  38. package/dist/es/layouts/sidebar/code-block/useContentMenu.js +18 -3
  39. package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +4 -0
  40. package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
  41. package/dist/es/layouts/sidebar/data-source/useContentMenu.js +18 -3
  42. package/dist/es/layouts/sidebar/layer/LayerNodeContent.js +5 -0
  43. package/dist/es/layouts/sidebar/layer/LayerNodeContent.vue_vue_type_script_setup_true_lang.js +46 -0
  44. package/dist/es/layouts/sidebar/layer/LayerPanel.vue_vue_type_script_setup_true_lang.js +2 -1
  45. package/dist/es/layouts/workspace/viewer/NodeListMenu.vue_vue_type_script_setup_true_lang.js +8 -2
  46. package/dist/es/plugin.js +3 -1
  47. package/dist/es/services/codeBlock.js +1 -1
  48. package/dist/es/services/editor.js +104 -5
  49. package/dist/es/services/history.js +1 -1
  50. package/dist/es/services/props.js +3 -2
  51. package/dist/es/services/storage.js +2 -2
  52. package/dist/es/services/ui.js +3 -0
  53. package/dist/es/style.css +104 -55
  54. package/dist/es/utils/data-source/index.js +30 -11
  55. package/dist/es/utils/event.js +123 -0
  56. package/dist/es/utils/monaco-editor.js +22 -6
  57. package/dist/es/utils/props.js +119 -8
  58. package/dist/es/utils/type-match-rules.js +360 -0
  59. package/dist/style.css +104 -55
  60. package/dist/tmagic-editor.umd.cjs +16996 -15806
  61. package/package.json +8 -8
  62. package/src/Editor.vue +3 -1
  63. package/src/components/CodeBlockEditor.vue +3 -0
  64. package/src/components/CompareForm.vue +16 -196
  65. package/src/components/FloatingBox.vue +77 -8
  66. package/src/components/ScrollBar.vue +0 -67
  67. package/src/components/ViewForm.vue +55 -0
  68. package/src/editorProps.ts +13 -0
  69. package/src/fields/CodeSelect.vue +4 -1
  70. package/src/fields/CondOpSelect.vue +2 -24
  71. package/src/fields/DataSourceFields.vue +5 -0
  72. package/src/fields/DataSourceInput.vue +7 -25
  73. package/src/fields/DataSourceMocks.vue +3 -0
  74. package/src/fields/DisplayConds.vue +14 -0
  75. package/src/fields/EventSelect.vue +84 -129
  76. package/src/fields/StyleSetter/Index.vue +2 -8
  77. package/src/fields/StyleSetter/pro/Background.vue +94 -55
  78. package/src/fields/StyleSetter/pro/Border.vue +9 -11
  79. package/src/fields/StyleSetter/pro/Font.vue +66 -64
  80. package/src/fields/StyleSetter/pro/Layout.vue +140 -138
  81. package/src/fields/StyleSetter/pro/Position.vue +69 -67
  82. package/src/fields/StyleSetter/pro/Transform.vue +23 -25
  83. package/src/fields/UISelect.vue +0 -12
  84. package/src/hooks/index.ts +1 -0
  85. package/src/hooks/use-compare-form.ts +187 -0
  86. package/src/index.ts +2 -0
  87. package/src/layouts/CodeEditor.vue +32 -6
  88. package/src/layouts/Framework.vue +7 -6
  89. package/src/layouts/history-list/HistoryDiffDialog.vue +11 -1
  90. package/src/layouts/history-list/useHistoryRevert.ts +4 -0
  91. package/src/layouts/props-panel/FormPanel.vue +66 -5
  92. package/src/layouts/props-panel/PropsPanel.vue +20 -6
  93. package/src/layouts/props-panel/use-style-panel.ts +4 -3
  94. package/src/layouts/sidebar/Sidebar.vue +2 -0
  95. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +4 -1
  96. package/src/layouts/sidebar/code-block/useContentMenu.ts +13 -1
  97. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +3 -0
  98. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +2 -2
  99. package/src/layouts/sidebar/data-source/useContentMenu.ts +13 -1
  100. package/src/layouts/sidebar/layer/LayerNodeContent.vue +57 -0
  101. package/src/layouts/sidebar/layer/LayerPanel.vue +4 -1
  102. package/src/layouts/workspace/viewer/NodeListMenu.vue +4 -1
  103. package/src/plugin.ts +3 -1
  104. package/src/services/editor.ts +168 -3
  105. package/src/services/ui.ts +6 -0
  106. package/src/theme/event.scss +8 -0
  107. package/src/theme/floating-box.scss +9 -1
  108. package/src/theme/layer-node-content.scss +14 -0
  109. package/src/theme/scroll-bar.scss +64 -0
  110. package/src/theme/theme.scss +3 -0
  111. package/src/theme/ui-select.scss +9 -0
  112. package/src/type.ts +105 -2
  113. package/src/utils/data-source/index.ts +30 -12
  114. package/src/utils/event.ts +224 -0
  115. package/src/utils/index.ts +2 -0
  116. package/src/utils/monaco-editor.ts +27 -0
  117. package/src/utils/props.ts +189 -2
  118. package/src/utils/type-match-rules.ts +678 -0
  119. package/types/index.d.ts +558 -213
@@ -33,7 +33,7 @@ import { getDesignConfig, TMagicSelect } from '@tmagic/design';
33
33
  import type { CondOpSelectConfig, FieldProps } from '@tmagic/form';
34
34
 
35
35
  import { useServices } from '@editor/hooks/use-services';
36
- import { arrayOptions, eqOptions, getFieldType, numberOptions } from '@editor/utils';
36
+ import { getCondOpOptionsByFieldType, getFieldType } from '@editor/utils';
37
37
 
38
38
  defineOptions({
39
39
  name: 'MFieldsCondOpSelect',
@@ -51,31 +51,9 @@ const optionComponent = getDesignConfig('components')?.option;
51
51
 
52
52
  const options = computed(() => {
53
53
  const [id, ...fieldNames] = [...(props.config.parentFields || []), ...props.model.field];
54
-
55
54
  const ds = dataSourceService.getDataSourceById(id);
56
-
57
55
  const type = getFieldType(ds, fieldNames);
58
-
59
- if (type === 'array') {
60
- return arrayOptions;
61
- }
62
-
63
- if (type === 'boolean' || type === 'null') {
64
- return [
65
- { text: '是', value: 'is' },
66
- { text: '不是', value: 'not' },
67
- ];
68
- }
69
-
70
- if (type === 'number') {
71
- return [...eqOptions, ...numberOptions];
72
- }
73
-
74
- if (type === 'string') {
75
- return [...arrayOptions, ...eqOptions];
76
- }
77
-
78
- return [...arrayOptions, ...eqOptions, ...numberOptions];
56
+ return getCondOpOptionsByFieldType(type);
79
57
  });
80
58
 
81
59
  const fieldChangeHandler = (v: string) => {
@@ -8,10 +8,12 @@
8
8
  </div>
9
9
 
10
10
  <FloatingBox
11
+ :body-style="{ padding: '0 16px' }"
11
12
  v-model:visible="addDialogVisible"
12
13
  v-model:width="width"
13
14
  v-model:height="editorHeight"
14
15
  :title="fieldTitle"
16
+ :framework-width="frameworkWidth"
15
17
  :position="boxPosition"
16
18
  >
17
19
  <template #body>
@@ -28,10 +30,12 @@
28
30
  </FloatingBox>
29
31
 
30
32
  <FloatingBox
33
+ :body-style="{ padding: '0 16px' }"
31
34
  v-model:visible="addFromJsonDialogVisible"
32
35
  v-model:width="width"
33
36
  v-model:height="editorHeight"
34
37
  title="快速添加数据定义"
38
+ :framework-width="frameworkWidth"
35
39
  :position="boxPosition"
36
40
  >
37
41
  <template #body>
@@ -358,6 +362,7 @@ const { height: editorHeight } = useEditorContentHeight();
358
362
 
359
363
  const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
360
364
  const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
365
+ const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
361
366
 
362
367
  /**
363
368
  * 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的字段路径(字段名数组)。
@@ -79,11 +79,11 @@ import { Coin } from '@element-plus/icons-vue';
79
79
  import type { DataSchema, DataSourceSchema } from '@tmagic/core';
80
80
  import { getDesignConfig, TMagicAutocomplete, TMagicInput, TMagicTag } from '@tmagic/design';
81
81
  import type { DataSourceInputConfig, FieldProps } from '@tmagic/form';
82
- import { getKeysArray, isNumber } from '@tmagic/utils';
82
+ import { getKeysArray } from '@tmagic/utils';
83
83
 
84
84
  import Icon from '@editor/components/Icon.vue';
85
85
  import { useServices } from '@editor/hooks/use-services';
86
- import { getDisplayField } from '@editor/utils/data-source';
86
+ import { getDisplayField, resolveFieldByPath } from '@editor/utils/data-source';
87
87
 
88
88
  defineOptions({
89
89
  name: 'MFieldsDataSourceInput',
@@ -251,34 +251,16 @@ const fieldQuerySearch = (
251
251
  return;
252
252
  }
253
253
 
254
- let fields = ds.fields || [];
255
-
256
- // 后面这些是字段
257
- let key = keys.shift();
258
- while (key) {
259
- if (isNumber(key)) {
260
- key = keys.shift();
261
- continue;
262
- }
263
-
264
- for (const field of fields) {
265
- if (field.name === key) {
266
- fields = field.fields || [];
267
- key = keys.shift();
268
- break;
269
- }
270
- }
271
- }
254
+ const { fields } = resolveFieldByPath(ds.fields, keys, { skipNumberIndices: true });
272
255
 
273
256
  if (curCharIsDot(dotIndex)) {
274
257
  // 当前输入的是.
275
- result = fields || [];
258
+ result = fields;
276
259
  } else if (dotIndex > -1) {
277
260
  const queryName = queryString.substring(dotIndex + 1).toLowerCase();
278
- result =
279
- fields.filter(
280
- (field) => field.name?.toLowerCase().includes(queryName) || field.title?.toLowerCase().includes(queryName),
281
- ) || [];
261
+ result = fields.filter(
262
+ (field) => field.name?.toLowerCase().includes(queryName) || field.title?.toLowerCase().includes(queryName),
263
+ );
282
264
  }
283
265
 
284
266
  cb(
@@ -7,10 +7,12 @@
7
7
  </div>
8
8
 
9
9
  <FloatingBox
10
+ :body-style="{ padding: '0 16px' }"
10
11
  v-model:visible="addDialogVisible"
11
12
  v-model:width="width"
12
13
  v-model:height="editorHeight"
13
14
  :title="drawerTitle"
15
+ :framework-width="frameworkWidth"
14
16
  :position="boxPosition"
15
17
  >
16
18
  <template #body>
@@ -254,4 +256,5 @@ const addDialogVisible = defineModel<boolean>('visible', { default: false });
254
256
  const { height: editorHeight } = useEditorContentHeight();
255
257
  const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
256
258
  const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
259
+ const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
257
260
  </script>
@@ -98,6 +98,11 @@ const config = computed<GroupListConfig>(() => ({
98
98
  label: '字段',
99
99
  checkStrictly: false,
100
100
  onChange: fieldOnChange,
101
+ defaultValue: () => [],
102
+ rules: [
103
+ { required: true, trigger: 'blur', message: '请选择字段' },
104
+ { typeMatch: true, trigger: 'change' },
105
+ ],
101
106
  }
102
107
  : {
103
108
  type: 'data-source-field-select',
@@ -107,6 +112,11 @@ const config = computed<GroupListConfig>(() => ({
107
112
  checkStrictly: false,
108
113
  dataSourceFieldType: ['string', 'number', 'boolean', 'any'],
109
114
  onChange: fieldOnChange,
115
+ defaultValue: () => [],
116
+ rules: [
117
+ { required: true, trigger: 'blur', message: '请选择字段' },
118
+ { typeMatch: true, trigger: 'change' },
119
+ ],
110
120
  },
111
121
  {
112
122
  type: 'cond-op-select',
@@ -114,6 +124,10 @@ const config = computed<GroupListConfig>(() => ({
114
124
  label: '条件',
115
125
  width: 140,
116
126
  name: 'op',
127
+ rules: [
128
+ { required: true, trigger: 'blur', message: '请选择条件' },
129
+ { typeMatch: true, trigger: 'change' },
130
+ ],
117
131
  },
118
132
  {
119
133
  label: '值',
@@ -67,11 +67,9 @@ import { computed } from 'vue';
67
67
  import { Delete } from '@element-plus/icons-vue';
68
68
  import { has } from 'lodash-es';
69
69
 
70
- import type { EventOption, MComponent, MContainer } from '@tmagic/core';
71
70
  import { ActionType } from '@tmagic/core';
72
71
  import { TMagicButton } from '@tmagic/design';
73
72
  import type {
74
- CascaderOption,
75
73
  CodeSelectColConfig,
76
74
  ContainerChangeEventData,
77
75
  DataSourceMethodSelectConfig,
@@ -84,10 +82,15 @@ import type {
84
82
  UISelectConfig,
85
83
  } from '@tmagic/form';
86
84
  import { defineFormItem, MContainer as MFormContainer, MPanel, MTable } from '@tmagic/form';
87
- import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX, traverseNode } from '@tmagic/utils';
88
85
 
89
86
  import { useServices } from '@editor/hooks/use-services';
90
- import { getCascaderOptionsFromFields } from '@editor/utils';
87
+ import {
88
+ getCompActionAllowedValues,
89
+ getCompActionOptions,
90
+ getEventNameAllowedValues,
91
+ getEventNameOptions,
92
+ normalizeCompActionValue,
93
+ } from '@editor/utils';
91
94
 
92
95
  defineOptions({
93
96
  name: 'MFieldsEventSelect',
@@ -118,68 +121,57 @@ const eventNameConfig = computed(() => {
118
121
  labelWidth: '40px',
119
122
  checkStrictly: () => props.config.src !== 'component',
120
123
  valueSeparator: '.',
121
- options: (mForm: FormState, { formValue }: any) => {
122
- let events: EventOption[] | CascaderOption[] = [];
123
-
124
- if (props.config.src === 'component') {
125
- events = eventsService.getEvent(formValue.type);
126
-
127
- if (formValue.type === 'page-fragment-container' && formValue.pageFragmentId) {
128
- const pageFragment = editorService.get('root')?.items?.find((page) => page.id === formValue.pageFragmentId);
129
- if (pageFragment) {
130
- events = [
131
- {
132
- label: pageFragment.name || '页面片容器',
133
- value: pageFragment.id,
134
- children: events,
135
- },
136
- ];
137
- pageFragment.items.forEach((node) => {
138
- traverseNode<MComponent | MContainer>(node, (node) => {
139
- const nodeEvents = (node.type && eventsService.getEvent(node.type)) || [];
140
-
141
- events.push({
142
- label: `${node.name}_${node.id}`,
143
- value: `${node.id}`,
144
- children: nodeEvents,
145
- });
146
- });
147
- });
148
-
149
- return events;
124
+ options: (_mForm: FormState, { formValue }: any) => getEventNameOptions(props.config.src, formValue),
125
+ rules: [
126
+ {
127
+ validator: ({ value, callback }: any, { formValue }: any) => {
128
+ const allowedNames = getEventNameAllowedValues(props.config as any, formValue);
129
+ if (allowedNames && allowedNames.size > 0 && value && !allowedNames.has(value)) {
130
+ return callback(`事件名(${value})不存在`);
150
131
  }
151
- }
152
-
153
- return events.map((option) => ({
154
- text: option.label,
155
- value: option.value,
156
- }));
157
- }
158
-
159
- if (props.config.src === 'datasource') {
160
- // 从数据源类型中获取到相关事件
161
- events = dataSourceService.getFormEvent(formValue.type);
162
- // 从数据源类型和实例中分别获取数据以追加数据变化的事件
163
- const dataSource = dataSourceService.getDataSourceById(formValue.id);
164
- const fields = dataSource?.fields || [];
165
- if (fields.length > 0) {
166
- return [
167
- ...events,
168
- {
169
- label: '数据变化',
170
- value: DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX,
171
- children: getCascaderOptionsFromFields(fields),
172
- },
173
- ];
174
- }
175
-
176
- return events;
177
- }
178
- },
132
+ callback();
133
+ },
134
+ trigger: 'blur',
135
+ },
136
+ ],
179
137
  };
180
138
  return { ...defaultEventNameConfig, ...props.config.eventNameConfig };
181
139
  });
182
140
 
141
+ const actionTypeOptions = computed(() => {
142
+ const o: {
143
+ text: string;
144
+ label: string;
145
+ value: string;
146
+ disabled?: boolean;
147
+ }[] = [
148
+ {
149
+ text: '组件',
150
+ label: '组件',
151
+ value: ActionType.COMP,
152
+ },
153
+ ];
154
+
155
+ if (!propsService.getDisabledCodeBlock()) {
156
+ o.push({
157
+ text: '代码',
158
+ label: '代码',
159
+ disabled: !Object.keys(codeBlockService.getCodeDsl() || {}).length,
160
+ value: ActionType.CODE,
161
+ });
162
+ }
163
+
164
+ if (!propsService.getDisabledDataSource()) {
165
+ o.push({
166
+ text: '数据源',
167
+ label: '数据源',
168
+ value: ActionType.DATA_SOURCE,
169
+ });
170
+ }
171
+
172
+ return o;
173
+ });
174
+
183
175
  // 联动类型
184
176
  const actionTypeConfig = computed(() => {
185
177
  const defaultActionTypeConfig = {
@@ -187,39 +179,17 @@ const actionTypeConfig = computed(() => {
187
179
  text: '联动类型',
188
180
  type: 'select',
189
181
  defaultValue: ActionType.COMP,
190
- options: () => {
191
- const o: {
192
- text: string;
193
- label: string;
194
- value: string;
195
- disabled?: boolean;
196
- }[] = [
197
- {
198
- text: '组件',
199
- label: '组件',
200
- value: ActionType.COMP,
201
- },
202
- ];
203
-
204
- if (!propsService.getDisabledCodeBlock()) {
205
- o.push({
206
- text: '代码',
207
- label: '代码',
208
- disabled: !Object.keys(codeBlockService.getCodeDsl() || {}).length,
209
- value: ActionType.CODE,
210
- });
211
- }
212
-
213
- if (!propsService.getDisabledDataSource()) {
214
- o.push({
215
- text: '数据源',
216
- label: '数据源',
217
- value: ActionType.DATA_SOURCE,
218
- });
219
- }
220
-
221
- return o;
222
- },
182
+ options: actionTypeOptions.value,
183
+ rules: [
184
+ {
185
+ required: true,
186
+ message: '联动类型不能为空',
187
+ },
188
+ {
189
+ typeMatch: true,
190
+ trigger: 'blur',
191
+ },
192
+ ],
223
193
  };
224
194
  return { ...defaultActionTypeConfig, ...props.config.actionTypeConfig };
225
195
  });
@@ -234,6 +204,12 @@ const targetCompConfig = computed(() => {
234
204
  onChange: (_MForm, _v, { setModel }) => {
235
205
  setModel('method', '');
236
206
  },
207
+ rules: [
208
+ {
209
+ typeMatch: true,
210
+ trigger: 'blur',
211
+ },
212
+ ],
237
213
  };
238
214
  return { ...defaultTargetCompConfig, ...props.config.targetCompConfig };
239
215
  });
@@ -254,41 +230,20 @@ const compActionConfig = computed(() => {
254
230
  },
255
231
  checkStrictly: () => props.config.src !== 'component',
256
232
  display: (mForm: FormState | undefined, { model }: any) => model.actionType === ActionType.COMP,
257
- options: (mForm: FormState, { model }: any) => {
258
- const node = editorService.getNodeById(model.to);
259
- if (!node?.type) return [];
260
-
261
- let methods: EventOption[] | CascaderOption[] = [];
262
-
263
- methods = eventsService.getMethod(node.type, model.to);
264
-
265
- if (node.type === 'page-fragment-container' && node.pageFragmentId) {
266
- const pageFragment = editorService.get('root')?.items?.find((page) => page.id === node.pageFragmentId);
267
- if (pageFragment) {
268
- methods = [];
269
- pageFragment.items.forEach((node: MComponent | MContainer) => {
270
- traverseNode<MComponent | MContainer>(node, (node) => {
271
- const nodeMethods = (node.type && eventsService.getMethod(node.type, node.id)) || [];
272
-
273
- if (nodeMethods.length) {
274
- methods.push({
275
- label: `${node.name}_${node.id}`,
276
- value: `${node.id}`,
277
- children: nodeMethods,
278
- });
279
- }
280
- });
281
- });
282
-
283
- return methods;
284
- }
285
- }
286
-
287
- return methods.map((method) => ({
288
- text: method.label,
289
- value: method.value,
290
- }));
291
- },
233
+ options: (_mForm: FormState, { model }: any) => getCompActionOptions(model.to),
234
+ rules: [
235
+ {
236
+ trigger: 'blur',
237
+ validator: ({ value, callback }: any, { model }: any) => {
238
+ const allowedMethods = getCompActionAllowedValues(props.config as any, model);
239
+ const normalized = normalizeCompActionValue(value);
240
+ if (allowedMethods && allowedMethods.size > 0 && normalized && !allowedMethods.has(normalized)) {
241
+ return callback(`动作名(${normalized})不存在`);
242
+ }
243
+ callback();
244
+ },
245
+ },
246
+ ],
292
247
  };
293
248
  return { ...defaultCompActionConfig, ...props.config.compActionConfig };
294
249
  });
@@ -7,6 +7,7 @@
7
7
  v-if="item.component"
8
8
  :is="item.component"
9
9
  :values="model[name]"
10
+ :prop="prop || name"
10
11
  :last-values="lastValues?.[name]"
11
12
  :is-compare="isCompare"
12
13
  :size="size"
@@ -35,7 +36,7 @@ defineOptions({
35
36
  name: 'MFieldsStyleSetter',
36
37
  });
37
38
 
38
- const props = defineProps<FieldProps<StyleSchema>>();
39
+ defineProps<FieldProps<StyleSchema>>();
39
40
 
40
41
  const emit = defineEmits<{
41
42
  change: [v: any, eventData: ContainerChangeEventData];
@@ -77,13 +78,6 @@ const collapseValue = shallowRef(
77
78
  );
78
79
 
79
80
  const change = (v: any, eventData: ContainerChangeEventData) => {
80
- eventData.changeRecords?.forEach((record) => {
81
- if (props.prop) {
82
- record.propPath = `${props.prop}.${record.propPath}`;
83
- } else if (props.name) {
84
- record.propPath = `${props.name}.${record.propPath}`;
85
- }
86
- });
87
81
  emit('change', v, eventData);
88
82
  };
89
83
 
@@ -1,6 +1,9 @@
1
1
  <template>
2
2
  <MContainer
3
- :config="config"
3
+ v-for="item in formConfig"
4
+ :prop="prop"
5
+ :key="item.name"
6
+ :config="item"
4
7
  :model="values"
5
8
  :last-values="lastValues"
6
9
  :is-compare="isCompare"
@@ -14,7 +17,7 @@
14
17
  <script lang="ts" setup>
15
18
  import { markRaw } from 'vue';
16
19
 
17
- import { type ContainerChangeEventData, defineFormItem, MContainer } from '@tmagic/form';
20
+ import { type ContainerChangeEventData, defineFormConfig, MContainer } from '@tmagic/form';
18
21
  import type { StyleSchema } from '@tmagic/schema';
19
22
 
20
23
  import BackgroundPosition from '../components/BackgroundPosition.vue';
@@ -26,6 +29,7 @@ defineProps<{
26
29
  isCompare?: boolean;
27
30
  disabled?: boolean;
28
31
  size?: 'large' | 'default' | 'small';
32
+ prop?: string;
29
33
  }>();
30
34
 
31
35
  const emit = defineEmits<{
@@ -33,60 +37,95 @@ const emit = defineEmits<{
33
37
  addDiffCount: [];
34
38
  }>();
35
39
 
36
- const config = defineFormItem({
37
- items: [
38
- {
39
- name: 'backgroundColor',
40
- text: '背景色',
41
- labelWidth: '68px',
42
- type: 'data-source-field-select',
43
- fieldConfig: {
44
- type: 'colorPicker',
45
- },
46
- },
47
- {
48
- name: 'backgroundImage',
49
- text: '背景图',
50
- labelWidth: '68px',
51
- type: 'data-source-field-select',
52
- fieldConfig: {
53
- type: 'img-upload',
54
- } as any,
55
- },
56
- {
57
- name: 'backgroundSize',
58
- text: '背景尺寸',
59
- type: 'radioGroup',
60
- childType: 'button',
61
- labelWidth: '68px',
62
- options: [
63
- { value: 'auto', text: '默认', tooltip: '默认 auto' },
64
- { value: 'contain', text: '等比填充', tooltip: '等比填充 contain' },
65
- { value: 'cover', text: '等比覆盖', tooltip: '等比覆盖 cover' },
66
- ],
67
- },
68
- {
69
- name: 'backgroundRepeat',
70
- text: '重复显示',
71
- type: 'radioGroup',
72
- childType: 'button',
73
- labelWidth: '68px',
74
- options: [
75
- { value: 'repeat', icon: markRaw(BackgroundRepeat), tooltip: '垂直和水平方向重复 repeat' },
76
- { value: 'repeat-x', icon: markRaw(BackgroundRepeatX), tooltip: '水平方向重复 repeat-x' },
77
- { value: 'repeat-y', icon: markRaw(BackgroundRepeatY), tooltip: '垂直方向重复 repeat-y' },
78
- { value: 'no-repeat', icon: markRaw(BackgroundNoRepeat), tooltip: '不重复 no-repeat' },
79
- ],
40
+ const formConfig = defineFormConfig([
41
+ {
42
+ name: 'backgroundColor',
43
+ text: '背景色',
44
+ labelWidth: '68px',
45
+ type: 'data-source-field-select',
46
+ fieldConfig: {
47
+ type: 'colorPicker',
80
48
  },
81
- {
82
- name: 'backgroundPosition',
83
- text: '背景定位',
84
- type: 'component',
85
- component: BackgroundPosition,
86
- labelWidth: '68px',
87
- },
88
- ],
89
- });
49
+ },
50
+ {
51
+ name: 'backgroundImage',
52
+ text: '背景图',
53
+ labelWidth: '68px',
54
+ type: 'data-source-field-select',
55
+ fieldConfig: {
56
+ type: 'img-upload',
57
+ } as any,
58
+ },
59
+ {
60
+ name: 'backgroundSize',
61
+ text: '背景尺寸',
62
+ type: 'radioGroup',
63
+ childType: 'button',
64
+ labelWidth: '68px',
65
+ options: [
66
+ { value: 'auto', text: '默认', tooltip: '默认 auto' },
67
+ { value: 'contain', text: '等比填充', tooltip: '等比填充 contain' },
68
+ { value: 'cover', text: '等比覆盖', tooltip: '等比覆盖 cover' },
69
+ ],
70
+ rules: [
71
+ {
72
+ typeMatch: false,
73
+ },
74
+ {
75
+ validator: ({ value, callback }) => {
76
+ if (value === '' || value === null || value === undefined) {
77
+ return callback();
78
+ }
79
+
80
+ const keywords = ['auto', 'cover', 'contain', 'inherit', 'initial', 'revert', 'unset'];
81
+ // 单值:关键字 或 长度/百分比
82
+ const lengthPercent = /^-?\d+(\.\d+)?(px|em|rem|ex|ch|vw|vh|vmin|vmax|cm|mm|in|pt|pc|%)$/;
83
+ const singleValue = (v: string) => keywords.includes(v) || lengthPercent.test(v);
84
+
85
+ const str = String(value).trim();
86
+ const parts = str.split(/\s+/);
87
+
88
+ // cover / contain 不能与其他值组合
89
+ if (parts.length > 1 && (parts.includes('cover') || parts.includes('contain'))) {
90
+ return callback('cover/contain 不能与其他值组合');
91
+ }
92
+
93
+ // 多值最多两个
94
+ if (parts.length > 2) {
95
+ return callback('backgroundSize 最多支持两个值');
96
+ }
97
+
98
+ // 关键字 auto 在多值场景中允许与其他长度/百分比组合
99
+ if (parts.every((part) => singleValue(part))) {
100
+ return callback();
101
+ }
102
+
103
+ return callback('backgroundSize 值不合法');
104
+ },
105
+ },
106
+ ],
107
+ },
108
+ {
109
+ name: 'backgroundRepeat',
110
+ text: '重复显示',
111
+ type: 'radioGroup',
112
+ childType: 'button',
113
+ labelWidth: '68px',
114
+ options: [
115
+ { value: 'repeat', icon: markRaw(BackgroundRepeat), tooltip: '垂直和水平方向重复 repeat' },
116
+ { value: 'repeat-x', icon: markRaw(BackgroundRepeatX), tooltip: '水平方向重复 repeat-x' },
117
+ { value: 'repeat-y', icon: markRaw(BackgroundRepeatY), tooltip: '垂直方向重复 repeat-y' },
118
+ { value: 'no-repeat', icon: markRaw(BackgroundNoRepeat), tooltip: '不重复 no-repeat' },
119
+ ],
120
+ },
121
+ {
122
+ name: 'backgroundPosition',
123
+ text: '背景定位',
124
+ type: 'component',
125
+ component: BackgroundPosition,
126
+ labelWidth: '68px',
127
+ },
128
+ ]);
90
129
 
91
130
  const change = (value: StyleSchema, eventData: ContainerChangeEventData) => {
92
131
  emit('change', value, eventData);