This commit is contained in:
asp_ly 2024-12-14 13:21:57 +08:00
parent b843b4603f
commit b0dadc9a24
2 changed files with 276 additions and 152 deletions

View File

@ -0,0 +1,110 @@
.workflow-design {
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
.header {
padding: 16px;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
.back-button {
margin-right: 16px;
}
.actions {
.ant-space-compact {
margin-right: 8px;
}
}
}
.content {
flex: 1;
min-height: 0;
display: flex;
padding: 16px;
gap: 16px;
.sidebar {
width: 280px;
flex-shrink: 0;
border-right: 1px solid #f0f0f0;
overflow-y: auto;
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
:global {
.ant-collapse {
border: none;
background: transparent;
.ant-collapse-item {
border-radius: 0;
.ant-collapse-header {
padding: 8px 16px;
}
.ant-collapse-content-box {
padding: 0;
}
}
}
}
.node-item {
padding: 8px 16px;
margin: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: move;
transition: all 0.3s;
&:hover {
background: #f5f5f5;
border-color: #1890ff;
}
}
}
.main-area {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
.workflow-container {
flex: 1;
position: relative;
border: 1px solid #d9d9d9;
border-radius: 4px;
background: #f5f5f5;
overflow: hidden;
.workflow-canvas {
width: 100%;
height: 100%;
}
.minimap-container {
position: absolute;
right: 20px;
bottom: 20px;
width: 200px;
height: 150px;
border: 1px solid #f0f0f0;
border-radius: 4px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
z-index: 1;
}
}
}
}
}

View File

@ -1,6 +1,6 @@
import React, {useEffect, useState, useRef} from 'react';
import {useParams, useNavigate} from 'react-router-dom';
import {Button, Space, Card, Row, Col, message, Modal} from 'antd';
import {Button, Space, Card, Row, Col, message, Modal, Collapse} from 'antd';
import {
ArrowLeftOutlined,
SaveOutlined,
@ -18,6 +18,7 @@ import '@antv/x6-plugin-snapline';
import '@antv/x6-plugin-clipboard';
import '@antv/x6-plugin-history';
import { Selection } from '@antv/x6-plugin-selection';
import { MiniMap } from '@antv/x6-plugin-minimap';
import {getDefinitionDetail, saveDefinition} from '../service';
import {getNodeDefinitionList} from './service';
import NodePanel from './components/NodePanel';
@ -32,12 +33,14 @@ import {
HIGHLIGHTING_CONFIG,
DEFAULT_STYLES,
} from './constants';
import './index.less';
const WorkflowDesign: React.FC = () => {
const {id} = useParams<{ id: string }>();
const navigate = useNavigate();
const [title, setTitle] = useState<string>('工作流设计');
const graphContainerRef = useRef<HTMLDivElement>(null);
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);
@ -79,6 +82,8 @@ const WorkflowDesign: React.FC = () => {
background: {
color: '#f5f5f5',
},
width: graphContainerRef.current.clientWidth,
height: graphContainerRef.current.clientHeight,
});
// 注册选择插件
@ -94,6 +99,18 @@ const WorkflowDesign: React.FC = () => {
})
);
// 注册小地图插件
if (minimapContainerRef.current) {
graph.use(
new MiniMap({
container: minimapContainerRef.current,
width: 200,
height: 150,
padding: 10,
})
);
}
// 添加右键菜单
graph.on('node:contextmenu', ({cell, view, e}) => {
e.preventDefault();
@ -233,6 +250,11 @@ const WorkflowDesign: React.FC = () => {
};
}, [graphContainerRef, id, nodeDefinitions, isNodeDefinitionsLoaded]);
useEffect(() => {
if (!graph || !minimapContainerRef.current) return;
}, [graph]);
const loadDefinitionDetail = async (graphInstance: Graph, definitionId: string) => {
try {
const response = await getDefinitionDetail(Number(definitionId));
@ -399,164 +421,156 @@ const WorkflowDesign: React.FC = () => {
};
return (
<div>
<Row gutter={16}>
<Col span={4}>
<div className="workflow-design">
<div className="header">
<Space>
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate('/workflow/definition')}
className="back-button"
>
</Button>
<span></span>
</Space>
<div className="actions">
<Space>
<Space.Compact>
<Button
icon={<SelectOutlined />}
onClick={() => {
if (!graph) return;
const cells = graph.getCells();
if (cells.length === 0) {
message.info('当前没有可选择的元素');
return;
}
graph.resetSelection();
graph.select(cells);
}}
title="全选"
/>
<Button
icon={<CopyOutlined />}
onClick={() => {
if (!graph) return;
const cells = graph.getSelectedCells();
if (cells.length === 0) {
message.info('请先选择要复制的元素');
return;
}
graph.copy(cells);
}}
title="复制"
/>
<Button
icon={<ScissorOutlined />}
onClick={() => {
if (!graph) return;
const cells = graph.getSelectedCells();
if (cells.length === 0) {
message.info('请先选择要剪切的元素');
return;
}
graph.cut(cells);
}}
title="剪切"
/>
<Button
icon={<SnippetsOutlined />}
onClick={() => {
if (!graph) return;
if (!graph.isClipboardEmpty()) {
const cells = graph.paste({ offset: 32 });
graph.cleanSelection();
graph.select(cells);
} else {
message.info('剪贴板为空');
}
}}
title="粘贴"
/>
<Button
icon={<DeleteOutlined />}
onClick={() => {
if (!graph) return;
const cells = graph.getSelectedCells();
if (cells.length === 0) {
message.info('请先选择要删除的元素');
return;
}
Modal.confirm({
title: '确认删除',
content: '确定要删除选中的元素吗?',
onOk: () => {
graph.removeCells(cells);
}
});
}}
danger
title="删除"
/>
</Space.Compact>
<Space.Compact>
<Button
icon={<UndoOutlined />}
onClick={() => {
if (!graph) return;
if (graph.canUndo()) {
graph.undo();
} else {
message.info('没有可撤销的操作');
}
}}
title="撤销"
/>
<Button
icon={<RedoOutlined />}
onClick={() => {
if (!graph) return;
if (graph.canRedo()) {
graph.redo();
} else {
message.info('没有可重做的操作');
}
}}
title="重做"
/>
</Space.Compact>
<Button onClick={handleSaveWorkflow} type="primary" icon={<SaveOutlined />}>
</Button>
</Space>
</div>
</div>
<div className="content">
<div className="sidebar">
<NodePanel
nodeDefinitions={nodeDefinitions}
onNodeDragStart={handleNodeDragStart}
/>
</Col>
<Col span={20}>
<Card
styles={{
body: {
padding: 0,
height: 'calc(100vh - 250px)',
background: '#f5f5f5',
border: '1px solid #d9d9d9',
borderRadius: '4px',
}
}}
extra={
<Space>
<Space.Compact>
<Button
icon={<SelectOutlined />}
onClick={() => {
if (!graph) return;
const cells = graph.getCells();
if (cells.length === 0) {
message.info('当前没有可选择的元素');
return;
}
graph.resetSelection();
graph.select(cells);
}}
title="全选"
/>
<Button
icon={<CopyOutlined />}
onClick={() => {
if (!graph) return;
const cells = graph.getSelectedCells();
if (cells.length === 0) {
message.info('请先选择要复制的元素');
return;
}
graph.copy(cells);
}}
title="复制"
/>
<Button
icon={<ScissorOutlined />}
onClick={() => {
if (!graph) return;
const cells = graph.getSelectedCells();
if (cells.length === 0) {
message.info('请先选择要剪切的元素');
return;
}
graph.cut(cells);
}}
title="剪切"
/>
<Button
icon={<SnippetsOutlined />}
onClick={() => {
if (!graph) return;
if (!graph.isClipboardEmpty()) {
const cells = graph.paste({ offset: 32 });
graph.cleanSelection();
graph.select(cells);
} else {
message.info('剪贴板为空');
}
}}
title="粘贴"
/>
<Button
icon={<DeleteOutlined />}
onClick={() => {
if (!graph) return;
const cells = graph.getSelectedCells();
if (cells.length === 0) {
message.info('请先选择要删除的元素');
return;
}
Modal.confirm({
title: '确认删除',
content: '确定要删除选中的元素吗?',
onOk: () => {
graph.removeCells(cells);
}
});
}}
danger
title="删除"
/>
</Space.Compact>
<Space.Compact>
<Button
icon={<UndoOutlined />}
onClick={() => {
if (!graph) return;
if (graph.canUndo()) {
graph.undo();
} else {
message.info('没有可撤销的操作');
}
}}
title="撤销"
/>
<Button
icon={<RedoOutlined />}
onClick={() => {
if (!graph) return;
if (graph.canRedo()) {
graph.redo();
} else {
message.info('没有可重做的操作');
}
}}
title="重做"
/>
</Space.Compact>
<Button
icon={<SaveOutlined/>}
type="primary"
onClick={handleSaveWorkflow}
>
</Button>
<Button
icon={<ArrowLeftOutlined/>}
onClick={() => navigate('/workflow/definition')}
>
</Button>
</Space>
}
>
<div
ref={graphContainerRef}
style={{
width: '100%',
height: '100%',
}}
</div>
<div className="main-area">
<div className="workflow-container">
<div
ref={graphContainerRef}
className="workflow-canvas"
onDrop={handleDrop}
onDragOver={handleDragOver}
/>
</Card>
</Col>
</Row>
<NodeConfigDrawer
visible={configModalVisible}
node={selectedNode}
nodeDefinition={selectedNodeDefinition}
onOk={handleNodeConfigUpdate}
onCancel={() => setConfigModalVisible(false)}
/>
<div ref={minimapContainerRef} className="minimap-container" />
</div>
</div>
</div>
{configModalVisible && selectedNode && selectedNodeDefinition && (
<NodeConfigDrawer
visible={configModalVisible}
node={selectedNode}
nodeDefinition={selectedNodeDefinition}
onCancel={() => setConfigModalVisible(false)}
onOk={handleNodeConfigUpdate}
/>
)}
</div>
);
};