437 lines
16 KiB
TypeScript
437 lines
16 KiB
TypeScript
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;
|