mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-04-06 03:57:56 +08:00
feat(core,editor,form): 页面片容器支持配置容器内组件的事件
This commit is contained in:
parent
cd191f6815
commit
2d133f47f1
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
import { has, isEmpty } from 'lodash-es';
|
import { has, isArray, isEmpty } from 'lodash-es';
|
||||||
|
|
||||||
import { createDataSourceManager, DataSource, DataSourceManager, ObservedDataClass } from '@tmagic/data-source';
|
import { createDataSourceManager, DataSource, DataSourceManager, ObservedDataClass } from '@tmagic/data-source';
|
||||||
import {
|
import {
|
||||||
@ -228,10 +228,20 @@ class App extends EventEmitter implements AppCore {
|
|||||||
|
|
||||||
for (const [, value] of this.page.nodes) {
|
for (const [, value] of this.page.nodes) {
|
||||||
value.events?.forEach((event, index) => {
|
value.events?.forEach((event, index) => {
|
||||||
const eventName = `${event.name}_${value.data.id}`;
|
let eventName = `${event.name}_${value.data.id}`;
|
||||||
const eventHandler = (fromCpt: Node, ...args: any[]) => {
|
let eventHandler = (fromCpt: Node, ...args: any[]) => {
|
||||||
this.eventHandler(index, fromCpt, args);
|
this.eventHandler(index, fromCpt, args);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 页面片容器可以配置页面片内组件的事件,形式为“${nodeId}.${eventName}”
|
||||||
|
const eventNames = event.name.split('.');
|
||||||
|
if (eventNames.length > 1) {
|
||||||
|
eventName = `${eventNames[1]}_${eventNames[0]}`;
|
||||||
|
eventHandler = (fromCpt: Node, ...args: any[]) => {
|
||||||
|
this.eventHandler(index, value, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.eventList.set(eventHandler, eventName);
|
this.eventList.set(eventHandler, eventName);
|
||||||
this.on(eventName, eventHandler);
|
this.on(eventName, eventHandler);
|
||||||
});
|
});
|
||||||
@ -269,7 +279,12 @@ class App extends EventEmitter implements AppCore {
|
|||||||
public async compActionHandler(eventConfig: CompItemConfig, fromCpt: Node | DataSource, args: any[]) {
|
public async compActionHandler(eventConfig: CompItemConfig, fromCpt: Node | DataSource, args: any[]) {
|
||||||
if (!this.page) throw new Error('当前没有页面');
|
if (!this.page) throw new Error('当前没有页面');
|
||||||
|
|
||||||
const { method: methodName, to } = eventConfig;
|
let { method: methodName, to } = eventConfig;
|
||||||
|
|
||||||
|
if (isArray(methodName)) {
|
||||||
|
[to, methodName] = methodName;
|
||||||
|
}
|
||||||
|
|
||||||
const toNode = this.page.getNode(to);
|
const toNode = this.page.getNode(to);
|
||||||
if (!toNode) throw `ID为${to}的组件不存在`;
|
if (!toNode) throw `ID为${to}的组件不存在`;
|
||||||
|
|
||||||
|
@ -26,14 +26,14 @@
|
|||||||
@change="onChangeHandler"
|
@change="onChangeHandler"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<MContainer
|
<MFormContainer
|
||||||
class="fullWidth"
|
class="fullWidth"
|
||||||
:config="eventNameConfig"
|
:config="eventNameConfig"
|
||||||
:model="cardItem"
|
:model="cardItem"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:size="size"
|
:size="size"
|
||||||
@change="onChangeHandler"
|
@change="onChangeHandler"
|
||||||
></MContainer>
|
></MFormContainer>
|
||||||
<TMagicButton
|
<TMagicButton
|
||||||
style="color: #f56c6c"
|
style="color: #f56c6c"
|
||||||
link
|
link
|
||||||
@ -55,13 +55,13 @@ import { has } from 'lodash-es';
|
|||||||
|
|
||||||
import type { EventOption } from '@tmagic/core';
|
import type { EventOption } from '@tmagic/core';
|
||||||
import { TMagicButton } from '@tmagic/design';
|
import { TMagicButton } from '@tmagic/design';
|
||||||
import type { CascaderOption, FieldProps, FormState, PanelConfig } from '@tmagic/form';
|
import type { CascaderOption, ChildConfig, FieldProps, FormState, PanelConfig } from '@tmagic/form';
|
||||||
import { MContainer, MPanel } from '@tmagic/form';
|
import { MContainer as MFormContainer, MPanel } from '@tmagic/form';
|
||||||
import { ActionType } from '@tmagic/schema';
|
import { ActionType, type MComponent, type MContainer } from '@tmagic/schema';
|
||||||
import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX } from '@tmagic/utils';
|
import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX } from '@tmagic/utils';
|
||||||
|
|
||||||
import type { CodeSelectColConfig, DataSourceMethodSelectConfig, EventSelectConfig, Services } from '@editor/type';
|
import type { CodeSelectColConfig, DataSourceMethodSelectConfig, EventSelectConfig, Services } from '@editor/type';
|
||||||
import { getCascaderOptionsFromFields } from '@editor/utils';
|
import { getCascaderOptionsFromFields, traverseNode } from '@editor/utils';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MFieldsEventSelect',
|
name: 'MFieldsEventSelect',
|
||||||
@ -80,13 +80,20 @@ const codeBlockService = services?.codeBlockService;
|
|||||||
|
|
||||||
// 事件名称下拉框表单配置
|
// 事件名称下拉框表单配置
|
||||||
const eventNameConfig = computed(() => {
|
const eventNameConfig = computed(() => {
|
||||||
const fieldType = props.config.src === 'component' ? 'select' : 'cascader';
|
const defaultEventNameConfig: ChildConfig = {
|
||||||
const defaultEventNameConfig = {
|
|
||||||
name: 'name',
|
name: 'name',
|
||||||
text: '事件',
|
text: '事件',
|
||||||
type: fieldType,
|
type: (mForm, { formValue }: any) => {
|
||||||
|
if (
|
||||||
|
props.config.src !== 'component' ||
|
||||||
|
(formValue.type === 'page-fragment-container' && formValue.pageFragmentId)
|
||||||
|
) {
|
||||||
|
return 'cascader';
|
||||||
|
}
|
||||||
|
return 'select';
|
||||||
|
},
|
||||||
labelWidth: '40px',
|
labelWidth: '40px',
|
||||||
checkStrictly: true,
|
checkStrictly: () => props.config.src !== 'component',
|
||||||
valueSeparator: '.',
|
valueSeparator: '.',
|
||||||
options: (mForm: FormState, { formValue }: any) => {
|
options: (mForm: FormState, { formValue }: any) => {
|
||||||
let events: EventOption[] | CascaderOption[] = [];
|
let events: EventOption[] | CascaderOption[] = [];
|
||||||
@ -95,11 +102,39 @@ const eventNameConfig = computed(() => {
|
|||||||
|
|
||||||
if (props.config.src === 'component') {
|
if (props.config.src === 'component') {
|
||||||
events = eventsService.getEvent(formValue.type);
|
events = eventsService.getEvent(formValue.type);
|
||||||
|
|
||||||
|
if (formValue.type === 'page-fragment-container' && formValue.pageFragmentId) {
|
||||||
|
const pageFragment = editorService?.get('root')?.items?.find((page) => page.id === formValue.pageFragmentId);
|
||||||
|
if (pageFragment) {
|
||||||
|
events = [
|
||||||
|
{
|
||||||
|
label: pageFragment.name || '迭代器容器',
|
||||||
|
value: pageFragment.id,
|
||||||
|
children: events,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
pageFragment.items.forEach((node) => {
|
||||||
|
traverseNode<MComponent | MContainer>(node, (node) => {
|
||||||
|
const nodeEvents = (node.type && eventsService.getEvent(node.type)) || [];
|
||||||
|
|
||||||
|
events.push({
|
||||||
|
label: `${node.name}_${node.id}`,
|
||||||
|
value: `${node.id}`,
|
||||||
|
children: nodeEvents,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return events.map((option) => ({
|
return events.map((option) => ({
|
||||||
text: option.label,
|
text: option.label,
|
||||||
value: option.value,
|
value: option.value,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.config.src === 'datasource') {
|
if (props.config.src === 'datasource') {
|
||||||
// 从数据源类型中获取到相关事件
|
// 从数据源类型中获取到相关事件
|
||||||
events = dataSourceService.getFormEvent(formValue.type);
|
events = dataSourceService.getFormEvent(formValue.type);
|
||||||
@ -163,24 +198,63 @@ const targetCompConfig = computed(() => {
|
|||||||
text: '联动组件',
|
text: '联动组件',
|
||||||
type: 'ui-select',
|
type: 'ui-select',
|
||||||
display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.actionType === ActionType.COMP,
|
display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.actionType === ActionType.COMP,
|
||||||
|
onChange: (MForm: FormState, v: string, { model }: any) => {
|
||||||
|
model.method = '';
|
||||||
|
return v;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return { ...defaultTargetCompConfig, ...props.config.targetCompConfig };
|
return { ...defaultTargetCompConfig, ...props.config.targetCompConfig };
|
||||||
});
|
});
|
||||||
|
|
||||||
// 联动组件动作配置
|
// 联动组件动作配置
|
||||||
const compActionConfig = computed(() => {
|
const compActionConfig = computed(() => {
|
||||||
const defaultCompActionConfig = {
|
const defaultCompActionConfig: ChildConfig = {
|
||||||
name: 'method',
|
name: 'method',
|
||||||
text: '动作',
|
text: '动作',
|
||||||
type: 'select',
|
type: (mForm, { model }: any) => {
|
||||||
display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.actionType === ActionType.COMP,
|
const to = editorService?.getNodeById(model.to);
|
||||||
|
|
||||||
|
if (to && to.type === 'page-fragment-container' && to.pageFragmentId) {
|
||||||
|
return 'cascader';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'select';
|
||||||
|
},
|
||||||
|
checkStrictly: () => props.config.src !== 'component',
|
||||||
|
display: (mForm, { model }: any) => model.actionType === ActionType.COMP,
|
||||||
options: (mForm: FormState, { model }: any) => {
|
options: (mForm: FormState, { model }: any) => {
|
||||||
const node = editorService?.getNodeById(model.to);
|
const node = editorService?.getNodeById(model.to);
|
||||||
if (!node?.type) return [];
|
if (!node?.type) return [];
|
||||||
|
|
||||||
return eventsService?.getMethod(node.type).map((option: any) => ({
|
let methods: EventOption[] | CascaderOption[] = [];
|
||||||
text: option.label,
|
|
||||||
value: option.value,
|
methods = eventsService?.getMethod(node.type) || [];
|
||||||
|
|
||||||
|
if (node.type === 'page-fragment-container' && node.pageFragmentId) {
|
||||||
|
const pageFragment = editorService?.get('root')?.items?.find((page) => page.id === node.pageFragmentId);
|
||||||
|
if (pageFragment) {
|
||||||
|
methods = [];
|
||||||
|
pageFragment.items.forEach((node: MComponent | MContainer) => {
|
||||||
|
traverseNode<MComponent | MContainer>(node, (node) => {
|
||||||
|
const nodeMethods = (node.type && eventsService?.getMethod(node.type)) || [];
|
||||||
|
|
||||||
|
if (nodeMethods.length) {
|
||||||
|
methods.push({
|
||||||
|
label: `${node.name}_${node.id}`,
|
||||||
|
value: `${node.id}`,
|
||||||
|
children: nodeMethods,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return methods.map((method) => ({
|
||||||
|
text: method.label,
|
||||||
|
value: method.value,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -220,13 +220,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject, ref, resolveComponent, watch, watchEffect } from 'vue';
|
import { computed, inject, ref, watch, watchEffect } from 'vue';
|
||||||
import { WarningFilled } from '@element-plus/icons-vue';
|
import { WarningFilled } from '@element-plus/icons-vue';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import { TMagicButton, TMagicFormItem, TMagicIcon, TMagicTooltip } from '@tmagic/design';
|
import { TMagicButton, TMagicFormItem, TMagicIcon, TMagicTooltip } from '@tmagic/design';
|
||||||
|
|
||||||
import { ChildConfig, ContainerCommonConfig, FormState, FormValue } from '../schema';
|
import { ChildConfig, ContainerCommonConfig, FormState, FormValue, TypeFunction } from '../schema';
|
||||||
import { display as displayFunction, filterFunction, getRules } from '../utils/form';
|
import { display as displayFunction, filterFunction, getRules } from '../utils/form';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@ -288,11 +288,7 @@ const itemProp = computed(() => {
|
|||||||
return `${props.prop}${props.prop ? '.' : ''}${n}`;
|
return `${props.prop}${props.prop ? '.' : ''}${n}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const tagName = computed(() => {
|
const tagName = computed(() => `m-${items.value ? 'form' : 'fields'}-${type.value}`);
|
||||||
const component = resolveComponent(`m-${items.value ? 'form' : 'fields'}-${type.value}`);
|
|
||||||
if (typeof component !== 'string') return component;
|
|
||||||
return 'm-fields-text';
|
|
||||||
});
|
|
||||||
|
|
||||||
const disabled = computed(() => props.disabled || filterFunction(mForm, props.config.disabled, props));
|
const disabled = computed(() => props.disabled || filterFunction(mForm, props.config.disabled, props));
|
||||||
|
|
||||||
@ -306,11 +302,7 @@ const rule = computed(() => getRules(mForm, props.config.rules, props));
|
|||||||
|
|
||||||
const type = computed((): string => {
|
const type = computed((): string => {
|
||||||
let { type } = props.config;
|
let { type } = props.config;
|
||||||
if (typeof type === 'function') {
|
type = type && (filterFunction<string | TypeFunction>(mForm, type, props) as string);
|
||||||
type = type(mForm, {
|
|
||||||
model: props.model,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (type === 'form') return '';
|
if (type === 'form') return '';
|
||||||
if (type === 'container') return '';
|
if (type === 'container') return '';
|
||||||
return type?.replace(/([A-Z])/g, '-$1').toLowerCase() || (items.value ? '' : 'text');
|
return type?.replace(/([A-Z])/g, '-$1').toLowerCase() || (items.value ? '' : 'text');
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
:props="{
|
:props="{
|
||||||
multiple: config.multiple ?? false,
|
multiple: config.multiple ?? false,
|
||||||
emitPath: config.emitPath ?? true,
|
emitPath: config.emitPath ?? true,
|
||||||
checkStrictly: config.checkStrictly ?? false,
|
checkStrictly: checkStrictly ?? false,
|
||||||
}"
|
}"
|
||||||
@change="changeHandler"
|
@change="changeHandler"
|
||||||
></TMagicCascader>
|
></TMagicCascader>
|
||||||
@ -26,6 +26,7 @@ import { TMagicCascader } from '@tmagic/design';
|
|||||||
|
|
||||||
import type { CascaderConfig, CascaderOption, FieldProps, FormState } from '../schema';
|
import type { CascaderConfig, CascaderOption, FieldProps, FormState } from '../schema';
|
||||||
import { getConfig } from '../utils/config';
|
import { getConfig } from '../utils/config';
|
||||||
|
import { filterFunction } from '../utils/form';
|
||||||
import { useAddField } from '../utils/useAddField';
|
import { useAddField } from '../utils/useAddField';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@ -36,7 +37,7 @@ const props = defineProps<FieldProps<CascaderConfig>>();
|
|||||||
|
|
||||||
const emit = defineEmits(['change']);
|
const emit = defineEmits(['change']);
|
||||||
|
|
||||||
const mForm = inject<FormState | null>('mForm');
|
const mForm = inject<FormState | undefined>('mForm');
|
||||||
|
|
||||||
useAddField(props.prop);
|
useAddField(props.prop);
|
||||||
|
|
||||||
@ -47,17 +48,20 @@ const tMagicCascader = ref<InstanceType<typeof TMagicCascader>>();
|
|||||||
const options = ref<CascaderOption[]>([]);
|
const options = ref<CascaderOption[]>([]);
|
||||||
const remoteData = ref<any>(null);
|
const remoteData = ref<any>(null);
|
||||||
|
|
||||||
|
const checkStrictly = computed(() => filterFunction(mForm, props.config.checkStrictly, props));
|
||||||
|
const valueSeparator = computed(() => filterFunction(mForm, props.config.valueSeparator, props));
|
||||||
|
|
||||||
const value = computed({
|
const value = computed({
|
||||||
get() {
|
get() {
|
||||||
if (typeof props.model[props.name] === 'string' && props.config.valueSeparator) {
|
if (typeof props.model[props.name] === 'string' && valueSeparator.value) {
|
||||||
return props.model[props.name].split(props.config.valueSeparator);
|
return props.model[props.name].split(valueSeparator.value);
|
||||||
}
|
}
|
||||||
return props.model[props.name];
|
return props.model[props.name];
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
let result = value;
|
let result = value;
|
||||||
if (props.config.valueSeparator) {
|
if (valueSeparator.value) {
|
||||||
result = value.join(props.config.valueSeparator);
|
result = value.join(valueSeparator.value);
|
||||||
}
|
}
|
||||||
props.model[props.name] = result;
|
props.model[props.name] = result;
|
||||||
},
|
},
|
||||||
|
@ -536,11 +536,11 @@ export interface CascaderConfig extends FormItem, Input {
|
|||||||
/** 是否多选,默认 false */
|
/** 是否多选,默认 false */
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
/** 是否严格的遵守父子节点不互相关联,默认 false */
|
/** 是否严格的遵守父子节点不互相关联,默认 false */
|
||||||
checkStrictly?: boolean;
|
checkStrictly?: boolean | FilterFunction<boolean>;
|
||||||
/** 弹出内容的自定义类名 */
|
/** 弹出内容的自定义类名 */
|
||||||
popperClass?: string;
|
popperClass?: string;
|
||||||
/** 合并成字符串时的分隔符 */
|
/** 合并成字符串时的分隔符 */
|
||||||
valueSeparator?: string;
|
valueSeparator?: string | FilterFunction<string>;
|
||||||
options?:
|
options?:
|
||||||
| ((
|
| ((
|
||||||
mForm: FormState | undefined,
|
mForm: FormState | undefined,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user