From a82d0e0566d117eec5e7e02ef31d7cefb41e2272 Mon Sep 17 00:00:00 2001 From: hsm-lv Date: Thu, 23 Jun 2022 14:11:33 +0800 Subject: [PATCH] =?UTF-8?q?update:=20=E4=BC=98=E5=8C=96=E5=8A=A8=E4=BD=9C?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Ie01a37a1a50cec51efc9dfd178daff5208c3f4b3 --- .../src/renderer/event-control/actions.tsx | 53 +- .../src/renderer/event-control/helper.tsx | 577 +++++++----------- .../src/renderer/event-control/index.tsx | 157 ++--- packages/amis-editor/src/util.ts | 308 ++++++++-- 4 files changed, 546 insertions(+), 549 deletions(-) diff --git a/packages/amis-editor/src/renderer/event-control/actions.tsx b/packages/amis-editor/src/renderer/event-control/actions.tsx index 436c2c87e..f8f785793 100644 --- a/packages/amis-editor/src/renderer/event-control/actions.tsx +++ b/packages/amis-editor/src/renderer/event-control/actions.tsx @@ -6,6 +6,7 @@ import { } from 'amis-editor-core'; import React from 'react'; import { + FORMITEM_CMPTS, getArgsWrapper, IS_DATA_CONTAINER, renderCmptActionSelect, @@ -29,8 +30,8 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actionLabel: '跳转链接', actionType: 'url', description: '跳转至指定链接的页面', - config: ['url', 'params', 'blank'], - desc: (info: any) => { + innerArgs: ['url', 'params', 'blank'], + descDetail: (info: any) => { return (
跳转{info?.args?.url} @@ -98,8 +99,8 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actionLabel: '打开页面', actionType: 'link', description: '打开指定页面', - config: ['link', 'params'], - desc: (info: any) => { + innerArgs: ['link', 'params'], + descDetail: (info: any) => { return (
打开 @@ -127,7 +128,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actionLabel: '回退页面', actionType: 'goBack', description: '浏览器回退', - desc: (info: any) =>
返回上一页
+ descDetail: (info: any) =>
返回上一页
} ] }, @@ -272,7 +273,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actionLabel: '消息提醒', actionType: 'toast', description: '弹出消息提醒', - config: [ + innerArgs: [ 'title', 'msgType', 'msg', @@ -281,7 +282,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { 'closeButton', 'showIcon' ], - desc: (info: any) => { + descDetail: (info: any) => { return (
@@ -407,8 +408,8 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actionLabel: '发送请求', actionType: 'ajax', description: '配置并发送API请求', - config: ['api'], - desc: (info: any) => { + innerArgs: ['api'], + descDetail: (info: any) => { return (
发送 @@ -436,7 +437,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actionLabel: '下载文件', actionType: 'download', description: '触发下载文件', - config: ['api'], + innerArgs: ['api'], schema: { type: 'wrapper', style: {padding: '0 0 0 32px'}, @@ -462,7 +463,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actions: [ { actionType: 'show', - desc: (info: any) => { + descDetail: (info: any) => { return (
显示 @@ -476,7 +477,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { }, { actionType: 'hidden', - desc: (info: any) => { + descDetail: (info: any) => { return (
隐藏 @@ -489,6 +490,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { } } ], + supportComponents: '*', schema: [ ...renderCmptSelect('选择组件', true), { @@ -519,7 +521,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actions: [ { actionType: 'enabled', - desc: (info: any) => { + descDetail: (info: any) => { return (
启用 @@ -533,7 +535,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { }, { actionType: 'disabled', - desc: (info: any) => { + descDetail: (info: any) => { return (
禁用 @@ -546,6 +548,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { } } ], + supportComponents: ['form', ...FORMITEM_CMPTS], schema: [ ...renderCmptSelect('选择组件', true), { @@ -573,7 +576,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actionLabel: '刷新组件', actionType: 'reload', description: '请求并重新加载所选组件的数据', - desc: (info: any) => { + descDetail: (info: any) => { return (
刷新 @@ -590,8 +593,8 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actionLabel: '设置组件数据', actionType: 'setValue', description: '设置数据容器或表单项的数据', - config: ['value', 'valueInput'], - desc: (info: any) => { + innerArgs: ['value', 'valueInput'], + descDetail: (info: any) => { return (
设置 @@ -708,7 +711,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actionLabel: '提交表单', actionType: 'submit', description: '提交表单数据至数据源', - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -716,13 +719,14 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
); }, + supportComponents: 'form', schema: renderCmptSelect('选择组件', true) }, { actionLabel: '清空表单', actionType: 'clear', description: '清空表单数据', - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -730,13 +734,14 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
); }, + supportComponents: 'form', schema: renderCmptSelect('选择组件', true) }, { actionLabel: '重置表单', actionType: 'reset', description: '重置表单数据', - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -744,13 +749,14 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
); }, + supportComponents: 'form', schema: renderCmptSelect('选择组件', true) }, { actionLabel: '校验表单', actionType: 'validate', description: '校验表单数据', - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -758,6 +764,7 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => {
); }, + supportComponents: 'form', schema: renderCmptSelect('选择组件', true) }, { @@ -777,8 +784,8 @@ const ACTION_TYPE_TREE = (manager: any): RendererPluginAction[] => { actionLabel: '复制内容', actionType: 'copy', description: '复制文本内容至粘贴板', - config: ['content', 'copyFormat'], - desc: (info: any) => { + innerArgs: ['content', 'copyFormat'], + descDetail: (info: any) => { return (
复制内容: diff --git a/packages/amis-editor/src/renderer/event-control/helper.tsx b/packages/amis-editor/src/renderer/event-control/helper.tsx index b171431af..f036e21da 100644 --- a/packages/amis-editor/src/renderer/event-control/helper.tsx +++ b/packages/amis-editor/src/renderer/event-control/helper.tsx @@ -5,7 +5,8 @@ import React from 'react'; import { PluginActions, RendererPluginAction, - RendererPluginEvent + RendererPluginEvent, + SubRendererPluginAction } from 'amis-editor-core'; import {ActionConfig, ComponentInfo, ContextVariables} from './types'; import {DataSchema, findTree} from 'amis-core'; @@ -28,361 +29,6 @@ export const IS_DATA_CONTAINER = `${JSON.stringify( DATA_CONTAINER )}.includes(__rendererName)`; -export const getArgsWrapper = (items: any, multiple: boolean = false) => ({ - type: 'combo', - name: 'args', - // label: '动作参数', - multiple, - strictMode: false, - items: Array.isArray(items) ? items : [items] -}); - -// 获取动作树中指定的动作 -export const findActionNode = ( - actions: RendererPluginAction[], - actionType: string -) => findTree(actions, node => node.actionType === actionType); - -// 获取包含指定子动作的动作 -export const findHasSubActionNode = ( - actions: RendererPluginAction[], - actionType: string -) => - findTree(actions, node => - node.actions?.find(item => item.actionType === actionType) - ); - -// 获取真实的动作类型 -export const getActionType = ( - action: ActionConfig, - hasSubActionNode: RendererPluginAction | null -) => - action.__isCmptAction - ? 'component' - : hasSubActionNode - ? hasSubActionNode.actionType - : action.actionType; - -// 获取事件Label文案 -export const getEventLabel = (events: RendererPluginEvent[], name: string) => - events.find(item => item.eventName === name)?.eventLabel; - -// 获取事件描述文案 -export const getEventDesc = (events: RendererPluginEvent[], name: string) => - events.find(item => item.eventName === name)?.description; - -// 获取动作Label文案 -export const getActionLabel = (events: RendererPluginAction[], name: string) => - findTree(events, item => item.actionType === name)?.actionLabel; - -// 根据动作类型获取过滤后的组件树 -export const getComponentTreeSource = ( - actionType: string, - pluginActions: PluginActions, - getComponents: () => ComponentInfo[], - commonActions?: {[propName: string]: RendererPluginAction} -) => { - if (!actionType) { - return []; - } - - const commonActionConfig = { - ...COMMON_ACTION_SCHEMA_MAP, - ...commonActions - }; - // 如果组件树结构没有传,则重新获取 - const components = getComponents ? getComponents() || [] : []; - return getComponentTreeByType( - components, - actionType, - pluginActions, - commonActionConfig - ); -}; - -/** - * 根据不同的动作类型过滤组件树 - * @param componentsTree 组件树 - * @param actionType 动作类型 - * @returns - */ -export const getComponentTreeByType = ( - componentsTree: ComponentInfo[] = [], - actionType: string = '', - pluginActions: { - [key: string]: any; - } = {}, - actionConfigItems: {[propName: string]: RendererPluginAction} -) => { - const hasActionType = (actions?: RendererPluginAction[]) => { - if (!Array.isArray(actions)) { - return false; - } - return !!actions?.find(item => - [item.actionType, 'component'].includes(actionType) - ); - }; - - const loopChildren = (nodes: ComponentInfo[]) => { - const temp: ComponentInfo[] = []; - for (let node of nodes) { - const actions = pluginActions[node.type]; - - if ( - ['visibility'].includes(actionType) || - (['usability'].includes(actionType) && - ['form', ...FORMITEM_CMPTS].includes(node.type)) || - (!['submit', 'clear', 'reset', 'validate'].includes(actionType) && - node.type && - hasActionType(actions)) || - (['submit', 'clear', 'reset', 'validate'].includes(actionType) && - node.type === 'form') || - ((actionType === 'component' || - actionConfigItems[actionType]?.withComponentId) && - actionConfigItems[actionType]?.supportComponents?.includes(node.type)) - ) { - // 组件特性动作,如果当前组件没有动作,则禁用 - const disabled = - actionType === 'component' && (!actions || !actions.length); - const newNode: ComponentInfo = { - ...node, - disabled: disabled || node.disabled, - children: [] - }; - if (node.children?.length) { - // 检查子项 - newNode.children?.push(...loopChildren(node.children)); - } - temp.push(newNode); - } else if (node.children?.length) { - const childNodes = loopChildren(node.children); - if (childNodes.length) { - temp.push(...childNodes); - } - } - } - return temp; - }; - - return loopChildren(componentsTree); -}; - -// 获取动作配置 -export const getAcionConfig = ( - action: ActionConfig, - actionTree: RendererPluginAction[], - pluginActions: PluginActions, - commonActions?: {[propName: string]: RendererPluginAction} -): RendererPluginAction | undefined => { - const commonActionConfig = { - ...COMMON_ACTION_SCHEMA_MAP, - ...commonActions - }; - const actionNode = findActionNode(actionTree, action.actionType); - if (actionNode) { - return actionNode; - } - - const hasSubActionNode = findHasSubActionNode(actionTree, action.actionType); - let actionConfig: RendererPluginAction | undefined = - hasSubActionNode?.actions?.find( - item => item.actionType === action.actionType - ) ?? commonActionConfig[action.actionType]; - - if (!actionConfig && action.componentId) { - // 尝试从actions中获取desc - actionConfig = pluginActions[action.__rendererName]?.find( - (item: RendererPluginAction) => item.actionType === action.actionType - ); - } - - return actionConfig; -}; - -// 格式化初始化时的动作配置 -export const formatActionInitConfig = ( - action: ActionConfig, - actionTree: RendererPluginAction[], - pluginActions: PluginActions, - getComponents: () => ComponentInfo[], - commonActions?: {[propName: string]: RendererPluginAction} -) => { - let config = {...action}; - - if (['setValue', 'url', 'link'].includes(action.actionType) && action.args) { - const prop = action.actionType === 'setValue' ? 'value' : 'params'; - !config.args && (config.args = {}); - if (Array.isArray(action.args[prop])) { - config.args[prop] = action.args[prop].reduce( - (arr: any, valueItem: any, index: number) => { - if (!arr[index]) { - arr[index] = {}; - } - arr[index].item = Object.entries(valueItem).map(([key, val]) => ({ - key, - val - })); - return arr; - }, - [] - ); - } else if (typeof action.args[prop] === 'object') { - config.args[prop] = Object.keys(action.args[prop]).map(key => ({ - key, - val: action.args?.[prop][key] - })); - } else if ( - action.actionType === 'setValue' && - typeof action.args[prop] === 'string' - ) { - config.args['valueInput'] = config.args['value']; - delete config.args?.value; - } - } - - // 获取动作配置 - const actionConfig: any = getAcionConfig( - action, - actionTree, - pluginActions, - commonActions - ); - // 还原args为可视化配置结构(args + addOnArgs) - if (config.args) { - if (actionConfig?.config) { - let tmpArgs = {}; - config.addOnArgs = []; - Object.keys(config.args).forEach(key => { - // 筛选出附加配置参数 - if (!actionConfig?.config.includes(key)) { - config.addOnArgs = [ - ...config.addOnArgs, - { - key: key, - val: config.args?.[key] - } - ]; - } else { - tmpArgs = { - ...tmpArgs, - [key]: config.args?.[key] - }; - } - }); - config.args = tmpArgs; - } - } - - // 获取左侧命中的动作节点 - const hasSubActionNode = findHasSubActionNode(actionTree, action.actionType); - const actionType = getActionType(action, hasSubActionNode); - - return { - ...config, - actionType, - __cmptTreeSource: getComponentTreeSource( - actionType!, - pluginActions, - getComponents, - commonActions - ), - __cmptActionType: - hasSubActionNode || action.componentId ? action.actionType : '', - __actionDesc: action.__actionDesc ?? hasSubActionNode?.desc ?? actionConfig.schema, // 树节点描述 - __actionSchema: action.__actionSchema ?? hasSubActionNode?.schema ?? actionConfig.schema, // 树节点schema - __subActions: hasSubActionNode?.actions // 树节点子动作 - // broadcastId: action.actionType === 'broadcast' ? action.eventName : '' - }; -}; - -// 渲染组件选择配置项 -export function renderCmptSelect( - componentLabel: string, - required: boolean, - onChange?: (value: string, oldVal: any, data: any, form: any) => void -) { - return [ - { - type: 'tree-select', - name: 'componentId', - label: componentLabel, - showIcon: false, - searchable: true, - required, - selfDisabledAffectChildren: false, - size: 'lg', - source: '${__cmptTreeSource}', - mode: 'horizontal', - autoFill: { - __rendererLabel: '${label}', - __rendererName: '${type}', - __nodeId: '${id}', - __nodeSchema: '${schema}' - }, - onChange: async (value: string, oldVal: any, data: any, form: any) => { - onChange?.(value, oldVal, data, form); - } - } - ]; -} - -// 渲染组件特性动作配置项 -export function renderCmptActionSelect( - componentLabel: string, - required: boolean, - onChange?: (value: string, oldVal: any, data: any, form: any) => void -) { - return [ - ...renderCmptSelect( - '选择组件', - true, - async (value: string, oldVal: any, data: any, form: any) => { - // 获取组件上下文 - if (form.data.__nodeId) { - const dataSchema: any = await form.data.getContextSchemas?.( - form.data.__nodeId, - true - ); - const dataSchemaIns = new DataSchema(dataSchema || []); - const variables = dataSchemaIns?.getDataPropsAsOptions() || []; - - form.setValueByName('__cmptDataSchema', dataSchema); - form.setValueByName('__cmptVariables', variables); // 组件上下文(不含父级) - form.setValueByName('__cmptVariablesWithSys', [ - // 组件上下文+页面+系统 - { - label: `${form.data.__rendererLabel}变量`, - children: variables - }, - ...form.data.rawVariables.filter((item: ContextVariables) => - ['页面变量', '系统变量'].includes(item.label) - ) - ]); - } - - if (form.data.actionType === 'setValue') { - // todo:这里会闪一下,需要从amis查下问题 - form.setValueByName('args.value', undefined); - form.setValueByName('args.valueInput', undefined); - } - form.setValueByName('__cmptActionType', ''); - - onChange?.(value, oldVal, data, form); - } - ), - { - asFormItem: true, - label: '组件动作', - name: '__cmptActionType', - mode: 'horizontal', - required: true, - visibleOn: 'data.actionType === "component"', - component: CmptActionSelect, - description: '${__cmptActionDesc}' - } - ]; -} - // 表单项组件 export const FORMITEM_CMPTS = [ 'button-group-select', @@ -449,13 +95,22 @@ export const FORMITEM_CMPTS = [ 'uuid' ]; +export const getArgsWrapper = (items: any, multiple: boolean = false) => ({ + type: 'combo', + name: 'args', + // label: '动作参数', + multiple, + strictMode: false, + items: Array.isArray(items) ? items : [items] +}); + // 动作配置项schema map export const COMMON_ACTION_SCHEMA_MAP: { [propName: string]: RendererPluginAction; } = { setValue: { - config: ['value', 'valueInput'], - desc: (info: any) => { + innerArgs: ['value', 'valueInput'], + descDetail: (info: any) => { return (
设置 @@ -566,7 +221,7 @@ export const COMMON_ACTION_SCHEMA_MAP: { }) }, reload: { - desc: (info: any) => { + descDetail: (info: any) => { return (
刷新 @@ -579,7 +234,7 @@ export const COMMON_ACTION_SCHEMA_MAP: { } }, clear: { - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -589,7 +244,7 @@ export const COMMON_ACTION_SCHEMA_MAP: { } }, reset: { - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -599,7 +254,7 @@ export const COMMON_ACTION_SCHEMA_MAP: { } }, submit: { - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -610,7 +265,7 @@ export const COMMON_ACTION_SCHEMA_MAP: { } }, validate: { - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -620,7 +275,7 @@ export const COMMON_ACTION_SCHEMA_MAP: { } }, prev: { - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -631,7 +286,7 @@ export const COMMON_ACTION_SCHEMA_MAP: { } }, next: { - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -642,7 +297,7 @@ export const COMMON_ACTION_SCHEMA_MAP: { } }, collapse: { - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -652,7 +307,7 @@ export const COMMON_ACTION_SCHEMA_MAP: { } }, selectAll: { - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -662,7 +317,7 @@ export const COMMON_ACTION_SCHEMA_MAP: { } }, focus: { - desc: (info: any) => { + descDetail: (info: any) => { return (
{info?.__rendererLabel} @@ -672,12 +327,192 @@ export const COMMON_ACTION_SCHEMA_MAP: { } }, refresh: { - desc: (info: any) =>
刷新页面
+ descDetail: (info: any) =>
刷新页面
}, alert: { - desc: (info: any) =>
打开提示对话框
+ descDetail: (info: any) =>
打开提示对话框
}, confirm: { - desc: (info: any) =>
打开确认对话框
+ descDetail: (info: any) =>
打开确认对话框
} }; + +// 获取动作树中指定的动作 +export const findActionNode = ( + actions: RendererPluginAction[], + actionType: string +) => findTree(actions, node => node.actionType === actionType); + +// 获取包含指定子动作的动作 +export const findSubActionNode = ( + actions: RendererPluginAction[], + actionType: string +) => + findTree(actions, node => + node.actions?.find( + (item: SubRendererPluginAction) => item.actionType === actionType + ) + ); + +// 获取真实的动作类型 +export const getActionType = ( + action: ActionConfig, + hasSubActionNode: RendererPluginAction | null +) => + action.__isCmptAction + ? 'component' + : hasSubActionNode + ? hasSubActionNode.actionType + : action.actionType; + +// 获取事件Label文案 +export const getEventLabel = (events: RendererPluginEvent[], name: string) => + events.find(item => item.eventName === name)?.eventLabel; + +// 获取事件描述文案 +export const getEventDesc = (events: RendererPluginEvent[], name: string) => + events.find(item => item.eventName === name)?.description; + +// 判断插件动作中是否存在指定动作 +export const hasActionType = (actionType: string, actions?: RendererPluginAction[]) => { + if (!Array.isArray(actions)) { + return false; + } + return !!actions?.find(item => + [item.actionType, 'component'].includes(actionType) + ); +}; + +// 获取动作配置,主要是为了获取config和desc,schema强制捆绑在动作树节点(动作配置可能在插件动作中 > 树节点 or 子动作) +export const getPropOfAcion = ( + action: ActionConfig, + propName: string, + actionTree: RendererPluginAction[], + pluginActions: PluginActions, + commonActions?: {[propName: string]: RendererPluginAction} +): any => { + let prop: any = null; + if (action.componentId) { + // 优先从组件特性动作中找 + pluginActions[action.__rendererName]?.find( + (item: RendererPluginAction) => item.actionType === action.actionType + )?.[propName as keyof RendererPluginAction]; + } + + if (!prop) { + prop = findActionNode(actionTree, action.actionType)?.[ + propName as keyof RendererPluginAction + ]; + } + + if (!prop) { + const commonActionConfig = { + ...COMMON_ACTION_SCHEMA_MAP, + ...commonActions + }; + const hasSubActionNode = findSubActionNode(actionTree, action.actionType); + + if (propName === 'actionLabel') { + prop = hasSubActionNode?.actionLabel; + } + else { + prop = + hasSubActionNode?.actions?.find( + (item: SubRendererPluginAction) => item.actionType === action.actionType + )?.[propName as keyof SubRendererPluginAction] ?? + commonActionConfig[action.actionType]?.[ + propName as keyof RendererPluginAction + ]; + } + } + + return prop; +}; + +// 渲染组件选择配置项 +export function renderCmptSelect( + componentLabel: string, + required: boolean, + onChange?: (value: string, oldVal: any, data: any, form: any) => void +) { + return [ + { + type: 'tree-select', + name: 'componentId', + label: componentLabel, + showIcon: false, + searchable: true, + required, + selfDisabledAffectChildren: false, + size: 'lg', + source: '${__cmptTreeSource}', + mode: 'horizontal', + autoFill: { + __rendererLabel: '${label}', + __rendererName: '${type}', + __nodeId: '${id}', + __nodeSchema: '${schema}' + }, + onChange: async (value: string, oldVal: any, data: any, form: any) => { + onChange?.(value, oldVal, data, form); + } + } + ]; +} + +// 渲染组件特性动作配置项 +export function renderCmptActionSelect( + componentLabel: string, + required: boolean, + onChange?: (value: string, oldVal: any, data: any, form: any) => void +) { + return [ + ...renderCmptSelect( + '选择组件', + true, + async (value: string, oldVal: any, data: any, form: any) => { + // 获取组件上下文 + if (form.data.__nodeId) { + const dataSchema: any = await form.data.getContextSchemas?.( + form.data.__nodeId, + true + ); + const dataSchemaIns = new DataSchema(dataSchema || []); + const variables = dataSchemaIns?.getDataPropsAsOptions() || []; + + form.setValueByName('__cmptDataSchema', dataSchema); + form.setValueByName('__cmptVariables', variables); // 组件上下文(不含父级) + form.setValueByName('__cmptVariablesWithSys', [ + // 组件上下文+页面+系统 + { + label: `${form.data.__rendererLabel}变量`, + children: variables + }, + ...form.data.rawVariables.filter((item: ContextVariables) => + ['页面变量', '系统变量'].includes(item.label) + ) + ]); + } + + if (form.data.actionType === 'setValue') { + // todo:这里会闪一下,需要从amis查下问题 + form.setValueByName('args.value', undefined); + form.setValueByName('args.valueInput', undefined); + } + form.setValueByName('__cmptActionType', ''); + + onChange?.(value, oldVal, data, form); + } + ), + { + asFormItem: true, + label: '组件动作', + name: '__cmptActionType', + mode: 'horizontal', + required: true, + visibleOn: 'data.actionType === "component"', + component: CmptActionSelect, + description: '${__cmptActionDesc}' + } + ]; +} diff --git a/packages/amis-editor/src/renderer/event-control/index.tsx b/packages/amis-editor/src/renderer/event-control/index.tsx index c5589c497..bbb90195b 100644 --- a/packages/amis-editor/src/renderer/event-control/index.tsx +++ b/packages/amis-editor/src/renderer/event-control/index.tsx @@ -3,22 +3,16 @@ import {findDOMNode} from 'react-dom'; import cx from 'classnames'; import Sortable from 'sortablejs'; import {DataSchema, FormItem, Icon, TooltipWrapper} from 'amis'; -import { - FormControlProps, - autobind, - render as amisRender -} from 'amis-core'; +import {FormControlProps, autobind, render as amisRender} from 'amis-core'; import {BASE_ACTION_PROPS} from './comp-action-select'; import ActionConfigPanel from './action-config-panel'; import { - findHasSubActionNode, - formatActionInitConfig, - getAcionConfig, - getActionLabel, - getComponentTreeSource, + findActionNode, + findSubActionNode, getEventDesc, - getEventLabel + getEventLabel, + getPropOfAcion } from './helper'; import { ActionConfig, @@ -30,7 +24,8 @@ import { PluginActions, PluginEvents, RendererPluginAction, - RendererPluginEvent + RendererPluginEvent, + SubRendererPluginAction } from 'amis-editor-core'; interface EventControlProps extends FormControlProps { @@ -46,8 +41,10 @@ interface EventControlProps extends FormControlProps { ) => void; addBroadcast?: (event: RendererPluginEvent) => void; removeBroadcast?: (eventName: string) => void; - getComponents: () => ComponentInfo[]; // 当前页面组件树 + getComponents: (action: RendererPluginAction) => ComponentInfo[]; // 当前页面组件树 getContextSchemas?: (id?: string, withoutSuper?: boolean) => DataSchema; // 获取上下文 + actionConfigInitFormatter?: (actionConfig: ActionConfig) => ActionConfig; // 动作配置初始化时格式化 + actionConfigSubmitFormatter?: (actionConfig: ActionConfig) => ActionConfig; // 动作配置提交时格式化 owner?: string; // 组件标识 } @@ -67,6 +64,11 @@ interface EventControlState { pluginActions: PluginActions; getContextSchemas?: (id?: string, withoutSuper?: boolean) => DataSchema; rawVariables: ContextVariables[]; + __cmptActionType?: string; + __actionDesc?: string; + __cmptTreeSource?: ComponentInfo[], + __actionSchema?: any; + __subActions?: SubRendererPluginAction[] } | undefined; type: 'update' | 'add'; @@ -425,9 +427,9 @@ export class EventControl extends React.Component< const { actions: pluginActions, getContextSchemas, - actionTree, - commonActions, - getComponents + actionConfigInitFormatter, + getComponents, + actionTree } = this.props; const {events, rawVariables} = this.state; @@ -454,6 +456,10 @@ export class EventControl extends React.Component< // 编辑操作,需要格式化动作配置 if (data.type === 'update') { + const action = data.actionData!.action!; + const actionConfig = actionConfigInitFormatter?.(action); + const actionNode = findActionNode(actionTree, actionConfig?.actionType!); + const hasSubActionNode = findSubActionNode(actionTree, action.actionType); data.actionData = { eventKey: data.actionData!.eventKey, actionIndex: data.actionData!.actionIndex, @@ -461,13 +467,14 @@ export class EventControl extends React.Component< pluginActions, getContextSchemas, rawVariables, - ...formatActionInitConfig( - data.actionData!.action!, - actionTree, - pluginActions, - getComponents, - commonActions - ) + ...actionConfig, + __cmptTreeSource: getComponents?.(actionNode!) ?? [], + __cmptActionType: + hasSubActionNode || action.componentId ? action.actionType : '', + __actionDesc: actionNode!.description!, // 树节点描述 + __actionSchema: actionNode!.schema, // 树节点schema + __subActions: hasSubActionNode?.actions // 树节点子动作 + // broadcastId: action.actionType === 'broadcast' ? action.eventName : '' }; } else { data.actionData = { @@ -484,17 +491,14 @@ export class EventControl extends React.Component< // 渲染描述信息 renderDesc(action: ActionConfig) { - const { - actions: pluginActions, - actionTree, - commonActions - } = this.props; - const desc = getAcionConfig( + const {actions: pluginActions, actionTree, commonActions} = this.props; + const desc = getPropOfAcion( action, + 'descDetail', actionTree, pluginActions, commonActions - )?.desc; + ); return typeof desc === 'function' ? (
{desc?.(action) || '-'}
@@ -591,19 +595,16 @@ export class EventControl extends React.Component< __cmptActionType = 'enabled'; } + const action = data.selectedOptions[0]; + form.setValues({ ...removeKeys, - __cmptTreeSource: getComponentTreeSource( - value, - pluginActions, - getComponents, - commonActions - ), - __cmptActionType, componentId: form.data.componentId ? '' : undefined, - __actionDesc: data.selectedOptions[0].description, - __actionSchema: data.selectedOptions[0].schema, - __subActions: data.selectedOptions[0].actions + __cmptTreeSource: getComponents?.(action) ?? [], + __cmptActionType, + __actionDesc: action.description, + __actionSchema: action.schema, + __subActions: action.actions }); } } @@ -681,78 +682,9 @@ export class EventControl extends React.Component< } submitConfig(type: string, config: any) { - const {actionTree} = this.props; + const {actionConfigSubmitFormatter} = this.props; + const action = actionConfigSubmitFormatter?.(config) ?? config; - let action = {...config}; - - // 修正动作名称 - if (config.actionType === 'component') { - // 标记一下组件特性动作 - action.__isCmptAction = true; - action.actionType = config.__cmptActionType; - } - const hasSubActionNode = findHasSubActionNode( - actionTree, - config.__cmptActionType - ); - if (hasSubActionNode) { - // 修正动作 - action.actionType = config.__cmptActionType; - } - action.__label = getActionLabel(actionTree, config.actionType); - - // 合并附加的动作参数 - if (config.addOnArgs) { - config.addOnArgs.forEach((args: any) => { - action.args = action.args ?? {}; - action.args = { - ...action.args, - [args.key]: args.val - }; - }); - delete action.addOnArgs; - } - // 转换下格式 - if (['setValue', 'url', 'link'].includes(action.actionType)) { - const prop = action.actionType === 'setValue' ? 'value' : 'params'; - - if (Array.isArray(config.args[prop])) { - action.args = action.args ?? {}; - if (action.__rendererName === 'combo') { - // combo特殊处理 - let tempArr: any = []; - config.args?.[prop].forEach((valueItem: any, index: number) => { - valueItem.item.forEach((item: any) => { - if (!tempArr[index]) { - tempArr[index] = {}; - } - tempArr[index][item.key] = item.val; - }); - }); - action.args = { - ...action.args, - [prop]: tempArr - }; - } else { - let tmpObj: any = {}; - config.args[prop].forEach((item: any) => { - tmpObj[item.key] = item.val; - }); - action.args = { - ...action.args, - [prop]: tmpObj - }; - } - } else if (action.actionType === 'setValue') { - // 处理变量赋值非数组的情况 - action.args = { - ...action.args, - value: config.args['valueInput'] - }; - } - } - - delete action.config; if (type === 'add') { this.addAction(config.eventKey, action); } else if (type === 'update') { @@ -763,6 +695,7 @@ export class EventControl extends React.Component< } render() { + const {actionTree, actions: pluginActions, commonActions} = this.props; const { onEvent, events, @@ -868,7 +801,7 @@ export class EventControl extends React.Component< />
- {action.__label || action.actionType} + {getPropOfAcion(action, 'actionLabel', actionTree, pluginActions, commonActions) || action.actionType}
diff --git a/packages/amis-editor/src/util.ts b/packages/amis-editor/src/util.ts index fc0e8d38c..db9d3556a 100644 --- a/packages/amis-editor/src/util.ts +++ b/packages/amis-editor/src/util.ts @@ -1,6 +1,20 @@ -import {BaseEventContext, EditorManager} from 'amis-editor-core'; -import {mapTree} from 'amis'; +import { + BaseEventContext, + EditorManager, + RendererPluginAction +} from 'amis-editor-core'; +import {filterTree, mapTree} from 'amis'; import ACTION_TYPE_TREE from './renderer/event-control/actions'; +import {ActionConfig, ComponentInfo} from './renderer/event-control/types'; +import { + COMMON_ACTION_SCHEMA_MAP, + findActionNode, + findSubActionNode, + FORMITEM_CMPTS, + getActionType, + getPropOfAcion, + hasActionType +} from './renderer/event-control/helper'; /** * 获取事件动作面板所需属性配置 @@ -8,45 +22,253 @@ import ACTION_TYPE_TREE from './renderer/event-control/actions'; export const getEventControlConfig = ( manager: EditorManager, context: BaseEventContext -) => ({ - actions: manager?.pluginActions, - events: manager?.pluginEvents, - getContextSchemas: async (id?: string, withoutSuper?: boolean) => { - const dataSchema = await manager.getContextSchemas( - id ?? context!.id, - withoutSuper - ); - // 存在指定id时,只需要当前层上下文 - if (id) { - return dataSchema; - } - return manager.dataSchema; - }, - getComponents: () => - mapTree( - manager?.store?.outline ?? [], - (item: any) => { - const schema = manager?.store?.getSchema(item.id); - return { - id: item.id, - label: item.label, - value: schema.id || item.id, - type: schema.type, - schema, - disabled: !!item.region, - children: item?.children - }; - }, - 1, - true - ), - actionTree: manager?.config.actionOptions?.actionTreeGetter +) => { + // 通用动作配置 + const commonActions = + manager?.config.actionOptions?.customActionGetter?.(manager); + // 动作树 + const actionTree = manager?.config.actionOptions?.actionTreeGetter ? manager?.config.actionOptions?.actionTreeGetter(ACTION_TYPE_TREE(manager)) - : ACTION_TYPE_TREE(manager), - commonActions: { - ...manager?.config.actionOptions?.customActionGetter?.(manager) - }, - owner: '', - addBroadcast: manager?.addBroadcast, - removeBroadcast: manager?.removeBroadcast -}); + : ACTION_TYPE_TREE(manager); + const commonActionConfig = { + ...COMMON_ACTION_SCHEMA_MAP, + ...commonActions + }; + return { + actions: manager?.pluginActions, + events: manager?.pluginEvents, + actionTree, + commonActions, + owner: '', + addBroadcast: manager?.addBroadcast, + removeBroadcast: manager?.removeBroadcast, + getContextSchemas: async (id?: string, withoutSuper?: boolean) => { + const dataSchema = await manager.getContextSchemas( + id ?? context!.id, + withoutSuper + ); + // 存在指定id时,只需要当前层上下文 + if (id) { + return dataSchema; + } + return manager.dataSchema; + }, + getComponents: (action: RendererPluginAction) => { + const components = mapTree( + manager?.store?.outline ?? [], + (item: any) => { + const schema = manager?.store?.getSchema(item.id); + return { + id: item.id, + label: item.label, + value: schema.id || item.id, + type: schema.type, + schema, + disabled: !!item.region, + children: item?.children + }; + }, + 1, + true + ); + + const actionType = action.actionType!; + const loopChildren = (nodes: ComponentInfo[]) => { + const temp: ComponentInfo[] = []; + for (let node of nodes) { + const actions = manager?.pluginActions[node.type]; + let isSupport = false; + if (typeof action.supportComponents === 'string') { + isSupport = action.supportComponents === '*' || action.supportComponents === node.type; + } + else if (Array.isArray(action.supportComponents)) { + isSupport = action.supportComponents.includes(node.type); + } + else { + isSupport = hasActionType(actionType, actions); + } + if (isSupport) { + // 组件特性动作,如果当前组件没有动作,则禁用 + const disabled = + actionType === 'component' && (!actions || !actions.length); + const newNode: ComponentInfo = { + ...node, + disabled: disabled || node.disabled, + children: [] + }; + if (node.children?.length) { + // 检查子项 + newNode.children?.push(...loopChildren(node.children)); + } + temp.push(newNode); + } else if (node.children?.length) { + const childNodes = loopChildren(node.children); + if (childNodes.length) { + temp.push(...childNodes); + } + } + } + return temp; + }; + + return loopChildren(components); + }, + actionConfigInitFormatter: (action: ActionConfig) => { + let config = {...action}; + + if ( + ['setValue', 'url', 'link'].includes(action.actionType) && + action.args + ) { + const prop = action.actionType === 'setValue' ? 'value' : 'params'; + !config.args && (config.args = {}); + if (Array.isArray(action.args[prop])) { + config.args[prop] = action.args[prop].reduce( + (arr: any, valueItem: any, index: number) => { + if (!arr[index]) { + arr[index] = {}; + } + arr[index].item = Object.entries(valueItem).map(([key, val]) => ({ + key, + val + })); + return arr; + }, + [] + ); + } else if (typeof action.args[prop] === 'object') { + config.args[prop] = Object.keys(action.args[prop]).map(key => ({ + key, + val: action.args?.[prop][key] + })); + } else if ( + action.actionType === 'setValue' && + typeof action.args[prop] === 'string' + ) { + config.args['valueInput'] = config.args['value']; + delete config.args?.value; + } + } + + // 获取动作专有配置参数 + const innerArgs: any = getPropOfAcion( + action, + 'innerArgs', + actionTree, + manager.pluginActions, + commonActions + ); + + // 还原args为可视化配置结构(args + addOnArgs) + if (config.args) { + if (innerArgs?.config) { + let tmpArgs = {}; + config.addOnArgs = []; + Object.keys(config.args).forEach(key => { + // 筛选出附加配置参数 + if (!innerArgs?.config.includes(key)) { + config.addOnArgs = [ + ...config.addOnArgs, + { + key: key, + val: config.args?.[key] + } + ]; + } else { + tmpArgs = { + ...tmpArgs, + [key]: config.args?.[key] + }; + } + }); + config.args = tmpArgs; + } + } + + // 获取左侧命中的动作节点 + const hasSubActionNode = findSubActionNode(actionTree, action.actionType); + + return { + ...config, + actionType: getActionType(action, hasSubActionNode) + }; + }, + actionConfigSubmitFormatter: (config: ActionConfig) => { + let action = {...config}; + action.__title = findActionNode( + actionTree, + config.actionType + )?.actionLabel; + + // 修正动作名称 + if (config.actionType === 'component') { + // 标记一下组件特性动作 + action.__isCmptAction = true; + action.actionType = config.__cmptActionType; + } + const hasSubActionNode = findSubActionNode( + actionTree, + config.__cmptActionType + ); + if (hasSubActionNode) { + // 修正动作 + action.actionType = config.__cmptActionType; + } + + // 合并附加的动作参数 + if (config.addOnArgs) { + config.addOnArgs.forEach((args: any) => { + action.args = action.args ?? {}; + action.args = { + ...action.args, + [args.key]: args.val + }; + }); + delete action.addOnArgs; + } + // 转换下格式 + if (['setValue', 'url', 'link'].includes(action.actionType)) { + const propName = action.actionType === 'setValue' ? 'value' : 'params'; + + if (Array.isArray(config.args?.[propName])) { + action.args = action.args ?? {}; + if (action.__rendererName === 'combo') { + // combo特殊处理 + let tempArr: any = []; + config.args?.[propName].forEach((valueItem: any, index: number) => { + valueItem.item.forEach((item: any) => { + if (!tempArr[index]) { + tempArr[index] = {}; + } + tempArr[index][item.key] = item.val; + }); + }); + action.args = { + ...action.args, + [propName]: tempArr + }; + } else { + let tmpObj: any = {}; + config.args?.[propName].forEach((item: any) => { + tmpObj[item.key] = item.val; + }); + action.args = { + ...action.args, + [propName]: tmpObj + }; + } + } else if (action.actionType === 'setValue') { + // 处理变量赋值非数组的情况 + action.args = { + ...action.args, + value: config.args?.['valueInput'] + }; + } + } + + delete action.config; + + return action; + } + }; +};