@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
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
|
3
|
+
*
|
|
4
|
+
* Copyright (C) 2025 Tencent. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { EventOption, Id, MComponent, MContainer } from '@tmagic/core';
|
|
20
|
+
import type { CascaderOption } from '@tmagic/form';
|
|
21
|
+
import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX, traverseNode } from '@tmagic/utils';
|
|
22
|
+
|
|
23
|
+
import dataSourceService from '@editor/services/dataSource';
|
|
24
|
+
import editorService from '@editor/services/editor';
|
|
25
|
+
import eventsService from '@editor/services/events';
|
|
26
|
+
import { getCascaderOptionsFromFields } from '@editor/utils/data-source';
|
|
27
|
+
|
|
28
|
+
export const EVENT_NAME_VALUE_SEPARATOR = '.';
|
|
29
|
+
|
|
30
|
+
export type EventNameSelectOption = { text: string; value: string };
|
|
31
|
+
export type EventNameOption = EventOption | CascaderOption | EventNameSelectOption;
|
|
32
|
+
|
|
33
|
+
/** 与 EventSelect 中 checkStrictly 一致:component 为 false,其余为 true */
|
|
34
|
+
export const isEventNameCheckStrictly = (src?: string) => src !== 'component';
|
|
35
|
+
|
|
36
|
+
/** 将动作 method 值规范为与 collectEventNameOptionValues 一致的字符串(cascader 无 valueSeparator 时存数组) */
|
|
37
|
+
export const normalizeCompActionValue = (value: unknown): string => {
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return value.map((item) => `${item}`).join(EVENT_NAME_VALUE_SEPARATOR);
|
|
40
|
+
}
|
|
41
|
+
if (value === null || value === undefined || value === '') {
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
return `${value}`;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 组装事件名称下拉/级联 options,与 EventSelect 默认 eventNameConfig.options 逻辑一致。
|
|
49
|
+
*/
|
|
50
|
+
export const getEventNameOptions = (
|
|
51
|
+
src: string | undefined,
|
|
52
|
+
formValue: Record<string, any> = {},
|
|
53
|
+
): EventNameOption[] => {
|
|
54
|
+
if (!formValue.type) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (src === 'component') {
|
|
59
|
+
let events: EventOption[] | CascaderOption[] = eventsService.getEvent(formValue.type) || [];
|
|
60
|
+
|
|
61
|
+
if (formValue.type === 'page-fragment-container' && formValue.pageFragmentId) {
|
|
62
|
+
const pageFragment = editorService.get('root')?.items?.find((page) => page.id === formValue.pageFragmentId);
|
|
63
|
+
if (!pageFragment) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
events = [
|
|
68
|
+
{
|
|
69
|
+
label: pageFragment.name || '页面片容器',
|
|
70
|
+
value: pageFragment.id,
|
|
71
|
+
children: events,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
(pageFragment.items || []).forEach((node) => {
|
|
76
|
+
traverseNode<MComponent | MContainer>(node, (current) => {
|
|
77
|
+
const nodeEvents = (current.type && eventsService.getEvent(current.type)) || [];
|
|
78
|
+
(events as CascaderOption[]).push({
|
|
79
|
+
label: `${current.name}_${current.id}`,
|
|
80
|
+
value: `${current.id}`,
|
|
81
|
+
children: nodeEvents,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return events;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (events as EventOption[]).map((option) => ({
|
|
90
|
+
text: option.label,
|
|
91
|
+
value: option.value,
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (src === 'datasource') {
|
|
96
|
+
const events: EventNameOption[] = dataSourceService.getFormEvent(formValue.type) || [];
|
|
97
|
+
const dataSource = dataSourceService.getDataSourceById(formValue.id);
|
|
98
|
+
const fields = dataSource?.fields || [];
|
|
99
|
+
|
|
100
|
+
if (fields.length > 0) {
|
|
101
|
+
return [
|
|
102
|
+
...events,
|
|
103
|
+
{
|
|
104
|
+
label: '数据变化',
|
|
105
|
+
value: DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX,
|
|
106
|
+
children: getCascaderOptionsFromFields(fields),
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return events;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return [];
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** 将 select / cascader options 展平为最终写入 name 的字符串集合(cascader 用 `.` 拼接) */
|
|
118
|
+
export const collectEventNameOptionValues = (
|
|
119
|
+
options: EventNameOption[],
|
|
120
|
+
checkStrictly: boolean,
|
|
121
|
+
prefix: any[] = [],
|
|
122
|
+
result: Set<string> = new Set(),
|
|
123
|
+
): Set<string> => {
|
|
124
|
+
options.forEach((option) => {
|
|
125
|
+
if (typeof option?.value === 'undefined') {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const path = [...prefix, option.value];
|
|
130
|
+
const joined = path.map((item) => `${item}`).join(EVENT_NAME_VALUE_SEPARATOR);
|
|
131
|
+
const children = 'children' in option ? option.children : undefined;
|
|
132
|
+
|
|
133
|
+
if (Array.isArray(children) && children.length) {
|
|
134
|
+
if (checkStrictly) {
|
|
135
|
+
result.add(joined);
|
|
136
|
+
}
|
|
137
|
+
collectEventNameOptionValues(children as EventNameOption[], checkStrictly, path, result);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
result.add(joined);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return result;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 解析 event-select 允许的 name 集合。
|
|
149
|
+
* 自定义 eventNameConfig.options 时返回 null(跳过枚举校验)。
|
|
150
|
+
*/
|
|
151
|
+
export const getEventNameAllowedValues = (
|
|
152
|
+
config: { src?: string; eventNameConfig?: { options?: unknown } } = {},
|
|
153
|
+
formValue: Record<string, any> = {},
|
|
154
|
+
): Set<string> | null => {
|
|
155
|
+
if (typeof config.eventNameConfig?.options !== 'undefined') {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (config.src !== 'component' && config.src !== 'datasource') {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return collectEventNameOptionValues(getEventNameOptions(config.src, formValue), isEventNameCheckStrictly(config.src));
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 组装联动组件动作下拉/级联 options,与 EventSelect 默认 compActionConfig.options 逻辑一致。
|
|
168
|
+
*/
|
|
169
|
+
export const getCompActionOptions = (toId?: Id): EventNameOption[] => {
|
|
170
|
+
if (typeof toId === 'undefined' || toId === null || toId === '') {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const node = editorService.getNodeById(toId);
|
|
175
|
+
if (!node?.type) {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let methods: EventOption[] | CascaderOption[] = eventsService.getMethod(node.type, toId) || [];
|
|
180
|
+
|
|
181
|
+
if (node.type === 'page-fragment-container' && node.pageFragmentId) {
|
|
182
|
+
const pageFragment = editorService.get('root')?.items?.find((page) => page.id === node.pageFragmentId);
|
|
183
|
+
if (!pageFragment) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
methods = [];
|
|
188
|
+
(pageFragment.items || []).forEach((item: MComponent | MContainer) => {
|
|
189
|
+
traverseNode<MComponent | MContainer>(item, (current) => {
|
|
190
|
+
const nodeMethods = (current.type && eventsService.getMethod(current.type, current.id)) || [];
|
|
191
|
+
|
|
192
|
+
if (nodeMethods.length) {
|
|
193
|
+
(methods as CascaderOption[]).push({
|
|
194
|
+
label: `${current.name}_${current.id}`,
|
|
195
|
+
value: `${current.id}`,
|
|
196
|
+
children: nodeMethods,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return methods;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return (methods as EventOption[]).map((method) => ({
|
|
206
|
+
text: method.label,
|
|
207
|
+
value: method.value,
|
|
208
|
+
}));
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 解析 event-select 联动组件动作允许的 method 集合。
|
|
213
|
+
* 自定义 compActionConfig.options 时返回 null(跳过枚举校验)。
|
|
214
|
+
*/
|
|
215
|
+
export const getCompActionAllowedValues = (
|
|
216
|
+
config: { src?: string; compActionConfig?: { options?: unknown } } = {},
|
|
217
|
+
model: { to?: Id } = {},
|
|
218
|
+
): Set<string> | null => {
|
|
219
|
+
if (typeof config.compActionConfig?.options !== 'undefined') {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return collectEventNameOptionValues(getCompActionOptions(model.to), isEventNameCheckStrictly(config.src));
|
|
224
|
+
};
|
package/src/utils/index.ts
CHANGED
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
let cached: Promise<typeof import('monaco-editor')> | undefined;
|
|
2
2
|
|
|
3
|
+
// 是否为 Monaco 内部抛出的取消错误(Canceled)
|
|
4
|
+
// 销毁编辑器时,WordHighlighter 内部挂起的 Delayer 会被取消,
|
|
5
|
+
// 取消时 reject 出的 Canceled 错误没有 catch,会变成 "Uncaught (in promise) Canceled",
|
|
6
|
+
// 该错误本身无害,这里做一次性、全局的兜底处理,仅吞掉这类取消错误。
|
|
7
|
+
const isMonacoCanceledError = (reason: any): boolean => {
|
|
8
|
+
if (!reason) return false;
|
|
9
|
+
if (reason instanceof Error) {
|
|
10
|
+
return reason.name === 'Canceled' || reason.message === 'Canceled';
|
|
11
|
+
}
|
|
12
|
+
return reason === 'Canceled';
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let canceledRejectionHandlerInstalled = false;
|
|
16
|
+
const installCanceledRejectionHandler = () => {
|
|
17
|
+
if (canceledRejectionHandlerInstalled || typeof globalThis.addEventListener !== 'function') return;
|
|
18
|
+
canceledRejectionHandlerInstalled = true;
|
|
19
|
+
|
|
20
|
+
globalThis.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => {
|
|
21
|
+
if (isMonacoCanceledError(event.reason)) {
|
|
22
|
+
// 阻止其冒泡到控制台,避免无意义的报错噪音
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
3
28
|
export default () => {
|
|
4
29
|
if (!cached) {
|
|
30
|
+
installCanceledRejectionHandler();
|
|
31
|
+
|
|
5
32
|
cached = Promise.all([import('emmet-monaco-es'), import('monaco-editor')]).then(([emmet, monaco]) => {
|
|
6
33
|
const { emmetHTML, emmetCSS } = emmet;
|
|
7
34
|
emmetHTML(monaco);
|
package/src/utils/props.ts
CHANGED
|
@@ -17,14 +17,28 @@
|
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import type { AppContext } from 'vue';
|
|
21
|
+
|
|
20
22
|
import {
|
|
23
|
+
HookType,
|
|
21
24
|
NODE_CONDS_KEY,
|
|
22
25
|
NODE_CONDS_RESULT_KEY,
|
|
23
26
|
NODE_DISABLE_CODE_BLOCK_KEY,
|
|
24
27
|
NODE_DISABLE_DATA_SOURCE_KEY,
|
|
25
28
|
} from '@tmagic/core';
|
|
26
29
|
import { tMagicMessage } from '@tmagic/design';
|
|
27
|
-
import type {
|
|
30
|
+
import type {
|
|
31
|
+
ChildConfig,
|
|
32
|
+
DisplayCondsConfig,
|
|
33
|
+
FormConfig,
|
|
34
|
+
FormState,
|
|
35
|
+
FormValue,
|
|
36
|
+
TabConfig,
|
|
37
|
+
TabPaneConfig,
|
|
38
|
+
} from '@tmagic/form';
|
|
39
|
+
import { validateForm } from '@tmagic/form';
|
|
40
|
+
|
|
41
|
+
import type { Services } from '@editor/type';
|
|
28
42
|
|
|
29
43
|
export const arrayOptions = [
|
|
30
44
|
{ text: '包含', value: 'include' },
|
|
@@ -45,10 +59,36 @@ export const numberOptions = [
|
|
|
45
59
|
{ text: '不在范围内', value: 'not_between' },
|
|
46
60
|
];
|
|
47
61
|
|
|
62
|
+
export const booleanOptions = [
|
|
63
|
+
{ text: '是', value: 'is' },
|
|
64
|
+
{ text: '不是', value: 'not' },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
/** 按字段类型返回条件运算符选项(UI 与 typeMatch 校验共用) */
|
|
68
|
+
export const getCondOpOptionsByFieldType = (type: string) => {
|
|
69
|
+
if (type === 'array') {
|
|
70
|
+
return arrayOptions;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (type === 'boolean' || type === 'null') {
|
|
74
|
+
return booleanOptions;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (type === 'number') {
|
|
78
|
+
return [...eqOptions, ...numberOptions];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (type === 'string') {
|
|
82
|
+
return [...arrayOptions, ...eqOptions];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [...arrayOptions, ...eqOptions, ...numberOptions];
|
|
86
|
+
};
|
|
87
|
+
|
|
48
88
|
export const styleTabConfig: TabPaneConfig = {
|
|
49
89
|
title: '样式',
|
|
50
90
|
lazy: true,
|
|
51
|
-
display: ({ services }: any) => !(services
|
|
91
|
+
display: ({ services }: any) => !(services?.uiService?.get('showStylePanel') ?? true),
|
|
52
92
|
items: [
|
|
53
93
|
{
|
|
54
94
|
name: 'style',
|
|
@@ -127,6 +167,7 @@ export const eventTabConfig: TabPaneConfig = {
|
|
|
127
167
|
src: 'component',
|
|
128
168
|
labelWidth: '100px',
|
|
129
169
|
type: 'event-select',
|
|
170
|
+
rules: [{ typeMatch: true }],
|
|
130
171
|
},
|
|
131
172
|
],
|
|
132
173
|
};
|
|
@@ -155,6 +196,17 @@ export const advancedTabConfig: TabPaneConfig = {
|
|
|
155
196
|
labelPosition: 'top',
|
|
156
197
|
type: 'code-select',
|
|
157
198
|
extra: '组件初始化时执行',
|
|
199
|
+
rules: [
|
|
200
|
+
{ typeMatch: true, trigger: 'change' },
|
|
201
|
+
{
|
|
202
|
+
validator: ({ value, callback }: any) => {
|
|
203
|
+
if (value && value.hookType !== HookType.CODE) {
|
|
204
|
+
return callback('hookType 必须是 code');
|
|
205
|
+
}
|
|
206
|
+
callback();
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
],
|
|
158
210
|
},
|
|
159
211
|
{
|
|
160
212
|
name: 'mounted',
|
|
@@ -162,6 +214,17 @@ export const advancedTabConfig: TabPaneConfig = {
|
|
|
162
214
|
labelPosition: 'top',
|
|
163
215
|
type: 'code-select',
|
|
164
216
|
extra: '组件挂载到dom时执行',
|
|
217
|
+
rules: [
|
|
218
|
+
{ typeMatch: true, trigger: 'change' },
|
|
219
|
+
{
|
|
220
|
+
validator: ({ value, callback }: any) => {
|
|
221
|
+
if (value && value.hookType !== HookType.CODE) {
|
|
222
|
+
return callback('hookType 必须是 code');
|
|
223
|
+
}
|
|
224
|
+
callback();
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
],
|
|
165
228
|
},
|
|
166
229
|
{
|
|
167
230
|
name: 'display',
|
|
@@ -169,6 +232,17 @@ export const advancedTabConfig: TabPaneConfig = {
|
|
|
169
232
|
extra: '控制组件是否渲染,关系的代码块返回值为false时不渲染',
|
|
170
233
|
labelPosition: 'top',
|
|
171
234
|
type: 'code-select',
|
|
235
|
+
rules: [
|
|
236
|
+
{ typeMatch: true, trigger: 'change' },
|
|
237
|
+
{
|
|
238
|
+
validator: ({ value, callback }: any) => {
|
|
239
|
+
if (value && value.hookType !== HookType.CODE) {
|
|
240
|
+
return callback('hookType 必须是 code');
|
|
241
|
+
}
|
|
242
|
+
callback();
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
],
|
|
172
246
|
},
|
|
173
247
|
],
|
|
174
248
|
};
|
|
@@ -194,6 +268,7 @@ export const displayTabConfig: TabPaneConfig<DisplayCondsConfig> = {
|
|
|
194
268
|
name: NODE_CONDS_KEY,
|
|
195
269
|
titlePrefix: '条件组',
|
|
196
270
|
defaultValue: [],
|
|
271
|
+
rules: [{ typeMatch: true }],
|
|
197
272
|
},
|
|
198
273
|
],
|
|
199
274
|
};
|
|
@@ -286,3 +361,86 @@ export const fillConfig = (
|
|
|
286
361
|
|
|
287
362
|
return [tabConfig];
|
|
288
363
|
};
|
|
364
|
+
|
|
365
|
+
// #region ValidatePropsFormOptions
|
|
366
|
+
/**
|
|
367
|
+
* validatePropsForm 参数
|
|
368
|
+
*/
|
|
369
|
+
export interface ValidatePropsFormOptions {
|
|
370
|
+
/** 组件属性表单配置 */
|
|
371
|
+
config: FormConfig;
|
|
372
|
+
/** 待校验的表单值 */
|
|
373
|
+
values: FormValue;
|
|
374
|
+
/**
|
|
375
|
+
* 当前组件实例的 appContext(通常为 `getCurrentInstance()?.appContext`)。
|
|
376
|
+
* 会与 services 一并合入临时 MForm 的 appContext,使编辑器字段组件(DataSourceInput 等)能正常 inject。
|
|
377
|
+
*/
|
|
378
|
+
appContext?: AppContext | null;
|
|
379
|
+
/** 编辑器服务集合,注入到临时表单的 formState */
|
|
380
|
+
services?: Services;
|
|
381
|
+
/** stage 实例,注入到临时表单的 formState */
|
|
382
|
+
stage?: any;
|
|
383
|
+
/** 外部扩展的 formState */
|
|
384
|
+
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
|
385
|
+
/**
|
|
386
|
+
* 调试模式,默认 `true`:以弹层形式可见地渲染表单,点击「确定」才触发校验。
|
|
387
|
+
* 置为 `false` 时静默挂载后自动校验。
|
|
388
|
+
*/
|
|
389
|
+
debug?: boolean;
|
|
390
|
+
typeMatchValid?: boolean;
|
|
391
|
+
}
|
|
392
|
+
// #endregion ValidatePropsFormOptions
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* 对一份「组件属性表单配置 + 值」做一次独立的校验,**不复用也不污染页面上正在展示的表单**。
|
|
396
|
+
*
|
|
397
|
+
* 内部基于 `@tmagic/form` 的 `validateForm` 另建一个独立的 MForm 实例完成校验,并统一处理
|
|
398
|
+
* 编辑器场景所需的上下文注入:将当前组件实例的 provides 合入 appContext,并向 formState 注入
|
|
399
|
+
* stage / services 及外部扩展状态,保证校验规则依赖的上下文可用。
|
|
400
|
+
*
|
|
401
|
+
* 常用于源码编辑器保存后,对最新配置做一次校验,并将校验结果(错误信息)随提交一并抛给上层记录,
|
|
402
|
+
* 使源码保存的错误状态与表单编辑保持一致。
|
|
403
|
+
*
|
|
404
|
+
* @returns 校验通过返回空字符串 `''`,否则返回以 `<br>` 拼接的错误文案。
|
|
405
|
+
* 仅在初始化超时或挂载失败等异常情况下才会 reject。
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```ts
|
|
409
|
+
* const error = await validatePropsForm({
|
|
410
|
+
* config,
|
|
411
|
+
* values,
|
|
412
|
+
* appContext: getCurrentInstance()?.appContext,
|
|
413
|
+
* services,
|
|
414
|
+
* stage: editorService.get('stage'),
|
|
415
|
+
* extendState,
|
|
416
|
+
* });
|
|
417
|
+
* if (error) {
|
|
418
|
+
* // 配置不合法,error 为错误文案
|
|
419
|
+
* }
|
|
420
|
+
* ```
|
|
421
|
+
*/
|
|
422
|
+
export const validatePropsForm = ({
|
|
423
|
+
config,
|
|
424
|
+
values,
|
|
425
|
+
appContext = null,
|
|
426
|
+
services,
|
|
427
|
+
stage,
|
|
428
|
+
extendState,
|
|
429
|
+
debug,
|
|
430
|
+
typeMatchValid,
|
|
431
|
+
}: ValidatePropsFormOptions): Promise<string> =>
|
|
432
|
+
validateForm({
|
|
433
|
+
config,
|
|
434
|
+
debug,
|
|
435
|
+
typeMatchValid,
|
|
436
|
+
initValues: values,
|
|
437
|
+
// 将当前组件实例的 provides(含 Editor 顶层的 services / codeOptions 等组件级 provide)
|
|
438
|
+
// 合入 appContext,使临时 MForm 中的编辑器字段组件(DataSourceInput 等)能正常 inject
|
|
439
|
+
appContext: appContext ? { ...appContext, provides: { services } } : null,
|
|
440
|
+
// 与页面表单保持一致:注入 stage/services 及外部扩展状态,保证校验规则依赖的上下文可用
|
|
441
|
+
extendState: async (state) => ({
|
|
442
|
+
...((await extendState?.(state)) || {}),
|
|
443
|
+
stage,
|
|
444
|
+
services,
|
|
445
|
+
}),
|
|
446
|
+
});
|