This commit is contained in:
dengqichen 2024-12-20 13:43:25 +08:00
parent d8cc3c0983
commit debef2f881
6 changed files with 88 additions and 124 deletions

View File

@ -1,13 +1,13 @@
import React, { useEffect, ReactNode } from 'react';
import { Drawer, Form, Input, Select, Button, Space, Divider, Tooltip } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Cell } from '@antv/x6';
import { NodeDefinition } from '../types';
import React, {useEffect, ReactNode} from 'react';
import {Drawer, Form, Input, Select, Button, Space, Divider, Tooltip} from 'antd';
import {InfoCircleOutlined} from '@ant-design/icons';
import {Cell} from '@antv/x6';
import {NodeDefinitionResponse} from "@/pages/Workflow/NodeDesign/types";
interface NodeConfigDrawerProps {
visible: boolean;
node: Cell | null;
nodeDefinition: NodeDefinition | null;
nodeDefinition: NodeDefinitionResponse | null;
onOk: (values: any) => void;
onCancel: () => void;
}
@ -22,11 +22,11 @@ interface SchemaProperty {
}
const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
visible,
node,
nodeDefinition,
onOk,
onCancel,
visible,
node,
nodeDefinition,
onOk,
onCancel,
}) => {
const [form] = Form.useForm();
@ -34,19 +34,8 @@ const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
if (visible && node && nodeDefinition) {
const currentConfig = node.getProp('config') || {};
if (!currentConfig.name) {
currentConfig.name = nodeDefinition.name;
currentConfig.name = nodeDefinition.nodeName;
}
// 从节点定义中获取 delegate 的默认值
const defaultDelegate = nodeDefinition.graphConfig.configSchema?.properties?.delegate?.default;
// 优先使用当前配置的值,如果没有则使用默认值
const delegateValue = currentConfig.delegate !== undefined ? currentConfig.delegate : defaultDelegate;
// 设置表单值,包括 code 和其他配置项
form.setFieldsValue({
code: node.getProp('code') === undefined ? nodeDefinition.graphConfig.code : node.getProp('code'),
delegate: delegateValue, // 使用当前值或默认值
...currentConfig
});
}
}, [visible, node, nodeDefinition, form]);
@ -73,12 +62,12 @@ const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
{property.title}
{property.description && (
<Tooltip title={property.description}>
<InfoCircleOutlined />
<InfoCircleOutlined/>
</Tooltip>
)}
</Space>
),
rules: [{ required, message: `请输入${property.title}` }],
rules: [{required, message: `请输入${property.title}`}],
};
switch (property.type) {
@ -99,16 +88,16 @@ const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
return (
<Form.Item key={key} {...baseProps}>
{property.format === 'textarea' ? (
<Input.TextArea rows={3} />
<Input.TextArea rows={3}/>
) : (
<Input />
<Input/>
)}
</Form.Item>
);
case 'number':
return (
<Form.Item key={key} {...baseProps}>
<Input type="number" />
<Input type="number"/>
</Form.Item>
);
case 'boolean':
@ -131,7 +120,7 @@ const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
return null;
}
const { configSchema } = nodeDefinition.graphConfig;
const {configSchema} = nodeDefinition.graphConfig;
console.log('NodeConfigModal - Rendering form items with schema:', configSchema);
const formItems: ReactNode[] = [];
@ -155,7 +144,7 @@ const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
const renderNodeDetails = () => {
if (!nodeDefinition?.graphConfig.details) return null;
const { details } = nodeDefinition.graphConfig;
const {details} = nodeDefinition.graphConfig;
return (
<>
<Divider orientation="left"></Divider>
@ -188,7 +177,7 @@ const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
return (
<Drawer
title={`编辑节点 - ${nodeDefinition?.graphConfig.name || ''}`}
title={`编辑节点 - ${nodeDefinition?.nodeName || ''}`}
placement="right"
width={480}
onClose={handleCancel}
@ -210,16 +199,14 @@ const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
initialValues={{}}
>
<Form.Item name="code" hidden>
<Input />
<Input/>
</Form.Item>
<Form.Item name="delegate" hidden>
<Input />
<Input/>
</Form.Item>
{nodeDefinition?.graphConfig.configSchema &&
Object.entries(nodeDefinition.graphConfig.configSchema.properties).map(([key, property]) => {
// 跳过 code 和 delegate 字段的显示
if (key === 'code' || key === 'delegate') return null;
const required = nodeDefinition.graphConfig.configSchema.required?.includes(key) || false;
{nodeDefinition?.panelVariablesSchema &&
Object.entries(nodeDefinition.panelVariablesSchema.properties).map(([key, property]) => {
const required = nodeDefinition.panelVariablesSchema?.properties.required?.includes(key) || false;
return renderFormItem(key, property as SchemaProperty, required);
})}
</Form>

View File

@ -1,5 +1,5 @@
import React, {useState, useEffect} from 'react';
import {Card, Collapse, Tooltip, message, Spin} from 'antd';
import {Card, Collapse, Tooltip, message} from 'antd';
import type {NodeDefinition, NodeCategory} from '../types';
import {
PlayCircleOutlined,
@ -13,6 +13,7 @@ import {
BranchesOutlined
} from '@ant-design/icons';
import {getNodeDefinitionList} from "@/pages/Workflow/Definition/Design/service";
import {NodeDefinitionResponse} from "@/pages/Workflow/NodeDesign/types";
const {Panel} = Collapse;
@ -53,19 +54,20 @@ const categoryConfig: Record<NodeCategory, {
};
interface NodePanelProps {
onNodeDragStart?: (node: NodeDefinition, e: React.DragEvent) => void,
nodeDefinitions?: NodeDefinition[]
onNodeDragStart?: (node: NodeDefinitionResponse, e: React.DragEvent) => void,
nodeDefinitions?: NodeDefinitionResponse[]
}
const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
const [loading, setLoading] = useState(false);
const [nodeDefinitions, setNodeDefinitions] = useState<NodeDefinition[]>([]);
const [nodeDefinitions, setNodeDefinitions] = useState<NodeDefinitionResponse[]>([]);
// 加载节点定义列表
const loadNodeDefinitions = async () => {
setLoading(true);
try {
const data = await getNodeDefinitionList();
console.log(data)
setNodeDefinitions(data);
} catch (error) {
if (error instanceof Error) {
@ -87,29 +89,29 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
}
acc[node.category].push(node);
return acc;
}, {} as Record<NodeCategory, NodeDefinition[]>);
}, {} as Record<NodeCategory, NodeDefinitionResponse[]>);
// 处理节点拖拽开始事件
const handleDragStart = (node: NodeDefinition, e: React.DragEvent) => {
const handleDragStart = (node: NodeDefinitionResponse, e: React.DragEvent) => {
e.dataTransfer.setData('node', JSON.stringify(node));
onNodeDragStart?.(node, e);
};
// 渲染节点图标
const renderNodeIcon = (node: NodeDefinition) => {
const iconName = node.graphConfig.uiSchema.style.icon;
const renderNodeIcon = (node: NodeDefinitionResponse) => {
const iconName = node.uiVariables?.style.icon;
// 首先尝试使用配置的图标
let IconComponent = iconMap[iconName];
// 如果没有找到对应的图标,使用节点类型对应的默认图标
if (!IconComponent) {
IconComponent = typeIconMap[node.type] || AppstoreOutlined;
IconComponent = typeIconMap[node.nodeType] || AppstoreOutlined;
}
return (
<IconComponent
style={{
color: node.graphConfig.uiSchema.style.iconColor || '#1890ff',
color: node.uiVariables?.style.iconColor || '#1890ff',
fontSize: '16px',
marginRight: '6px'
}}
@ -117,17 +119,17 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
);
};
const getNodeItemStyle = (node: NodeDefinition) => ({
const getNodeItemStyle = (node: NodeDefinitionResponse) => ({
width: '100%',
padding: '10px 12px',
border: `1px solid ${node.graphConfig.uiSchema.style.stroke}`,
border: `1px solid ${node.uiVariables?.style.stroke}`,
borderRadius: '6px',
cursor: 'move',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '10px',
background: node.graphConfig.uiSchema.style.fill,
background: node.uiVariables?.style.fill,
transition: 'all 0.3s',
boxShadow: '0 1px 2px rgba(0,0,0,0.05)',
'&:hover': {
@ -163,19 +165,6 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
title={
<div>
<div style={{fontSize: '14px', fontWeight: 500}}>{node.description}</div>
<div style={{marginTop: 12}}>
<div style={{fontSize: '13px', color: '#8c8c8c'}}></div>
<ul style={{
paddingLeft: 16,
margin: '8px 0',
fontSize: '13px',
color: '#595959'
}}>
{node.graphConfig.details.features.map((feature, index) => (
<li key={index}>{feature}</li>
))}
</ul>
</div>
</div>
}
overlayStyle={tooltipStyle}
@ -201,7 +190,7 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}>{node.name}</span>
}}>{node.nodeName}({node.nodeType})</span>
</div>
</div>
</Tooltip>

View File

@ -43,6 +43,7 @@ import {
DEFAULT_STYLES,
} from './constants';
import './index.less';
import {NodeDefinitionResponse, NodeDesignDataResponse} from "@/pages/Workflow/NodeDesign/types";
const WorkflowDesign: React.FC = () => {
const {id} = useParams<{ id: string }>();
@ -52,10 +53,10 @@ const WorkflowDesign: React.FC = () => {
const minimapContainerRef = useRef<HTMLDivElement>(null);
const [graph, setGraph] = useState<Graph | null>(null);
const [selectedNode, setSelectedNode] = useState<Cell | null>(null);
const [selectedNodeDefinition, setSelectedNodeDefinition] = useState<NodeDefinition | null>(null);
const [selectedNodeDefinition, setSelectedNodeDefinition] = useState<NodeDefinitionResponse | null>(null);
const [configModalVisible, setConfigModalVisible] = useState(false);
const [definitionData, setDefinitionData] = useState<any>(null);
const [nodeDefinitions, setNodeDefinitions] = useState<NodeDefinition[]>([]);
const [nodeDefinitions, setNodeDefinitions] = useState<NodeDefinitionResponse[]>([]);
const [isNodeDefinitionsLoaded, setIsNodeDefinitionsLoaded] = useState(false);
const [forceUpdate, setForceUpdate] = useState(false);
const [scale, setScale] = useState(1);
@ -571,9 +572,8 @@ const WorkflowDesign: React.FC = () => {
// 节点双击事件
graph.on('node:dblclick', ({node}) => {
const nodeType = node.getProp('type');
console.log(nodeType)
// 从节点定义列表中找到对应的定义
const nodeDefinition = nodeDefinitions.find(def => def.type === nodeType);
const nodeDefinition = nodeDefinitions.find(def => def.nodeType === nodeType);
if (nodeDefinition) {
setSelectedNode(node);
setSelectedNodeDefinition(nodeDefinition);
@ -1027,7 +1027,7 @@ const WorkflowDesign: React.FC = () => {
}, [graphContainerRef, id, nodeDefinitions, isNodeDefinitionsLoaded]);
// 处理节点拖拽开始
const handleNodeDragStart = (node: NodeDefinition, e: React.DragEvent) => {
const handleNodeDragStart = (node: NodeDefinitionResponse, e: React.DragEvent) => {
e.dataTransfer.setData('node', JSON.stringify(node));
};
@ -1043,6 +1043,7 @@ const WorkflowDesign: React.FC = () => {
const node = JSON.parse(nodeData);
const {clientX, clientY} = e;
const point = graph.clientToLocal({x: clientX, y: clientY});
console.log("dasdasd", graph, node, nodeDefinitions, point);
addNodeToGraph(true, graph, node, nodeDefinitions, point);
} catch (error) {
console.error('创建节点失败:', error);

View File

@ -5,7 +5,7 @@ import {convertPortConfig} from '../constants';
*
* @param isNew
* @param graph X6 Graph实例
* @param workflowDefinitionNode
* @param nodeDefinitions
* @param workflowNodeDefinitionList
* @param position
* @returns
@ -13,13 +13,13 @@ import {convertPortConfig} from '../constants';
export const addNodeToGraph = (
isNew: boolean,
graph: Graph,
workflowDefinitionNode: any,
nodeDefinitions: any,
workflowNodeDefinitionList: any,
position?: { x: number; y: number }
) => {
let nodeDefinition = workflowNodeDefinitionList.find(def => def.type === workflowDefinitionNode.type);
let uiGraph = isNew ? nodeDefinition.graphConfig.uiSchema : workflowDefinitionNode.graph;
let nodeDefinition = workflowNodeDefinitionList.find(def => def.nodeType === nodeDefinitions.nodeType);
let uiGraph = isNew ? nodeDefinition.uiVariables : nodeDefinitions.graph;
console.log(uiGraph)
// 根据形状类型设置正确的 shape
let shape = 'rect'; // 默认使用矩形
if (uiGraph.shape === 'circle') {
@ -41,12 +41,12 @@ export const addNodeToGraph = (
} : {})
},
label: {
text: isNew ? nodeDefinition.name : workflowDefinitionNode.name
text: isNew ? nodeDefinition.nodeName : nodeDefinitions.name
},
},
shape,
type: isNew ? nodeDefinition.type : workflowDefinitionNode.type,
code: uiGraph.code,
type: isNew ? nodeDefinition.nodeType : nodeDefinitions.type,
code: uiGraph.nodeCode,
ports: convertPortConfig(uiGraph.ports),
nodeDefinition: nodeDefinition
};
@ -55,9 +55,9 @@ export const addNodeToGraph = (
if (isNew && position) {
// 新节点使用传入的position
Object.assign(nodeConfig, { x: position.x, y: position.y });
} else if (!isNew && workflowDefinitionNode.graph?.position) {
} else if (!isNew && nodeDefinitions.graph?.position) {
// 已有节点使用后端返回的position
Object.assign(nodeConfig, { position: workflowDefinitionNode.graph.position });
Object.assign(nodeConfig, { position: nodeDefinitions.graph.position });
}
// 设置节点ID如果有

View File

@ -65,8 +65,7 @@ const FormRenderer: React.FC<{
schema: any;
path?: string;
readOnly?: boolean;
currentTab?: any;
}> = ({schema, path = '', readOnly = false, currentTab}) => {
}> = ({schema, path = '', readOnly = false}) => {
if (!schema || !schema.properties) return null;
const renderPortConfig = (portSchema: any, portPath: string) => {
if (!portSchema || !portSchema.properties) return null;
@ -86,12 +85,6 @@ const FormRenderer: React.FC<{
required={portSchema.required?.includes('position')}
initialValue={'default' in position ? position.default : undefined}
style={{marginBottom: 16}}
rules={[
{
required: portSchema.required?.includes('position'),
message: `${position.type === 'string' ? '输入' : '选择'}${position.title}`
}
]}
>
{readOnly ? (
<span style={{color: '#666'}}>{position.default || '-'}</span>
@ -119,7 +112,6 @@ const FormRenderer: React.FC<{
schema={attrs}
path={`${portPath}.attrs`}
readOnly={readOnly}
currentTab={currentTab}
/>
</Card>
)}
@ -200,7 +192,6 @@ const FormRenderer: React.FC<{
schema={value}
path={fieldPath}
readOnly={readOnly}
currentTab={currentTab}
/>
</Card>
);
@ -216,15 +207,9 @@ const FormRenderer: React.FC<{
</span>
}
tooltip={value.description}
required={currentTab?.schemaKey === 'uiVariables' && schema.required?.includes(key)}
required={schema.required?.includes(key)}
initialValue={'default' in value ? value.default : undefined}
style={{marginBottom: 16}}
rules={currentTab?.schemaKey === 'uiVariables' ? [
{
required: schema.required?.includes(key),
message: `${value.type === 'string' ? '输入' : '选择'}${value.title}`
}
] : undefined}
>
{readOnly ? (
<span style={{color: '#666'}}>{value.default || '-'}</span>
@ -276,7 +261,7 @@ const NodeDesignForm: React.FC = () => {
useEffect(() => {
const loadNodeDetail = async () => {
if (!id) return;
try {
setLoading(true);
const data = await service.getNodeDefinition(id);
@ -318,7 +303,7 @@ const NodeDesignForm: React.FC = () => {
console.log('选择节点:', node);
console.log('当前编辑状态:', isEdit);
console.log('当前编辑数据:', editData);
setSelectedNode(node);
// 更新表单数据
form.setFieldsValue({
@ -341,7 +326,7 @@ const NodeDesignForm: React.FC = () => {
try {
const values = await form.validateFields();
console.log('Form values:', values);
// 提取基本信息字段
const baseFields = {
nodeType: values['base.nodeType'],
@ -362,7 +347,7 @@ const NodeDesignForm: React.FC = () => {
// 处理颜色值转换为十六进制
const processColorValue = (value: any): any => {
if (!value || typeof value !== 'object') return value;
// 如果是 ColorPicker 的值,转换为十六进制
if (value.metaColor) {
const { r, g, b } = value.metaColor;
@ -372,7 +357,7 @@ const NodeDesignForm: React.FC = () => {
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
// 如果是普通对象,递归处理
if (typeof value === 'object') {
const result: any = {};
@ -381,39 +366,39 @@ const NodeDesignForm: React.FC = () => {
});
return result;
}
return value;
};
// 将扁平的键值对转换为嵌套对象
const convertToNestedObject = (flatObj: any) => {
const result: any = {};
Object.keys(flatObj).forEach(key => {
const parts = key.split('.');
let current = result;
for (let i = 0; i < parts.length - 1; i++) {
current[parts[i]] = current[parts[i]] || {};
current = current[parts[i]];
}
// 处理颜色值
const value = processColorValue(flatObj[key]);
current[parts[parts.length - 1]] = value;
});
return result;
};
const saveData = {
...selectedNode,
...baseFields,
uiVariables: convertToNestedObject(uiValues)
};
console.log('Save data:', saveData);
// 根据是否是编辑模式调用不同的接口
if (isEdit && editData?.id) {
await service.updateNodeDefinition(editData.id, saveData);
@ -438,7 +423,7 @@ const NodeDesignForm: React.FC = () => {
console.log('当前 schema:', schema);
console.log('是否编辑模式:', isEdit);
console.log('编辑数据:', editData);
// 如果是编辑模式且有保存的数据,合并到 schema
if (isEdit && editData?.uiVariables) {
console.log('开始合并 UI 配置数据');
@ -446,7 +431,7 @@ const NodeDesignForm: React.FC = () => {
// 处理颜色值
const processColorValue = (value: any): string => {
if (!value || typeof value !== 'object') return value;
// 如果是颜色对象结构
if (value.metaColor) {
const { r, g, b } = value.metaColor;
@ -457,7 +442,7 @@ const NodeDesignForm: React.FC = () => {
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
return value;
};
@ -466,7 +451,7 @@ const NodeDesignForm: React.FC = () => {
console.log('正在处理 schema:', schemaObj);
console.log('UI 数据:', uiData);
console.log('当前路径:', parentPath);
const result = {...schemaObj};
// 颜色字段路径列表
@ -484,7 +469,7 @@ const NodeDesignForm: React.FC = () => {
Object.keys(result.properties).forEach(key => {
const currentPath = parentPath ? `${parentPath}.${key}` : key;
console.log('处理属性:', currentPath);
// 处理嵌套对象
if (result.properties[key].type === 'object') {
const nestedValue = currentPath.split('.').reduce((obj, key) => obj?.[key], uiData);
@ -492,7 +477,7 @@ const NodeDesignForm: React.FC = () => {
if (nestedValue !== undefined) {
result.properties[key].default = nestedValue;
}
}
}
// 处理基本类型
else {
const value = currentPath.split('.').reduce((obj, key) => obj?.[key], uiData);
@ -506,11 +491,11 @@ const NodeDesignForm: React.FC = () => {
}
}
}
// 递归处理嵌套属性
result.properties[key] = mergeUiVariables(
result.properties[key],
uiData,
result.properties[key],
uiData,
currentPath
);
});
@ -527,7 +512,7 @@ const NodeDesignForm: React.FC = () => {
console.log('合并后的 schema:', mergedSchema);
return mergedSchema;
}
console.log('使用原始 schema');
return schema;
};
@ -551,8 +536,8 @@ const NodeDesignForm: React.FC = () => {
<Tabs
tabPosition="left"
type="card"
activeKey={isEdit && editData?.nodeType ?
nodeDefinitionsDefined.find(n => n.nodeType === editData.nodeType)?.nodeCode || selectedNode?.nodeCode :
activeKey={isEdit && editData?.nodeType ?
nodeDefinitionsDefined.find(n => n.nodeType === editData.nodeType)?.nodeCode || selectedNode?.nodeCode :
selectedNode?.nodeCode || ''
}
onChange={(key) => {
@ -653,7 +638,6 @@ const NodeDesignForm: React.FC = () => {
<FormRenderer
schema={getCurrentSchema()}
readOnly={tab.readonly}
currentTab={tab}
/>
</div>
) : null

View File

@ -71,11 +71,12 @@ export interface UIVariables extends BaseSchema {
}
// 节点设计数据
export interface NodeDesignDataResponse extends BaseResponse{
export interface NodeDesignDataResponse extends BaseResponse {
nodeCode: string;
nodeName: string;
nodeType: string;
category: string;
description: string;
panelVariablesSchema: NodeVariablesSchema | null;
localVariablesSchema: NodeVariablesSchema | null;
formVariablesSchema: NodeVariablesSchema | null;
@ -100,6 +101,8 @@ export interface NodeDefinitionResponse extends BaseResponse {
nodeCode: string;
nodeName: string;
nodeType: NodeTypeEnum;
category: string;
description: string;
panelVariablesSchema: NodeVariablesSchema | null;
uiVariables: UIVariables | null;
localVariablesSchema: NodeVariablesSchema | null;