deploy-ease-platform/frontend/src/pages/Workflow/NodeDesign/Design.tsx
2024-12-19 20:15:12 +08:00

437 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState, useRef } from 'react';
import { PageContainer } from '@ant-design/pro-components';
import { Row, Col, Menu, Tabs, Card, Button, message } from 'antd';
import { BetaSchemaForm, ProForm, ProFormGroup, ProFormText, ProFormTextArea } from '@ant-design/pro-form';
import type { NodeDesignData } from './types';
import * as service from './service';
import { useLocation } from 'react-router-dom';
// Tab 配置
const TAB_CONFIG = [
{ key: 'panel', label: '属性(预览)', schemaKey: 'panelVariablesSchema', readonly: true },
{ key: 'local', label: '环境变量(预览)', schemaKey: 'localVariablesSchema', readonly: true },
{ key: 'form', label: '表单(预览)', schemaKey: 'formVariablesSchema', readonly: true },
{ key: 'ui', label: 'UI配置', schemaKey: 'uiVariables', readonly: false }
];
const NodeDesignForm: React.FC = () => {
const location = useLocation();
const nodeData = location.state?.nodeData;
// 缓存节点定义数据
const [nodeDefinitions, setNodeDefinitions] = useState<NodeDesignData[]>([]);
const [loading, setLoading] = useState(false);
// 当前选中的节点
const [selectedNode, setSelectedNode] = useState<NodeDesignData | null>(null);
// 当前选中的 tab
const [activeTab, setActiveTab] = useState<string>('panel');
const formRef = useRef<any>();
const uiFormRef = useRef<any>(); // 添加 UI 配置表单的 ref
// 加载节点定义数据
useEffect(() => {
const loadNodeDefinitions = async () => {
try {
setLoading(true);
const data = await service.getNodeDefinitionsDefined();
setNodeDefinitions(data);
// 如果是修改模式,设置当前节点为选中状态
if (nodeData) {
setSelectedNode(nodeData);
} else if (data.length > 0) {
setSelectedNode(data[0]);
}
} catch (error) {
console.error('加载节点定义失败:', error);
} finally {
setLoading(false);
}
};
loadNodeDefinitions();
}, [nodeData]);
// 获取当前节点可用的 Tab 列表
const getAvailableTabs = (node: NodeDesignData | null) => {
if (!node) return [];
// 如果是修改模式从nodeDefinitions中找到对应的节点定义
if (nodeData) {
const nodeDefinition = nodeDefinitions.find(def => def.nodeCode === nodeData.nodeType);
if (nodeDefinition) {
return TAB_CONFIG.filter(tab => {
// 使用节点定义中的schema
const value = nodeDefinition[tab.schemaKey as keyof NodeDesignData];
return value !== null && value !== undefined;
});
}
}
// 新建模式
return TAB_CONFIG.filter(tab => {
const value = node[tab.schemaKey as keyof NodeDesignData];
return value !== null && value !== undefined;
});
};
// 处理节点选择
const handleNodeSelect = (nodeCode: string) => {
// 如果是修改模式,不允许选择其他节点
if (nodeData) return;
const node = nodeDefinitions.find(n => n.nodeCode === nodeCode);
if (node) {
setSelectedNode(node);
// 获取可用的 tab 列表
const availableTabs = getAvailableTabs(node);
// 如果当前选中的 tab 不在可用列表中,则选择第一个可用的 tab
if (availableTabs.length > 0 && !availableTabs.find(tab => tab.key === activeTab)) {
setActiveTab(availableTabs[0].key);
}
}
};
// 处理 Tab 切换
const handleTabChange = (newTab: string) => {
setActiveTab(newTab);
};
// 处理表单提交
const handleFormSubmit = async (values: any) => {
console.log('表单提交:', values);
// TODO: 调用接口保存数据
};
// 递归获取默认值
const getDefaultValues = (schema: any) => {
console.log('Current Schema:', schema);
if (!schema || !schema.properties) return {};
const result = Object.entries(schema.properties).reduce((acc, [key, value]: [string, any]) => {
console.log(`Processing key: ${key}, value:`, value);
if (value.type === 'object') {
const nestedDefaults = getDefaultValues(value);
console.log(`Nested defaults for ${key}:`, nestedDefaults);
acc[key] = nestedDefaults;
} else if (value.type === 'array' && value.items) {
acc[key] = value.default || [];
} else {
acc[key] = value.default;
}
return acc;
}, {});
console.log('Result for schema:', result);
return result;
};
// 将 JSON Schema 转换为 ProForm Schema
const convertToProFormSchema = (schema: any, parentKey = '', level = 0) => {
if (!schema || !schema.properties) return [];
return Object.entries(schema.properties).map(([key, value]: [string, any]) => {
const fullKey = parentKey ? `${parentKey}.${key}` : key;
console.log('Converting schema key:', fullKey, 'value:', value);
// 基础字段配置
const baseField = {
title: value.title,
dataIndex: fullKey,
tooltip: value.description,
initialValue: value.default
};
// 处理不同类型的字段
if (value.type === 'object') {
return {
...baseField,
dataIndex: fullKey,
valueType: 'group',
columns: convertToProFormSchema(value, fullKey, level + 1),
fieldProps: {
title: value.title,
style: {
padding: level === 0 ? '16px' : '8px',
marginBottom: '16px',
border: '1px solid #f0f0f0',
borderRadius: '4px',
background: level === 0 ? '#fafafa' : 'transparent'
}
}
};
}
// 处理枚举类型
if (value.enum) {
return {
...baseField,
valueType: 'select',
valueEnum: value.enum.reduce((acc: any, item: string, index: number) => {
acc[item] = { text: value.enumNames?.[index] || item };
return acc;
}, {}),
placeholder: `请选择${value.title}`
};
}
// 处理数组类型
if (value.type === 'array') {
const itemColumns = value.items ? convertToProFormSchema(
{ properties: { item: value.items } },
`${fullKey}.item`,
level + 1
) : [];
return {
...baseField,
valueType: 'formList',
columns: itemColumns,
placeholder: `请添加${value.title}`
};
}
// 处理基础类型
const typeMap: Record<string, string> = {
'string': 'text',
'integer': 'digit',
'number': 'digit',
'boolean': 'switch'
};
// 判断是否是颜色字段
const isColorField = value.type === 'string' &&
(value.title?.includes('颜色') || value.description?.includes('颜色'));
if (isColorField) {
return {
...baseField,
valueType: 'color',
placeholder: `请选择${value.title}`,
fieldProps: {
showText: true,
format: 'hex'
}
};
}
return {
...baseField,
valueType: typeMap[value.type] || 'text',
placeholder: `请输入${value.title}`
};
});
};
// 渲染 Tab 内容
const renderTabContent = (schemaKey: string) => {
if (!selectedNode) return null;
// 获取schema和初始值
let schema;
let initialValues = {};
if (nodeData) {
// 编辑模式
const nodeDefinition = nodeDefinitions.find(def => def.nodeCode === nodeData.nodeType);
if (!nodeDefinition) return null;
if (schemaKey === 'uiVariables') {
// UI配置使用nodeDefinitions中的schema定义使用nodeData中的实际数据作为初始值
schema = nodeDefinition.uiVariables;
initialValues = nodeData.uiVariables || {};
} else {
// 其他配置使用schema中的定义
schema = nodeDefinition[schemaKey as keyof NodeDesignData];
// 只在非UI配置时使用默认值
initialValues = getDefaultValues(schema);
}
} else {
// 新建模式
schema = selectedNode[schemaKey as keyof NodeDesignData];
initialValues = getDefaultValues(schema);
}
if (!schema) return null;
console.log(`Rendering ${schemaKey}:`, {
schema,
initialValues
});
const formColumns = convertToProFormSchema(schema);
return (
<BetaSchemaForm
key={`${selectedNode.nodeCode}-${schemaKey}`}
formRef={schemaKey === 'uiVariables' ? uiFormRef : undefined}
layoutType="Form"
initialValues={initialValues}
columns={formColumns}
submitter={false}
/>
);
};
// 将扁平的对象转换为嵌套对象
const flatToNested = (obj: Record<string, any>) => {
const result: Record<string, any> = {};
Object.keys(obj).forEach(key => {
const parts = key.split('.');
let current = result;
parts.forEach((part, index) => {
if (index === parts.length - 1) {
current[part] = obj[key];
} else {
current[part] = current[part] || {};
current = current[part];
}
});
});
return result;
};
const handleSave = async () => {
if (!selectedNode) return;
try {
const formValues = await formRef.current?.validateFields();
const uiValues = await uiFormRef.current?.validateFields();
const saveData = {
nodeType: formValues.nodeType,
nodeCode: formValues.nodeCode,
nodeName: formValues.nodeName,
category: formValues.category,
description: formValues.description,
uiVariables: flatToNested(uiValues), // 将扁平结构转换为嵌套结构
localVariablesSchema: selectedNode.localVariablesSchema,
panelVariablesSchema: selectedNode.panelVariablesSchema,
formVariablesSchema: selectedNode.formVariablesSchema
};
await service.saveNodeDefinition(saveData);
message.success('保存成功');
} catch (error) {
console.error('Save failed:', error);
message.error('保存失败');
}
};
const renderBasicForm = () => {
if (!selectedNode) return null;
return (
<ProForm
formRef={formRef}
key={selectedNode.nodeCode}
layout="horizontal"
submitter={false}
initialValues={{
nodeType: selectedNode.nodeCode,
nodeCode: selectedNode.nodeCode,
nodeName: selectedNode.nodeName,
category: selectedNode.category,
}}
>
<ProForm.Group>
<ProFormText
name="nodeType"
label="节点类型"
disabled
colProps={{ span: 12 }}
/>
<ProFormText
name="nodeCode"
label="节点编码"
disabled
colProps={{ span: 12 }}
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
name="nodeName"
label="节点名称"
disabled
colProps={{ span: 12 }}
/>
<ProFormText
name="category"
label="节点类别"
disabled
colProps={{ span: 12 }}
/>
</ProForm.Group>
<ProForm.Group>
<ProFormTextArea
name="description"
label="节点描述"
placeholder="请输入节点描述"
colProps={{ span: 24 }}
/>
</ProForm.Group>
</ProForm>
);
};
return (
<PageContainer
title={nodeData ? `修改节点设计 - ${nodeData.nodeName}` : '新建节点设计'}
loading={loading}
extra={[
<Button
key="save"
type="primary"
onClick={handleSave}
disabled={!selectedNode}
>
</Button>
]}
>
<div style={{ display: 'flex', height: '100%' }}>
<div style={{ width: 200, borderRight: '1px solid #f0f0f0', marginRight: 24 }}>
<div style={{
padding: '8px 16px',
fontWeight: 'bold',
borderBottom: '1px solid #f0f0f0'
}}>
</div>
<Menu
mode="vertical"
selectedKeys={[selectedNode?.nodeCode || '']}
items={nodeDefinitions.map(node => ({
key: node.nodeCode,
label: `${node.nodeName}${node.nodeCode}`,
disabled: nodeData ? node.nodeCode !== nodeData.nodeCode : false // 只在修改模式下禁用不匹配的节点
}))}
onClick={({ key }) => handleNodeSelect(key)}
style={{
border: 'none',
padding: '0 16px'
}}
/>
</div>
<div style={{ flex: 1 }}>
{renderBasicForm()}
<Tabs
activeKey={activeTab}
onChange={handleTabChange}
items={getAvailableTabs(selectedNode).map(tab => ({
key: tab.schemaKey,
label: tab.label,
children: renderTabContent(tab.schemaKey)
}))}
/>
</div>
</div>
</PageContainer>
);
};
export default NodeDesignForm;