diff --git a/frontend/src/pages/Workflow/Design/components/NodeConfigModal.tsx b/frontend/src/pages/Workflow/Design/components/NodeConfigModal.tsx index 4efa0e7a..d9eb43ed 100644 --- a/frontend/src/pages/Workflow/Design/components/NodeConfigModal.tsx +++ b/frontend/src/pages/Workflow/Design/components/NodeConfigModal.tsx @@ -38,6 +38,10 @@ const NodeConfigModal: React.FC = ({ const [loading, setLoading] = useState(false); const { toast } = useToast(); + // 固定的节点信息(不通过 schema,直接管理) + const [nodeName, setNodeName] = useState(''); + const [description, setDescription] = useState(''); + // 动态数据源缓存 const [dataSourceCache, setDataSourceCache] = useState>({}); const [loadingDataSources, setLoadingDataSources] = useState(false); @@ -45,12 +49,7 @@ const NodeConfigModal: React.FC = ({ // 获取节点定义 const nodeDefinition: WorkflowNodeDefinition | null = node?.data?.nodeDefinition || null; - // ✅ 生成 Zod Schema - const configSchema = useMemo(() => { - if (!nodeDefinition?.configSchema) return null; - return convertJsonSchemaToZod(nodeDefinition.configSchema); - }, [nodeDefinition?.configSchema]); - + // ✅ 生成 Zod Schema(仅输入映射) const inputMappingSchema = useMemo(() => { if (!nodeDefinition || !isConfigurableNode(nodeDefinition) || !nodeDefinition.inputMappingSchema) { return null; @@ -58,31 +57,22 @@ const NodeConfigModal: React.FC = ({ return convertJsonSchemaToZod(nodeDefinition.inputMappingSchema); }, [nodeDefinition]); - // ✅ 创建表单实例(基本配置) - const configForm = useForm({ - resolver: configSchema ? zodResolver(configSchema) : undefined, - defaultValues: {} - }); - - // ✅ 创建表单实例(输入映射) + // ✅ 创建表单实例(仅输入映射) const inputForm = useForm({ resolver: inputMappingSchema ? zodResolver(inputMappingSchema) : undefined, defaultValues: {} }); - // ✅ 预加载动态数据源 + // ✅ 预加载动态数据源(仅输入映射) useEffect(() => { if (!visible || !nodeDefinition) return; const loadDynamicData = async () => { - const configTypes = extractDataSourceTypes(nodeDefinition.configSchema); const inputTypes = isConfigurableNode(nodeDefinition) && nodeDefinition.inputMappingSchema ? extractDataSourceTypes(nodeDefinition.inputMappingSchema) : []; - const allTypes = [...new Set([...configTypes, ...inputTypes])]; - - if (allTypes.length === 0) { + if (inputTypes.length === 0) { return; } @@ -90,7 +80,7 @@ const NodeConfigModal: React.FC = ({ try { const cache: Record = {}; await Promise.all( - allTypes.map(async (type) => { + inputTypes.map(async (type) => { const data = await loadDataSource(type as DataSourceType); cache[type] = data; }) @@ -116,21 +106,11 @@ const NodeConfigModal: React.FC = ({ if (visible && node && nodeDefinition) { const nodeData = node.data || {}; - // 设置基本配置默认值 - const defaultConfig = { - nodeName: nodeDefinition.nodeName, - nodeCode: nodeDefinition.nodeCode, - description: nodeDefinition.description || '' - }; + // 设置固定节点信息(从 configs 或 nodeDefinition 获取) + setNodeName(nodeData.configs?.nodeName || nodeDefinition.nodeName); + setDescription(nodeData.configs?.description || nodeDefinition.description || ''); - // 转换 UUID 格式为显示名称格式 - const displayConfigs = convertObjectToDisplayName( - { ...defaultConfig, ...(nodeData.configs || {}) }, - allNodes - ); - configForm.reset(displayConfigs); - - // 设置输入映射默认值(也需要转换) + // 设置输入映射默认值(转换 UUID 为显示名称) if (isConfigurableNode(nodeDefinition)) { const displayInputMapping = convertObjectToDisplayName( nodeData.inputMapping || {}, @@ -148,8 +128,18 @@ const NodeConfigModal: React.FC = ({ const handleSave = () => { if (!node || !nodeDefinition) return; - // 使用 handleSubmit 验证并获取数据 - configForm.handleSubmit(async (configData) => { + // 验证节点名称(必填) + if (!nodeName || !nodeName.trim()) { + toast({ + title: '保存失败', + description: '节点名称不能为空', + variant: 'destructive' + }); + return; + } + + // 使用 handleSubmit 验证输入映射(如果存在) + const submitForm = async () => { setLoading(true); try { // 获取输入映射数据 @@ -158,14 +148,20 @@ const NodeConfigModal: React.FC = ({ inputData = inputForm.getValues(); } - // 转换显示名称格式为 UUID 格式 - const uuidConfigs = convertObjectToUUID(configData as Record, allNodes); + // 转换显示名称格式为 UUID 格式(仅输入映射需要) const uuidInputMapping = convertObjectToUUID(inputData as Record, allNodes); + // 构建 configs(包含元数据) + const configs = { + nodeName: nodeName.trim(), + nodeCode: nodeDefinition.nodeCode, // 只读,从定义获取 + description: description.trim() + }; + // 构建更新数据 const updatedData: Partial = { - label: (configData as any).nodeName || nodeDefinition.nodeName, - configs: uuidConfigs, + label: nodeName.trim(), + configs, inputMapping: uuidInputMapping, outputs: isConfigurableNode(nodeDefinition) ? nodeDefinition.outputs || [] : [] }; @@ -189,19 +185,25 @@ const NodeConfigModal: React.FC = ({ } finally { setLoading(false); } - })(); + }; + + // 如果有输入映射schema,使用表单验证 + if (inputMappingSchema) { + inputForm.handleSubmit(submitForm)(); + } else { + submitForm(); + } }; // ✅ 重置表单 const handleReset = () => { if (!node || !nodeDefinition) return; - const defaultConfig = { - nodeName: nodeDefinition.nodeName, - nodeCode: nodeDefinition.nodeCode, - description: nodeDefinition.description || '' - }; - configForm.reset(defaultConfig); + // 重置节点信息 + setNodeName(nodeDefinition.nodeName); + setDescription(nodeDefinition.description || ''); + + // 重置输入映射 inputForm.reset({}); toast({ @@ -413,18 +415,62 @@ const NodeConfigModal: React.FC = ({
- {/* 基本配置 */} - + {/* 节点信息(固定表单,不通过 schema) */} + - 基本配置 + 节点信息 -
- {renderFormFields(nodeDefinition.configSchema, configForm)} -
+ {/* 节点编码(只读) */} +
+ + +

+ 节点的唯一标识符(只读) +

+
+ + {/* 节点名称 */} +
+ + setNodeName(e.target.value)} + placeholder="请输入节点名称" + disabled={loading} + /> +

+ 节点在流程图中显示的名称 +

+
+ + {/* 节点描述 */} +
+ +