增加工具栏提示。

This commit is contained in:
dengqichen 2024-12-13 09:36:34 +08:00
parent ca749aa7af
commit 56861d57e9
2 changed files with 301 additions and 4 deletions

View File

@ -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;

View File

@ -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>
); );
}; };