1
This commit is contained in:
parent
e6bd17d9d0
commit
0801071ac6
@ -1,10 +1,8 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { PageContainer } from '@ant-design/pro-layout';
|
||||||
import { Row, Col, Menu, Tabs, Card, Button, message } from 'antd';
|
import { Button, Card, Form, Input, InputNumber, Select, Switch, Space, Menu, Tabs, Row, Col, message } from 'antd';
|
||||||
import { BetaSchemaForm, ProForm, ProFormGroup, ProFormText, ProFormTextArea } from '@ant-design/pro-form';
|
|
||||||
import type { NodeDesignData } from './types';
|
import type { NodeDesignData } from './types';
|
||||||
import * as service from './service';
|
import * as service from './service';
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
|
|
||||||
// Tab 配置
|
// Tab 配置
|
||||||
const TAB_CONFIG = [
|
const TAB_CONFIG = [
|
||||||
@ -14,61 +12,323 @@ const TAB_CONFIG = [
|
|||||||
{ key: 'ui', label: 'UI配置', schemaKey: 'uiVariables', readonly: false }
|
{ key: 'ui', label: 'UI配置', schemaKey: 'uiVariables', readonly: false }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 渲染具体的表单控件
|
||||||
|
const renderField = (schema: any) => {
|
||||||
|
const commonProps = {
|
||||||
|
placeholder: `请输入${schema.title}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (schema.enum) {
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
{...commonProps}
|
||||||
|
options={schema.enum.map((value: string, index: number) => ({
|
||||||
|
label: schema.enumNames?.[index] || value,
|
||||||
|
value
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (schema.type) {
|
||||||
|
case 'string':
|
||||||
|
return <Input {...commonProps} />;
|
||||||
|
case 'integer':
|
||||||
|
case 'number':
|
||||||
|
return <InputNumber {...commonProps} style={{ width: '100%' }} />;
|
||||||
|
case 'boolean':
|
||||||
|
return <Switch />;
|
||||||
|
default:
|
||||||
|
return <Input {...commonProps} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表单渲染器组件
|
||||||
|
const FormRenderer: React.FC<{
|
||||||
|
schema: any;
|
||||||
|
path?: string;
|
||||||
|
readOnly?: boolean;
|
||||||
|
}> = ({ schema, path = '', readOnly = false }) => {
|
||||||
|
if (!schema || !schema.properties) return null;
|
||||||
|
|
||||||
|
const renderPortConfig = (portSchema: any, portPath: string) => {
|
||||||
|
if (!portSchema || !portSchema.properties) return null;
|
||||||
|
|
||||||
|
const { position, attrs } = portSchema.properties;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* 先渲染端口位置 */}
|
||||||
|
<Form.Item
|
||||||
|
name={`${portPath}.position`}
|
||||||
|
label={
|
||||||
|
<span style={{ fontSize: '14px' }}>
|
||||||
|
{position.title}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
tooltip={position.description}
|
||||||
|
required={portSchema.required?.includes('position')}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
>
|
||||||
|
{readOnly ? (
|
||||||
|
<span style={{ color: '#666' }}>{position.default || '-'}</span>
|
||||||
|
) : (
|
||||||
|
renderField(position)
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{/* 再渲染端口属性 */}
|
||||||
|
{attrs && (
|
||||||
|
<Card
|
||||||
|
title={attrs.title}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
boxShadow: '0 1px 2px rgba(0,0,0,0.03)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #f0f0f0'
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: { padding: '16px' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormRenderer
|
||||||
|
schema={attrs}
|
||||||
|
path={`${portPath}.attrs`}
|
||||||
|
readOnly={readOnly}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查是否是顶层配置
|
||||||
|
const isTopLevel = !path;
|
||||||
|
const hasNodeConfigs = isTopLevel &&
|
||||||
|
schema.properties.size &&
|
||||||
|
schema.properties.shape &&
|
||||||
|
schema.properties.style;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '8px 0' }}>
|
||||||
|
{hasNodeConfigs ? (
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={8}>
|
||||||
|
<Card
|
||||||
|
title={schema.properties.size.title}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
boxShadow: '0 1px 2px rgba(0,0,0,0.03)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #f0f0f0'
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: { padding: '16px' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormRenderer
|
||||||
|
schema={schema.properties.size}
|
||||||
|
path="size"
|
||||||
|
readOnly={readOnly}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Card
|
||||||
|
title={schema.properties.shape.title}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
boxShadow: '0 1px 2px rgba(0,0,0,0.03)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #f0f0f0'
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: { padding: '16px' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="shape"
|
||||||
|
label={
|
||||||
|
<span style={{ fontSize: '14px' }}>
|
||||||
|
{schema.properties.shape.title}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
tooltip={schema.properties.shape.description}
|
||||||
|
required={schema.required?.includes('shape')}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
>
|
||||||
|
{readOnly ? (
|
||||||
|
<span style={{ color: '#666' }}>{schema.properties.shape.default || '-'}</span>
|
||||||
|
) : (
|
||||||
|
renderField(schema.properties.shape)
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Card
|
||||||
|
title={schema.properties.style.title}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
boxShadow: '0 1px 2px rgba(0,0,0,0.03)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #f0f0f0'
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: { padding: '16px' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormRenderer
|
||||||
|
schema={schema.properties.style}
|
||||||
|
path="style"
|
||||||
|
readOnly={readOnly}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{Object.entries(schema.properties).map(([key, value]: [string, any]) => {
|
||||||
|
// 如果是已经在上面渲染的配置,则跳过
|
||||||
|
if (hasNodeConfigs && (key === 'size' || key === 'shape' || key === 'style')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldPath = path ? `${path}.${key}` : key;
|
||||||
|
|
||||||
|
if (value.type === 'object') {
|
||||||
|
// 特殊处理端口组配置
|
||||||
|
if (key === 'groups' && path.endsWith('ports')) {
|
||||||
|
const inPort = value.properties?.in;
|
||||||
|
const outPort = value.properties?.out;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row key={fieldPath} gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Card
|
||||||
|
title={inPort.title}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
boxShadow: '0 1px 2px rgba(0,0,0,0.03)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #f0f0f0'
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: { padding: '16px' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderPortConfig(inPort, `${fieldPath}.in`)}
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Card
|
||||||
|
title={outPort.title}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
boxShadow: '0 1px 2px rgba(0,0,0,0.03)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #f0f0f0'
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: { padding: '16px' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderPortConfig(outPort, `${fieldPath}.out`)}
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={fieldPath}
|
||||||
|
title={
|
||||||
|
<span style={{ fontSize: '14px', fontWeight: 500 }}>
|
||||||
|
{value.title}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
boxShadow: '0 1px 2px rgba(0,0,0,0.03)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #f0f0f0'
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: { padding: '16px' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormRenderer
|
||||||
|
schema={value}
|
||||||
|
path={fieldPath}
|
||||||
|
readOnly={readOnly}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
key={fieldPath}
|
||||||
|
name={fieldPath}
|
||||||
|
label={
|
||||||
|
<span style={{ fontSize: '14px' }}>
|
||||||
|
{value.title}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
tooltip={value.description}
|
||||||
|
required={schema.required?.includes(key)}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
>
|
||||||
|
{readOnly ? (
|
||||||
|
<span style={{ color: '#666' }}>{value.default || '-'}</span>
|
||||||
|
) : (
|
||||||
|
renderField(value)
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const NodeDesignForm: React.FC = () => {
|
const NodeDesignForm: React.FC = () => {
|
||||||
const location = useLocation();
|
const [nodeDefinitionsDefined, setNodeDefinitionsDefined] = useState<NodeDesignData[]>([]);
|
||||||
const nodeData = location.state?.nodeData;
|
|
||||||
|
|
||||||
// 缓存节点定义数据
|
|
||||||
const [nodeDefinitions, setNodeDefinitions] = useState<NodeDesignData[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
// 当前选中的节点
|
|
||||||
const [selectedNode, setSelectedNode] = useState<NodeDesignData | null>(null);
|
const [selectedNode, setSelectedNode] = useState<NodeDesignData | null>(null);
|
||||||
// 当前选中的 tab
|
|
||||||
const [activeTab, setActiveTab] = useState<string>('panel');
|
const [activeTab, setActiveTab] = useState<string>('panel');
|
||||||
|
const [form] = Form.useForm();
|
||||||
const formRef = useRef<any>();
|
|
||||||
const uiFormRef = useRef<any>(); // 添加 UI 配置表单的 ref
|
|
||||||
|
|
||||||
// 加载节点定义数据
|
// 加载节点定义数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadNodeDefinitions = async () => {
|
const loadNodeDefinitionsDefined = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const data = await service.getNodeDefinitionsDefined();
|
const data = await service.getNodeDefinitionsDefined();
|
||||||
setNodeDefinitions(data);
|
setNodeDefinitionsDefined(data);
|
||||||
// 如果是修改模式,设置当前节点为选中状态
|
if (data.length > 0) {
|
||||||
if (nodeData) {
|
|
||||||
setSelectedNode(nodeData);
|
|
||||||
} else if (data.length > 0) {
|
|
||||||
setSelectedNode(data[0]);
|
setSelectedNode(data[0]);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载节点定义失败:', error);
|
console.error('加载节点定义失败:', error);
|
||||||
|
message.error('加载节点定义失败');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadNodeDefinitions();
|
loadNodeDefinitionsDefined();
|
||||||
}, [nodeData]);
|
}, []);
|
||||||
|
|
||||||
// 获取当前节点可用的 Tab 列表
|
// 获取当前节点可用的 Tab 列表
|
||||||
const getAvailableTabs = (node: NodeDesignData | null) => {
|
const getAvailableTabs = (node: NodeDesignData | null) => {
|
||||||
if (!node) return [];
|
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 => {
|
return TAB_CONFIG.filter(tab => {
|
||||||
const value = node[tab.schemaKey as keyof NodeDesignData];
|
const value = node[tab.schemaKey as keyof NodeDesignData];
|
||||||
return value !== null && value !== undefined;
|
return value !== null && value !== undefined;
|
||||||
@ -77,357 +337,148 @@ const NodeDesignForm: React.FC = () => {
|
|||||||
|
|
||||||
// 处理节点选择
|
// 处理节点选择
|
||||||
const handleNodeSelect = (nodeCode: string) => {
|
const handleNodeSelect = (nodeCode: string) => {
|
||||||
// 如果是修改模式,不允许选择其他节点
|
const node = nodeDefinitionsDefined.find(n => n.nodeCode === nodeCode);
|
||||||
if (nodeData) return;
|
|
||||||
|
|
||||||
const node = nodeDefinitions.find(n => n.nodeCode === nodeCode);
|
|
||||||
if (node) {
|
if (node) {
|
||||||
setSelectedNode(node);
|
setSelectedNode(node);
|
||||||
// 获取可用的 tab 列表
|
|
||||||
const availableTabs = getAvailableTabs(node);
|
const availableTabs = getAvailableTabs(node);
|
||||||
// 如果当前选中的 tab 不在可用列表中,则选择第一个可用的 tab
|
if (availableTabs.length > 0) {
|
||||||
if (availableTabs.length > 0 && !availableTabs.find(tab => tab.key === activeTab)) {
|
|
||||||
setActiveTab(availableTabs[0].key);
|
setActiveTab(availableTabs[0].key);
|
||||||
}
|
}
|
||||||
|
form.resetFields();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理 Tab 切换
|
// 处理Tab切换
|
||||||
const handleTabChange = (newTab: string) => {
|
const handleTabChange = (key: string) => {
|
||||||
setActiveTab(newTab);
|
setActiveTab(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前schema
|
||||||
|
const getCurrentSchema = () => {
|
||||||
|
if (!selectedNode) return null;
|
||||||
|
const currentTab = TAB_CONFIG.find(tab => tab.key === activeTab);
|
||||||
|
if (!currentTab) return null;
|
||||||
|
return selectedNode[currentTab.schemaKey as keyof NodeDesignData];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理表单提交
|
// 处理表单提交
|
||||||
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 () => {
|
const handleSave = async () => {
|
||||||
if (!selectedNode) return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const formValues = await formRef.current?.validateFields();
|
const values = await form.validateFields();
|
||||||
const uiValues = await uiFormRef.current?.validateFields();
|
console.log('表单数据:', values);
|
||||||
|
|
||||||
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('保存成功');
|
message.success('保存成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Save failed:', error);
|
console.error('表单验证失败:', error);
|
||||||
message.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 (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
title={nodeData ? `修改节点设计 - ${nodeData.nodeName}` : '新建节点设计'}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
extra={[
|
extra={[
|
||||||
<Button
|
<Button
|
||||||
key="save"
|
key="save"
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={!selectedNode}
|
|
||||||
>
|
>
|
||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', height: '100%' }}>
|
<div style={{ display: 'flex', gap: '24px' }}>
|
||||||
<div style={{ width: 200, borderRight: '1px solid #f0f0f0', marginRight: 24 }}>
|
<Card
|
||||||
|
style={{
|
||||||
|
width: 280,
|
||||||
|
height: 'fit-content',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: '0 1px 2px rgba(0,0,0,0.03)'
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
body: { padding: 0 }
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '8px 16px',
|
padding: '16px',
|
||||||
fontWeight: 'bold',
|
borderBottom: '1px solid #f0f0f0',
|
||||||
borderBottom: '1px solid #f0f0f0'
|
fontWeight: 500,
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#333'
|
||||||
}}>
|
}}>
|
||||||
节点类型
|
节点类型
|
||||||
</div>
|
</div>
|
||||||
<Menu
|
<Menu
|
||||||
mode="vertical"
|
mode="vertical"
|
||||||
selectedKeys={[selectedNode?.nodeCode || '']}
|
selectedKeys={[selectedNode?.nodeCode || '']}
|
||||||
items={nodeDefinitions.map(node => ({
|
items={nodeDefinitionsDefined.map(node => ({
|
||||||
key: node.nodeCode,
|
key: node.nodeCode,
|
||||||
label: `${node.nodeName}(${node.nodeCode})`,
|
label: (
|
||||||
disabled: nodeData ? node.nodeCode !== nodeData.nodeCode : false // 只在修改模式下禁用不匹配的节点
|
<div style={{
|
||||||
|
padding: '4px 0',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
<div>{node.nodeName}</div>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#999',
|
||||||
|
marginTop: '4px'
|
||||||
|
}}>
|
||||||
|
{node.nodeCode}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
}))}
|
}))}
|
||||||
onClick={({ key }) => handleNodeSelect(key)}
|
onClick={({ key }) => handleNodeSelect(key)}
|
||||||
style={{
|
style={{
|
||||||
border: 'none',
|
border: 'none',
|
||||||
padding: '0 16px'
|
padding: '8px 0'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Card>
|
||||||
<div style={{ flex: 1 }}>
|
<Card
|
||||||
{renderBasicForm()}
|
style={{
|
||||||
<Tabs
|
flex: 1,
|
||||||
activeKey={activeTab}
|
borderRadius: '8px',
|
||||||
onChange={handleTabChange}
|
boxShadow: '0 1px 2px rgba(0,0,0,0.03)'
|
||||||
items={getAvailableTabs(selectedNode).map(tab => ({
|
}}
|
||||||
key: tab.schemaKey,
|
styles={{
|
||||||
label: tab.label,
|
body: { padding: '24px' }
|
||||||
children: renderTabContent(tab.schemaKey)
|
}}
|
||||||
}))}
|
>
|
||||||
/>
|
<Form
|
||||||
</div>
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={handleTabChange}
|
||||||
|
items={getAvailableTabs(selectedNode).map(tab => ({
|
||||||
|
key: tab.key,
|
||||||
|
label: (
|
||||||
|
<span style={{ fontSize: '14px' }}>
|
||||||
|
{tab.label}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
children: (
|
||||||
|
<div style={{
|
||||||
|
padding: '16px 0',
|
||||||
|
minHeight: '400px'
|
||||||
|
}}>
|
||||||
|
<FormRenderer
|
||||||
|
schema={getCurrentSchema()}
|
||||||
|
readOnly={tab.readonly}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}))}
|
||||||
|
style={{
|
||||||
|
marginTop: '-16px',
|
||||||
|
marginLeft: '-16px',
|
||||||
|
marginRight: '-16px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// 节点设计相关服务
|
// 节点设计相关服务
|
||||||
|
|
||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import type { NodeDesignQuery, NodeDesignResponse, NodeDesignData } from './types';
|
import type { NodeDesignQuery, NodeDesignResponse, NodeDesignData, NodeDefinitionData } from './types';
|
||||||
|
|
||||||
const BASE_URL = '/api/v1/workflow/node-definition';
|
const BASE_URL = '/api/v1/workflow/node-definition';
|
||||||
|
|
||||||
@ -17,9 +17,10 @@ export const getNodeDesign = (nodeCode: string) =>
|
|||||||
export const updateNodeUIConfig = (nodeCode: string, uiVariables: any) =>
|
export const updateNodeUIConfig = (nodeCode: string, uiVariables: any) =>
|
||||||
request.put(`${BASE_URL}/${nodeCode}/ui`, uiVariables);
|
request.put(`${BASE_URL}/${nodeCode}/ui`, uiVariables);
|
||||||
|
|
||||||
// 保存节点定义
|
// 获取已定义的节点类型配置
|
||||||
export const saveNodeDefinition = (data: NodeDesignData) =>
|
|
||||||
request.post(`${BASE_URL}`, data);
|
|
||||||
|
|
||||||
export const getNodeDefinitionsDefined = () =>
|
export const getNodeDefinitionsDefined = () =>
|
||||||
request.get<NodeDesignData[]>(`${BASE_URL}/defined`);
|
request.get<NodeDefinitionData[]>(`${BASE_URL}/defined`);
|
||||||
|
|
||||||
|
// 保存节点定义
|
||||||
|
export const saveNodeDefinition = (data: NodeDefinitionData) =>
|
||||||
|
request.post<void>(`${BASE_URL}`, data);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// 节点设计相关类型定义
|
// 节点设计相关类型定义
|
||||||
|
|
||||||
// 基础Schema接口
|
// 基础Schema接口
|
||||||
import {BaseQuery, Page} from "@/types/base";
|
import {BaseQuery, BaseResponse} from "@/types/base";
|
||||||
|
|
||||||
export interface BaseSchema {
|
export interface BaseSchema {
|
||||||
type: string;
|
type: string;
|
||||||
@ -95,6 +95,6 @@ export interface NodeDesignQuery extends BaseQuery{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 分页响应接口
|
// 分页响应接口
|
||||||
export interface NodeDesignResponse extends Page<NodeDesignData>{
|
export interface NodeDesignResponse extends BaseResponse{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user