1
This commit is contained in:
parent
e2f176503d
commit
0713c2c83e
@ -0,0 +1,67 @@
|
|||||||
|
.node-panel {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-panel-loading {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-panel-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: move;
|
||||||
|
transition: all 0.3s;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-card:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-name {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-tabs {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-tabs :global(.ant-tabs-content) {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-tabs :global(.ant-tabs-tabpane) {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
.nodeTabs {
|
:global {
|
||||||
|
.workflow-node-tabs {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
:global {
|
|
||||||
.ant-tabs {
|
.ant-tabs {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
@ -68,9 +68,8 @@
|
|||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.nodeCard {
|
.workflow-node-card {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -84,8 +83,9 @@
|
|||||||
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
|
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nodeIcon {
|
.workflow-node-icon {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -100,17 +100,17 @@
|
|||||||
.anticon {
|
.anticon {
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .nodeIcon {
|
&:hover {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
|
|
||||||
.anticon {
|
.anticon {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nodeName {
|
.workflow-node-name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: #333;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -118,8 +118,4 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,36 +1,35 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {Card, Empty, Spin, Tabs, Tooltip} from 'antd';
|
import { Tabs, Spin } from 'antd';
|
||||||
import {NodeCategory, NodeType} from '../../types';
|
import { NodeType, getNodeTypes } from '../../service';
|
||||||
import {getNodeTypes} from '../../service';
|
import './index.css';
|
||||||
import * as Icons from '@ant-design/icons';
|
|
||||||
import styles from './NodePanel.module.less';
|
|
||||||
|
|
||||||
interface NodePanelProps {
|
interface NodePanelProps {
|
||||||
onNodeDragStart: (nodeType: NodeType) => void;
|
onNodeDragStart: (nodeType: NodeType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
|
const NodePanel: React.FC<NodePanelProps> = ({ onNodeDragStart }) => {
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [nodeTypes, setNodeTypes] = useState<NodeType[]>([]);
|
const [nodeTypes, setNodeTypes] = useState<NodeType[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
// 获取节点类型列表
|
useEffect(() => {
|
||||||
const fetchNodeTypes = async () => {
|
const fetchNodeTypes = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await getNodeTypes({enabled: true});
|
const response = await getNodeTypes({ enabled: true });
|
||||||
if (response) {
|
if (response) {
|
||||||
setNodeTypes(response);
|
setNodeTypes(response);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取节点类型失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchNodeTypes();
|
fetchNodeTypes();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 按分类分组节点类型
|
// 按类别分组节点类型
|
||||||
const groupedNodeTypes = nodeTypes.reduce((acc, nodeType) => {
|
const groupedNodeTypes = nodeTypes.reduce((acc, nodeType) => {
|
||||||
const category = nodeType.category;
|
const category = nodeType.category;
|
||||||
if (!acc[category]) {
|
if (!acc[category]) {
|
||||||
@ -38,84 +37,62 @@ const NodePanel: React.FC<NodePanelProps> = ({onNodeDragStart}) => {
|
|||||||
}
|
}
|
||||||
acc[category].push(nodeType);
|
acc[category].push(nodeType);
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<NodeCategory, NodeType[]>);
|
}, {} as Record<string, NodeType[]>);
|
||||||
|
|
||||||
// 渲染图标
|
// 将分组转换为 Tabs 项
|
||||||
const renderIcon = (iconName: string) => {
|
const tabItems = Object.entries(groupedNodeTypes).map(([category, types]) => ({
|
||||||
// @ts-ignore
|
key: category,
|
||||||
const Icon = Icons[iconName];
|
label: getCategoryLabel(category),
|
||||||
return Icon ? <Icon/> : null;
|
children: (
|
||||||
};
|
<div className="node-panel-content">
|
||||||
|
{types.map((nodeType) => (
|
||||||
// 渲染节点类型卡片
|
<div
|
||||||
const renderNodeTypeCard = (nodeType: NodeType) => (
|
|
||||||
<Tooltip
|
|
||||||
key={nodeType.code}
|
key={nodeType.code}
|
||||||
title={nodeType.description}
|
className="node-card"
|
||||||
placement="right"
|
|
||||||
mouseEnterDelay={0.5}
|
|
||||||
>
|
|
||||||
<Card
|
|
||||||
className={styles.nodeCard}
|
|
||||||
draggable
|
draggable
|
||||||
onDragStart={(e) => {
|
onDragStart={(e) => {
|
||||||
e.dataTransfer.setData('nodeType', JSON.stringify(nodeType));
|
e.dataTransfer.setData('node-type', JSON.stringify(nodeType));
|
||||||
onNodeDragStart(nodeType);
|
onNodeDragStart(nodeType);
|
||||||
}}
|
}}
|
||||||
|
style={{ borderColor: nodeType.color }}
|
||||||
>
|
>
|
||||||
<div className={styles.nodeIcon} style={{backgroundColor: nodeType.color}}>
|
<div className="node-icon" style={{ color: nodeType.color }}>
|
||||||
{renderIcon(nodeType.icon)}
|
<i className={nodeType.icon} />
|
||||||
|
</div>
|
||||||
|
<div className="node-name">{nodeType.name}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.nodeName}>{nodeType.name}</div>
|
|
||||||
</Card>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
|
|
||||||
const categoryIcons = {
|
|
||||||
[NodeCategory.TASK]: renderIcon('CodeOutlined'),
|
|
||||||
[NodeCategory.EVENT]: renderIcon('ThunderboltOutlined'),
|
|
||||||
[NodeCategory.GATEWAY]: renderIcon('ForkOutlined')
|
|
||||||
};
|
|
||||||
|
|
||||||
const categoryLabels = {
|
|
||||||
[NodeCategory.TASK]: '任务节点',
|
|
||||||
[NodeCategory.EVENT]: '事件节点',
|
|
||||||
[NodeCategory.GATEWAY]: '网关节点'
|
|
||||||
};
|
|
||||||
|
|
||||||
const tabItems = Object.values(NodeCategory).map(category => ({
|
|
||||||
key: category,
|
|
||||||
label: (
|
|
||||||
<span>
|
|
||||||
{categoryIcons[category]} {categoryLabels[category]}
|
|
||||||
<span style={{color: '#999', fontSize: 12, marginLeft: 4}}>
|
|
||||||
({groupedNodeTypes[category]?.length || 0})
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
),
|
),
|
||||||
children: groupedNodeTypes[category]?.map(renderNodeTypeCard) || (
|
|
||||||
<Empty
|
|
||||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
||||||
description={`暂无${categoryLabels[category]}`}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div style={{textAlign: 'center', padding: '20px 0'}}>
|
<div className="node-panel-loading">
|
||||||
<Spin/>
|
<Spin />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="node-panel">
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultActiveKey={NodeCategory.TASK}
|
className="node-tabs"
|
||||||
|
defaultActiveKey="TASK"
|
||||||
items={tabItems}
|
items={tabItems}
|
||||||
className={styles.nodeTabs}
|
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取类别标签
|
||||||
|
const getCategoryLabel = (category: string) => {
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
TASK: '任务节点',
|
||||||
|
EVENT: '事件节点',
|
||||||
|
GATEWAY: '网关节点',
|
||||||
|
};
|
||||||
|
return labels[category] || category;
|
||||||
|
};
|
||||||
|
|
||||||
export default NodePanel;
|
export default NodePanel;
|
||||||
@ -1,30 +1,28 @@
|
|||||||
.container {
|
:global {
|
||||||
height: calc(100vh - 180px);
|
.workflow-designer {
|
||||||
border: 1px solid #f0f0f0;
|
&-container {
|
||||||
|
height: calc(100vh - 170px);
|
||||||
|
background: #fff;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
.sider {
|
|
||||||
border-right: 1px solid #f0f0f0;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
.nodePanel {
|
|
||||||
padding: 16px;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
&-sider {
|
||||||
|
background: #fff;
|
||||||
|
border-right: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.graph {
|
&-graph {
|
||||||
position: absolute;
|
width: 100%;
|
||||||
top: 0;
|
height: 100%;
|
||||||
right: 0;
|
background: #fff;
|
||||||
bottom: 0;
|
border: 1px solid #e8e8e8;
|
||||||
left: 0;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,14 +1,13 @@
|
|||||||
declare namespace IndexModuleLessNamespace {
|
declare namespace DesignerModuleLessNamespace {
|
||||||
export interface IIndexModuleLess {
|
export interface IDesignerModuleLess {
|
||||||
container: string;
|
container: string;
|
||||||
sider: string;
|
sider: string;
|
||||||
nodePanel: string;
|
|
||||||
content: string;
|
content: string;
|
||||||
graph: string;
|
graph: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '*.less' {
|
declare module '*.less' {
|
||||||
const content: IndexModuleLessNamespace.IIndexModuleLess;
|
const content: DesignerModuleLessNamespace.IDesignerModuleLess;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
@ -3,11 +3,11 @@ import {useNavigate, useParams} from 'react-router-dom';
|
|||||||
import {Button, Card, Layout, message, Space, Spin} from 'antd';
|
import {Button, Card, Layout, message, Space, Spin} from 'antd';
|
||||||
import {ArrowLeftOutlined, SaveOutlined} from '@ant-design/icons';
|
import {ArrowLeftOutlined, SaveOutlined} from '@ant-design/icons';
|
||||||
import {getDefinition, updateDefinition} from '../../service';
|
import {getDefinition, updateDefinition} from '../../service';
|
||||||
import {WorkflowDefinition, WorkflowStatus, NodeType} from '../../types';
|
import {NodeType, WorkflowDefinition, WorkflowStatus} from '../../../Workflow/types';
|
||||||
import {Graph} from '@antv/x6';
|
import {Graph} from '@antv/x6';
|
||||||
import '@antv/x6-react-shape';
|
import '@antv/x6-react-shape';
|
||||||
import styles from './index.module.less';
|
import './index.module.less';
|
||||||
import NodePanel from './NodePanel';
|
import NodePanel from './components/NodePanel';
|
||||||
|
|
||||||
const {Sider, Content} = Layout;
|
const {Sider, Content} = Layout;
|
||||||
|
|
||||||
@ -220,12 +220,12 @@ const FlowDesigner: React.FC = () => {
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Layout className={styles.container}>
|
<Layout className="workflow-designer-container">
|
||||||
<Sider width={280} className={styles.sider}>
|
<Sider width={280} className="workflow-designer-sider">
|
||||||
<NodePanel onNodeDragStart={handleNodeDragStart}/>
|
<NodePanel onNodeDragStart={handleNodeDragStart}/>
|
||||||
</Sider>
|
</Sider>
|
||||||
<Content className={styles.content}>
|
<Content className="workflow-designer-content">
|
||||||
<div ref={containerRef} className={styles.graph}/>
|
<div ref={containerRef} className="workflow-designer-graph"/>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
31
frontend/src/pages/Workflow/Definition/Designer/service.ts
Normal file
31
frontend/src/pages/Workflow/Definition/Designer/service.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
export interface NodeExecutor {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
configSchema: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeType {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
category: 'TASK' | 'EVENT' | 'GATEWAY';
|
||||||
|
description: string;
|
||||||
|
enabled: boolean;
|
||||||
|
icon: string;
|
||||||
|
color: string;
|
||||||
|
executors: NodeExecutor[];
|
||||||
|
createTime: string;
|
||||||
|
updateTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeTypeQuery {
|
||||||
|
enabled?: boolean;
|
||||||
|
category?: 'TASK' | 'EVENT' | 'GATEWAY';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取节点类型列表
|
||||||
|
export const getNodeTypes = (params?: NodeTypeQuery) =>
|
||||||
|
request.get<NodeType[]>('/api/v1/node-types', { params });
|
||||||
17
frontend/src/pages/Workflow/Definition/constants.ts
Normal file
17
frontend/src/pages/Workflow/Definition/constants.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { NodeCategory } from './types';
|
||||||
|
|
||||||
|
// 节点分类配置
|
||||||
|
export const NODE_CATEGORY_CONFIG = {
|
||||||
|
[NodeCategory.TASK]: {
|
||||||
|
icon: 'CodeOutlined',
|
||||||
|
label: '任务节点',
|
||||||
|
},
|
||||||
|
[NodeCategory.EVENT]: {
|
||||||
|
icon: 'ThunderboltOutlined',
|
||||||
|
label: '事件节点',
|
||||||
|
},
|
||||||
|
[NodeCategory.GATEWAY]: {
|
||||||
|
icon: 'ForkOutlined',
|
||||||
|
label: '网关节点',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
@ -10,7 +10,6 @@ import {
|
|||||||
publishDefinition
|
publishDefinition
|
||||||
} from '../service';
|
} from '../service';
|
||||||
import {
|
import {
|
||||||
CreateWorkflowDefinitionRequest,
|
|
||||||
WorkflowDefinition,
|
WorkflowDefinition,
|
||||||
WorkflowStatus,
|
WorkflowStatus,
|
||||||
WorkflowDefinitionBase
|
WorkflowDefinitionBase
|
||||||
|
|||||||
129
frontend/src/pages/Workflow/Definition/service.ts
Normal file
129
frontend/src/pages/Workflow/Definition/service.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import { NodeCategory, NodeType } from '../types';
|
||||||
|
|
||||||
|
// Mock 节点类型数据
|
||||||
|
const mockNodeTypes: NodeType[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
code: 'start',
|
||||||
|
name: '开始节点',
|
||||||
|
color: '#67C23A',
|
||||||
|
icon: 'PlayCircleOutlined',
|
||||||
|
category: NodeCategory.EVENT,
|
||||||
|
description: '流程的开始节点',
|
||||||
|
enabled: true,
|
||||||
|
executors: [],
|
||||||
|
configSchema: '{}',
|
||||||
|
defaultConfig: '{}',
|
||||||
|
createTime: new Date().toISOString(),
|
||||||
|
updateTime: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
code: 'end',
|
||||||
|
name: '结束节点',
|
||||||
|
color: '#F56C6C',
|
||||||
|
icon: 'StopOutlined',
|
||||||
|
category: NodeCategory.EVENT,
|
||||||
|
description: '流程的结束节点',
|
||||||
|
enabled: true,
|
||||||
|
executors: [],
|
||||||
|
configSchema: '{}',
|
||||||
|
defaultConfig: '{}',
|
||||||
|
createTime: new Date().toISOString(),
|
||||||
|
updateTime: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
code: 'approval',
|
||||||
|
name: '审批节点',
|
||||||
|
color: '#409EFF',
|
||||||
|
icon: 'AuditOutlined',
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: '人工审批节点',
|
||||||
|
enabled: true,
|
||||||
|
executors: [],
|
||||||
|
configSchema: '{}',
|
||||||
|
defaultConfig: '{}',
|
||||||
|
createTime: new Date().toISOString(),
|
||||||
|
updateTime: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
code: 'script',
|
||||||
|
name: '脚本节点',
|
||||||
|
color: '#909399',
|
||||||
|
icon: 'CodeOutlined',
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: '执行自定义脚本',
|
||||||
|
enabled: true,
|
||||||
|
executors: [],
|
||||||
|
configSchema: '{}',
|
||||||
|
defaultConfig: '{}',
|
||||||
|
createTime: new Date().toISOString(),
|
||||||
|
updateTime: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
code: 'webhook',
|
||||||
|
name: 'Webhook',
|
||||||
|
color: '#9C27B0',
|
||||||
|
icon: 'ApiOutlined',
|
||||||
|
category: NodeCategory.TASK,
|
||||||
|
description: '调用外部 Webhook',
|
||||||
|
enabled: true,
|
||||||
|
executors: [],
|
||||||
|
configSchema: '{}',
|
||||||
|
defaultConfig: '{}',
|
||||||
|
createTime: new Date().toISOString(),
|
||||||
|
updateTime: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
code: 'exclusive',
|
||||||
|
name: '排他网关',
|
||||||
|
color: '#FF9800',
|
||||||
|
icon: 'ForkOutlined',
|
||||||
|
category: NodeCategory.GATEWAY,
|
||||||
|
description: '根据条件选择一条分支执行',
|
||||||
|
enabled: true,
|
||||||
|
executors: [],
|
||||||
|
configSchema: '{}',
|
||||||
|
defaultConfig: '{}',
|
||||||
|
createTime: new Date().toISOString(),
|
||||||
|
updateTime: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
code: 'parallel',
|
||||||
|
name: '并行网关',
|
||||||
|
color: '#795548',
|
||||||
|
icon: 'PartitionOutlined',
|
||||||
|
category: NodeCategory.GATEWAY,
|
||||||
|
description: '同时执行所有分支',
|
||||||
|
enabled: true,
|
||||||
|
executors: [],
|
||||||
|
configSchema: '{}',
|
||||||
|
defaultConfig: '{}',
|
||||||
|
createTime: new Date().toISOString(),
|
||||||
|
updateTime: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
interface GetNodeTypesParams {
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取节点类型列表
|
||||||
|
export const getNodeTypes = async (params?: GetNodeTypesParams): Promise<NodeType[]> => {
|
||||||
|
// 模拟 API 延迟
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
let result = [...mockNodeTypes];
|
||||||
|
|
||||||
|
// 根据启用状态筛选
|
||||||
|
if (params?.enabled !== undefined) {
|
||||||
|
result = result.filter(node => node.enabled === params.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
15
frontend/src/pages/Workflow/Definition/types.ts
Normal file
15
frontend/src/pages/Workflow/Definition/types.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export enum NodeCategory {
|
||||||
|
TASK = 'TASK',
|
||||||
|
EVENT = 'EVENT',
|
||||||
|
GATEWAY = 'GATEWAY'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeType {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
icon: string;
|
||||||
|
category: NodeCategory;
|
||||||
|
description?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
4
frontend/src/types/less.d.ts
vendored
Normal file
4
frontend/src/types/less.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module '*.less' {
|
||||||
|
const classes: { readonly [key: string]: string };
|
||||||
|
export default classes;
|
||||||
|
}
|
||||||
@ -1,26 +1,25 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ESNext",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
"module": "ESNext",
|
"allowJs": false,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"moduleResolution": "bundler",
|
"esModuleInterop": false,
|
||||||
"allowImportingTsExtensions": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"strict": true,
|
"typeRoots": ["./node_modules/@types", "./src/types"],
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
},
|
}
|
||||||
"types": ["@antv/x6"],
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user