mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-04-06 03:57:56 +08:00
feat(editor,form,core,schema): 事件支持触发代码块
This commit is contained in:
parent
cfd2a6eee3
commit
39468f3b95
@ -18,7 +18,18 @@
|
|||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
import type { CodeBlockDSL, EventItemConfig, Id, MApp } from '@tmagic/schema';
|
import { has, isEmpty } from 'lodash-es';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ActionType,
|
||||||
|
CodeBlockDSL,
|
||||||
|
CodeItemConfig,
|
||||||
|
CompItemConfig,
|
||||||
|
DeprecatedEventConfig,
|
||||||
|
EventConfig,
|
||||||
|
Id,
|
||||||
|
MApp,
|
||||||
|
} from '@tmagic/schema';
|
||||||
|
|
||||||
import Env from './Env';
|
import Env from './Env';
|
||||||
import { bindCommonEventListener, isCommonMethod, triggerCommonMethod } from './events';
|
import { bindCommonEventListener, isCommonMethod, triggerCommonMethod } from './events';
|
||||||
@ -37,7 +48,7 @@ interface AppOptionsConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface EventCache {
|
interface EventCache {
|
||||||
eventConfig: EventItemConfig;
|
eventConfig: CompItemConfig | DeprecatedEventConfig;
|
||||||
fromCpt: any;
|
fromCpt: any;
|
||||||
args: any[];
|
args: any[];
|
||||||
}
|
}
|
||||||
@ -221,10 +232,10 @@ class App extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bindEvent(event: EventItemConfig, id: string) {
|
public bindEvent(event: EventConfig | DeprecatedEventConfig, id: string) {
|
||||||
const { name } = event;
|
const { name } = event;
|
||||||
this.on(`${name}_${id}`, (fromCpt: Node, ...args) => {
|
this.on(`${name}_${id}`, async (fromCpt: Node, ...args) => {
|
||||||
this.eventHandler(event, fromCpt, args);
|
await this.eventHandler(event, fromCpt, args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,11 +246,53 @@ class App extends EventEmitter {
|
|||||||
return super.emit(name, node, ...args);
|
return super.emit(name, node, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public eventHandler(eventConfig: EventItemConfig, fromCpt: any, args: any[]) {
|
/**
|
||||||
|
* 事件联动处理函数
|
||||||
|
* @param eventConfig 事件配置
|
||||||
|
* @param fromCpt 触发事件的组件
|
||||||
|
* @param args 事件参数
|
||||||
|
*/
|
||||||
|
public async eventHandler(eventConfig: EventConfig | DeprecatedEventConfig, fromCpt: any, args: any[]) {
|
||||||
|
if (has(eventConfig, 'actions')) {
|
||||||
|
// EventConfig类型
|
||||||
|
const { actions } = eventConfig as EventConfig;
|
||||||
|
for (const actionItem of actions) {
|
||||||
|
if (actionItem.actionType === ActionType.COMP) {
|
||||||
|
// 组件动作
|
||||||
|
await this.compActionHandler(actionItem as CompItemConfig, fromCpt, args);
|
||||||
|
} else if (actionItem.actionType === ActionType.CODE) {
|
||||||
|
// 执行代码块
|
||||||
|
await this.codeActionHandler(actionItem as CodeItemConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 兼容DeprecatedEventConfig类型 组件动作
|
||||||
|
await this.compActionHandler(eventConfig as DeprecatedEventConfig, fromCpt, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行代码块动作
|
||||||
|
* @param eventConfig 代码动作的配置
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
public async codeActionHandler(eventConfig: CodeItemConfig) {
|
||||||
|
const { codeId = '', params = {} } = eventConfig;
|
||||||
|
if (!codeId || isEmpty(this.codeDsl)) return;
|
||||||
|
if (this.codeDsl![codeId] && typeof this.codeDsl![codeId]?.content === 'function') {
|
||||||
|
await this.codeDsl![codeId].content({ app: this, params });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行联动组件动作
|
||||||
|
* @param eventConfig 联动组件的配置
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
public async compActionHandler(eventConfig: CompItemConfig | DeprecatedEventConfig, fromCpt: any, args: any[]) {
|
||||||
if (!this.page) throw new Error('当前没有页面');
|
if (!this.page) throw new Error('当前没有页面');
|
||||||
|
|
||||||
const { method: methodName, to } = eventConfig;
|
const { method: methodName, to } = eventConfig;
|
||||||
|
|
||||||
const toNode = this.page.getNode(to);
|
const toNode = this.page.getNode(to);
|
||||||
if (!toNode) throw `ID为${to}的组件不存在`;
|
if (!toNode) throw `ID为${to}的组件不存在`;
|
||||||
|
|
||||||
@ -249,7 +302,7 @@ class App extends EventEmitter {
|
|||||||
|
|
||||||
if (toNode.instance) {
|
if (toNode.instance) {
|
||||||
if (typeof toNode.instance[methodName] === 'function') {
|
if (typeof toNode.instance[methodName] === 'function') {
|
||||||
toNode.instance[methodName](fromCpt, ...args);
|
await toNode.instance[methodName](fromCpt, ...args);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.addEventToMap({
|
this.addEventToMap({
|
||||||
|
@ -20,7 +20,7 @@ import { EventEmitter } from 'events';
|
|||||||
|
|
||||||
import { isEmpty } from 'lodash-es';
|
import { isEmpty } from 'lodash-es';
|
||||||
|
|
||||||
import { EventItemConfig, HookType, MComponent, MContainer, MPage } from '@tmagic/schema';
|
import { DeprecatedEventConfig, EventConfig, HookType, MComponent, MContainer, MPage } from '@tmagic/schema';
|
||||||
|
|
||||||
import type App from './App';
|
import type App from './App';
|
||||||
import type Page from './Page';
|
import type Page from './Page';
|
||||||
@ -37,7 +37,7 @@ class Node extends EventEmitter {
|
|||||||
public style?: {
|
public style?: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
public events?: EventItemConfig[];
|
public events?: DeprecatedEventConfig[] | EventConfig[];
|
||||||
public instance?: any;
|
public instance?: any;
|
||||||
public page?: Page;
|
public page?: Page;
|
||||||
public parent?: Node;
|
public parent?: Node;
|
||||||
@ -86,7 +86,7 @@ class Node extends EventEmitter {
|
|||||||
const eventConfigQueue = this.app.eventQueueMap[instance.config.id] || [];
|
const eventConfigQueue = this.app.eventQueueMap[instance.config.id] || [];
|
||||||
|
|
||||||
for (let eventConfig = eventConfigQueue.shift(); eventConfig; eventConfig = eventConfigQueue.shift()) {
|
for (let eventConfig = eventConfigQueue.shift(); eventConfig; eventConfig = eventConfigQueue.shift()) {
|
||||||
this.app.eventHandler(eventConfig.eventConfig, eventConfig.fromCpt, eventConfig.args);
|
this.app.compActionHandler(eventConfig.eventConfig, eventConfig.fromCpt, eventConfig.args);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.runCodeBlock('mounted');
|
await this.runCodeBlock('mounted');
|
||||||
@ -99,11 +99,11 @@ class Node extends EventEmitter {
|
|||||||
await this.data[hook](this);
|
await this.data[hook](this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.data[hook]?.hookType !== HookType.CODE || !this.app.codeDsl || isEmpty(this.app?.codeDsl)) return;
|
if (this.data[hook]?.hookType !== HookType.CODE || isEmpty(this.app.codeDsl)) return;
|
||||||
for (const item of this.data[hook].hookData) {
|
for (const item of this.data[hook].hookData) {
|
||||||
const { codeId, params = {} } = item;
|
const { codeId, params = {} } = item;
|
||||||
if (this.app.codeDsl[codeId] && typeof this.app?.codeDsl[codeId]?.content === 'function') {
|
if (this.app.codeDsl![codeId] && typeof this.app.codeDsl![codeId]?.content === 'function') {
|
||||||
await this.app.codeDsl[codeId].content({ app: this.app, params });
|
await this.app.codeDsl![codeId].content({ app: this.app, params });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup name="MEditorCodeDraftEditor">
|
<script lang="ts" setup name="MEditorCodeDraftEditor">
|
||||||
import { computed, inject, ref, watchEffect } from 'vue';
|
import { computed, inject, ref, watchEffect } from 'vue';
|
||||||
|
import type { Action } from 'element-plus';
|
||||||
import type * as monaco from 'monaco-editor';
|
import type * as monaco from 'monaco-editor';
|
||||||
|
|
||||||
import { TMagicButton, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
import { TMagicButton, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
||||||
@ -109,18 +110,21 @@ const close = async (): Promise<void> => {
|
|||||||
if (codeDraft) {
|
if (codeDraft) {
|
||||||
tMagicMessageBox
|
tMagicMessageBox
|
||||||
.confirm('您有代码修改未保存,是否保存后再关闭?', '提示', {
|
.confirm('您有代码修改未保存,是否保存后再关闭?', '提示', {
|
||||||
confirmButtonText: '确认',
|
confirmButtonText: '保存并关闭',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '直接关闭',
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
distinguishCancelAndClose: true,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
// 保存之后再关闭
|
// 保存之后再关闭
|
||||||
saveAndClose();
|
saveAndClose();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((action: Action) => {
|
||||||
// 删除草稿 直接关闭
|
if (action === 'cancel') {
|
||||||
services?.codeBlockService.removeCodeDraft(props.id);
|
// 删除草稿 直接关闭
|
||||||
emit('close');
|
services?.codeBlockService.removeCodeDraft(props.id);
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
emit('close');
|
emit('close');
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="m-fields-code-select">
|
<div class="m-fields-code-select" :class="config.className">
|
||||||
<m-form-table
|
<m-form-table
|
||||||
:config="tableConfig"
|
:config="tableConfig"
|
||||||
:model="model[name]"
|
:model="model[name]"
|
||||||
@ -11,7 +11,7 @@
|
|||||||
>
|
>
|
||||||
<template #operateCol="{ scope }">
|
<template #operateCol="{ scope }">
|
||||||
<Icon
|
<Icon
|
||||||
v-if="scope.row.codeId"
|
v-if="scope.row.codeId && config.editable"
|
||||||
:icon="editable ? Edit : View"
|
:icon="editable ? Edit : View"
|
||||||
class="edit-icon"
|
class="edit-icon"
|
||||||
@click="editCode(scope.row.codeId)"
|
@click="editCode(scope.row.codeId)"
|
||||||
@ -35,15 +35,25 @@ const services = inject<Services>('services');
|
|||||||
const mForm = inject<FormState>('mForm');
|
const mForm = inject<FormState>('mForm');
|
||||||
const emit = defineEmits(['change']);
|
const emit = defineEmits(['change']);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
config: {
|
defineProps<{
|
||||||
tableConfig?: TableConfig;
|
config: {
|
||||||
};
|
tableConfig?: TableConfig;
|
||||||
model: any;
|
className?: string;
|
||||||
prop: string;
|
editable?: boolean;
|
||||||
name: string;
|
};
|
||||||
size: 'mini' | 'small' | 'medium';
|
model: any;
|
||||||
}>();
|
prop: string;
|
||||||
|
name: string;
|
||||||
|
size: 'small' | 'default' | 'large';
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
config: () => ({
|
||||||
|
editable: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const codeDsl = computed(() => services?.codeBlockService.getCodeDsl());
|
const codeDsl = computed(() => services?.codeBlockService.getCodeDsl());
|
||||||
|
|
||||||
const tableConfig = computed<FormItem>(() => {
|
const tableConfig = computed<FormItem>(() => {
|
||||||
@ -80,7 +90,7 @@ const tableConfig = computed<FormItem>(() => {
|
|||||||
itemsFunction: (row: HookData) => {
|
itemsFunction: (row: HookData) => {
|
||||||
const paramsConfig = getParamsConfig(row.codeId);
|
const paramsConfig = getParamsConfig(row.codeId);
|
||||||
// 如果参数没有填值,则使用createValues补全空值
|
// 如果参数没有填值,则使用createValues补全空值
|
||||||
if (isEmpty(row.params) || !row.params) {
|
if (!row.params || isEmpty(row.params)) {
|
||||||
createValues(mForm, paramsConfig, {}, row.params);
|
createValues(mForm, paramsConfig, {}, row.params);
|
||||||
}
|
}
|
||||||
return paramsConfig;
|
return paramsConfig;
|
||||||
@ -124,6 +134,7 @@ const getParamsConfig = (codeId: Id): CodeParamStatement[] => {
|
|||||||
return paramStatements.map((paramState: CodeParamStatement) => ({
|
return paramStatements.map((paramState: CodeParamStatement) => ({
|
||||||
labelWidth: '100px',
|
labelWidth: '100px',
|
||||||
text: paramState.name,
|
text: paramState.name,
|
||||||
|
inline: true,
|
||||||
...paramState,
|
...paramState,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
117
packages/editor/src/fields/CodeSelectCol.vue
Normal file
117
packages/editor/src/fields/CodeSelectCol.vue
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<div class="m-fields-code-select-col">
|
||||||
|
<!-- 代码块下拉框 -->
|
||||||
|
<m-form-container :config="selectConfig" :model="model" @change="onParamsChangeHandler"></m-form-container>
|
||||||
|
<!-- 参数填写框 -->
|
||||||
|
<m-form-container :config="codeParamsConfig" :model="model" @change="onParamsChangeHandler"></m-form-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup name="MEditorCodeSelectCol">
|
||||||
|
import { computed, defineEmits, defineProps, inject, ref, watch } from 'vue';
|
||||||
|
import { isEmpty, map } from 'lodash-es';
|
||||||
|
|
||||||
|
import { createValues, FieldsetConfig, FormState } from '@tmagic/form';
|
||||||
|
import { Id } from '@tmagic/schema';
|
||||||
|
|
||||||
|
import { CodeParamStatement, Services } from '../type';
|
||||||
|
const services = inject<Services>('services');
|
||||||
|
const mForm = inject<FormState>('mForm');
|
||||||
|
const emit = defineEmits(['change']);
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
config: any;
|
||||||
|
model: any;
|
||||||
|
prop: string;
|
||||||
|
name: string;
|
||||||
|
size: 'small' | 'default' | 'large';
|
||||||
|
}>(),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const codeDsl = computed(() => services?.codeBlockService.getCodeDsl());
|
||||||
|
const codeParamsConfig = ref<FieldsetConfig>({
|
||||||
|
type: 'fieldset',
|
||||||
|
items: [],
|
||||||
|
legend: '参数',
|
||||||
|
labelWidth: '70px',
|
||||||
|
name: 'params',
|
||||||
|
display: false,
|
||||||
|
});
|
||||||
|
const selectConfig = {
|
||||||
|
type: 'select',
|
||||||
|
text: '代码块',
|
||||||
|
name: 'codeId',
|
||||||
|
labelWidth: '70px',
|
||||||
|
options: () => {
|
||||||
|
if (codeDsl.value) {
|
||||||
|
return map(codeDsl.value, (value, key) => ({
|
||||||
|
text: `${value.name}(${key})`,
|
||||||
|
label: `${value.name}(${key})`,
|
||||||
|
value: key,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
onChange: (formState: any, codeId: Id, { model }: any) => {
|
||||||
|
// 通过下拉框选择的codeId变化后修正model的值,避免写入其他codeId的params
|
||||||
|
model.params = {};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据代码块id获取代码块参数配置
|
||||||
|
* @param codeId 代码块ID
|
||||||
|
*/
|
||||||
|
const getParamItemsConfig = (codeId: Id): CodeParamStatement[] => {
|
||||||
|
if (!codeDsl.value) return [];
|
||||||
|
const paramStatements = codeDsl.value[codeId]?.params;
|
||||||
|
if (isEmpty(paramStatements)) return [];
|
||||||
|
return paramStatements.map((paramState: CodeParamStatement) => ({
|
||||||
|
labelWidth: '100px',
|
||||||
|
text: paramState.name,
|
||||||
|
...paramState,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据代码块id获取参数fieldset表单配置
|
||||||
|
* @param codeId 代码块ID
|
||||||
|
*/
|
||||||
|
const getCodeParamsConfig = (codeId: Id) => {
|
||||||
|
const paramsConfig = getParamItemsConfig(codeId);
|
||||||
|
// 如果参数没有填值,则使用createValues补全空值
|
||||||
|
if (!props.model.params || isEmpty(props.model.params)) {
|
||||||
|
props.model.params = {};
|
||||||
|
createValues(mForm, paramsConfig, {}, props.model.params);
|
||||||
|
}
|
||||||
|
codeParamsConfig.value = {
|
||||||
|
type: 'fieldset',
|
||||||
|
items: paramsConfig,
|
||||||
|
legend: '参数',
|
||||||
|
labelWidth: '70px',
|
||||||
|
name: 'params',
|
||||||
|
display: !isEmpty(paramsConfig),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数值修改更新
|
||||||
|
*/
|
||||||
|
const onParamsChangeHandler = () => {
|
||||||
|
emit('change', props.model);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听代码块顺序变化以及下拉选择的变化,并更新参数配置
|
||||||
|
// TODO onchange在watch之前触发
|
||||||
|
watch(
|
||||||
|
() => props.model.codeId,
|
||||||
|
(codeId: Id) => {
|
||||||
|
if (!codeId) return;
|
||||||
|
getCodeParamsConfig(codeId);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
216
packages/editor/src/fields/EventSelect.vue
Normal file
216
packages/editor/src/fields/EventSelect.vue
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<div class="m-fields-event-select">
|
||||||
|
<m-form-container
|
||||||
|
v-if="isOldVersion"
|
||||||
|
ref="eventForm"
|
||||||
|
:size="props.size"
|
||||||
|
:model="model"
|
||||||
|
:config="tableConfig"
|
||||||
|
@change="onChangeHandler"
|
||||||
|
></m-form-container>
|
||||||
|
|
||||||
|
<div v-else class="fullWidth">
|
||||||
|
<TMagicButton class="create-button" type="primary" size="small" @click="addEvent()">添加事件</TMagicButton>
|
||||||
|
<m-form-panel
|
||||||
|
v-for="(cardItem, index) in model[name]"
|
||||||
|
:key="index"
|
||||||
|
:config="actionsConfig"
|
||||||
|
:model="cardItem"
|
||||||
|
@change="onChangeHandler"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<m-form-container
|
||||||
|
class="fullWidth"
|
||||||
|
:config="eventNameConfig"
|
||||||
|
:model="cardItem"
|
||||||
|
@change="onChangeHandler"
|
||||||
|
></m-form-container>
|
||||||
|
<TMagicButton style="color: #f56c6c" text :icon="Delete" @click="removeEvent(index)"></TMagicButton>
|
||||||
|
</template>
|
||||||
|
</m-form-panel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup name="MEditorEventSelect">
|
||||||
|
import { computed, defineProps, inject } from 'vue';
|
||||||
|
import { Delete } from '@element-plus/icons-vue';
|
||||||
|
import { has } from 'lodash-es';
|
||||||
|
|
||||||
|
import { TMagicButton } from '@tmagic/design';
|
||||||
|
import { FormState } from '@tmagic/form';
|
||||||
|
import { ActionType } from '@tmagic/schema';
|
||||||
|
|
||||||
|
import { EventSelectConfig, Services } from '../type';
|
||||||
|
const services = inject<Services>('services');
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
config: EventSelectConfig;
|
||||||
|
model: any;
|
||||||
|
prop: string;
|
||||||
|
name: string;
|
||||||
|
size: 'small' | 'default' | 'large';
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits(['change']);
|
||||||
|
|
||||||
|
// 事件名称下拉框表单配置
|
||||||
|
const eventNameConfig = computed(() => {
|
||||||
|
const defaultEventNameConfig = {
|
||||||
|
name: 'name',
|
||||||
|
text: '事件',
|
||||||
|
type: 'select',
|
||||||
|
labelWidth: '40px',
|
||||||
|
options: (mForm: FormState, { formValue }: any) =>
|
||||||
|
services?.eventsService.getEvent(formValue.type).map((option: any) => ({
|
||||||
|
text: option.label,
|
||||||
|
value: option.value,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
return { ...defaultEventNameConfig, ...props.config.eventNameConfig };
|
||||||
|
});
|
||||||
|
|
||||||
|
// 联动类型
|
||||||
|
const actionTypeConfig = computed(() => {
|
||||||
|
const defaultActionTypeConfig = {
|
||||||
|
name: 'actionType',
|
||||||
|
text: '联动类型',
|
||||||
|
labelWidth: '70px',
|
||||||
|
type: 'select',
|
||||||
|
defaultValue: ActionType.COMP,
|
||||||
|
options: () => [
|
||||||
|
{
|
||||||
|
text: '组件',
|
||||||
|
label: '组件',
|
||||||
|
value: ActionType.COMP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '代码',
|
||||||
|
label: '代码',
|
||||||
|
value: ActionType.CODE,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return { ...defaultActionTypeConfig, ...props.config.actionTypeConfig };
|
||||||
|
});
|
||||||
|
|
||||||
|
// 联动组件配置
|
||||||
|
const targetCompConfig = computed(() => {
|
||||||
|
const defaultTargetCompConfig = {
|
||||||
|
name: 'to',
|
||||||
|
text: '联动组件',
|
||||||
|
labelWidth: '70px',
|
||||||
|
type: 'ui-select',
|
||||||
|
display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.actionType === ActionType.COMP,
|
||||||
|
};
|
||||||
|
return { ...defaultTargetCompConfig, ...props.config.targetCompConfig };
|
||||||
|
});
|
||||||
|
|
||||||
|
// 联动组件动作配置
|
||||||
|
const compActionConfig = computed(() => {
|
||||||
|
const defaultCompActionConfig = {
|
||||||
|
name: 'method',
|
||||||
|
text: '动作',
|
||||||
|
labelWidth: '70px',
|
||||||
|
type: 'select',
|
||||||
|
display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.actionType === ActionType.COMP,
|
||||||
|
options: (mForm: FormState, { model }: any) => {
|
||||||
|
const node = services?.editorService.getNodeById(model.to);
|
||||||
|
if (!node?.type) return [];
|
||||||
|
|
||||||
|
return services?.eventsService.getMethod(node.type).map((option: any) => ({
|
||||||
|
text: option.label,
|
||||||
|
value: option.value,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return { ...defaultCompActionConfig, ...props.config.compActionConfig };
|
||||||
|
});
|
||||||
|
|
||||||
|
// 代码联动配置
|
||||||
|
const codeActionConfig = computed(() => {
|
||||||
|
const defaultCodeActionConfig = {
|
||||||
|
type: 'code-select-col',
|
||||||
|
display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.actionType === ActionType.CODE,
|
||||||
|
};
|
||||||
|
return { ...defaultCodeActionConfig, ...props.config.codeActionConfig };
|
||||||
|
});
|
||||||
|
|
||||||
|
// 兼容旧的数据格式
|
||||||
|
const tableConfig = computed(() => ({
|
||||||
|
type: 'table',
|
||||||
|
name: 'events',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: '事件名',
|
||||||
|
type: eventNameConfig.value.type,
|
||||||
|
options: (mForm: FormState, { formValue }: any) =>
|
||||||
|
services?.eventsService.getEvent(formValue.type).map((option: any) => ({
|
||||||
|
text: option.label,
|
||||||
|
value: option.value,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'to',
|
||||||
|
label: '联动组件',
|
||||||
|
type: 'ui-select',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'method',
|
||||||
|
label: '动作',
|
||||||
|
type: compActionConfig.value.type,
|
||||||
|
options: (mForm: FormState, { model }: any) => {
|
||||||
|
const node = services?.editorService.getNodeById(model.to);
|
||||||
|
if (!node?.type) return [];
|
||||||
|
|
||||||
|
return services?.eventsService.getMethod(node.type).map((option: any) => ({
|
||||||
|
text: option.label,
|
||||||
|
value: option.value,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 组件动作组表单配置
|
||||||
|
const actionsConfig = computed(() => ({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'group-list',
|
||||||
|
name: 'actions',
|
||||||
|
enableToggleMode: false,
|
||||||
|
items: [actionTypeConfig.value, targetCompConfig.value, compActionConfig.value, codeActionConfig.value],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 是否为旧的数据格式
|
||||||
|
const isOldVersion = computed(() => {
|
||||||
|
if (props.model[props.name].length === 0) return false;
|
||||||
|
return !has(props.model[props.name][0], 'actions');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加事件
|
||||||
|
const addEvent = () => {
|
||||||
|
const defaultEvent = {
|
||||||
|
name: '',
|
||||||
|
actions: [],
|
||||||
|
};
|
||||||
|
if (!props.model[props.name]) {
|
||||||
|
props.model[props.name] = [];
|
||||||
|
}
|
||||||
|
props.model[props.name].push(defaultEvent);
|
||||||
|
onChangeHandler();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除事件
|
||||||
|
const removeEvent = (index: number) => {
|
||||||
|
if (!props.name) return;
|
||||||
|
props.model[props.name].splice(index, 1);
|
||||||
|
onChangeHandler();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeHandler = () => {
|
||||||
|
emit('change', props.model);
|
||||||
|
};
|
||||||
|
</script>
|
@ -20,6 +20,8 @@ import { App } from 'vue';
|
|||||||
import Code from './fields/Code.vue';
|
import Code from './fields/Code.vue';
|
||||||
import CodeLink from './fields/CodeLink.vue';
|
import CodeLink from './fields/CodeLink.vue';
|
||||||
import CodeSelect from './fields/CodeSelect.vue';
|
import CodeSelect from './fields/CodeSelect.vue';
|
||||||
|
import CodeSelectCol from './fields/CodeSelectCol.vue';
|
||||||
|
import EventSelect from './fields/EventSelect.vue';
|
||||||
import uiSelect from './fields/UISelect.vue';
|
import uiSelect from './fields/UISelect.vue';
|
||||||
import CodeEditor from './layouts/CodeEditor.vue';
|
import CodeEditor from './layouts/CodeEditor.vue';
|
||||||
import { setConfig } from './utils/config';
|
import { setConfig } from './utils/config';
|
||||||
@ -44,6 +46,8 @@ export { default as depService } from './services/dep';
|
|||||||
export { default as ComponentListPanel } from './layouts/sidebar/ComponentListPanel.vue';
|
export { default as ComponentListPanel } from './layouts/sidebar/ComponentListPanel.vue';
|
||||||
export { default as LayerPanel } from './layouts/sidebar/LayerPanel.vue';
|
export { default as LayerPanel } from './layouts/sidebar/LayerPanel.vue';
|
||||||
export { default as CodeSelect } from './fields/CodeSelect.vue';
|
export { default as CodeSelect } from './fields/CodeSelect.vue';
|
||||||
|
export { default as CodeSelectCol } from './fields/CodeSelectCol.vue';
|
||||||
|
export { default as EventSelect } from './fields/EventSelect.vue';
|
||||||
export { default as CodeBlockList } from './layouts/sidebar/code-block/CodeBlockList.vue';
|
export { default as CodeBlockList } from './layouts/sidebar/code-block/CodeBlockList.vue';
|
||||||
export { default as PropsPanel } from './layouts/PropsPanel.vue';
|
export { default as PropsPanel } from './layouts/PropsPanel.vue';
|
||||||
export { default as ToolButton } from './components/ToolButton.vue';
|
export { default as ToolButton } from './components/ToolButton.vue';
|
||||||
@ -68,5 +72,7 @@ export default {
|
|||||||
app.component('m-fields-vs-code', Code);
|
app.component('m-fields-vs-code', Code);
|
||||||
app.component('magic-code-editor', CodeEditor);
|
app.component('magic-code-editor', CodeEditor);
|
||||||
app.component('m-fields-code-select', CodeSelect);
|
app.component('m-fields-code-select', CodeSelect);
|
||||||
|
app.component('m-fields-code-select-col', CodeSelectCol);
|
||||||
|
app.component('m-fields-event-select', EventSelect);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -171,7 +171,7 @@ class CodeBlock extends BaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置编辑状态
|
* 设置编辑状态
|
||||||
* @param {boolean} 是否可编辑
|
* @param {boolean} status 是否可编辑
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
public async setEditStatus(status: boolean): Promise<void> {
|
public async setEditStatus(status: boolean): Promise<void> {
|
||||||
|
24
packages/editor/src/theme/event.scss
Normal file
24
packages/editor/src/theme/event.scss
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
.m-fields-event-select {
|
||||||
|
width: 100%;
|
||||||
|
.fullWidth {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.m-form-panel .el-card__body {
|
||||||
|
padding: 10px 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-select-code {
|
||||||
|
margin-left: 20px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.m-form-panel {
|
||||||
|
margin: 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card.is-always-shadow {
|
||||||
|
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.m-fields-code-select-col {
|
||||||
|
width: 100%;
|
||||||
|
}
|
@ -12,5 +12,6 @@
|
|||||||
@import "./code-editor.scss";
|
@import "./code-editor.scss";
|
||||||
@import "./icon.scss";
|
@import "./icon.scss";
|
||||||
@import "./code-block.scss";
|
@import "./code-block.scss";
|
||||||
|
@import "./event.scss";
|
||||||
@import "./layout.scss";
|
@import "./layout.scss";
|
||||||
@import "./breadcrumb.scss";
|
@import "./breadcrumb.scss";
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
import type { Component } from 'vue';
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
import type { FormConfig } from '@tmagic/form';
|
import type { FormConfig, FormItem } from '@tmagic/form';
|
||||||
import type { CodeBlockContent, CodeBlockDSL, Id, MApp, MContainer, MNode, MPage } from '@tmagic/schema';
|
import type { CodeBlockContent, CodeBlockDSL, Id, MApp, MContainer, MNode, MPage } from '@tmagic/schema';
|
||||||
import type StageCore from '@tmagic/stage';
|
import type StageCore from '@tmagic/stage';
|
||||||
import type {
|
import type {
|
||||||
@ -410,3 +410,18 @@ export interface HistoryState {
|
|||||||
canRedo: boolean;
|
canRedo: boolean;
|
||||||
canUndo: boolean;
|
canUndo: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EventSelectConfig {
|
||||||
|
name: string;
|
||||||
|
type: 'event-select';
|
||||||
|
/** 事件名称表单配置 */
|
||||||
|
eventNameConfig?: FormItem;
|
||||||
|
/** 动作类型配置 */
|
||||||
|
actionTypeConfig?: FormItem;
|
||||||
|
/** 联动组件配置 */
|
||||||
|
targetCompConfig?: FormItem;
|
||||||
|
/** 联动组件动作配置 */
|
||||||
|
compActionConfig?: FormItem;
|
||||||
|
/** 联动代码配置 */
|
||||||
|
codeActionConfig?: FormItem;
|
||||||
|
}
|
||||||
|
@ -18,9 +18,6 @@
|
|||||||
|
|
||||||
import { FormConfig, FormState } from '@tmagic/form';
|
import { FormConfig, FormState } from '@tmagic/form';
|
||||||
|
|
||||||
import editorService from '../services/editor';
|
|
||||||
import eventsService from '../services/events';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统一为组件属性表单加上事件、高级、样式配置
|
* 统一为组件属性表单加上事件、高级、样式配置
|
||||||
* @param config 组件属性配置
|
* @param config 组件属性配置
|
||||||
@ -183,39 +180,8 @@ export const fillConfig = (config: FormConfig = []) => [
|
|||||||
title: '事件',
|
title: '事件',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: 'table',
|
|
||||||
name: 'events',
|
name: 'events',
|
||||||
items: [
|
type: 'event-select',
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
label: '事件名',
|
|
||||||
type: 'select',
|
|
||||||
options: (mForm: FormState, { formValue }: any) =>
|
|
||||||
eventsService.getEvent(formValue.type).map((option) => ({
|
|
||||||
text: option.label,
|
|
||||||
value: option.value,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'to',
|
|
||||||
label: '联动组件',
|
|
||||||
type: 'ui-select',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'method',
|
|
||||||
label: '动作',
|
|
||||||
type: 'select',
|
|
||||||
options: (mForm: FormState, { model }: any) => {
|
|
||||||
const node = editorService.getNodeById(model.to);
|
|
||||||
if (!node?.type) return [];
|
|
||||||
|
|
||||||
return eventsService.getMethod(node.type).map((option) => ({
|
|
||||||
text: option.label,
|
|
||||||
value: option.value,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="m-fields-group-list-item">
|
<div class="m-fields-group-list-item">
|
||||||
<div>
|
<div>
|
||||||
<TMagicIcon style="margin-right: 7px" @click="expandHandler"
|
<TMagicButton text :disabled="disabled" :icon="expand ? CaretBottom : CaretRight" @click="expandHandler">{{
|
||||||
><CaretBottom v-if="expand" /><CaretRight v-else
|
title
|
||||||
/></TMagicIcon>
|
}}</TMagicButton>
|
||||||
|
|
||||||
<TMagicButton text :disabled="disabled" @click="expandHandler">{{ title }}</TMagicButton>
|
|
||||||
|
|
||||||
<TMagicButton
|
<TMagicButton
|
||||||
v-show="showDelete(parseInt(String(index)))"
|
v-show="showDelete(parseInt(String(index)))"
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
:body-style="{ display: expand ? 'block' : 'none' }"
|
:body-style="{ display: expand ? 'block' : 'none' }"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="clearfix">
|
<div style="width: 100%; display: flex; align-items: center">
|
||||||
<a href="javascript:" style="width: 100%; display: block" @click="expand = !expand">
|
<TMagicButton style="padding: 0" text :icon="expand ? CaretBottom : CaretRight" @click="expand = !expand">
|
||||||
<TMagicIcon><CaretBottom v-if="expand" /><CaretRight v-else /></TMagicIcon> {{ filter(config.title) }}
|
</TMagicButton>
|
||||||
<span v-if="config && config.extra" v-html="config.extra" class="m-form-tip"></span>
|
<span v-if="config && config.extra" v-html="config.extra" class="m-form-tip"></span>
|
||||||
</a>
|
<slot name="header">{{ filter(config.title) }}</slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -61,7 +61,7 @@
|
|||||||
import { computed, inject, ref } from 'vue';
|
import { computed, inject, ref } from 'vue';
|
||||||
import { CaretBottom, CaretRight } from '@element-plus/icons-vue';
|
import { CaretBottom, CaretRight } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
import { TMagicCard, TMagicIcon } from '@tmagic/design';
|
import { TMagicButton, TMagicCard } from '@tmagic/design';
|
||||||
|
|
||||||
import { FormState, PanelConfig } from '../schema';
|
import { FormState, PanelConfig } from '../schema';
|
||||||
import { filterFunction } from '../utils/form';
|
import { filterFunction } from '../utils/form';
|
||||||
@ -73,7 +73,7 @@ const props = defineProps<{
|
|||||||
lastValues?: any;
|
lastValues?: any;
|
||||||
isCompare?: boolean;
|
isCompare?: boolean;
|
||||||
config: PanelConfig;
|
config: PanelConfig;
|
||||||
name: string;
|
name?: string;
|
||||||
labelWidth?: string;
|
labelWidth?: string;
|
||||||
prop?: string;
|
prop?: string;
|
||||||
size?: string;
|
size?: string;
|
||||||
|
@ -24,15 +24,51 @@ export enum NodeType {
|
|||||||
|
|
||||||
export type Id = string | number;
|
export type Id = string | number;
|
||||||
|
|
||||||
export interface EventItemConfig {
|
// 事件联动的动作类型
|
||||||
|
export enum ActionType {
|
||||||
|
/** 联动组件 */
|
||||||
|
COMP = 'comp',
|
||||||
|
/** 联动代码 */
|
||||||
|
CODE = 'code',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 事件类型(已废弃,后续不建议继续使用) */
|
||||||
|
export interface DeprecatedEventConfig {
|
||||||
|
/** 待触发的事件名称 */
|
||||||
|
name: string;
|
||||||
/** 被选中组件ID */
|
/** 被选中组件ID */
|
||||||
to: Id;
|
to: Id;
|
||||||
/** 被选中组件名称 */
|
|
||||||
name: string;
|
|
||||||
/** 触发事件后执行被选中组件的方法 */
|
/** 触发事件后执行被选中组件的方法 */
|
||||||
method: string;
|
method: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EventConfig {
|
||||||
|
/** 待触发的事件名称 */
|
||||||
|
name: string;
|
||||||
|
/** 动作响应配置 */
|
||||||
|
actions: EventActionItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CodeItemConfig {
|
||||||
|
/** 动作类型 */
|
||||||
|
actionType: ActionType;
|
||||||
|
/** 代码ID */
|
||||||
|
codeId: Id;
|
||||||
|
/** 代码参数 */
|
||||||
|
params?: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompItemConfig {
|
||||||
|
/** 动作类型 */
|
||||||
|
actionType: ActionType;
|
||||||
|
/** 被选中组件ID */
|
||||||
|
to: Id;
|
||||||
|
/** 触发事件后执行被选中组件的方法 */
|
||||||
|
method: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventActionItem = CompItemConfig | CodeItemConfig;
|
||||||
|
|
||||||
export interface MComponent {
|
export interface MComponent {
|
||||||
/** 组件ID,默认为${type}_${number}}形式, 如:page_123 */
|
/** 组件ID,默认为${type}_${number}}形式, 如:page_123 */
|
||||||
id: Id;
|
id: Id;
|
||||||
@ -43,7 +79,7 @@ export interface MComponent {
|
|||||||
/** 组件根Dom上的class */
|
/** 组件根Dom上的class */
|
||||||
className?: string;
|
className?: string;
|
||||||
/* 关联事件集合 */
|
/* 关联事件集合 */
|
||||||
events?: EventItemConfig[];
|
events?: EventConfig[] | DeprecatedEventConfig[];
|
||||||
/** 组件根Dom的style */
|
/** 组件根Dom的style */
|
||||||
style?: {
|
style?: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
@ -28,9 +28,11 @@ export default {
|
|||||||
params: [
|
params: [
|
||||||
{
|
{
|
||||||
name: 'age',
|
name: 'age',
|
||||||
|
type: 'number',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'studentName',
|
name: 'studentName',
|
||||||
|
type: 'text',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -64,7 +66,35 @@ export default {
|
|||||||
fontSize: '',
|
fontSize: '',
|
||||||
fontWeight: '',
|
fontWeight: '',
|
||||||
},
|
},
|
||||||
events: [],
|
events: [
|
||||||
|
{
|
||||||
|
name: 'magic:common:events:click', // 事件名
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
actionType: 'code', // 联动动作类型
|
||||||
|
codeId: 'code_5336', // 代码块id
|
||||||
|
params: {
|
||||||
|
age: 12,
|
||||||
|
}, // 参数
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actionType: 'comp',
|
||||||
|
to: 'overlay_2159', // 联动组件id
|
||||||
|
method: 'openOverlay', // 联动组件方法
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'magic:common:events:click', // 事件名
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
actionType: 'code', // 联动动作类型
|
||||||
|
codeId: 'code_5316', // 代码块id
|
||||||
|
params: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
created: {
|
created: {
|
||||||
hookType: 'code',
|
hookType: 'code',
|
||||||
hookData: [
|
hookData: [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user