@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.
- package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +7 -4
- package/dist/es/components/CodeBlockEditor.vue_vue_type_script_setup_true_lang.js +4 -0
- package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/components/FloatingBox.vue_vue_type_script_setup_true_lang.js +60 -14
- package/dist/es/components/ScrollBar.js +0 -1
- package/dist/es/editorProps.js +6 -1
- package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +15 -3
- package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +2 -17
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +7 -0
- package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +5 -17
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +4 -0
- package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +29 -3
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +50 -88
- package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +2 -5
- package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +48 -23
- package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +28 -22
- package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +28 -22
- package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +28 -22
- package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/fields/UISelect.js +0 -1
- package/dist/es/index.js +9 -6
- package/dist/es/layouts/CodeEditor.vue_vue_type_script_setup_true_lang.js +21 -6
- package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +9 -6
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/layouts/history-list/composables.js +1 -1
- package/dist/es/layouts/history-list/useHistoryRevert.js +7 -3
- package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +44 -5
- package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +15 -8
- package/dist/es/layouts/props-panel/use-style-panel.js +3 -2
- package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +15 -31
- package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/sidebar/code-block/useContentMenu.js +18 -3
- package/dist/es/layouts/sidebar/data-source/DataSourceConfigPanel.vue_vue_type_script_setup_true_lang.js +4 -0
- package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/es/layouts/sidebar/data-source/useContentMenu.js +18 -3
- package/dist/es/layouts/sidebar/layer/LayerNodeContent.js +5 -0
- package/dist/es/layouts/sidebar/layer/LayerNodeContent.vue_vue_type_script_setup_true_lang.js +46 -0
- package/dist/es/layouts/sidebar/layer/LayerPanel.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/layouts/workspace/viewer/NodeListMenu.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/es/plugin.js +3 -1
- package/dist/es/services/editor.js +104 -5
- package/dist/es/services/props.js +2 -1
- package/dist/es/services/storage.js +1 -1
- package/dist/es/services/ui.js +3 -0
- package/dist/es/style.css +104 -55
- package/dist/es/utils/data-source/index.js +30 -11
- package/dist/es/utils/event.js +123 -0
- package/dist/es/utils/monaco-editor.js +22 -6
- package/dist/es/utils/props.js +94 -8
- package/dist/es/utils/type-match-rules.js +360 -0
- package/dist/style.css +104 -55
- package/dist/tmagic-editor.umd.cjs +16895 -15757
- package/package.json +8 -8
- package/src/Editor.vue +3 -1
- package/src/components/CodeBlockEditor.vue +3 -0
- package/src/components/CompareForm.vue +7 -0
- package/src/components/FloatingBox.vue +77 -8
- package/src/components/ScrollBar.vue +0 -67
- package/src/editorProps.ts +13 -0
- package/src/fields/CodeSelect.vue +4 -1
- package/src/fields/CondOpSelect.vue +2 -24
- package/src/fields/DataSourceFields.vue +5 -0
- package/src/fields/DataSourceInput.vue +7 -25
- package/src/fields/DataSourceMocks.vue +3 -0
- package/src/fields/DisplayConds.vue +14 -0
- package/src/fields/EventSelect.vue +84 -129
- package/src/fields/StyleSetter/Index.vue +2 -8
- package/src/fields/StyleSetter/pro/Background.vue +94 -55
- package/src/fields/StyleSetter/pro/Border.vue +9 -11
- package/src/fields/StyleSetter/pro/Font.vue +66 -64
- package/src/fields/StyleSetter/pro/Layout.vue +140 -138
- package/src/fields/StyleSetter/pro/Position.vue +69 -67
- package/src/fields/StyleSetter/pro/Transform.vue +23 -25
- package/src/fields/UISelect.vue +0 -12
- package/src/index.ts +1 -0
- package/src/layouts/CodeEditor.vue +32 -6
- package/src/layouts/Framework.vue +7 -6
- package/src/layouts/history-list/HistoryDiffDialog.vue +11 -1
- package/src/layouts/history-list/useHistoryRevert.ts +4 -0
- package/src/layouts/props-panel/FormPanel.vue +66 -5
- package/src/layouts/props-panel/PropsPanel.vue +20 -6
- package/src/layouts/props-panel/use-style-panel.ts +4 -3
- package/src/layouts/sidebar/Sidebar.vue +2 -0
- package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +4 -1
- package/src/layouts/sidebar/code-block/useContentMenu.ts +13 -1
- package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +3 -0
- package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +2 -2
- package/src/layouts/sidebar/data-source/useContentMenu.ts +13 -1
- package/src/layouts/sidebar/layer/LayerNodeContent.vue +57 -0
- package/src/layouts/sidebar/layer/LayerPanel.vue +4 -1
- package/src/layouts/workspace/viewer/NodeListMenu.vue +4 -1
- package/src/plugin.ts +3 -1
- package/src/services/editor.ts +168 -3
- package/src/services/ui.ts +6 -0
- package/src/theme/event.scss +8 -0
- package/src/theme/floating-box.scss +9 -1
- package/src/theme/layer-node-content.scss +14 -0
- package/src/theme/scroll-bar.scss +64 -0
- package/src/theme/theme.scss +3 -0
- package/src/theme/ui-select.scss +9 -0
- package/src/type.ts +53 -2
- package/src/utils/data-source/index.ts +30 -12
- package/src/utils/event.ts +224 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/monaco-editor.ts +27 -0
- package/src/utils/props.ts +160 -2
- package/src/utils/type-match-rules.ts +678 -0
- 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 :
|
|
29
|
-
:min-right="
|
|
30
|
-
:min-center="
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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 (
|
|
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, {
|
|
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 {
|
|
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
|
-
|
|
51
|
-
columnWidth.
|
|
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)[]>(() =>
|
|
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"
|
|
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);
|