feat(flowEditor): 重构节点编辑模态框,支持自定义节点编辑器

- 拆分 NodeEditModal 组件中的节点编辑逻辑
- 新增独立的节点编辑器组件:EndNodeEditor、StartNodeEditor、BasicNodeEditor、LocalNodeEditor
- 实现节点编辑器的动态加载和注册机制
- 优化节点数据的管理和更新逻辑
master
钟良源 5 months ago
parent c1f6448ad6
commit 9a4187a8c3

@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import { Node } from '@xyflow/react';
import { Modal, Input, Form, Select } from '@arco-design/web-react';
import { Modal } from '@arco-design/web-react';
import { getNodeEditorByType } from './nodeEditors';
interface NodeData {
type?: string;
@ -57,43 +58,24 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({
}));
};
// 根据节点类型渲染不同的内容
const renderContent = () => {
// 获取节点编辑器组件
const getNodeEditor = () => {
const nodeType = node?.type || 'basic';
// 检查是否为本地节点通过nodeTypeMap中不存在的类型来判断
const isLocalNode = nodeType && !['start', 'end', 'basic'].includes(nodeType.toLowerCase());
switch (nodeType) {
case 'start':
case 'end':
case 'basic':
default:
return (
<Form layout="vertical">
<Form.Item label="节点标题">
<Input
value={nodeData.title || ''}
onChange={(value) => updateNodeData('title', value)}
/>
</Form.Item>
<Form.Item label="描述">
<Input.TextArea
value={nodeData.description || ''}
onChange={(value) => updateNodeData('description', value)}
/>
</Form.Item>
<Form.Item label="节点类型">
<Select
value={nodeData.category || ''}
onChange={(value) => updateNodeData('category', value)}
options={[
{ label: '数据处理', value: 'data' },
{ label: '逻辑判断', value: 'logic' },
{ label: '外部接口', value: 'api' }
]}
/>
</Form.Item>
</Form>
);
}
const EditorComponent = getNodeEditorByType(
nodeType.toLowerCase(),
isLocalNode ? nodeType : undefined
);
return (
<EditorComponent
node={node}
nodeData={nodeData}
updateNodeData={updateNodeData}
/>
);
};
return (
@ -104,8 +86,9 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({
onCancel={handleClose}
autoFocus={false}
focusLock={true}
style={{ width: '45%' }}
>
{renderContent()}
{getNodeEditor()}
</Modal>
);
};

@ -0,0 +1,39 @@
import React from 'react';
import { NodeEditorProps } from './index';
import { Form, Input, Select } from '@arco-design/web-react';
const BasicNodeEditor: React.FC<NodeEditorProps> = ({
node,
nodeData,
updateNodeData
}) => {
return (
<Form layout="vertical">
<Form.Item label="节点标题">
<Input
value={nodeData.title || ''}
onChange={(value) => updateNodeData('title', value)}
/>
</Form.Item>
<Form.Item label="描述">
<Input.TextArea
value={nodeData.description || ''}
onChange={(value) => updateNodeData('description', value)}
/>
</Form.Item>
<Form.Item label="节点类型">
<Select
value={nodeData.category || ''}
onChange={(value) => updateNodeData('category', value)}
options={[
{ label: '数据处理', value: 'data' },
{ label: '逻辑判断', value: 'logic' },
{ label: '外部接口', value: 'api' }
]}
/>
</Form.Item>
</Form>
);
};
export default BasicNodeEditor;

@ -0,0 +1,174 @@
import React, { useState, useEffect } from 'react';
import { NodeEditorProps } from './index';
import { Input, Select, Typography, Table, Button } from '@arco-design/web-react';
import { IconUnorderedList, IconDelete } from '@arco-design/web-react/icon';
const EndNodeEditor: React.FC<NodeEditorProps> = ({
node,
nodeData,
updateNodeData
}) => {
const [data, setData] = useState([]);
useEffect(() => {
// 为现有数据添加key属性如果不存在
const dataWithKeys = nodeData.parameters.dataIns?.map((item, index) => ({
...item,
key: item.key ?? index
})) || [];
setData(dataWithKeys);
}, [nodeData]);
const dataTypeOptions = [
{ label: '字符串', value: 'string' },
{ label: '数字', value: 'number' },
{ label: '布尔值', value: 'boolean' },
{ label: '数组', value: 'array' },
{ label: '对象', value: 'object' }
];
const arrayTypeOptions = [
{ label: '字符串数组', value: 'string' },
{ label: '数字数组', value: 'number' },
{ label: '布尔数组', value: 'boolean' }
];
const columns = [
{
title: '标识',
dataIndex: 'id',
render: (_, record) => (
<Input
value={record.id}
onChange={(value) => handleSave({ ...record, id: value })}
/>
)
},
{
title: '数据类型',
dataIndex: 'dataType',
render: (_, record) => (
<Select
autoWidth={{ minWidth: 200, maxWidth: 500 }}
options={dataTypeOptions}
value={record.dataType}
onChange={(value) => handleSave({ ...record, dataType: value })}
placeholder="请选择数据类型"
/>
)
},
{
title: '数组类型',
dataIndex: 'arrayType',
render: (_, record) => (
record.dataType === 'array' ? (
<Select
autoWidth={{ minWidth: 200, maxWidth: 500 }}
options={arrayTypeOptions}
value={record.arrayType}
onChange={(value) => handleSave({ ...record, arrayType: value })}
placeholder="请选择数组类型"
/>
) : (
<span></span>
)
)
},
{
title: '描述',
dataIndex: 'desc',
render: (_, record) => (
<Input
value={record.desc}
onChange={(value) => handleSave({ ...record, desc: value })}
/>
)
},
{
title: '默认值',
dataIndex: 'defaultValue',
render: (_, record) => (
<Input
value={record.defaultValue}
onChange={(value) => handleSave({ ...record, defaultValue: value })}
/>
)
},
{
title: '操作',
dataIndex: 'op',
render: (_, record) => (
<Button onClick={() => removeRow(record.key)} type="text" status="danger">
<IconDelete />
</Button>
)
}
];
const handleSave = (row) => {
const newData = [...data];
const index = newData.findIndex((item) => row.key === item.key);
if (index >= 0) {
newData.splice(index, 1, { ...newData[index], ...row });
}
else {
newData.push(row);
}
setData(newData);
// 更新节点数据
updateNodeData('parameters', {
...nodeData.parameters,
dataIns: newData
});
};
const removeRow = (key) => {
const newData = data.filter((item) => item.key !== key);
setData(newData);
// 更新节点数据
updateNodeData('parameters', {
...nodeData.parameters,
dataIns: newData
});
};
const addRow = () => {
const newKey = Date.now();
const newRow = {
key: newKey,
id: '',
dataType: '',
arrayType: '',
desc: '',
defaultValue: ''
};
const newData = [...data, newRow];
setData(newData);
// 更新节点数据
updateNodeData('parameters', {
...nodeData.parameters,
dataIns: newData
});
};
return (
<>
<Typography.Title heading={5}><IconUnorderedList style={{ marginRight: 5 }} /></Typography.Title>
<Table columns={columns} data={data} pagination={false} />
<Button
style={{ height: 45 }}
long
type="outline"
onClick={addRow}
>
+
</Button>
</>
);
};
export default EndNodeEditor;

@ -0,0 +1,112 @@
import React from 'react';
import { NodeEditorProps } from './index';
import { Form, Input, Switch, Select } from '@arco-design/web-react';
const LocalNodeEditor: React.FC<NodeEditorProps> = ({
node,
nodeData,
updateNodeData
}) => {
// 根据LocalNode的不同类型渲染不同的编辑界面
const renderLocalNodeEditor = () => {
const localNodeType = nodeData.type || '';
switch (localNodeType) {
case 'CONDITION':
return (
<>
<Form.Item label="节点标题">
<Input
value={nodeData.title || ''}
onChange={(value) => updateNodeData('title', value)}
/>
</Form.Item>
<Form.Item label="条件表达式">
<Input.TextArea
value={nodeData.expression || ''}
onChange={(value) => updateNodeData('expression', value)}
placeholder="请输入条件表达式"
/>
</Form.Item>
<Form.Item label="是否启用">
<Switch
checked={nodeData.enabled || false}
onChange={(value) => updateNodeData('enabled', value)}
/>
</Form.Item>
</>
);
case 'WAIT':
return (
<>
<Form.Item label="节点标题">
<Input
value={nodeData.title || ''}
onChange={(value) => updateNodeData('title', value)}
/>
</Form.Item>
<Form.Item label="等待时间(秒)">
<Input
value={nodeData.waitTime || ''}
onChange={(value) => updateNodeData('waitTime', value)}
type="number"
/>
</Form.Item>
</>
);
case 'LOOP':
return (
<>
<Form.Item label="节点标题">
<Input
value={nodeData.title || ''}
onChange={(value) => updateNodeData('title', value)}
/>
</Form.Item>
<Form.Item label="循环次数">
<Input
value={nodeData.loopCount || ''}
onChange={(value) => updateNodeData('loopCount', value)}
type="number"
/>
</Form.Item>
<Form.Item label="循环条件">
<Input.TextArea
value={nodeData.condition || ''}
onChange={(value) => updateNodeData('condition', value)}
placeholder="请输入循环条件"
/>
</Form.Item>
</>
);
default:
return (
<>
<Form.Item label="节点标题">
<Input
value={nodeData.title || ''}
onChange={(value) => updateNodeData('title', value)}
/>
</Form.Item>
<Form.Item label="描述">
<Input.TextArea
value={nodeData.description || ''}
onChange={(value) => updateNodeData('description', value)}
/>
</Form.Item>
</>
);
}
};
return (
<Form layout="vertical">
{renderLocalNodeEditor()}
</Form>
);
};
export default LocalNodeEditor;

@ -0,0 +1,28 @@
import React from 'react';
import { NodeEditorProps } from './index';
import { Form, Input } from '@arco-design/web-react';
const StartNodeEditor: React.FC<NodeEditorProps> = ({
node,
nodeData,
updateNodeData
}) => {
return (
<Form layout="vertical">
<Form.Item label="节点标题">
<Input
value={nodeData.title || ''}
onChange={(value) => updateNodeData('title', value)}
/>
</Form.Item>
<Form.Item label="描述">
<Input.TextArea
value={nodeData.description || ''}
onChange={(value) => updateNodeData('description', value)}
/>
</Form.Item>
</Form>
);
};
export default StartNodeEditor;

@ -0,0 +1,46 @@
import React from 'react';
import { Node } from '@xyflow/react';
import BasicNodeEditor from './BasicNodeEditor';
import StartNodeEditor from './StartNodeEditor';
import EndNodeEditor from './EndNodeEditor';
import LocalNodeEditor from './LocalNodeEditor';
// 定义节点编辑器的属性接口
export interface NodeEditorProps {
node: Node;
nodeData: any;
updateNodeData: (key: string, value: any) => void;
}
// 节点编辑器映射
export const nodeEditors: Record<string, React.FC<NodeEditorProps>> = {
'basic': BasicNodeEditor,
'start': StartNodeEditor,
'end': EndNodeEditor,
'local': LocalNodeEditor // 用于所有本地节点类型的编辑器
};
// 注册新的节点编辑器
export const registerNodeEditor = (
type: string,
component: React.FC<NodeEditorProps>
) => {
nodeEditors[type] = component;
};
// 根据节点类型获取对应的编辑器
export const getNodeEditorByType = (nodeType: string, localNodeType?: string) => {
// 对于本地节点,使用专门的本地节点编辑器
if (localNodeType) {
return nodeEditors['local'] || nodeEditors['basic'];
}
// 对于预定义节点类型,直接返回对应编辑器
return nodeEditors[nodeType] || nodeEditors['basic'];
};
export default {
nodeEditors,
registerNodeEditor,
getNodeEditorByType
};
Loading…
Cancel
Save