diff --git a/src/components/FlowEditor/node/codeNode/CodeNode.tsx b/src/components/FlowEditor/node/codeNode/CodeNode.tsx new file mode 100644 index 0000000..97746f9 --- /dev/null +++ b/src/components/FlowEditor/node/codeNode/CodeNode.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { useStore } from '@xyflow/react'; +import styles from '@/components/FlowEditor/node/style/baseOther.module.less'; +import DynamicIcon from '@/components/DynamicIcon'; +import NodeContentCode from '@/pages/flowEditor/components/nodeContentCode'; + +const setIcon = () => { + return ; +}; + +const CodeNode = ({ data, id }: { data: any; id: string }) => { + const title = data.title || '代码编辑器'; + + // 获取节点选中状态 - 适配React Flow v12 API + const isSelected = useStore((state) => + state.nodeLookup.get(id)?.selected || false + ); + + return ( +
+
+ {setIcon()} + {title} +
+ +
+ ); +}; + +export default CodeNode; \ No newline at end of file diff --git a/src/components/FlowEditor/nodeEditors/components/CodeEditor.tsx b/src/components/FlowEditor/nodeEditors/components/CodeEditor.tsx index 41a0dda..bd7ef9b 100644 --- a/src/components/FlowEditor/nodeEditors/components/CodeEditor.tsx +++ b/src/components/FlowEditor/nodeEditors/components/CodeEditor.tsx @@ -1,10 +1,46 @@ -import React from 'react'; +import React, { useState, useEffect, useCallback } 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 CodeMirror from '@/components/FlowEditor/nodeEditors/components/CodeMirror'; + const CodeEditor: React.FC = ({ nodeData, updateNodeData }) => { + const [codeComponent, setCodeComponent] = useState(nodeData.component || {}); + + // 当组件加载时,主动触发 CodeMirror 的 onUpdateData 方法 + useEffect(() => { + // 如果是新创建的节点,没有默认的 component 数据,则设置默认值 + if (!nodeData.component || Object.keys(nodeData.component).length === 0) { + const defaultData = { + customDef: { + languageId: '63', // 默认 Java + sourceCode: '/**\n' + + 'ExecClass类main 方法是固定的启动函数,参数个数、类型、返回类型不可更改\n' + + '当前版本gson-2.10.1.jar的文档地址:\n' + + 'https://www.javadoc.io/doc/com.google.code.gson/gson/2.10.1/index.html\n' + + '*/\n' + + 'import com.google.gson.JsonObject;\n' + + 'class ExecClass{ \n' + + ' public JsonObject main(JsonObject args){\n' + + ' return args;\n' + + ' }\n' + + '}' + }, + type: 'CODE' + }; + + // 更新节点数据 + updateNodeData('component', defaultData); + setCodeComponent(defaultData); + } + else { + // 如果已有 component 数据,则使用现有数据 + setCodeComponent(nodeData.component); + } + }, []); + return ( <> 输入参数 @@ -17,6 +53,26 @@ const CodeEditor: React.FC = ({ nodeData, updateNodeData }) => }); }} /> + 输出参数 + { + updateNodeData('parameters', { + ...nodeData.parameters, + dataOuts: data + }); + }} + /> + 代码段 + { + setCodeComponent(data); + updateNodeData('component', { + ...data + }); + }} + /> ); }; diff --git a/src/components/FlowEditor/nodeEditors/components/CodeMirror.tsx b/src/components/FlowEditor/nodeEditors/components/CodeMirror.tsx new file mode 100644 index 0000000..4d9056f --- /dev/null +++ b/src/components/FlowEditor/nodeEditors/components/CodeMirror.tsx @@ -0,0 +1,98 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { Select } from '@arco-design/web-react'; +import CodeMirror from '@uiw/react-codemirror'; +import { java } from '@codemirror/lang-java'; +import { python } from '@codemirror/lang-python'; +import { githubLight } from '@uiw/codemirror-theme-github'; + +const Option = Select.Option; + +interface TableDataItem { + key: number | string; + id: string; + dataType: string; + arrayType: string; + desc: string; + defaultValue: string; + + [key: string]: any; // 允许其他自定义字段 +} + +interface CodeMirrorProps { + initialData: TableDataItem[], + onUpdateData: (data) => void, +} + +const extensions = [java(), python(), githubLight]; +const options = ['java', 'python']; +const defaultCode = { + 'java': '/**\n' + + 'ExecClass类main 方法是固定的启动函数,参数个数、类型、返回类型不可更改\n' + + '当前版本gson-2.10.1.jar的文档地址:\n' + + 'https://www.javadoc.io/doc/com.google.code.gson/gson/2.10.1/index.html\n' + + '*/\n' + + 'import com.google.gson.JsonObject;\n' + + 'class ExecClass{ \n' + + ' public JsonObject main(JsonObject args){\n' + + ' return args;\n' + + ' }\n' + + '}', + 'python': '# main函数是启动函数,参数类型、个数、返回类型不可更改\n' + + 'def main(a:dict)->dict:\n' + + ' return {\'b\': a.get(\'a\')+[4,5]}' +}; +const nameToCode = { + 'java': '63', + 'python': '70' +}; + + +const CodeMirrorComp: React.FC = ({ + initialData, + onUpdateData + }) => { + const [value, setValue] = useState('console.log(\'hello world!\');'); + const [language, setLanguage] = useState('java'); + + const onChange = useCallback((val, viewUpdate) => { + setValue(val); + const data = { + customDef: { + languageId: nameToCode[language], + sourceCode: val + } + }; + console.log('data:', data); + onUpdateData(data); + }, []); + + useEffect(() => { + setValue(defaultCode[language]); + }, []); + return ( + <> + + + + ); +}; + +export default CodeMirrorComp; \ No newline at end of file diff --git a/src/hooks/useFlowCallbacks.ts b/src/hooks/useFlowCallbacks.ts index 25777da..6ef3cda 100644 --- a/src/hooks/useFlowCallbacks.ts +++ b/src/hooks/useFlowCallbacks.ts @@ -21,6 +21,7 @@ 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 ImageNode from '@/components/FlowEditor/node/imageNode/ImageNode'; +import CodeNode from '@/components/FlowEditor/node/codeNode/CodeNode'; import { updateCanvasDataMap } from '@/store/ideContainer'; import { Dispatch } from 'redux'; @@ -34,6 +35,8 @@ const getNodeComponent = (nodeType: string) => { return SwitchNode; case 'IMAGE': return ImageNode; + case 'CODE': + return CodeNode; default: return LocalNode; } diff --git a/src/pages/flowEditor/components/nodeContentCode.tsx b/src/pages/flowEditor/components/nodeContentCode.tsx new file mode 100644 index 0000000..b509400 --- /dev/null +++ b/src/pages/flowEditor/components/nodeContentCode.tsx @@ -0,0 +1,185 @@ +import React from 'react'; +import styles from '@/components/FlowEditor/node/style/baseOther.module.less'; +import { Handle, Position, useStore } from '@xyflow/react'; +import { deserializeValue, isJSON } from '@/utils/common'; +import cronstrue from 'cronstrue/i18n'; + +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 showFooter = data?.component?.customDef || false; + + // 判断节点类型 + 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} +
+ ))} +
+ )} +
+
+ {(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} ${output.dataType}` || `输出${index + 1}`} +
+ ))} +
+ )} +
+
+ + )} + + + {/* 根据节点类型渲染不同的句柄 */} + {renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)} + + ); +}; + +export default NodeContent; \ No newline at end of file diff --git a/src/pages/flowEditor/components/nodeContentOther.tsx b/src/pages/flowEditor/components/nodeContentOther.tsx index 2237afb..84a48b3 100644 --- a/src/pages/flowEditor/components/nodeContentOther.tsx +++ b/src/pages/flowEditor/components/nodeContentOther.tsx @@ -1,7 +1,7 @@ import React from 'react'; import styles from '@/components/FlowEditor/node/style/baseOther.module.less'; import { Handle, Position, useStore } from '@xyflow/react'; -import { deserializeValue } from '@/utils/common'; +import { deserializeValue, isJSON } from '@/utils/common'; import cronstrue from 'cronstrue/i18n'; interface NodeContentData { @@ -192,7 +192,7 @@ const formatFooter = (data: any) => { return cronstrue.toString(intervalSeconds, { locale: 'zh_CN' }); case 'EVENTSEND': case 'EVENTLISTENE': - const { name } = data.customDef; + const { name } = isJSON(data.customDef) ? JSON.parse(data.customDef) : data.customDef; return `事件: ${name}`; default: return '这个类型还没开发'; diff --git a/src/pages/flowEditor/sideBar/config/localNodeData.ts b/src/pages/flowEditor/sideBar/config/localNodeData.ts index 3d3b0ef..d9b5f4b 100644 --- a/src/pages/flowEditor/sideBar/config/localNodeData.ts +++ b/src/pages/flowEditor/sideBar/config/localNodeData.ts @@ -84,6 +84,28 @@ const imageParameters = { }], dataOuts: [] }; +const codeParameters = { + apiIns: [{ + name: 'start', + desc: '', + dataType: '', + defaultValue: '' + }], + apiOuts: [{ + name: 'done', + desc: '', + dataType: '', + defaultValue: '' + }], + dataIns: [], + dataOuts: [{ + 'arrayType': null, + 'dataType': 'STRING', + 'defaultValue': 'STRING', + 'desc': '输出参数', + 'id': 'arg' + }] +}; // 定义节点基本信息 画布中添加的组件列表依赖这里 const nodeDefinitions = [ @@ -121,6 +143,9 @@ export const localNodeData = nodeDefinitions.map(({ nodeName, nodeType, nodeGrou else if (nodeType === 'IMAGE') { parameters = imageParameters; } + else if (nodeType === 'CODE') { + parameters = codeParameters; + } return { nodeName,