diff --git a/src/components/FlowEditor/node/localNode/LocalNode.tsx b/src/components/FlowEditor/node/localNode/LocalNode.tsx index 8d347bf..e694256 100644 --- a/src/components/FlowEditor/node/localNode/LocalNode.tsx +++ b/src/components/FlowEditor/node/localNode/LocalNode.tsx @@ -10,7 +10,7 @@ import NodeContentOther from '@/pages/flowEditor/components/nodeContentOther'; const setIcon = (nodeType: string) => { let type = 'IconApps'; switch (nodeType) { - case 'CONDITION': + case 'SWITCH': type = 'IconBranch'; break; case 'AND': diff --git a/src/components/FlowEditor/node/loopNode/LoopNode.tsx b/src/components/FlowEditor/node/loopNode/LoopNode.tsx index b54cacd..a698c70 100644 --- a/src/components/FlowEditor/node/loopNode/LoopNode.tsx +++ b/src/components/FlowEditor/node/loopNode/LoopNode.tsx @@ -126,7 +126,7 @@ const LoopNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => { // 合并list数组与data.parameters.apiOuts数组 const apiOuts = data.parameters?.apiOuts || []; - const mergedApiOuts = [...apiOuts, ...list]; + const mergedApiOuts = [...apiOuts]; setNewData(mergedApiOuts); } else { diff --git a/src/components/FlowEditor/node/switchNode/SwitchNode.tsx b/src/components/FlowEditor/node/switchNode/SwitchNode.tsx new file mode 100644 index 0000000..541c3a6 --- /dev/null +++ b/src/components/FlowEditor/node/switchNode/SwitchNode.tsx @@ -0,0 +1,157 @@ +import React, { useEffect, useState } from 'react'; +import { useStore } from '@xyflow/react'; +import styles from '@/components/FlowEditor/node/style/baseOther.module.less'; +import DynamicIcon from '@/components/DynamicIcon'; +import { Handle, Position } from '@xyflow/react'; +import NodeContentSwitch from '@/pages/flowEditor/components/nodeContentSwitch'; +import { defaultNodeTypes } from '@/components/FlowEditor/node/types/defaultType'; + +// 循环节点组件,用于显示循环开始和循环结束节点 +const LoopNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => { + const [newData, setNewData] = useState([]); + const title = data.title || '条件选择'; + + // 获取节点选中状态 - 适配React Flow v12 API + const isSelected = useStore((state) => + state.nodeLookup.get(id)?.selected || false + ); + + // 设置图标 + const setIcon = () => { + return ; + }; + + const getOperator = (expr: string) => { + let operator; + if (expr.includes('==')) { + operator = '=='; + } + else if (expr.includes('>=')) { + operator = '>='; + } + else if (expr.includes('<=')) { + operator = '<='; + } + else if (expr.includes('<')) { + operator = '<'; + } + else if (expr.includes('>')) { + operator = '>'; + } + else { + operator = '!='; + } + return operator; + }; + + const reverseDataStructure = (processedData: any) => { + if (!processedData) { + return []; + } + try { + const parsedCustomDef = JSON.parse(processedData?.customDef); + if (!parsedCustomDef.conditions) { + return []; + } + + return parsedCustomDef.conditions.map((condition: any, index: number) => { + // 解析表达式以获取左值、操作符和右值 + let lftVal = ''; + let operator = ''; + let rgtVal = ''; + const valueType = condition.valueType || ''; + + if (condition.expression) { + // 处理布尔值表达式 + if (valueType.includes('boolean')) { + const splitStr = valueType.split('-')[1]; + operator = getOperator(condition.expression); + const pattern = new RegExp(`\\$\\.(.+)(${operator})${splitStr}`); + const match = condition.expression.match(pattern); + if (match) { + lftVal = match[1]; + operator = match[2]; + } + } + // 处理其他类型的表达式 + else { + // 简单的解析逻辑,可能需要根据实际表达式格式进行调整 + const match = condition.expression.match(/\$\.([^=!<>]+)(==|!=|>=|<=|>|<)(.+)/); + if (match) { + lftVal = match[1]; + operator = match[2]; + rgtVal = match[3]; + } + } + } + + return { + key: index, + id: Date.now(), + apiOutId: condition.apiOutId || '', + lftVal, + operator, + valueType, + rgtVal + }; + }); + } catch (e) { + console.error('Error parsing customDef:', e); + return []; + } + }; + + useEffect(() => { + if (data) { + const reverseData = reverseDataStructure(data.component); + if (reverseData.length > 0) { + const list = reverseData.map(item => { + let expression = ''; + if (item.valueType.includes('boolean')) { + const splitStr = item.valueType.split('-')[1]; + expression = `$.${item.lftVal}${item.operator}${splitStr}`; + } + else expression = `$.${item.lftVal}${item.operator}${item.rgtVal}`; + return { + name: item.apiOutId, + id: item.apiOutId, + desc: '', + defaultValue: item.valueType, + dataType: expression + }; + }); + + // 合并list数组与data.parameters.apiOuts数组 + const apiOuts = data.parameters?.apiOuts || []; + const mergedApiOuts = [...apiOuts]; + setNewData(mergedApiOuts); + } + else { + // 如果没有reverseData,则直接使用原始apiOuts + setNewData(data.parameters?.apiOuts || []); + } + } + }, [data]); + + // 创建包含额外apiOuts的新data对象 + const modifiedData = { + ...data, + parameters: { + ...data.parameters, + apiOuts: newData + } + }; + + return ( +
+
+ {setIcon()} + {title} +
+ + +
+ ); +}; + +export default LoopNode; \ No newline at end of file diff --git a/src/components/FlowEditor/nodeEditors/LocalNodeEditor.tsx b/src/components/FlowEditor/nodeEditors/LocalNodeEditor.tsx index baa192e..4c0f918 100644 --- a/src/components/FlowEditor/nodeEditors/LocalNodeEditor.tsx +++ b/src/components/FlowEditor/nodeEditors/LocalNodeEditor.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { NodeEditorProps } from './index'; import { Form, Input } from '@arco-design/web-react'; -import ConditionEditor from './components/ConditionEditor'; +import SwitchEditor from './components/SwitchEditor'; import AndEditor from './components/AndEditor'; import OrEditor from './components/OrEditor'; import WaitEditor from './components/WaitEditor'; @@ -27,8 +27,8 @@ const LocalNodeEditor: React.FC = ({ const localNodeType = nodeData.type || ''; switch (localNodeType) { - case 'CONDITION': // 条件选择 - return ; + case 'SWITCH': // 条件选择 + return ; case 'AND': // 与门 return ; case 'OR': // 或门 diff --git a/src/components/FlowEditor/nodeEditors/components/ConditionEditor.tsx b/src/components/FlowEditor/nodeEditors/components/ConditionEditor.tsx deleted file mode 100644 index 02e8f7d..0000000 --- a/src/components/FlowEditor/nodeEditors/components/ConditionEditor.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { NodeEditorProps } from '@/components/FlowEditor/nodeEditors'; -import { Typography } from '@arco-design/web-react'; -import { IconUnorderedList } from '@arco-design/web-react/icon'; -import ParamsTable from './ParamsTable'; - -const ConditionEditor: React.FC = ({ nodeData, updateNodeData }) => { - return ( - <> - 输入参数 - { - updateNodeData('parameters', { - ...nodeData.parameters, - dataIns: data - }); - }} - /> - - ); -}; - -export default ConditionEditor; \ No newline at end of file diff --git a/src/components/FlowEditor/nodeEditors/components/ConditionsTable.tsx b/src/components/FlowEditor/nodeEditors/components/ConditionsTable.tsx index 276819b..af646ae 100644 --- a/src/components/FlowEditor/nodeEditors/components/ConditionsTable.tsx +++ b/src/components/FlowEditor/nodeEditors/components/ConditionsTable.tsx @@ -19,6 +19,7 @@ interface ConditionsTableProps { initialData: any; nodeData: any; onUpdateData: (data: any) => void; + type?: string; } const dataTypeOptions = [ @@ -51,9 +52,11 @@ const operationOptions = [ const ConditionsTable: React.FC = ({ initialData, nodeData, - onUpdateData + onUpdateData, + type = 'LOOP' }) => { const [data, setData] = useState([]); + const [rowData, setRowData] = useState({}); const [apiOutsList, setApiOutsList] = useState([]); const [leftList, setLeftList] = useState([]); @@ -71,7 +74,29 @@ const ConditionsTable: React.FC = ({ render: (_: any, record: TableDataItem) => ( handleSave({ ...record, apiOutId: value })} + onChange={(value) => { + // 仅更新本地状态,不立即触发保存 + const newData = [...data]; + const index = newData.findIndex((item) => record.key === item.key); + if (index >= 0) { + newData.splice(index, 1, { ...newData[index], apiOutId: value }); + setData(newData); + } + }} + onBlur={() => { + // 失去焦点时才触发保存 + const currentRow = data.find(item => item.key === record.key); + if (currentRow) { + handleSave(currentRow); + } + }} + onPressEnter={() => { + // 按回车键时也触发保存 + const currentRow = data.find(item => item.key === record.key); + if (currentRow) { + handleSave(currentRow); + } + }} placeholder="请输入逻辑出口" /> ) @@ -84,7 +109,22 @@ const ConditionsTable: React.FC = ({ autoWidth={{ minWidth: 200, maxWidth: 500 }} options={leftList} value={record.lftVal} - onChange={(value) => handleSave({ ...record, lftVal: value })} + onChange={(value) => { + // 仅更新本地状态,不立即触发保存 + const newData = [...data]; + const index = newData.findIndex((item) => record.key === item.key); + if (index >= 0) { + newData.splice(index, 1, { ...newData[index], lftVal: value }); + setData(newData); + } + }} + onBlur={() => { + // 失去焦点时才触发保存 + const currentRow = data.find(item => item.key === record.key); + if (currentRow) { + handleSave(currentRow); + } + }} placeholder="请选择需要引用的出入参数" /> ) @@ -97,7 +137,22 @@ const ConditionsTable: React.FC = ({ autoWidth={{ minWidth: 200, maxWidth: 500 }} options={operationOptions} value={record.operator} - onChange={(value) => handleSave({ ...record, operator: value })} + onChange={(value) => { + // 仅更新本地状态,不立即触发保存 + const newData = [...data]; + const index = newData.findIndex((item) => record.key === item.key); + if (index >= 0) { + newData.splice(index, 1, { ...newData[index], operator: value }); + setData(newData); + } + }} + onBlur={() => { + // 失去焦点时才触发保存 + const currentRow = data.find(item => item.key === record.key); + if (currentRow) { + handleSave(currentRow); + } + }} placeholder="请选择运算/比较符" /> ) @@ -111,7 +166,27 @@ const ConditionsTable: React.FC = ({ autoWidth={{ minWidth: 200, maxWidth: 500 }} options={dataTypeOptions} value={record.valueType} - onChange={(value) => handleSave({ ...record, valueType: value })} + onChange={(value) => { + // 仅更新本地状态,不立即触发保存 + const newData = [...data]; + const index = newData.findIndex((item) => record.key === item.key); + if (index >= 0) { + newData.splice(index, 1, { + ...newData[index], + valueType: value, + // 如果类型不是string、number或expression,则清空rgtVal + rgtVal: ['string', 'number', 'expression'].includes(value) ? newData[index].rgtVal : '' + }); + setData(newData); + } + }} + onBlur={() => { + // 失去焦点时才触发保存 + const currentRow = data.find(item => item.key === record.key); + if (currentRow) { + handleSave(currentRow); + } + }} placeholder="请选择右值类型" /> {['string', 'number', 'expression'].includes(record.valueType) ? ( @@ -119,7 +194,29 @@ const ConditionsTable: React.FC = ({ style={{ marginTop: 8 }} autoWidth={{ minWidth: 200, maxWidth: 500 }} value={record.rgtVal} - onChange={(value) => handleSave({ ...record, rgtVal: value })} + onChange={(value) => { + // 仅更新本地状态,不立即触发保存 + const newData = [...data]; + const index = newData.findIndex((item) => record.key === item.key); + if (index >= 0) { + newData.splice(index, 1, { ...newData[index], rgtVal: value }); + setData(newData); + } + }} + onBlur={() => { + // 失去焦点时才触发保存 + const currentRow = data.find(item => item.key === record.key); + if (currentRow) { + handleSave(currentRow); + } + }} + onPressEnter={() => { + // 按回车键时也触发保存 + const currentRow = data.find(item => item.key === record.key); + if (currentRow) { + handleSave(currentRow); + } + }} placeholder={'请输入'} /> ) : ()} @@ -155,14 +252,16 @@ const ConditionsTable: React.FC = ({ expression: expression }; }); + const customDef = { + apiOutIds, + conditions + }; + // 只需要动态添加开始节点的NodeId, 循环开始的节点不允许编辑信息的,所以接口需要的信息会在节点实例的时候配置 + if (type === 'LOOP') customDef['loopStartNodeId'] = nodeData.component.loopStartNodeId; + return { type: nodeData.type, - customDef: JSON.stringify({ - apiOutIds, - conditions, - // 只需要动态添加开始节点的NodeId, 循环开始的节点不允许编辑信息的,所以接口需要的信息会在节点实例的时候配置 - loopStartNodeId: nodeData.component.loopStartNodeId - }) + customDef: JSON.stringify(customDef) }; }; @@ -248,7 +347,8 @@ const ConditionsTable: React.FC = ({ const extractApiNames = () => { const apiInsNames = nodeData.parameters?.apiIns?.map((item: any) => item.name) || []; const apiOutsNames = nodeData.parameters?.apiOuts?.map((item: any) => item.name) || []; - return [...apiInsNames, ...apiOutsNames]; + if (type === 'LOOP') return [...apiInsNames, ...apiOutsNames]; + else return [...apiOutsNames]; }; // 提取dataIns中的属性,并构造成options结构 @@ -260,6 +360,73 @@ const ConditionsTable: React.FC = ({ return [...dataInsOptions]; }; + const updateOriginData = (data) => { + // 获取现有的apiOuts数组,确保我们保留所有原始数据 + const existingApiOuts = nodeData.parameters?.apiOuts || []; + + // 创建一个Map来存储当前表格中的数据,便于快速查找 + const tableDataMap = new Map(); + data.forEach(item => { + if (item.apiOutId) { + tableDataMap.set(item.apiOutId, item); + } + }); + + // 更新现有的数据或标记需要删除的数据 + const updatedApiOuts = existingApiOuts.map(item => { + // 如果在表格数据中存在,则更新它 + if (item.id && tableDataMap.has(item.id)) { + const tableItem = tableDataMap.get(item.id); + let expression = ''; + if (tableItem.valueType.includes('boolean')) { + const splitStr = tableItem.valueType.split('-')[1]; + expression = `$.${tableItem.lftVal}${tableItem.operator}${splitStr}`; + } + else { + expression = `$.${tableItem.lftVal}${tableItem.operator}${tableItem.rgtVal}`; + } + + // 更新现有项,但保留原始属性 + return { + ...item, + name: tableItem.apiOutId, + id: tableItem.apiOutId, + desc: item.desc || '', + defaultValue: tableItem.valueType, + dataType: expression + }; + } + // 如果不在表格数据中,保持原样(可能是其他地方引用的数据) + return item; + }); + + // 添加表格中有但原始数据中没有的新项 + const existingIds = new Set(existingApiOuts.map(item => item.id)); + const newItems = data + .filter(item => item.apiOutId && !existingIds.has(item.apiOutId)) + .map(tableItem => { + let expression = ''; + if (tableItem.valueType.includes('boolean')) { + const splitStr = tableItem.valueType.split('-')[1]; + expression = `$.${tableItem.lftVal}${tableItem.operator}${splitStr}`; + } + else { + expression = `$.${tableItem.lftVal}${tableItem.operator}${tableItem.rgtVal}`; + } + + return { + name: tableItem.apiOutId, + id: tableItem.apiOutId, + desc: '', + defaultValue: tableItem.valueType, + dataType: expression + }; + }); + + // 合并更新后的数据和新增数据 + return [...updatedApiOuts, ...newItems]; + }; + const handleSave = (row: TableDataItem) => { const newData = [...data]; const index = newData.findIndex((item) => row.key === item.key); @@ -271,16 +438,32 @@ const ConditionsTable: React.FC = ({ } setData(newData); // 重新构建数据结构 + const newApiOuts = updateOriginData(newData); const newComponentData = convertData(newData); - onUpdateData(newComponentData); + onUpdateData({ + ...nodeData, + parameters: { + ...nodeData.parameters, + apiOuts: newApiOuts + }, + component: newComponentData + }); }; const removeRow = (key: number | string) => { const newData = data.filter((item) => item.key !== key); setData(newData); // 重新构建数据结构 + const newApiOuts = updateOriginData(newData); const newComponentData = convertData(newData); - onUpdateData(newComponentData); + onUpdateData({ + ...nodeData, + parameters: { + ...nodeData.parameters, + apiOuts: newApiOuts + }, + component: newComponentData + }); }; const addRow = () => { @@ -296,9 +479,17 @@ const ConditionsTable: React.FC = ({ }; const newData = [...data, newRow]; setData(newData); - // 重新构建数据结构 - const newComponentData = convertData(newData); - onUpdateData(newComponentData); + // 重新构建数据结构 添加新行时不更新 + // const newApiOuts = updateOriginData(newData); + // const newComponentData = convertData(newData); + // onUpdateData({ + // ...nodeData, + // parameters: { + // ...nodeData.parameters, + // apiOuts: newApiOuts + // }, + // component: newComponentData + // }); }; // 监听nodeData.parameters.dataIns的变化,更新leftList diff --git a/src/components/FlowEditor/nodeEditors/components/LoopEditor.tsx b/src/components/FlowEditor/nodeEditors/components/LoopEditor.tsx index b667b6e..ec42ab9 100644 --- a/src/components/FlowEditor/nodeEditors/components/LoopEditor.tsx +++ b/src/components/FlowEditor/nodeEditors/components/LoopEditor.tsx @@ -27,7 +27,11 @@ const LoopEditor: React.FC = ({ nodeData, updateNodeData }) => nodeData={nodeData || null} onUpdateData={(data) => { updateNodeData('component', { - ...data + ...data.component + }); + updateNodeData('parameters', { + ...nodeData.parameters, + apiOuts: data.parameters.apiOuts }); }} /> diff --git a/src/components/FlowEditor/nodeEditors/components/SwitchEditor.tsx b/src/components/FlowEditor/nodeEditors/components/SwitchEditor.tsx new file mode 100644 index 0000000..c31e203 --- /dev/null +++ b/src/components/FlowEditor/nodeEditors/components/SwitchEditor.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { NodeEditorProps } from '@/components/FlowEditor/nodeEditors'; +import { Typography } from '@arco-design/web-react'; +import { IconUnorderedList } from '@arco-design/web-react/icon'; +import ParamsTable from './ParamsTable'; +import ConditionsTable from '@/components/FlowEditor/nodeEditors/components/ConditionsTable'; + +const SwitchEditor: React.FC = ({ nodeData, updateNodeData }) => { + return ( + <> + 输入参数 + { + updateNodeData('parameters', { + ...nodeData.parameters, + dataIns: data + }); + }} + /> + + + 条件表达式 + + { + updateNodeData('component', { + ...data.component + }); + updateNodeData('parameters', { + ...nodeData.parameters, + apiOuts: data.parameters.apiOuts + }); + }} + type="switch" + /> + + ); +}; + +export default SwitchEditor; \ No newline at end of file diff --git a/src/hooks/useFlowCallbacks.ts b/src/hooks/useFlowCallbacks.ts index 7502345..f7361f4 100644 --- a/src/hooks/useFlowCallbacks.ts +++ b/src/hooks/useFlowCallbacks.ts @@ -19,6 +19,7 @@ import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines'; import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode'; import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode'; import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode'; +import SwitchNode from '@/components/FlowEditor/node/switchNode/SwitchNode'; import { updateCanvasDataMap } from '@/store/ideContainer'; import { Dispatch } from 'redux'; @@ -59,6 +60,8 @@ export const useFlowCallbacks = ( const apiOuts = nodeParams.apiOuts || []; const apiIns = nodeParams.apiIns || []; + console.log('123:', apiIns, apiOuts); + if (apiOuts.some((api: any) => (api.name || api.id) === handleId) || apiIns.some((api: any) => (api.name || api.id) === handleId) || (handleId.includes('loop'))) { return 'api'; @@ -192,6 +195,8 @@ export const useFlowCallbacks = ( const sourceParams = sourceNode.data?.parameters || {}; const targetParams = targetNode.data?.parameters || {}; + console.log('params:', params, sourceParams, targetParams); + // 获取源handle和目标handle的类型 (api或data) const sourceHandleType = getHandleType(params.sourceHandle, sourceParams); const targetHandleType = getHandleType(params.targetHandle, targetParams); @@ -603,7 +608,7 @@ export const useFlowCallbacks = ( // 将未定义的节点动态追加进nodeTypes const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); if (!nodeMap.includes(nodeType)) { - registerNodeType(nodeType, nodeType === 'BASIC' ? BasicNode : LocalNode, nodeDefinition.nodeName); + registerNodeType(nodeType, nodeType === 'BASIC' ? BasicNode : nodeType === 'SWITCH' ? SwitchNode : LocalNode, nodeDefinition.nodeName); } // 添加新节点 @@ -706,7 +711,7 @@ export const useFlowCallbacks = ( const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); // 目前默认添加的都是系统组件/本地组件 if (!nodeMap.includes(nodeType)) { - registerNodeType(nodeType, nodeType === 'BASIC' ? BasicNode : LocalNode, nodeDefinition.nodeName); + registerNodeType(nodeType, nodeType === 'BASIC' ? BasicNode : nodeType === 'SWITCH' ? SwitchNode : LocalNode, nodeDefinition.nodeName); } setNodes((nds: Node[]) => { @@ -745,7 +750,7 @@ export const useFlowCallbacks = ( try { // 转换会原始数据类型 const revertedData = revertFlowData(nodes, edges); - console.log('revertedData:', revertedData); + console.log('revertedData(中断):', revertedData); // return; const res: any = await setMainFlow(revertedData, initialData.appId); diff --git a/src/pages/flowEditor/components/addNodeMenu.tsx b/src/pages/flowEditor/components/addNodeMenu.tsx index b71580a..a5ddcd4 100644 --- a/src/pages/flowEditor/components/addNodeMenu.tsx +++ b/src/pages/flowEditor/components/addNodeMenu.tsx @@ -69,8 +69,6 @@ const AddNodeMenu: React.FC = ({ // } // }; // }); - - console.log(projectCompList, projectFlowList, initialGroupedNodes); } // 更新状态以触发重新渲染 diff --git a/src/pages/flowEditor/components/nodeContentLoop.tsx b/src/pages/flowEditor/components/nodeContentLoop.tsx index b40d570..d34bca6 100644 --- a/src/pages/flowEditor/components/nodeContentLoop.tsx +++ b/src/pages/flowEditor/components/nodeContentLoop.tsx @@ -102,37 +102,12 @@ const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[] ); }; -const formatFooter = (data: any) => { - try { - switch (data.type) { - case 'WAIT': - const { duration } = deserializeValue(data.customDef); - const hours = Math.floor(duration / 3600); - const minutes = Math.floor((duration % 3600) / 60); - const seconds = Math.floor(duration % 60); - return `${hours}小时${minutes}分钟${seconds}秒`; - case 'CYCLE': - const { intervalSeconds } = deserializeValue(data.customDef); - return cronstrue.toString(intervalSeconds, { locale: 'zh_CN' }); - case 'EVENTSEND': - case 'EVENTLISTENE': - const { name } = data.customDef; - return `事件: ${name}`; - default: - return '这个类型还没开发'; - } - } catch (e) { - console.log(e); - } -}; - const NodeContent = ({ data }: { data: NodeContentData }) => { const apiIns = data.parameters?.apiIns || []; const apiOuts = data.parameters?.apiOuts || []; const dataIns = data.parameters?.dataIns || []; const dataOuts = data.parameters?.dataOuts || []; const showFooter = data?.component?.customDef || false; - const footerData = (showFooter && data.component) || {}; // 判断节点类型 const isStartNode = data.type === 'start'; @@ -196,13 +171,6 @@ const NodeContent = ({ data }: { data: NodeContentData }) => { )} - {/*footer栏*/} - {/*{showFooter && (*/} - {/*
*/} - {/* {formatFooter(footerData)}*/} - {/*
*/} - {/*)}*/} - {renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)} ); diff --git a/src/pages/flowEditor/components/nodeContentSwitch.tsx b/src/pages/flowEditor/components/nodeContentSwitch.tsx new file mode 100644 index 0000000..4120a2c --- /dev/null +++ b/src/pages/flowEditor/components/nodeContentSwitch.tsx @@ -0,0 +1,176 @@ +import React from 'react'; +import styles from '@/components/FlowEditor/node/style/baseOther.module.less'; +import { Handle, Position, useStore } from '@xyflow/react'; + +interface NodeContentData { + parameters?: { + dataIns?: any[]; + dataOuts?: any[]; + apiIns?: any[]; + apiOuts?: any[]; + }; + showFooter?: boolean; + type?: string; + + [key: string]: any; +} + +// 定义通用的句柄样式 +const handleStyles = { + mainSource: { + background: '#2290f6', + width: '8px', + height: '8px', + border: '2px solid #fff', + boxShadow: '0 0 4px rgba(0,0,0,0.2)' + }, + mainTarget: { + background: '#2290f6', + width: '8px', + height: '8px', + border: '2px solid #fff', + boxShadow: '0 0 4px rgba(0,0,0,0.2)' + }, + data: { + background: '#555', + width: '6px', + height: '6px', + border: '1px solid #fff', + boxShadow: '0 0 2px rgba(0,0,0,0.2)' + } +}; + +// 渲染普通节点的句柄 +const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[], apiOuts: any[]) => { + return ( + <> + {apiOuts.map((_, index) => ( + + ))} + {apiIns.map((_, index) => ( + + ))} + + {/* 输入参数连接端点 */} + {dataIns.map((_, index) => ( + + ))} + + {/* 输出参数连接端点 */} + {dataOuts.map((_, index) => ( + + ))} + + ); +}; + +const NodeContent = ({ data }: { data: NodeContentData }) => { + const apiIns = data.parameters?.apiIns || []; + const apiOuts = data.parameters?.apiOuts || []; + const dataIns = data.parameters?.dataIns || []; + const dataOuts = data.parameters?.dataOuts || []; + + // 判断节点类型 + const isStartNode = data.type === 'start'; + const isEndNode = data.type === 'end'; + + return ( + <> + {/*content栏-api部分*/} +
+
+ {apiIns.length > 0 && ( +
+ {apiIns.map((input, index) => ( +
+ {input.desc} +
+ ))} +
+ )} + + {apiOuts.length > 0 && ( +
+ {apiOuts.map((output, index) => ( +
+ {output.desc || output.id || output.name} +
+ ))} +
+ )} +
+
+ {(dataIns.length > 0 || dataOuts.length > 0) && ( + <> + {/*分割*/} +
+ + {/*content栏-data部分*/} +
+
+ {dataIns.length > 0 && !isStartNode && ( +
+ {dataIns.map((input, index) => ( +
+ {input.id || `输入${index + 1}`} +
+ ))} +
+ )} + + {dataOuts.length > 0 && !isEndNode && ( +
+ {dataOuts.map((output, index) => ( +
+ {output.id || `输出${index + 1}`} +
+ ))} +
+ )} +
+
+ + )} + + {renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)} + + ); +}; + +export default NodeContent; \ No newline at end of file diff --git a/src/pages/flowEditor/sideBar/config/localNodeData.ts b/src/pages/flowEditor/sideBar/config/localNodeData.ts index aaa78cd..2d69939 100644 --- a/src/pages/flowEditor/sideBar/config/localNodeData.ts +++ b/src/pages/flowEditor/sideBar/config/localNodeData.ts @@ -46,10 +46,26 @@ const str2jsonParameters = { dataIns: [], dataOuts: [] }; +const switchParameters = { + apiIns: [{ + name: 'start', + desc: '', + dataType: '', + defaultValue: '' + }], + apiOuts: [{ + name: 'default', + desc: '', + dataType: '', + defaultValue: '' + }], + dataIns: [], + dataOuts: [] +}; // 定义节点基本信息 画布中添加的组件列表依赖这里 const nodeDefinitions = [ - { nodeName: '条件选择', nodeType: 'CONDITION', nodeGroup: 'common', icon: 'IconBranch' }, + { nodeName: '条件选择', nodeType: 'SWITCH', nodeGroup: 'common', icon: 'IconBranch' }, { nodeName: '与门', nodeType: 'AND', nodeGroup: 'common', icon: 'IconShareAlt' }, { nodeName: '或门', nodeType: 'OR', nodeGroup: 'common', icon: 'IconPause' }, { nodeName: '等待', nodeType: 'WAIT', nodeGroup: 'common', icon: 'IconClockCircle' }, @@ -77,6 +93,9 @@ export const localNodeData = nodeDefinitions.map(({ nodeName, nodeType, nodeGrou else if (nodeType === 'STR2JSON') { parameters = str2jsonParameters; } + else if (nodeType === 'SWITCH') { + parameters = switchParameters; + } return { nodeName,