@tmagic/editor 1.8.0-beta.10 → 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 +14 -4
  28. package/dist/es/layouts/history-list/composables.js +1 -1
  29. package/dist/es/layouts/history-list/useHistoryRevert.js +17 -6
  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 +16923 -15771
  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 +23 -5
  82. package/src/layouts/history-list/useHistoryRevert.ts +25 -5
  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 +72 -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 +391 -85
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.8.0-beta.10",
2
+ "version": "1.8.0-beta.12",
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.10",
62
- "@tmagic/form": "1.8.0-beta.10",
63
- "@tmagic/table": "1.8.0-beta.10",
64
- "@tmagic/utils": "1.8.0-beta.10",
65
- "@tmagic/stage": "1.8.0-beta.10"
61
+ "@tmagic/stage": "1.8.0-beta.12",
62
+ "@tmagic/design": "1.8.0-beta.12",
63
+ "@tmagic/utils": "1.8.0-beta.12",
64
+ "@tmagic/form": "1.8.0-beta.12",
65
+ "@tmagic/table": "1.8.0-beta.12"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/events": "^3.0.3",
@@ -75,8 +75,8 @@
75
75
  "monaco-editor": "^0.55.1 ",
76
76
  "type-fest": "^5.2.0",
77
77
  "typescript": "^6.0.3",
78
- "vue": "^3.5.38",
79
- "@tmagic/core": "1.8.0-beta.10"
78
+ "vue": "^3.5.40",
79
+ "@tmagic/core": "1.8.0-beta.12"
80
80
  },
81
81
  "peerDependenciesMeta": {
82
82
  "typescript": {
package/src/Editor.vue CHANGED
@@ -158,7 +158,7 @@ import stageOverlayService from './services/stageOverlay';
158
158
  import storageService from './services/storage';
159
159
  import uiService from './services/ui';
160
160
  import keybindingConfig from './utils/keybinding-config';
161
- import { defaultEditorProps, EditorProps } from './editorProps';
161
+ import { defaultEditorProps, EditorProps, ENABLE_PROPS_FORM_VALIDATE } from './editorProps';
162
162
  import { initServiceEvents, initServiceState } from './initService';
163
163
  import type { TreeNodeData } from './type';
164
164
  import type { EditorSlots, EventBus, Services, StageOptions } from './type';
@@ -233,6 +233,8 @@ provide('services', services);
233
233
 
234
234
  provide('codeOptions', props.codeOptions);
235
235
  provide('stageOptions', stageOptions);
236
+ /** 是否启用「属性配置表单校验」联动能力,供 PropsPanel / FormPanel 判断校验失败时是否仍更新节点并记录错误 */
237
+ provide(ENABLE_PROPS_FORM_VALIDATE, props.enablePropsFormValidate ?? false);
236
238
  /**
237
239
  * 把顶层 `extendFormState` 提供给非 PropsPanel 链路上的组件使用(例如历史差异对话框 HistoryDiffDialog
238
240
  * 内部的 CompareForm)。这样所有依赖业务上下文的表单 filterFunction 都能拿到一致的扩展状态,
@@ -4,7 +4,9 @@
4
4
  v-model:visible="boxVisible"
5
5
  v-model:width="width"
6
6
  v-model:height="codeBlockEditorHeight"
7
+ :body-style="{ padding: '0 16px' }"
7
8
  :title="content.name ? `${disabled ? '查看' : '编辑'}${content.name}` : '新增代码'"
9
+ :framework-width="frameworkWidth"
8
10
  :position="boxPosition"
9
11
  :before-close="beforeClose"
10
12
  >
@@ -206,6 +208,7 @@ const closedHandler = () => {
206
208
 
207
209
  const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
208
210
  const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
211
+ const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
209
212
 
210
213
  watch(boxVisible, (visible) => {
211
214
  nextTick(() => {
@@ -13,6 +13,7 @@
13
13
  :extend-state="mergedExtendState"
14
14
  :show-diff="showDiff"
15
15
  :self-diff-field-types="selfDiffFieldTypes"
16
+ :size="size"
16
17
  ></MForm>
17
18
  </div>
18
19
  </template>
@@ -22,6 +23,7 @@ import { computed, inject, provide, type Ref, ref, type ShallowRef, useTemplateR
22
23
  import { isEqual } from 'lodash-es';
23
24
 
24
25
  import { type CodeBlockContent, type DataSourceSchema, HookType, type MNode } from '@tmagic/core';
26
+ import { type FieldSize } from '@tmagic/design';
25
27
  import { type FormConfig, type FormState, type FormValue, MForm } from '@tmagic/form';
26
28
 
27
29
  import type { CompareCategory, CompareFormLoadConfig, Services } from '@editor/type';
@@ -68,6 +70,11 @@ const props = withDefaults(
68
70
  baseFormState?: FormState;
69
71
  /** 需要走 self diff 的字段类型(例如 mod-cond)。 */
70
72
  selfDiffFieldTypes?: string[];
73
+ /**
74
+ * 表单内组件的尺寸(透传给 MForm 的 `size`),可选 'large' | 'default' | 'small'。
75
+ * 缺省时使用 MForm 内置默认尺寸。
76
+ */
77
+ size?: FieldSize;
71
78
  /**
72
79
  * 自定义 FormConfig 加载逻辑。传入后将接管内置的按 `category`(node/data-source/code-block)
73
80
  * 取配置逻辑,调用方可根据业务自行返回(或异步返回)表单配置。可通过
@@ -1,6 +1,12 @@
1
1
  <template>
2
2
  <Teleport to="body" v-if="visible">
3
- <div ref="target" class="m-editor-float-box" :style="{ ...style, zIndex: curZIndex }" @mousedown="nextZIndex">
3
+ <div
4
+ ref="target"
5
+ class="m-editor-float-box"
6
+ v-bind="$attrs"
7
+ :style="{ ...style, zIndex: curZIndex }"
8
+ @mousedown="nextZIndex"
9
+ >
4
10
  <div ref="title" class="m-editor-float-box-title">
5
11
  <slot name="title">
6
12
  <span>{{ title }}</span>
@@ -9,7 +15,7 @@
9
15
  <TMagicButton link size="small" @click="closeHandler"><MIcon :icon="Close"></MIcon></TMagicButton>
10
16
  </div>
11
17
  </div>
12
- <div class="m-editor-float-box-body" :style="{ height: `${bodyHeight}px` }">
18
+ <div class="m-editor-float-box-body" :style="{ height: `${bodyHeight}px`, ...bodyStyle }">
13
19
  <slot name="body"></slot>
14
20
  </div>
15
21
  </div>
@@ -17,14 +23,13 @@
17
23
  </template>
18
24
 
19
25
  <script setup lang="ts">
20
- import { computed, nextTick, onBeforeUnmount, provide, ref, useTemplateRef, watch } from 'vue';
26
+ import { computed, type CSSProperties, nextTick, onBeforeUnmount, provide, ref, useTemplateRef, watch } from 'vue';
21
27
  import { Close } from '@element-plus/icons-vue';
22
28
  import VanillaMoveable from 'moveable';
23
29
 
24
30
  import { TMagicButton, useZIndex } from '@tmagic/design';
25
31
 
26
32
  import MIcon from '@editor/components/Icon.vue';
27
- import { useServices } from '@editor/hooks/use-services';
28
33
 
29
34
  interface Position {
30
35
  left: number;
@@ -39,11 +44,18 @@ const props = withDefaults(
39
44
  defineProps<{
40
45
  position?: Position;
41
46
  title?: string;
47
+ bodyStyle?: CSSProperties;
48
+ /** 浮窗初始样式,会与内部计算样式合并,外部传入优先 */
49
+ initialStyle?: CSSProperties;
50
+ /** 用于约束浮窗 left 的容器宽度,传入时按宽度收敛 left,避免超出右边界;默认取视窗宽度 */
51
+ frameworkWidth?: number;
42
52
  beforeClose?: (_done: (_cancel?: boolean) => void) => void;
43
53
  }>(),
44
54
  {
45
55
  title: '',
46
56
  position: () => ({ left: 0, top: 0 }),
57
+ initialStyle: () => ({}),
58
+ frameworkWidth: 0,
47
59
  },
48
60
  );
49
61
 
@@ -66,12 +78,11 @@ const bodyHeight = computed(() => {
66
78
  return 'auto';
67
79
  });
68
80
 
69
- const { uiService } = useServices();
70
- const frameworkWidth = computed(() => uiService.get('frameworkRect').width || 0);
71
81
  const style = computed(() => {
72
82
  let { left } = props.position;
73
- if (width.value) {
74
- left = left + width.value > frameworkWidth.value ? frameworkWidth.value - width.value : left;
83
+ const frameworkWidth = props.frameworkWidth || globalThis.window?.innerWidth || 0;
84
+ if (width.value && frameworkWidth) {
85
+ left = left + width.value > frameworkWidth ? frameworkWidth - width.value : left;
75
86
  }
76
87
 
77
88
  return {
@@ -79,11 +90,60 @@ const style = computed(() => {
79
90
  top: `${props.position.top}px`,
80
91
  width: width.value ? `${width.value}px` : 'auto',
81
92
  height: height.value ? `${height.value}px` : 'auto',
93
+ ...props.initialStyle,
82
94
  };
83
95
  });
84
96
 
85
97
  let moveable: VanillaMoveable | null = null;
86
98
 
99
+ // 拖拽/缩放时用于覆盖 iframe 的遮罩,防止鼠标进入 iframe 区域后事件被 iframe 吞掉导致拖拽丢失
100
+ let dragMask: HTMLDivElement | null = null;
101
+ let dragMaskVisible = false;
102
+
103
+ // 兜底:正常情况下遮罩由 moveable 的 dragEnd/resizeEnd 移除;但某些极端场景
104
+ // (拖拽中鼠标移出窗口后松开、alt-tab 切走窗口失焦、标签页被隐藏等)结束事件不会触发,
105
+ // 会导致全屏透明遮罩残留、整个编辑器无法点击。这里挂全局 pointerup/blur/visibilitychange 强制收尾。
106
+ const bindDragMaskSafety = () => {
107
+ globalThis.window?.addEventListener('pointerup', hideDragMask, true);
108
+ globalThis.window?.addEventListener('blur', hideDragMask);
109
+ globalThis.document?.addEventListener('visibilitychange', hideDragMask);
110
+ };
111
+
112
+ const unbindDragMaskSafety = () => {
113
+ globalThis.window?.removeEventListener('pointerup', hideDragMask, true);
114
+ globalThis.window?.removeEventListener('blur', hideDragMask);
115
+ globalThis.document?.removeEventListener('visibilitychange', hideDragMask);
116
+ };
117
+
118
+ const showDragMask = () => {
119
+ if (dragMaskVisible) {
120
+ return;
121
+ }
122
+ if (!dragMask) {
123
+ dragMask = globalThis.document.createElement('div');
124
+ dragMask.className = 'm-editor-float-box-drag-mask';
125
+ }
126
+ globalThis.document.body.appendChild(dragMask);
127
+ dragMaskVisible = true;
128
+ bindDragMaskSafety();
129
+
130
+ // 拖拽标题时,root 上的 @mousedown="nextZIndex" 会把浮窗 z-index 抬高,
131
+ // 若此时才读取会拿到旧值导致遮罩被浮窗(及其内部 iframe)盖住,故用 nextTick 在 z-index 稳定后再设置到浮窗之上
132
+ const setMaskZIndex = () => {
133
+ if (dragMask) {
134
+ dragMask.style.zIndex = `${curZIndex.value + 1}`;
135
+ }
136
+ };
137
+ setMaskZIndex();
138
+ nextTick(setMaskZIndex);
139
+ };
140
+
141
+ const hideDragMask = () => {
142
+ unbindDragMaskSafety();
143
+ dragMask?.parentNode?.removeChild(dragMask);
144
+ dragMaskVisible = false;
145
+ };
146
+
87
147
  const initMoveable = () => {
88
148
  moveable = new VanillaMoveable(globalThis.document.body, {
89
149
  className: 'm-editor-floating-box-moveable',
@@ -101,6 +161,14 @@ const initMoveable = () => {
101
161
  bounds: { left: 0, top: 0, right: 0, bottom: 0, position: 'css' },
102
162
  });
103
163
 
164
+ // 仅在真正发生拖拽/缩放位移时插入遮罩:moveable 的 dragStart/resizeStart 在 mousedown 时即触发,
165
+ // 若此时就盖遮罩,会盖住浮窗本身导致 mouseup 落在遮罩上、关闭按钮的 click 无法触发(点击关闭不了)。
166
+ // 改为在 drag/resize(实际位移)时才显示,纯点击不再触发遮罩。
167
+ moveable.on('drag', showDragMask);
168
+ moveable.on('resize', showDragMask);
169
+ moveable.on('dragEnd', hideDragMask);
170
+ moveable.on('resizeEnd', hideDragMask);
171
+
104
172
  moveable.on('drag', (e) => {
105
173
  e.target.style.transform = e.transform;
106
174
  });
@@ -115,6 +183,7 @@ const initMoveable = () => {
115
183
  };
116
184
 
117
185
  const destroyMoveable = () => {
186
+ hideDragMask();
118
187
  moveable?.destroy();
119
188
  moveable = null;
120
189
  };
@@ -87,70 +87,3 @@ const scrollBy = (delta: number) => {
87
87
  }
88
88
  };
89
89
  </script>
90
-
91
- <style lang="scss">
92
- .m-editor-scroll-bar {
93
- position: absolute;
94
- background-color: transparent;
95
- opacity: 0.3;
96
- transition:
97
- background-color 0.2s linear,
98
- opacity 0.2s linear;
99
-
100
- .m-editor-scroll-bar-thumb {
101
- background-color: #aaa;
102
- border-radius: 6px;
103
- position: absolute;
104
- }
105
-
106
- &.horizontal {
107
- width: 100%;
108
- height: 15px;
109
- bottom: 0;
110
-
111
- .m-editor-scroll-bar-thumb {
112
- height: 6px;
113
- transition:
114
- background-color 0.2s linear,
115
- height 0.2s ease-in-out;
116
- bottom: 2px;
117
- }
118
- }
119
-
120
- &.vertical {
121
- height: 100%;
122
- width: 15px;
123
- right: 5px;
124
-
125
- .m-editor-scroll-bar-thumb {
126
- width: 6px;
127
- transition:
128
- background-color 0.2s linear,
129
- width 0.2s ease-in-out;
130
- right: 2px;
131
- }
132
- }
133
-
134
- &:hover,
135
- &:focus {
136
- background-color: #eee;
137
- opacity: 0.9;
138
-
139
- .m-editor-scroll-bar-thumb {
140
- background-color: #999;
141
- }
142
-
143
- &.horizontal {
144
- .m-editor-scroll-bar-thumb {
145
- height: 11px;
146
- }
147
- }
148
-
149
- &.vertical {
150
- .m-editor-scroll-bar-thumb {
151
- width: 11px;
152
- }
153
- }
154
- }
155
- }
156
- </style>
@@ -1,3 +1,5 @@
1
+ import type { InjectionKey } from 'vue';
2
+
1
3
  import type { DataSourceSchema, EventOption, Id, MApp, MNode, MPage, MPageFragment } from '@tmagic/core';
2
4
  import type { FormConfig, FormState } from '@tmagic/form';
3
5
  import StageCore, {
@@ -26,6 +28,12 @@ import type {
26
28
  TreeNodeData,
27
29
  } from './type';
28
30
 
31
+ /**
32
+ * 「属性配置表单校验」联动能力的 provide/inject 注入键。
33
+ * 使用 Symbol 避免与其它字符串键冲突,供 PropsPanel / FormPanel 注入判断校验失败时是否仍更新节点并记录错误。
34
+ */
35
+ export const ENABLE_PROPS_FORM_VALIDATE: InjectionKey<boolean> = Symbol('enablePropsFormValidate');
36
+
29
37
  export interface EditorProps {
30
38
  /** 页面初始值 */
31
39
  modelValue?: MApp;
@@ -91,6 +99,11 @@ export interface EditorProps {
91
99
  disabledFlashTip?: boolean;
92
100
  /** 禁用双击在浮层中单独编辑选中组件 */
93
101
  disabledStageOverlay?: boolean;
102
+ /**
103
+ * 是否启用「属性配置表单校验」联动能力:开启后属性/样式表单校验失败时仍更新节点,
104
+ * 并把错误信息集中记录到 editorService,用于组件树标红提示与保存拦截;默认 false(关闭)。
105
+ */
106
+ enablePropsFormValidate?: boolean;
94
107
  /** 禁用属性配置面板右下角显示源码的按钮 */
95
108
  disabledShowSrc?: boolean;
96
109
  /** 禁用数据源 */
@@ -93,7 +93,8 @@ const codeConfig = computed<GroupListConfig>(() => ({
93
93
  { value: HookCodeType.CODE, text: '代码块' },
94
94
  { value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' },
95
95
  ],
96
- defaultValue: 'code',
96
+ rules: [{ typeMatch: true, trigger: 'change' }],
97
+ defaultValue: HookCodeType.CODE,
97
98
  onChange: (_mForm, v: HookCodeType, { setModel }) => {
98
99
  if (v === HookCodeType.DATA_SOURCE_METHOD) {
99
100
  setModel('codeId', []);
@@ -111,6 +112,7 @@ const codeConfig = computed<GroupListConfig>(() => ({
111
112
  labelWidth: 0,
112
113
  display: (_mForm, { model }) => model.codeType !== HookCodeType.DATA_SOURCE_METHOD,
113
114
  notEditable: () => !codeBlockService.getEditStatus(),
115
+ rules: [{ typeMatch: true, trigger: 'change' }],
114
116
  },
115
117
  {
116
118
  type: 'data-source-method-select',
@@ -119,6 +121,7 @@ const codeConfig = computed<GroupListConfig>(() => ({
119
121
  labelWidth: 0,
120
122
  display: (_mForm, { model }) => model.codeType === HookCodeType.DATA_SOURCE_METHOD,
121
123
  notEditable: () => !dataSourceService.get('editable'),
124
+ rules: [{ typeMatch: true, trigger: 'change' }],
122
125
  },
123
126
  ],
124
127
  },
@@ -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: '值',