增加工具栏提示。
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 React, { useEffect, useState, useRef } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
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 { ArrowLeftOutlined, SaveOutlined, PlayCircleOutlined } from '@ant-design/icons';
|
||||||
import { Graph, Shape } from '@antv/x6';
|
import { Graph, Cell } from '@antv/x6';
|
||||||
import { getDefinitionDetail } from '../service';
|
import { getDefinitionDetail, saveDefinition } from '../service';
|
||||||
import NodePanel from './components/NodePanel';
|
import NodePanel from './components/NodePanel';
|
||||||
|
import NodeConfigDrawer from './components/NodeConfigModal';
|
||||||
import { NodeDefinition } from './types';
|
import { NodeDefinition } from './types';
|
||||||
import {
|
import {
|
||||||
NODE_REGISTRY_CONFIG,
|
NODE_REGISTRY_CONFIG,
|
||||||
@ -20,6 +21,10 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
const [title, setTitle] = useState<string>('工作流设计');
|
const [title, setTitle] = useState<string>('工作流设计');
|
||||||
const graphContainerRef = useRef<HTMLDivElement>(null);
|
const graphContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const [graph, setGraph] = useState<Graph | null>(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(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
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);
|
setGraph(graph);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -111,6 +124,7 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const response = await getDefinitionDetail(id);
|
const response = await getDefinitionDetail(id);
|
||||||
setTitle(`工作流设计 - ${response.name}`);
|
setTitle(`工作流设计 - ${response.name}`);
|
||||||
|
setDefinitionData(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load workflow definition:', error);
|
console.error('Failed to load workflow definition:', error);
|
||||||
}
|
}
|
||||||
@ -155,6 +169,9 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
...nodeConfig,
|
...nodeConfig,
|
||||||
x: point.x,
|
x: point.x,
|
||||||
y: point.y,
|
y: point.y,
|
||||||
|
data: { type: node.type },
|
||||||
|
// 保存节点定义,用于后续编辑
|
||||||
|
nodeDefinition: node,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,6 +181,72 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
e.preventDefault();
|
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 (
|
return (
|
||||||
<div style={{ padding: '24px' }}>
|
<div style={{ padding: '24px' }}>
|
||||||
<Card
|
<Card
|
||||||
@ -173,7 +256,7 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
icon={<SaveOutlined />}
|
icon={<SaveOutlined />}
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => console.log('Save workflow')}
|
onClick={handleSaveWorkflow}
|
||||||
>
|
>
|
||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
@ -222,6 +305,14 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<NodeConfigDrawer
|
||||||
|
visible={configModalVisible}
|
||||||
|
node={selectedNode}
|
||||||
|
nodeDefinition={selectedNodeDefinition}
|
||||||
|
onOk={handleNodeConfigUpdate}
|
||||||
|
onCancel={() => setConfigModalVisible(false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user