feat(flow): 实现条件选择节点功能

- 新增SwitchNode组件用于渲染条件选择节点- 新增nodeContentSwitch组件用于展示条件选择节点内容
- 修改LocalNode组件中的节点类型判断逻辑- 更新localNodeData配置文件,添加switchParameters配置
- 将ConditionEditor重命名为SwitchEditor并调整其内部实现
- 调整ConditionsTable组件以支持switch类型节点
-优化LoopNode和LoopEditor中参数更新逻辑
- 移除addNodeMenu中无用的日志输出
- 更新useFlowCallbacks中节点注册逻辑以支持SWITCH类型
- 调整节点句柄渲染逻辑以适配新结构
master
钟良源 4 months ago
parent 0a70157262
commit 4e594e1368

@ -10,7 +10,7 @@ import NodeContentOther from '@/pages/flowEditor/components/nodeContentOther';
const setIcon = (nodeType: string) => { const setIcon = (nodeType: string) => {
let type = 'IconApps'; let type = 'IconApps';
switch (nodeType) { switch (nodeType) {
case 'CONDITION': case 'SWITCH':
type = 'IconBranch'; type = 'IconBranch';
break; break;
case 'AND': case 'AND':

@ -126,7 +126,7 @@ const LoopNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => {
// 合并list数组与data.parameters.apiOuts数组 // 合并list数组与data.parameters.apiOuts数组
const apiOuts = data.parameters?.apiOuts || []; const apiOuts = data.parameters?.apiOuts || [];
const mergedApiOuts = [...apiOuts, ...list]; const mergedApiOuts = [...apiOuts];
setNewData(mergedApiOuts); setNewData(mergedApiOuts);
} }
else { else {

@ -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<any[]>([]);
const title = data.title || '条件选择';
// 获取节点选中状态 - 适配React Flow v12 API
const isSelected = useStore((state) =>
state.nodeLookup.get(id)?.selected || false
);
// 设置图标
const setIcon = () => {
return <DynamicIcon type="IconBranch" style={{ fontSize: '16px', marginRight: '5px' }} />;
};
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 (
<div className={`${styles['node-container']} ${isSelected ? styles.selected : ''}`}>
<div className={styles['node-header']} style={{ backgroundColor: '#1890ff' }}>
{setIcon()}
{title}
</div>
<NodeContentSwitch data={modifiedData} />
</div>
);
};
export default LoopNode;

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { NodeEditorProps } from './index'; import { NodeEditorProps } from './index';
import { Form, Input } from '@arco-design/web-react'; import { Form, Input } from '@arco-design/web-react';
import ConditionEditor from './components/ConditionEditor'; import SwitchEditor from './components/SwitchEditor';
import AndEditor from './components/AndEditor'; import AndEditor from './components/AndEditor';
import OrEditor from './components/OrEditor'; import OrEditor from './components/OrEditor';
import WaitEditor from './components/WaitEditor'; import WaitEditor from './components/WaitEditor';
@ -27,8 +27,8 @@ const LocalNodeEditor: React.FC<NodeEditorProps> = ({
const localNodeType = nodeData.type || ''; const localNodeType = nodeData.type || '';
switch (localNodeType) { switch (localNodeType) {
case 'CONDITION': // 条件选择 case 'SWITCH': // 条件选择
return <ConditionEditor nodeData={nodeData} updateNodeData={updateNodeData} />; return <SwitchEditor nodeData={nodeData} updateNodeData={updateNodeData} />;
case 'AND': // 与门 case 'AND': // 与门
return <AndEditor nodeData={nodeData} updateNodeData={updateNodeData} />; return <AndEditor nodeData={nodeData} updateNodeData={updateNodeData} />;
case 'OR': // 或门 case 'OR': // 或门

@ -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<NodeEditorProps> = ({ nodeData, updateNodeData }) => {
return (
<>
<Typography.Title heading={5}><IconUnorderedList style={{ marginRight: 5 }} /></Typography.Title>
<ParamsTable
initialData={nodeData.parameters.dataIns || []}
onUpdateData={(data) => {
updateNodeData('parameters', {
...nodeData.parameters,
dataIns: data
});
}}
/>
</>
);
};
export default ConditionEditor;

@ -19,6 +19,7 @@ interface ConditionsTableProps {
initialData: any; initialData: any;
nodeData: any; nodeData: any;
onUpdateData: (data: any) => void; onUpdateData: (data: any) => void;
type?: string;
} }
const dataTypeOptions = [ const dataTypeOptions = [
@ -51,9 +52,11 @@ const operationOptions = [
const ConditionsTable: React.FC<ConditionsTableProps> = ({ const ConditionsTable: React.FC<ConditionsTableProps> = ({
initialData, initialData,
nodeData, nodeData,
onUpdateData onUpdateData,
type = 'LOOP'
}) => { }) => {
const [data, setData] = useState<any[]>([]); const [data, setData] = useState<any[]>([]);
const [rowData, setRowData] = useState<any>({});
const [apiOutsList, setApiOutsList] = useState([]); const [apiOutsList, setApiOutsList] = useState([]);
const [leftList, setLeftList] = useState([]); const [leftList, setLeftList] = useState([]);
@ -71,7 +74,29 @@ const ConditionsTable: React.FC<ConditionsTableProps> = ({
render: (_: any, record: TableDataItem) => ( render: (_: any, record: TableDataItem) => (
<Input <Input
value={record.apiOutId} value={record.apiOutId}
onChange={(value) => 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="请输入逻辑出口" placeholder="请输入逻辑出口"
/> />
) )
@ -84,7 +109,22 @@ const ConditionsTable: React.FC<ConditionsTableProps> = ({
autoWidth={{ minWidth: 200, maxWidth: 500 }} autoWidth={{ minWidth: 200, maxWidth: 500 }}
options={leftList} options={leftList}
value={record.lftVal} 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="请选择需要引用的出入参数" placeholder="请选择需要引用的出入参数"
/> />
) )
@ -97,7 +137,22 @@ const ConditionsTable: React.FC<ConditionsTableProps> = ({
autoWidth={{ minWidth: 200, maxWidth: 500 }} autoWidth={{ minWidth: 200, maxWidth: 500 }}
options={operationOptions} options={operationOptions}
value={record.operator} 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="请选择运算/比较符" placeholder="请选择运算/比较符"
/> />
) )
@ -111,7 +166,27 @@ const ConditionsTable: React.FC<ConditionsTableProps> = ({
autoWidth={{ minWidth: 200, maxWidth: 500 }} autoWidth={{ minWidth: 200, maxWidth: 500 }}
options={dataTypeOptions} options={dataTypeOptions}
value={record.valueType} 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="请选择右值类型" placeholder="请选择右值类型"
/> />
{['string', 'number', 'expression'].includes(record.valueType) ? ( {['string', 'number', 'expression'].includes(record.valueType) ? (
@ -119,7 +194,29 @@ const ConditionsTable: React.FC<ConditionsTableProps> = ({
style={{ marginTop: 8 }} style={{ marginTop: 8 }}
autoWidth={{ minWidth: 200, maxWidth: 500 }} autoWidth={{ minWidth: 200, maxWidth: 500 }}
value={record.rgtVal} 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={'请输入'} placeholder={'请输入'}
/> />
) : (<span></span>)} ) : (<span></span>)}
@ -155,14 +252,16 @@ const ConditionsTable: React.FC<ConditionsTableProps> = ({
expression: expression expression: expression
}; };
}); });
const customDef = {
apiOutIds,
conditions
};
// 只需要动态添加开始节点的NodeId 循环开始的节点不允许编辑信息的,所以接口需要的信息会在节点实例的时候配置
if (type === 'LOOP') customDef['loopStartNodeId'] = nodeData.component.loopStartNodeId;
return { return {
type: nodeData.type, type: nodeData.type,
customDef: JSON.stringify({ customDef: JSON.stringify(customDef)
apiOutIds,
conditions,
// 只需要动态添加开始节点的NodeId 循环开始的节点不允许编辑信息的,所以接口需要的信息会在节点实例的时候配置
loopStartNodeId: nodeData.component.loopStartNodeId
})
}; };
}; };
@ -248,7 +347,8 @@ const ConditionsTable: React.FC<ConditionsTableProps> = ({
const extractApiNames = () => { const extractApiNames = () => {
const apiInsNames = nodeData.parameters?.apiIns?.map((item: any) => item.name) || []; const apiInsNames = nodeData.parameters?.apiIns?.map((item: any) => item.name) || [];
const apiOutsNames = nodeData.parameters?.apiOuts?.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结构 // 提取dataIns中的属性并构造成options结构
@ -260,6 +360,73 @@ const ConditionsTable: React.FC<ConditionsTableProps> = ({
return [...dataInsOptions]; 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 handleSave = (row: TableDataItem) => {
const newData = [...data]; const newData = [...data];
const index = newData.findIndex((item) => row.key === item.key); const index = newData.findIndex((item) => row.key === item.key);
@ -271,16 +438,32 @@ const ConditionsTable: React.FC<ConditionsTableProps> = ({
} }
setData(newData); setData(newData);
// 重新构建数据结构 // 重新构建数据结构
const newApiOuts = updateOriginData(newData);
const newComponentData = convertData(newData); const newComponentData = convertData(newData);
onUpdateData(newComponentData); onUpdateData({
...nodeData,
parameters: {
...nodeData.parameters,
apiOuts: newApiOuts
},
component: newComponentData
});
}; };
const removeRow = (key: number | string) => { const removeRow = (key: number | string) => {
const newData = data.filter((item) => item.key !== key); const newData = data.filter((item) => item.key !== key);
setData(newData); setData(newData);
// 重新构建数据结构 // 重新构建数据结构
const newApiOuts = updateOriginData(newData);
const newComponentData = convertData(newData); const newComponentData = convertData(newData);
onUpdateData(newComponentData); onUpdateData({
...nodeData,
parameters: {
...nodeData.parameters,
apiOuts: newApiOuts
},
component: newComponentData
});
}; };
const addRow = () => { const addRow = () => {
@ -296,9 +479,17 @@ const ConditionsTable: React.FC<ConditionsTableProps> = ({
}; };
const newData = [...data, newRow]; const newData = [...data, newRow];
setData(newData); setData(newData);
// 重新构建数据结构 // 重新构建数据结构 添加新行时不更新
const newComponentData = convertData(newData); // const newApiOuts = updateOriginData(newData);
onUpdateData(newComponentData); // const newComponentData = convertData(newData);
// onUpdateData({
// ...nodeData,
// parameters: {
// ...nodeData.parameters,
// apiOuts: newApiOuts
// },
// component: newComponentData
// });
}; };
// 监听nodeData.parameters.dataIns的变化更新leftList // 监听nodeData.parameters.dataIns的变化更新leftList

@ -27,7 +27,11 @@ const LoopEditor: React.FC<NodeEditorProps> = ({ nodeData, updateNodeData }) =>
nodeData={nodeData || null} nodeData={nodeData || null}
onUpdateData={(data) => { onUpdateData={(data) => {
updateNodeData('component', { updateNodeData('component', {
...data ...data.component
});
updateNodeData('parameters', {
...nodeData.parameters,
apiOuts: data.parameters.apiOuts
}); });
}} /> }} />
</> </>

@ -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<NodeEditorProps> = ({ nodeData, updateNodeData }) => {
return (
<>
<Typography.Title heading={5}><IconUnorderedList style={{ marginRight: 5 }} /></Typography.Title>
<ParamsTable
initialData={nodeData.parameters.dataIns || []}
onUpdateData={(data) => {
updateNodeData('parameters', {
...nodeData.parameters,
dataIns: data
});
}}
/>
<Typography.Title heading={5}>
<IconUnorderedList style={{ marginRight: 5, marginTop: 20 }} />
</Typography.Title>
<ConditionsTable
initialData={nodeData.component || {}}
nodeData={nodeData || null}
onUpdateData={(data) => {
updateNodeData('component', {
...data.component
});
updateNodeData('parameters', {
...nodeData.parameters,
apiOuts: data.parameters.apiOuts
});
}}
type="switch"
/>
</>
);
};
export default SwitchEditor;

@ -19,6 +19,7 @@ import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines';
import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode'; import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode';
import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode'; import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode';
import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode'; import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode';
import SwitchNode from '@/components/FlowEditor/node/switchNode/SwitchNode';
import { updateCanvasDataMap } from '@/store/ideContainer'; import { updateCanvasDataMap } from '@/store/ideContainer';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
@ -59,6 +60,8 @@ export const useFlowCallbacks = (
const apiOuts = nodeParams.apiOuts || []; const apiOuts = nodeParams.apiOuts || [];
const apiIns = nodeParams.apiIns || []; const apiIns = nodeParams.apiIns || [];
console.log('123:', apiIns, apiOuts);
if (apiOuts.some((api: any) => (api.name || api.id) === handleId) || if (apiOuts.some((api: any) => (api.name || api.id) === handleId) ||
apiIns.some((api: any) => (api.name || api.id) === handleId) || (handleId.includes('loop'))) { apiIns.some((api: any) => (api.name || api.id) === handleId) || (handleId.includes('loop'))) {
return 'api'; return 'api';
@ -192,6 +195,8 @@ export const useFlowCallbacks = (
const sourceParams = sourceNode.data?.parameters || {}; const sourceParams = sourceNode.data?.parameters || {};
const targetParams = targetNode.data?.parameters || {}; const targetParams = targetNode.data?.parameters || {};
console.log('params:', params, sourceParams, targetParams);
// 获取源handle和目标handle的类型 (api或data) // 获取源handle和目标handle的类型 (api或data)
const sourceHandleType = getHandleType(params.sourceHandle, sourceParams); const sourceHandleType = getHandleType(params.sourceHandle, sourceParams);
const targetHandleType = getHandleType(params.targetHandle, targetParams); const targetHandleType = getHandleType(params.targetHandle, targetParams);
@ -603,7 +608,7 @@ export const useFlowCallbacks = (
// 将未定义的节点动态追加进nodeTypes // 将未定义的节点动态追加进nodeTypes
const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
if (!nodeMap.includes(nodeType)) { 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)); const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
// 目前默认添加的都是系统组件/本地组件 // 目前默认添加的都是系统组件/本地组件
if (!nodeMap.includes(nodeType)) { 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[]) => { setNodes((nds: Node[]) => {
@ -745,7 +750,7 @@ export const useFlowCallbacks = (
try { try {
// 转换会原始数据类型 // 转换会原始数据类型
const revertedData = revertFlowData(nodes, edges); const revertedData = revertFlowData(nodes, edges);
console.log('revertedData:', revertedData); console.log('revertedData(中断):', revertedData);
// return; // return;
const res: any = await setMainFlow(revertedData, initialData.appId); const res: any = await setMainFlow(revertedData, initialData.appId);

@ -69,8 +69,6 @@ const AddNodeMenu: React.FC<AddNodeMenuProps> = ({
// } // }
// }; // };
// }); // });
console.log(projectCompList, projectFlowList, initialGroupedNodes);
} }
// 更新状态以触发重新渲染 // 更新状态以触发重新渲染

@ -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 NodeContent = ({ data }: { data: NodeContentData }) => {
const apiIns = data.parameters?.apiIns || []; const apiIns = data.parameters?.apiIns || [];
const apiOuts = data.parameters?.apiOuts || []; const apiOuts = data.parameters?.apiOuts || [];
const dataIns = data.parameters?.dataIns || []; const dataIns = data.parameters?.dataIns || [];
const dataOuts = data.parameters?.dataOuts || []; const dataOuts = data.parameters?.dataOuts || [];
const showFooter = data?.component?.customDef || false; const showFooter = data?.component?.customDef || false;
const footerData = (showFooter && data.component) || {};
// 判断节点类型 // 判断节点类型
const isStartNode = data.type === 'start'; const isStartNode = data.type === 'start';
@ -196,13 +171,6 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
</> </>
)} )}
{/*footer栏*/}
{/*{showFooter && (*/}
{/* <div className={styles['node-footer']}>*/}
{/* {formatFooter(footerData)}*/}
{/* </div>*/}
{/*)}*/}
{renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)} {renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)}
</> </>
); );

@ -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) => (
<Handle
key={`api-output-handle-${index}`}
type="source"
position={Position.Right}
id={apiOuts[index].name || apiOuts[index].id || `output-${index}`}
style={{
...handleStyles.mainSource,
top: `${35 + index * 20}px`
}}
/>
))}
{apiIns.map((_, index) => (
<Handle
key={`api-input-handle-${index}`}
type="target"
position={Position.Left}
id={apiIns[index].name || apiIns[index].id || `input-${index}`}
style={{
...handleStyles.mainTarget,
top: `${35 + index * 20}px`
}}
/>
))}
{/* 输入参数连接端点 */}
{dataIns.map((_, index) => (
<Handle
key={`data-input-handle-${index}`}
type="target"
position={Position.Left}
id={dataIns[index].name || dataIns[index].id || `input-${index}`}
style={{
...handleStyles.data,
top: `${65 + (apiOuts.length + index) * 20}px`
}}
/>
))}
{/* 输出参数连接端点 */}
{dataOuts.map((_, index) => (
<Handle
key={`data-output-handle-${index}`}
type="source"
position={Position.Right}
id={dataOuts[index].name || dataOuts[index].id || `output-${index}`}
style={{
...handleStyles.data,
top: `${65 + (apiOuts.length + index) * 20}px`
}}
/>
))}
</>
);
};
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部分*/}
<div className={styles['node-api-box']}>
<div className={styles['node-content-api']}>
{apiIns.length > 0 && (
<div className={styles['node-inputs']}>
{apiIns.map((input, index) => (
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{input.desc}
</div>
))}
</div>
)}
{apiOuts.length > 0 && (
<div className={styles['node-outputs-api']}>
{apiOuts.map((output, index) => (
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
{output.desc || output.id || output.name}
</div>
))}
</div>
)}
</div>
</div>
{(dataIns.length > 0 || dataOuts.length > 0) && (
<>
{/*分割*/}
<div className={styles['node-split-line']}></div>
{/*content栏-data部分*/}
<div className={styles['node-data-box']}>
<div className={styles['node-content']}>
{dataIns.length > 0 && !isStartNode && (
<div className={styles['node-inputs']}>
{dataIns.map((input, index) => (
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{input.id || `输入${index + 1}`}
</div>
))}
</div>
)}
{dataOuts.length > 0 && !isEndNode && (
<div className={styles['node-outputs']}>
{dataOuts.map((output, index) => (
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
{output.id || `输出${index + 1}`}
</div>
))}
</div>
)}
</div>
</div>
</>
)}
{renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)}
</>
);
};
export default NodeContent;

@ -46,10 +46,26 @@ const str2jsonParameters = {
dataIns: [], dataIns: [],
dataOuts: [] dataOuts: []
}; };
const switchParameters = {
apiIns: [{
name: 'start',
desc: '',
dataType: '',
defaultValue: ''
}],
apiOuts: [{
name: 'default',
desc: '',
dataType: '',
defaultValue: ''
}],
dataIns: [],
dataOuts: []
};
// 定义节点基本信息 画布中添加的组件列表依赖这里 // 定义节点基本信息 画布中添加的组件列表依赖这里
const nodeDefinitions = [ 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: 'AND', nodeGroup: 'common', icon: 'IconShareAlt' },
{ nodeName: '或门', nodeType: 'OR', nodeGroup: 'common', icon: 'IconPause' }, { nodeName: '或门', nodeType: 'OR', nodeGroup: 'common', icon: 'IconPause' },
{ nodeName: '等待', nodeType: 'WAIT', nodeGroup: 'common', icon: 'IconClockCircle' }, { nodeName: '等待', nodeType: 'WAIT', nodeGroup: 'common', icon: 'IconClockCircle' },
@ -77,6 +93,9 @@ export const localNodeData = nodeDefinitions.map(({ nodeName, nodeType, nodeGrou
else if (nodeType === 'STR2JSON') { else if (nodeType === 'STR2JSON') {
parameters = str2jsonParameters; parameters = str2jsonParameters;
} }
else if (nodeType === 'SWITCH') {
parameters = switchParameters;
}
return { return {
nodeName, nodeName,

Loading…
Cancel
Save