增加工具栏提示。
This commit is contained in:
parent
ca749aa7af
commit
56861d57e9
@ -0,0 +1,206 @@
|
||||
import React, { useEffect } 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';
|
||||
|
||||
interface NodeConfigDrawerProps {
|
||||
visible: boolean;
|
||||
node: Cell | null;
|
||||
nodeDefinition: NodeDefinition | null;
|
||||
onOk: (values: any) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
interface SchemaProperty {
|
||||
type: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
enum?: string[];
|
||||
enumNames?: string[];
|
||||
format?: string;
|
||||
}
|
||||
|
||||
const NodeConfigDrawer: React.FC<NodeConfigDrawerProps> = ({
|
||||
visible,
|
||||
node,
|
||||
nodeDefinition,
|
||||
onOk,
|
||||
onCancel,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
if (visible && node) {
|
||||
// 获取节点当前的配置
|
||||
const currentConfig = {
|
||||
code: node.getProp('code'),
|
||||
name: node.attr('label/text'),
|
||||
description: node.getProp('description'),
|
||||
...node.getProp('config'),
|
||||
};
|
||||
|
||||
form.setFieldsValue(currentConfig);
|
||||
}
|
||||
}, [visible, node, form]);
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
onOk(values);
|
||||
form.resetFields();
|
||||
} catch (error) {
|
||||
console.error('Validation failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
form.resetFields();
|
||||
onCancel();
|
||||
};
|
||||
|
||||
const renderFormItem = (key: string, property: SchemaProperty, required: boolean) => {
|
||||
const baseProps = {
|
||||
name: key,
|
||||
label: (
|
||||
<Space>
|
||||
{property.title}
|
||||
{property.description && (
|
||||
<Tooltip title={property.description}>
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
rules: [{ required, message: `请输入${property.title}` }],
|
||||
};
|
||||
|
||||
switch (property.type) {
|
||||
case 'string':
|
||||
if (property.enum) {
|
||||
return (
|
||||
<Form.Item key={key} {...baseProps}>
|
||||
<Select>
|
||||
{property.enum.map((value, index) => (
|
||||
<Select.Option key={value} value={value}>
|
||||
{property.enumNames?.[index] || value}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Form.Item key={key} {...baseProps}>
|
||||
{property.format === 'textarea' ? (
|
||||
<Input.TextArea rows={3} />
|
||||
) : (
|
||||
<Input />
|
||||
)}
|
||||
</Form.Item>
|
||||
);
|
||||
case 'number':
|
||||
return (
|
||||
<Form.Item key={key} {...baseProps}>
|
||||
<Input type="number" />
|
||||
</Form.Item>
|
||||
);
|
||||
case 'boolean':
|
||||
return (
|
||||
<Form.Item key={key} {...baseProps} valuePropName="checked">
|
||||
<Select>
|
||||
<Select.Option value={true}>是</Select.Option>
|
||||
<Select.Option value={false}>否</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const renderFormItems = () => {
|
||||
if (!nodeDefinition?.graphConfig.configSchema) return null;
|
||||
|
||||
const { configSchema } = nodeDefinition.graphConfig;
|
||||
const formItems = [];
|
||||
|
||||
// 根据 configSchema 生成表单项
|
||||
if (configSchema.properties) {
|
||||
Object.entries(configSchema.properties).forEach(([key, property]: [string, SchemaProperty]) => {
|
||||
const isRequired = configSchema.required?.includes(key) || false;
|
||||
const formItem = renderFormItem(key, property, isRequired);
|
||||
if (formItem) {
|
||||
formItems.push(formItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return formItems;
|
||||
};
|
||||
|
||||
// 显示节点详细信息
|
||||
const renderNodeDetails = () => {
|
||||
if (!nodeDefinition?.graphConfig.details) return null;
|
||||
|
||||
const { details } = nodeDefinition.graphConfig;
|
||||
return (
|
||||
<>
|
||||
<Divider orientation="left">节点说明</Divider>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<p>{details.description}</p>
|
||||
{details.features && details.features.length > 0 && (
|
||||
<div>
|
||||
<strong>功能特点:</strong>
|
||||
<ul>
|
||||
{details.features.map((feature, index) => (
|
||||
<li key={index}>{feature}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{details.scenarios && details.scenarios.length > 0 && (
|
||||
<div>
|
||||
<strong>适用场景:</strong>
|
||||
<ul>
|
||||
{details.scenarios.map((scenario, index) => (
|
||||
<li key={index}>{scenario}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title={`编辑节点 - ${nodeDefinition?.graphConfig.name || ''}`}
|
||||
placement="right"
|
||||
width={480}
|
||||
onClose={handleCancel}
|
||||
open={visible}
|
||||
extra={
|
||||
<Space>
|
||||
<Button onClick={handleCancel}>取消</Button>
|
||||
<Button type="primary" onClick={handleOk}>
|
||||
确定
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
{renderNodeDetails()}
|
||||
<Divider orientation="left">节点配置</Divider>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{}}
|
||||
>
|
||||
{renderFormItems()}
|
||||
</Form>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeConfigDrawer;
|
||||
@ -1,10 +1,11 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Button, Space, Card, Row, Col } from 'antd';
|
||||
import { Button, Space, Card, Row, Col, message } from 'antd';
|
||||
import { ArrowLeftOutlined, SaveOutlined, PlayCircleOutlined } from '@ant-design/icons';
|
||||
import { Graph, Shape } from '@antv/x6';
|
||||
import { getDefinitionDetail } from '../service';
|
||||
import { Graph, Cell } from '@antv/x6';
|
||||
import { getDefinitionDetail, saveDefinition } from '../service';
|
||||
import NodePanel from './components/NodePanel';
|
||||
import NodeConfigDrawer from './components/NodeConfigModal';
|
||||
import { NodeDefinition } from './types';
|
||||
import {
|
||||
NODE_REGISTRY_CONFIG,
|
||||
@ -20,6 +21,10 @@ const WorkflowDesign: React.FC = () => {
|
||||
const [title, setTitle] = useState<string>('工作流设计');
|
||||
const graphContainerRef = 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 [configModalVisible, setConfigModalVisible] = useState(false);
|
||||
const [definitionData, setDefinitionData] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
@ -99,6 +104,14 @@ const WorkflowDesign: React.FC = () => {
|
||||
});
|
||||
});
|
||||
|
||||
// 节点双击事件
|
||||
graph.on('node:dblclick', ({ node }) => {
|
||||
const nodeDefinition = node.getProp('nodeDefinition');
|
||||
setSelectedNode(node);
|
||||
setSelectedNodeDefinition(nodeDefinition);
|
||||
setConfigModalVisible(true);
|
||||
});
|
||||
|
||||
setGraph(graph);
|
||||
|
||||
return () => {
|
||||
@ -111,6 +124,7 @@ const WorkflowDesign: React.FC = () => {
|
||||
try {
|
||||
const response = await getDefinitionDetail(id);
|
||||
setTitle(`工作流设计 - ${response.name}`);
|
||||
setDefinitionData(response);
|
||||
} catch (error) {
|
||||
console.error('Failed to load workflow definition:', error);
|
||||
}
|
||||
@ -155,6 +169,9 @@ const WorkflowDesign: React.FC = () => {
|
||||
...nodeConfig,
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
data: { type: node.type },
|
||||
// 保存节点定义,用于后续编辑
|
||||
nodeDefinition: node,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -164,6 +181,72 @@ const WorkflowDesign: React.FC = () => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const handleNodeConfigUpdate = (values: any) => {
|
||||
if (!selectedNode) return;
|
||||
|
||||
// 更新节点配置
|
||||
selectedNode.setProp('config', values);
|
||||
// 更新节点显示名称
|
||||
if (values.name) {
|
||||
selectedNode.attr('label/text', values.name);
|
||||
}
|
||||
|
||||
setConfigModalVisible(false);
|
||||
message.success('节点配置已更新');
|
||||
};
|
||||
|
||||
const handleSaveWorkflow = async () => {
|
||||
if (!graph || !definitionData) return;
|
||||
|
||||
try {
|
||||
// 获取所有节点和边的数据
|
||||
const nodes = graph.getNodes().map(node => {
|
||||
const nodeType = node.getProp('type');
|
||||
const nodeDefinition = NODE_REGISTRY_CONFIG[nodeType];
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
code: node.getProp('code'),
|
||||
type: nodeType,
|
||||
name: node.attr('label/text'),
|
||||
graph: {
|
||||
shape: nodeDefinition?.graphConfig.uiSchema.shape,
|
||||
size: node.size(),
|
||||
style: nodeDefinition?.graphConfig.uiSchema.style,
|
||||
ports: nodeDefinition?.graphConfig.uiSchema.ports
|
||||
},
|
||||
config: node.getProp('config') || {}
|
||||
};
|
||||
});
|
||||
|
||||
const edges = graph.getEdges().map(edge => ({
|
||||
id: edge.id,
|
||||
from: edge.getSourceCellId(),
|
||||
to: edge.getTargetCellId(),
|
||||
name: edge.getLabels()?.[0]?.attrs?.label?.text || '',
|
||||
config: {
|
||||
type: 'sequence'
|
||||
}
|
||||
}));
|
||||
|
||||
// 构建保存数据
|
||||
const saveData = {
|
||||
...definitionData,
|
||||
graph: {
|
||||
nodes,
|
||||
edges
|
||||
}
|
||||
};
|
||||
|
||||
// 调用保存接口
|
||||
await saveDefinition(saveData);
|
||||
message.success('流程保存成功');
|
||||
} catch (error) {
|
||||
console.error('保存流程失败:', error);
|
||||
message.error('保存流程失败');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: '24px' }}>
|
||||
<Card
|
||||
@ -173,7 +256,7 @@ const WorkflowDesign: React.FC = () => {
|
||||
<Button
|
||||
icon={<SaveOutlined />}
|
||||
type="primary"
|
||||
onClick={() => console.log('Save workflow')}
|
||||
onClick={handleSaveWorkflow}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
@ -222,6 +305,14 @@ const WorkflowDesign: React.FC = () => {
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<NodeConfigDrawer
|
||||
visible={configModalVisible}
|
||||
node={selectedNode}
|
||||
nodeDefinition={selectedNodeDefinition}
|
||||
onOk={handleNodeConfigUpdate}
|
||||
onCancel={() => setConfigModalVisible(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user