@tmagic/editor 1.8.0-beta.24 → 1.8.0-beta.26
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 +10 -16
- package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +5 -8
- package/dist/es/components/ViewForm.vue_vue_type_script_setup_true_lang.js +3 -5
- package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +3 -11
- package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +29 -124
- package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +5 -6
- package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +7 -6
- package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +6 -10
- package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +5 -7
- package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +1 -4
- package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +4 -6
- package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +9 -137
- package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +51 -326
- package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +2 -4
- package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +3 -34
- package/dist/es/fields/StyleSetter/configs.js +663 -0
- package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +5 -108
- package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +3 -9
- package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +6 -103
- package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +5 -265
- package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +5 -85
- package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +3 -22
- package/dist/es/fields/configs/codeSelect.js +98 -0
- package/dist/es/fields/configs/displayConds.js +158 -0
- package/dist/es/fields/configs/eventSelect.js +225 -0
- package/dist/es/fields/configs/stickyAddButton.js +20 -0
- package/dist/es/fields/headless-validation.js +139 -0
- package/dist/es/headless.js +2 -0
- package/dist/es/hooks/use-compare-form.js +6 -23
- package/dist/es/hooks/use-form-context.js +19 -0
- package/dist/es/index.js +3 -2
- package/dist/es/initService.js +17 -1
- package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +1 -7
- package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +1 -11
- package/dist/es/layouts/history-list/useHistoryRevert.js +3 -11
- package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +10 -23
- package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +3 -13
- package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/code-block/useContentMenu.js +1 -1
- package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/es/layouts/sidebar/data-source/useContentMenu.js +1 -1
- package/dist/es/plugin.js +33 -32
- package/dist/es/style.css +120 -60
- package/dist/es/utils/data-source/index.js +2 -1
- package/dist/es/utils/props.js +1 -4
- package/dist/es/utils/type-match-rules.js +23 -22
- package/dist/style.css +120 -60
- package/dist/themes/magic-admin.css +132 -67
- package/dist/tmagic-editor-headless.umd.cjs +10867 -0
- package/dist/tmagic-editor.umd.cjs +2151 -2034
- package/package.json +12 -7
- package/src/Editor.vue +12 -16
- package/src/components/CodeParams.vue +10 -4
- package/src/components/CompareForm.vue +4 -5
- package/src/components/ViewForm.vue +2 -3
- package/src/editorProps.ts +1 -2
- package/src/fields/Code.vue +2 -10
- package/src/fields/CodeSelect.vue +20 -116
- package/src/fields/CodeSelectCol.vue +3 -5
- package/src/fields/CondOpSelect.vue +3 -2
- package/src/fields/DataSourceFieldSelect/FieldSelect.vue +1 -1
- package/src/fields/DataSourceFieldSelect/Index.vue +7 -6
- package/src/fields/DataSourceFields.vue +0 -7
- package/src/fields/DataSourceMethodSelect.vue +2 -5
- package/src/fields/DataSourceMethods.vue +1 -6
- package/src/fields/DataSourceMocks.vue +1 -10
- package/src/fields/DataSourceSelect.vue +1 -1
- package/src/fields/DisplayConds.vue +17 -153
- package/src/fields/EventSelect.vue +28 -375
- package/src/fields/KeyValue.vue +3 -4
- package/src/fields/StyleSetter/components/Border.vue +4 -38
- package/src/fields/StyleSetter/configs.ts +690 -0
- package/src/fields/StyleSetter/pro/Background.vue +4 -104
- package/src/fields/StyleSetter/pro/Border.vue +3 -12
- package/src/fields/StyleSetter/pro/Font.vue +5 -124
- package/src/fields/StyleSetter/pro/Layout.vue +4 -219
- package/src/fields/StyleSetter/pro/Position.vue +6 -110
- package/src/fields/StyleSetter/pro/Transform.vue +4 -30
- package/src/fields/configs/codeSelect.ts +127 -0
- package/src/fields/configs/displayConds.ts +184 -0
- package/src/fields/configs/eventSelect.ts +309 -0
- package/src/fields/configs/stickyAddButton.ts +31 -0
- package/src/fields/headless-validation.ts +177 -0
- package/src/headless.ts +33 -0
- package/src/hooks/use-compare-form.ts +7 -25
- package/src/hooks/use-form-context.ts +40 -0
- package/src/index.ts +2 -0
- package/src/initService.ts +23 -1
- package/src/layouts/history-list/HistoryDiffDialog.vue +0 -10
- package/src/layouts/history-list/HistoryListPanel.vue +1 -13
- package/src/layouts/history-list/useHistoryRevert.ts +7 -13
- package/src/layouts/props-panel/FormPanel.vue +17 -28
- package/src/layouts/props-panel/PropsPanel.vue +1 -4
- package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +1 -1
- package/src/layouts/sidebar/code-block/useContentMenu.ts +1 -1
- package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +1 -1
- package/src/layouts/sidebar/data-source/useContentMenu.ts +1 -1
- package/src/plugin.ts +32 -33
- package/src/theme/code-block.scss +0 -37
- package/src/theme/display-conds.scss +4 -0
- package/src/theme/event.scss +22 -45
- package/src/theme/props-panel.scss +17 -3
- package/src/theme/themes/magic-admin/index.scss +12 -1
- package/src/type.ts +10 -29
- package/src/utils/data-source/index.ts +1 -0
- package/src/utils/props.ts +1 -4
- package/src/utils/type-match-rules.ts +37 -31
- package/types/headless.d.ts +18 -0
- package/types/index.d.ts +155 -180
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<MContainer
|
|
3
3
|
:prop="prop"
|
|
4
|
-
:config="
|
|
4
|
+
:config="transformConfig"
|
|
5
5
|
:model="values"
|
|
6
6
|
:last-values="lastValues"
|
|
7
7
|
:is-compare="isCompare"
|
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
15
|
<script lang="ts" setup>
|
|
16
|
-
import { type ContainerChangeEventData,
|
|
16
|
+
import { type ContainerChangeEventData, MContainer } from '@tmagic/form';
|
|
17
17
|
import type { StyleSchema } from '@tmagic/schema';
|
|
18
18
|
|
|
19
|
+
import { transformConfig } from '../configs';
|
|
20
|
+
|
|
19
21
|
defineProps<{
|
|
20
22
|
values: Partial<StyleSchema>;
|
|
21
23
|
lastValues?: Partial<StyleSchema>;
|
|
@@ -30,34 +32,6 @@ const emit = defineEmits<{
|
|
|
30
32
|
addDiffCount: [];
|
|
31
33
|
}>();
|
|
32
34
|
|
|
33
|
-
const config = defineFormItem({
|
|
34
|
-
name: 'transform',
|
|
35
|
-
items: [
|
|
36
|
-
{
|
|
37
|
-
name: 'rotate',
|
|
38
|
-
text: '旋转角度',
|
|
39
|
-
labelWidth: '68px',
|
|
40
|
-
type: 'data-source-field-select',
|
|
41
|
-
checkStrictly: false,
|
|
42
|
-
dataSourceFieldType: ['string', 'number'],
|
|
43
|
-
fieldConfig: {
|
|
44
|
-
type: 'text',
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
name: 'scale',
|
|
49
|
-
text: '缩放',
|
|
50
|
-
labelWidth: '68px',
|
|
51
|
-
type: 'data-source-field-select',
|
|
52
|
-
checkStrictly: false,
|
|
53
|
-
dataSourceFieldType: ['string', 'number'],
|
|
54
|
-
fieldConfig: {
|
|
55
|
-
type: 'text',
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
});
|
|
60
|
-
|
|
61
35
|
const change = (value: StyleSchema, eventData: ContainerChangeEventData) => {
|
|
62
36
|
emit('change', value, eventData);
|
|
63
37
|
};
|
|
@@ -0,0 +1,127 @@
|
|
|
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 { isEmpty } from 'lodash-es';
|
|
20
|
+
|
|
21
|
+
import { HookCodeType, HookType } from '@tmagic/core';
|
|
22
|
+
import type { CodeSelectConfig, FormValue, GroupListConfig } from '@tmagic/form/headless';
|
|
23
|
+
|
|
24
|
+
import codeBlockService from '@editor/services/codeBlock';
|
|
25
|
+
import dataSourceService from '@editor/services/dataSource';
|
|
26
|
+
|
|
27
|
+
import { stickyAddButton } from './stickyAddButton';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* `fields/CodeSelect.vue` 内部渲染的钩子列表配置。
|
|
31
|
+
*
|
|
32
|
+
* 由组件与无渲染校验的嵌套配置共用:组件用它渲染,嵌套配置用它让父表单校验到这些字段。
|
|
33
|
+
*/
|
|
34
|
+
export const createCodeSelectConfig = (config: CodeSelectConfig): GroupListConfig => {
|
|
35
|
+
const groupListConfig: GroupListConfig = {
|
|
36
|
+
type: 'group-list',
|
|
37
|
+
name: 'hookData',
|
|
38
|
+
enableToggleMode: false,
|
|
39
|
+
expandAll: true,
|
|
40
|
+
defaultAdd: () => ({
|
|
41
|
+
codeType: HookCodeType.CODE,
|
|
42
|
+
codeId: '',
|
|
43
|
+
}),
|
|
44
|
+
...stickyAddButton(`添加${config.text || ''}`),
|
|
45
|
+
title: (_mForm: any, { model, index }: any) => {
|
|
46
|
+
if (model.codeType === HookCodeType.DATA_SOURCE_METHOD) {
|
|
47
|
+
if (Array.isArray(model.codeId)) {
|
|
48
|
+
if (model.codeId.length < 2) {
|
|
49
|
+
return index;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ds = dataSourceService.getDataSourceById(model.codeId[0]);
|
|
53
|
+
return `${ds?.title} / ${model.codeId[1]}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return Array.isArray(model.codeId) ? model.codeId.join('/') : index;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const codeContent = codeBlockService.getCodeContentById(model.codeId);
|
|
60
|
+
|
|
61
|
+
if (codeContent) {
|
|
62
|
+
return codeContent.name;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return model.codeId || index;
|
|
66
|
+
},
|
|
67
|
+
titlePrefix: config.name === undefined ? undefined : String(config.name),
|
|
68
|
+
items: [
|
|
69
|
+
{
|
|
70
|
+
text: '代码类型',
|
|
71
|
+
type: 'select',
|
|
72
|
+
name: 'codeType',
|
|
73
|
+
rules: [{ typeMatch: true, trigger: 'change' }],
|
|
74
|
+
options: [
|
|
75
|
+
{ value: HookCodeType.CODE, text: '代码块' },
|
|
76
|
+
{ value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' },
|
|
77
|
+
],
|
|
78
|
+
defaultValue: HookCodeType.CODE,
|
|
79
|
+
onChange: (_mForm: any, v: HookCodeType, { setModel }: any) => {
|
|
80
|
+
if (v === HookCodeType.DATA_SOURCE_METHOD) {
|
|
81
|
+
setModel('codeId', []);
|
|
82
|
+
} else {
|
|
83
|
+
setModel('codeId', '');
|
|
84
|
+
}
|
|
85
|
+
return v;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'code-select-col',
|
|
90
|
+
name: 'codeId',
|
|
91
|
+
text: '代码块',
|
|
92
|
+
rules: [{ typeMatch: true, trigger: 'change' }],
|
|
93
|
+
|
|
94
|
+
display: (_mForm: any, { model }: any) => model.codeType !== HookCodeType.DATA_SOURCE_METHOD,
|
|
95
|
+
notEditable: () => !codeBlockService.getEditStatus(),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
type: 'data-source-method-select',
|
|
99
|
+
name: 'codeId',
|
|
100
|
+
text: '数据源字段',
|
|
101
|
+
rules: [{ typeMatch: true, trigger: 'change' }],
|
|
102
|
+
display: (_mForm: any, { model }: any) => model.codeType === HookCodeType.DATA_SOURCE_METHOD,
|
|
103
|
+
notEditable: () => !dataSourceService.get('editable'),
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
} as any as GroupListConfig;
|
|
107
|
+
|
|
108
|
+
return groupListConfig;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* `code-select` 的旧数据兼容:钩子值可能是空值或空数组,改写成 `{ hookType, hookData }`。
|
|
113
|
+
*
|
|
114
|
+
* 表单值初始化时由 `code-select` 的 `effect` 执行(`applyMountValueEffects`);
|
|
115
|
+
* 组件里的 `watch`(无 immediate)只兜运行期被置空。必须幂等。
|
|
116
|
+
*/
|
|
117
|
+
export const normalizeCodeSelectValue = (model: FormValue | undefined, name: string): void => {
|
|
118
|
+
if (!model) return;
|
|
119
|
+
|
|
120
|
+
// 空值或者空数组
|
|
121
|
+
if (isEmpty(model[name])) {
|
|
122
|
+
model[name] = {
|
|
123
|
+
hookType: HookType.CODE,
|
|
124
|
+
hookData: [],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
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 { DisplayCondsConfig, FormState, GroupListConfig } from '@tmagic/form/headless';
|
|
20
|
+
import { removeDataSourceFieldPrefix } from '@tmagic/utils';
|
|
21
|
+
|
|
22
|
+
import dataSourceService from '@editor/services/dataSource';
|
|
23
|
+
import { getCascaderOptionsFromFields, getFieldType } from '@editor/utils/data-source';
|
|
24
|
+
|
|
25
|
+
import { stickyAddButton } from './stickyAddButton';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* `fields/DisplayConds.vue` 内部渲染的条件列表配置。
|
|
29
|
+
*
|
|
30
|
+
* 外层是条件组,内层 `cond` 是组内条件(groupList,每个字段单独一行)。
|
|
31
|
+
* 由组件与无渲染校验的嵌套配置共用。
|
|
32
|
+
*
|
|
33
|
+
* `parentFields` 由调用方求值:有父级字段路径时用 cascader 在该路径下选字段,
|
|
34
|
+
* 没有时用 data-source-field-select 从头选。
|
|
35
|
+
*/
|
|
36
|
+
export const createDisplayCondsConfig = (
|
|
37
|
+
config: Pick<DisplayCondsConfig, 'titlePrefix' | 'flat' | 'defaultValue' | 'rules'>,
|
|
38
|
+
name: string,
|
|
39
|
+
parentFields: string[],
|
|
40
|
+
): GroupListConfig => {
|
|
41
|
+
const resolveFieldPath = (path: string[]) => {
|
|
42
|
+
const [id, ...fieldNames] = path;
|
|
43
|
+
const ds = id ? dataSourceService.getDataSourceById(removeDataSourceFieldPrefix(`${id}`)) : undefined;
|
|
44
|
+
return { ds, fieldNames };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// 字段变更后按新字段的类型把已填的值转成对应类型,避免类型校验与运行期取值不一致
|
|
48
|
+
const fieldOnChange = (_formState: FormState | undefined, v: string[], { model }: { model: Record<string, any> }) => {
|
|
49
|
+
const { ds, fieldNames } = resolveFieldPath([...parentFields, ...v]);
|
|
50
|
+
const type = getFieldType(ds, fieldNames);
|
|
51
|
+
if (type === 'number') {
|
|
52
|
+
model.value = Number(model.value);
|
|
53
|
+
} else if (type === 'boolean') {
|
|
54
|
+
model.value = Boolean(model.value);
|
|
55
|
+
} else if (type === 'null') {
|
|
56
|
+
model.value = null;
|
|
57
|
+
} else {
|
|
58
|
+
model.value = `${model.value}`;
|
|
59
|
+
}
|
|
60
|
+
return v;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const fieldItem = parentFields.length
|
|
64
|
+
? {
|
|
65
|
+
type: 'cascader',
|
|
66
|
+
options: () => {
|
|
67
|
+
const { ds, fieldNames } = resolveFieldPath(parentFields);
|
|
68
|
+
if (!ds) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let fields = ds.fields || [];
|
|
73
|
+
fieldNames.forEach((key) => {
|
|
74
|
+
const field = fields.find((f) => f.name === key);
|
|
75
|
+
fields = field?.fields || [];
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return getCascaderOptionsFromFields(fields, ['string', 'number', 'boolean', 'any']);
|
|
79
|
+
},
|
|
80
|
+
name: 'field',
|
|
81
|
+
value: 'key',
|
|
82
|
+
text: '字段',
|
|
83
|
+
checkStrictly: false,
|
|
84
|
+
onChange: fieldOnChange,
|
|
85
|
+
defaultValue: () => [],
|
|
86
|
+
rules: [
|
|
87
|
+
{ required: true, trigger: 'blur', message: '请选择字段' },
|
|
88
|
+
{ typeMatch: true, trigger: 'change' },
|
|
89
|
+
],
|
|
90
|
+
}
|
|
91
|
+
: {
|
|
92
|
+
type: 'data-source-field-select',
|
|
93
|
+
name: 'field',
|
|
94
|
+
value: 'key',
|
|
95
|
+
text: '字段',
|
|
96
|
+
checkStrictly: false,
|
|
97
|
+
dataSourceFieldType: ['string', 'number', 'boolean', 'any'],
|
|
98
|
+
onChange: fieldOnChange,
|
|
99
|
+
defaultValue: () => [],
|
|
100
|
+
rules: [
|
|
101
|
+
{ required: true, trigger: 'blur', message: '请选择字段' },
|
|
102
|
+
{ typeMatch: true, trigger: 'change' },
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
type: 'groupList',
|
|
108
|
+
name,
|
|
109
|
+
titlePrefix: config.titlePrefix,
|
|
110
|
+
expandAll: true,
|
|
111
|
+
enableToggleMode: false,
|
|
112
|
+
defaultAdd: { cond: [] },
|
|
113
|
+
...stickyAddButton(`新增${config.titlePrefix || '条件组'}`),
|
|
114
|
+
flat: config.flat,
|
|
115
|
+
defaultValue: config.defaultValue ?? [],
|
|
116
|
+
rules: config.rules ?? [{ typeMatch: true }],
|
|
117
|
+
items: [
|
|
118
|
+
{
|
|
119
|
+
type: 'groupList',
|
|
120
|
+
name: 'cond',
|
|
121
|
+
titlePrefix: '条件',
|
|
122
|
+
expandAll: true,
|
|
123
|
+
enableToggleMode: false,
|
|
124
|
+
copyable: true,
|
|
125
|
+
movable: false,
|
|
126
|
+
flat: true,
|
|
127
|
+
labelWidth: '80px',
|
|
128
|
+
...stickyAddButton('新增条件'),
|
|
129
|
+
items: [
|
|
130
|
+
fieldItem,
|
|
131
|
+
{
|
|
132
|
+
type: 'cond-op-select',
|
|
133
|
+
parentFields,
|
|
134
|
+
text: '条件',
|
|
135
|
+
name: 'op',
|
|
136
|
+
rules: [
|
|
137
|
+
{ required: true, trigger: 'blur', message: '请选择条件' },
|
|
138
|
+
{ typeMatch: true, trigger: 'change' },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'value',
|
|
143
|
+
text: '值',
|
|
144
|
+
type: (_mForm: FormState | undefined, { model }: any) => {
|
|
145
|
+
const { ds, fieldNames } = resolveFieldPath([...parentFields, ...(model.field || [])]);
|
|
146
|
+
const type = getFieldType(ds, fieldNames);
|
|
147
|
+
|
|
148
|
+
if (type === 'number') {
|
|
149
|
+
return 'number';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (type === 'boolean') {
|
|
153
|
+
return 'select';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (type === 'null') {
|
|
157
|
+
return 'display';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return 'text';
|
|
161
|
+
},
|
|
162
|
+
options: [
|
|
163
|
+
{ text: 'true', value: true },
|
|
164
|
+
{ text: 'false', value: false },
|
|
165
|
+
],
|
|
166
|
+
display: (_mForm: FormState | undefined, { model }: any) => !['between', 'not_between'].includes(model.op),
|
|
167
|
+
displayText: (_mForm: FormState | undefined, { model }: any) => {
|
|
168
|
+
if (model.value === null) {
|
|
169
|
+
return 'null';
|
|
170
|
+
}
|
|
171
|
+
return model.value;
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'range',
|
|
176
|
+
text: '值',
|
|
177
|
+
type: 'number-range',
|
|
178
|
+
display: (_mForm: FormState | undefined, { model }: any) => ['between', 'not_between'].includes(model.op),
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
} as any as GroupListConfig;
|
|
184
|
+
};
|
|
@@ -0,0 +1,309 @@
|
|
|
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 { has } from 'lodash-es';
|
|
20
|
+
|
|
21
|
+
import { ActionType, type MNode } from '@tmagic/core';
|
|
22
|
+
import type {
|
|
23
|
+
CodeSelectColConfig,
|
|
24
|
+
DataSourceMethodSelectConfig,
|
|
25
|
+
DynamicTypeConfig,
|
|
26
|
+
EventSelectConfig,
|
|
27
|
+
FormState,
|
|
28
|
+
GroupListConfig,
|
|
29
|
+
TableConfig,
|
|
30
|
+
UISelectConfig,
|
|
31
|
+
} from '@tmagic/form/headless';
|
|
32
|
+
import { defineFormItem } from '@tmagic/form/headless';
|
|
33
|
+
|
|
34
|
+
import codeBlockService from '@editor/services/codeBlock';
|
|
35
|
+
import dataSourceService from '@editor/services/dataSource';
|
|
36
|
+
import editorService from '@editor/services/editor';
|
|
37
|
+
import eventsService from '@editor/services/events';
|
|
38
|
+
import propsService from '@editor/services/props';
|
|
39
|
+
import {
|
|
40
|
+
getCompActionAllowedValues,
|
|
41
|
+
getCompActionOptions,
|
|
42
|
+
getEventNameAllowedValues,
|
|
43
|
+
getEventNameOptions,
|
|
44
|
+
normalizeCompActionValue,
|
|
45
|
+
} from '@editor/utils';
|
|
46
|
+
|
|
47
|
+
import { stickyAddButton } from './stickyAddButton';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* `fields/EventSelect.vue` 内部渲染的各段配置。
|
|
51
|
+
*
|
|
52
|
+
* 由组件与无渲染校验的嵌套配置共用:组件用它们渲染,嵌套配置用它们让父表单校验到这些字段。
|
|
53
|
+
* 每段都支持通过 `config.xxxConfig` 覆盖,覆盖逻辑也必须两边一致,因此一并收口在此。
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/** 事件名称下拉框配置,渲染在每张事件卡片的头部 */
|
|
57
|
+
export const createEventNameConfig = (config: EventSelectConfig) => {
|
|
58
|
+
const defaultEventNameConfig = {
|
|
59
|
+
name: 'name',
|
|
60
|
+
text: '事件类型',
|
|
61
|
+
type: (_mForm: FormState | undefined, { formValue }: any) => {
|
|
62
|
+
if (config.src !== 'component' || (formValue.type === 'page-fragment-container' && formValue.pageFragmentId)) {
|
|
63
|
+
return 'cascader';
|
|
64
|
+
}
|
|
65
|
+
return 'select';
|
|
66
|
+
},
|
|
67
|
+
labelWidth: '70px',
|
|
68
|
+
checkStrictly: () => config.src !== 'component',
|
|
69
|
+
valueSeparator: '.',
|
|
70
|
+
options: (_mForm: FormState, { formValue }: any) => getEventNameOptions(config.src, formValue),
|
|
71
|
+
rules: [
|
|
72
|
+
{
|
|
73
|
+
validator: ({ value, callback }: any, { formValue }: any) => {
|
|
74
|
+
const allowedNames = getEventNameAllowedValues(config as any, formValue);
|
|
75
|
+
if (allowedNames && allowedNames.size > 0 && value && !allowedNames.has(value)) {
|
|
76
|
+
return callback(`事件名(${value})不存在`);
|
|
77
|
+
}
|
|
78
|
+
callback();
|
|
79
|
+
},
|
|
80
|
+
trigger: 'blur',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
return { ...defaultEventNameConfig, ...config.eventNameConfig };
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const createActionTypeOptions = () => {
|
|
88
|
+
const o: {
|
|
89
|
+
text: string;
|
|
90
|
+
label: string;
|
|
91
|
+
value: string;
|
|
92
|
+
disabled?: boolean;
|
|
93
|
+
}[] = [
|
|
94
|
+
{
|
|
95
|
+
text: '组件',
|
|
96
|
+
label: '组件',
|
|
97
|
+
value: ActionType.COMP,
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
if (!propsService.getDisabledCodeBlock()) {
|
|
102
|
+
o.push({
|
|
103
|
+
text: '代码',
|
|
104
|
+
label: '代码',
|
|
105
|
+
disabled: !Object.keys(codeBlockService.getCodeDsl() || {}).length,
|
|
106
|
+
value: ActionType.CODE,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!propsService.getDisabledDataSource()) {
|
|
111
|
+
o.push({
|
|
112
|
+
text: '数据源',
|
|
113
|
+
label: '数据源',
|
|
114
|
+
value: ActionType.DATA_SOURCE,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return o;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/** 联动类型 */
|
|
122
|
+
const createActionTypeConfig = (config: EventSelectConfig) => {
|
|
123
|
+
const defaultActionTypeConfig = {
|
|
124
|
+
name: 'actionType',
|
|
125
|
+
text: '联动类型',
|
|
126
|
+
type: 'select',
|
|
127
|
+
defaultValue: ActionType.COMP,
|
|
128
|
+
options: createActionTypeOptions(),
|
|
129
|
+
rules: [
|
|
130
|
+
{
|
|
131
|
+
required: true,
|
|
132
|
+
message: '联动类型不能为空',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
typeMatch: true,
|
|
136
|
+
trigger: 'blur',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
onChange: (_mForm: FormState, _v: string, { setModel }: any) => {
|
|
140
|
+
setModel('to', '');
|
|
141
|
+
setModel('method', '');
|
|
142
|
+
setModel('codeId', '');
|
|
143
|
+
setModel('dataSourceMethod', []);
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
return { ...defaultActionTypeConfig, ...config.actionTypeConfig };
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** 联动组件 */
|
|
150
|
+
const createTargetCompConfig = (config: EventSelectConfig) => {
|
|
151
|
+
const defaultTargetCompConfig: UISelectConfig = {
|
|
152
|
+
name: 'to',
|
|
153
|
+
text: '联动组件',
|
|
154
|
+
type: 'ui-select',
|
|
155
|
+
display: (_mForm, { model }) => model.actionType === ActionType.COMP,
|
|
156
|
+
onChange: (_mForm, _v, { setModel }) => {
|
|
157
|
+
setModel('method', '');
|
|
158
|
+
},
|
|
159
|
+
rules: [
|
|
160
|
+
{
|
|
161
|
+
typeMatch: true,
|
|
162
|
+
trigger: 'blur',
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
return { ...defaultTargetCompConfig, ...config.targetCompConfig };
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** 联动组件动作 */
|
|
170
|
+
const createCompActionConfig = (config: EventSelectConfig) => {
|
|
171
|
+
const defaultCompActionConfig: DynamicTypeConfig = {
|
|
172
|
+
name: 'method',
|
|
173
|
+
text: '动作',
|
|
174
|
+
type: (_mForm: FormState | undefined, { model }: any) => {
|
|
175
|
+
const to = editorService.getNodeById(model.to);
|
|
176
|
+
|
|
177
|
+
if (to?.type === 'page-fragment-container' && to.pageFragmentId) {
|
|
178
|
+
return 'cascader';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return 'select';
|
|
182
|
+
},
|
|
183
|
+
checkStrictly: () => config.src !== 'component',
|
|
184
|
+
display: (_mForm: FormState | undefined, { model }: any) => model.actionType === ActionType.COMP,
|
|
185
|
+
options: (_mForm: FormState, { model }: any) => getCompActionOptions(model.to),
|
|
186
|
+
rules: [
|
|
187
|
+
{
|
|
188
|
+
trigger: 'blur',
|
|
189
|
+
validator: ({ value, callback }: any, { model }: any) => {
|
|
190
|
+
const allowedMethods = getCompActionAllowedValues(config as any, model);
|
|
191
|
+
const normalized = normalizeCompActionValue(value);
|
|
192
|
+
if (allowedMethods && allowedMethods.size > 0 && normalized && !allowedMethods.has(normalized)) {
|
|
193
|
+
return callback(`动作名(${normalized})不存在`);
|
|
194
|
+
}
|
|
195
|
+
callback();
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
return { ...defaultCompActionConfig, ...config.compActionConfig };
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/** 代码联动 */
|
|
204
|
+
const createCodeActionConfig = (config: EventSelectConfig) => {
|
|
205
|
+
const defaultCodeActionConfig: CodeSelectColConfig = {
|
|
206
|
+
type: 'code-select-col',
|
|
207
|
+
text: '代码块',
|
|
208
|
+
name: 'codeId',
|
|
209
|
+
notEditable: () => !codeBlockService.getEditStatus(),
|
|
210
|
+
display: (_mForm, { model }) => model.actionType === ActionType.CODE,
|
|
211
|
+
};
|
|
212
|
+
return { ...defaultCodeActionConfig, ...config.codeActionConfig };
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/** 数据源联动 */
|
|
216
|
+
const createDataSourceActionConfig = (config: EventSelectConfig) => {
|
|
217
|
+
const defaultDataSourceActionConfig: DataSourceMethodSelectConfig = {
|
|
218
|
+
type: 'data-source-method-select',
|
|
219
|
+
text: '数据源方法',
|
|
220
|
+
name: 'dataSourceMethod',
|
|
221
|
+
notEditable: () => !dataSourceService.get('editable'),
|
|
222
|
+
display: (_mForm, { model }) => model.actionType === ActionType.DATA_SOURCE,
|
|
223
|
+
};
|
|
224
|
+
return { ...defaultDataSourceActionConfig, ...config.dataSourceActionConfig };
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/** 单张事件里的动作组 */
|
|
228
|
+
export const createActionsConfig = (config: EventSelectConfig): GroupListConfig =>
|
|
229
|
+
defineFormItem({
|
|
230
|
+
type: 'group-list',
|
|
231
|
+
name: 'actions',
|
|
232
|
+
expandAll: true,
|
|
233
|
+
enableToggleMode: false,
|
|
234
|
+
titlePrefix: '动作',
|
|
235
|
+
labelPosition: 'top',
|
|
236
|
+
flat: true,
|
|
237
|
+
...stickyAddButton('新增动作'),
|
|
238
|
+
items: [
|
|
239
|
+
createActionTypeConfig(config),
|
|
240
|
+
createTargetCompConfig(config),
|
|
241
|
+
createCompActionConfig(config),
|
|
242
|
+
createCodeActionConfig(config),
|
|
243
|
+
createDataSourceActionConfig(config),
|
|
244
|
+
],
|
|
245
|
+
}) as GroupListConfig;
|
|
246
|
+
|
|
247
|
+
/** 事件列表(外层 group-list)。事件名走 title slot,body 只放动作组。 */
|
|
248
|
+
export const createEventSelectConfig = (
|
|
249
|
+
config: EventSelectConfig,
|
|
250
|
+
name: string,
|
|
251
|
+
options?: { includeEventName?: boolean },
|
|
252
|
+
): GroupListConfig =>
|
|
253
|
+
defineFormItem({
|
|
254
|
+
type: 'group-list',
|
|
255
|
+
name,
|
|
256
|
+
titlePrefix: '事件',
|
|
257
|
+
expandAll: true,
|
|
258
|
+
enableToggleMode: false,
|
|
259
|
+
movable: false,
|
|
260
|
+
defaultAdd: { name: '', actions: [] },
|
|
261
|
+
...stickyAddButton('添加事件'),
|
|
262
|
+
items: [...(options?.includeEventName ? [createEventNameConfig(config)] : []), createActionsConfig(config)],
|
|
263
|
+
}) as GroupListConfig;
|
|
264
|
+
|
|
265
|
+
/** 兼容旧数据格式(事件列表里没有 actions)时渲染的表格配置,本身不带校验规则 */
|
|
266
|
+
export const createLegacyTableConfig = (config: EventSelectConfig): TableConfig =>
|
|
267
|
+
defineFormItem({
|
|
268
|
+
type: 'table',
|
|
269
|
+
name: 'events',
|
|
270
|
+
items: [
|
|
271
|
+
{
|
|
272
|
+
name: 'name',
|
|
273
|
+
label: '事件名',
|
|
274
|
+
type: createEventNameConfig(config).type,
|
|
275
|
+
options: (_mForm: FormState, { formValue }: any) =>
|
|
276
|
+
eventsService
|
|
277
|
+
.getEvent(formValue.type, { node: editorService.getNodeById(formValue.id) })
|
|
278
|
+
.map((option: any) => ({
|
|
279
|
+
text: option.label,
|
|
280
|
+
value: option.value,
|
|
281
|
+
})),
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: 'to',
|
|
285
|
+
label: '联动组件',
|
|
286
|
+
type: 'ui-select',
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: 'method',
|
|
290
|
+
label: '动作',
|
|
291
|
+
type: createCompActionConfig(config).type,
|
|
292
|
+
options: (_mForm: FormState, { model, formValue }: any) => {
|
|
293
|
+
const node = editorService.getNodeById(model.to) || (formValue as MNode);
|
|
294
|
+
if (!node?.type) return [];
|
|
295
|
+
|
|
296
|
+
return eventsService.getMethod(node.type, { targetId: model.to, node }).map((option: any) => ({
|
|
297
|
+
text: option.label,
|
|
298
|
+
value: option.value,
|
|
299
|
+
}));
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
}) as TableConfig;
|
|
304
|
+
|
|
305
|
+
/** 事件列表是否为旧数据格式(列表项里没有 actions) */
|
|
306
|
+
export const isLegacyEventValue = (events: any): boolean => {
|
|
307
|
+
if (!Array.isArray(events) || events.length === 0) return false;
|
|
308
|
+
return !has(events[0], 'actions');
|
|
309
|
+
};
|