From 2868e294d39186735cd3fab8c901d713e12dc4e8 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Tue, 28 Oct 2025 09:59:20 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A8=E5=8D=95CRUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Workflow/Design/hooks/useWorkflowSave.ts | 2 + .../Instance/components/DetailModal.tsx | 417 +++++------------- .../components/tabs/DetailInfoTab.tsx | 219 +++++++++ .../Instance/components/tabs/FlowGraphTab.tsx | 334 ++++++++++++++ .../Instance/components/tabs/TimelineTab.tsx | 167 +++++++ frontend/src/pages/Workflow/Instance/types.ts | 2 + 6 files changed, 830 insertions(+), 311 deletions(-) create mode 100644 frontend/src/pages/Workflow/Instance/components/tabs/DetailInfoTab.tsx create mode 100644 frontend/src/pages/Workflow/Instance/components/tabs/FlowGraphTab.tsx create mode 100644 frontend/src/pages/Workflow/Instance/components/tabs/TimelineTab.tsx diff --git a/frontend/src/pages/Workflow/Design/hooks/useWorkflowSave.ts b/frontend/src/pages/Workflow/Design/hooks/useWorkflowSave.ts index 006f4469..fc336f0d 100644 --- a/frontend/src/pages/Workflow/Design/hooks/useWorkflowSave.ts +++ b/frontend/src/pages/Workflow/Design/hooks/useWorkflowSave.ts @@ -77,6 +77,8 @@ export const useWorkflowSave = () => { from: edge.source, // 后端使用from字段 to: edge.target, // 后端使用to字段 name: edge.data?.label || "", // 边的名称 + sourceHandle: edge.sourceHandle, // 保存源连接点 + targetHandle: edge.targetHandle, // 保存目标连接点 config: { type: "sequence", // 固定为sequence类型 condition: edge.data?.condition // 保存边条件 diff --git a/frontend/src/pages/Workflow/Instance/components/DetailModal.tsx b/frontend/src/pages/Workflow/Instance/components/DetailModal.tsx index 7cf86273..869cbca5 100644 --- a/frontend/src/pages/Workflow/Instance/components/DetailModal.tsx +++ b/frontend/src/pages/Workflow/Instance/components/DetailModal.tsx @@ -1,15 +1,15 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { Dialog, DialogPortal, DialogOverlay, DialogHeader, DialogTitle } from '@/components/ui/dialog'; -import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Badge } from '@/components/ui/badge'; -import { WorkflowHistoricalInstance, WorkflowInstanceStage } from '../types'; -import ReactFlow, { Background, Controls, MiniMap, Node, Edge } from 'reactflow'; -import dayjs from 'dayjs'; -import 'reactflow/dist/style.css'; import * as DialogPrimitive from "@radix-ui/react-dialog"; -import { X, Clock, CheckCircle2, XCircle, AlertCircle } from "lucide-react"; +import { X } from "lucide-react"; import { cn } from "@/lib/utils"; +import { WorkflowHistoricalInstance } from '../types'; +import { NodeType } from '../../Design/nodes/types'; +import FlowGraphTab from './tabs/FlowGraphTab'; +import TimelineTab from './tabs/TimelineTab'; +import DetailInfoTab from './tabs/DetailInfoTab'; interface DetailModalProps { visible: boolean; @@ -20,340 +20,135 @@ interface DetailModalProps { const DetailModal: React.FC = ({ visible, onCancel, instanceData }) => { if (!instanceData) return null; + // 获取状态徽章 const getStatusBadge = (status: string) => { - const statusMap: Record = { - COMPLETED: { variant: 'success', text: '已完成' }, - RUNNING: { variant: 'default', text: '运行中' }, - FAILED: { variant: 'destructive', text: '失败' }, - TERMINATED: { variant: 'secondary', text: '已终止' }, - NOT_STARTED: { variant: 'outline', text: '未执行' } + const statusMap: Record = { + COMPLETED: { + variant: 'success', + text: '已完成', + className: 'bg-green-100 text-green-700 border-green-200' + }, + RUNNING: { + variant: 'default', + text: '运行中', + className: 'bg-blue-100 text-blue-700 border-blue-200 animate-pulse' + }, + FAILED: { + variant: 'destructive', + text: '失败', + className: 'bg-red-100 text-red-700 border-red-200' + }, + TERMINATED: { + variant: 'secondary', + text: '已终止', + className: 'bg-orange-100 text-orange-700 border-orange-200' + }, + NOT_STARTED: { + variant: 'outline', + text: '未执行', + className: 'bg-gray-50 text-gray-600 border-gray-300' + } }; - const statusInfo = statusMap[status] || { variant: 'outline', text: status }; - return {statusInfo.text}; + const statusInfo = statusMap[status] || statusMap.NOT_STARTED; + return ( + + {statusInfo.text} + + ); }; + // 获取节点类型文本 const getNodeTypeText = (nodeType: string) => { - const nodeTypeMap: Record = { - START_EVENT: '开始节点', - END_EVENT: '结束节点', - SERVICE_TASK: '服务任务', - USER_TASK: '用户任务', - SCRIPT_TASK: '脚本任务', - NOTIFICATION: '通知任务', - JENKINS_BUILD: 'Jenkins构建', - APPROVAL: '审批任务', - StartEvent: '开始节点', - EndEvent: '结束节点', - ServiceTask: '服务任务', - UserTask: '用户任务', - ScriptTask: '脚本任务' + const nodeTypeMap: Record = { + [NodeType.START_EVENT]: '开始节点', + [NodeType.END_EVENT]: '结束节点', + [NodeType.SERVICE_TASK]: '服务任务', + [NodeType.USER_TASK]: '用户任务', + [NodeType.SCRIPT_TASK]: '脚本任务', + [NodeType.NOTIFICATION]: '通知任务', + [NodeType.JENKINS_BUILD]: 'Jenkins构建', + [NodeType.APPROVAL]: '审批任务', + [NodeType.DEPLOY_NODE]: '部署节点', + [NodeType.GATEWAY_NODE]: '网关节点', + [NodeType.SUB_PROCESS]: '子流程', + [NodeType.CALL_ACTIVITY]: '调用活动', }; - return nodeTypeMap[nodeType] || nodeType; + return nodeTypeMap[nodeType as NodeType] || nodeType; }; - const getStatusIcon = (status: string) => { - switch (status) { - case 'COMPLETED': - return ; - case 'RUNNING': - return ; - case 'FAILED': - return ; - default: - return ; - } - }; - - // 构建节点状态映射 - const nodeStatusMap = useMemo(() => { - const map = new Map(); - instanceData.stages.forEach(stage => { - map.set(stage.nodeId, stage); - }); - return map; - }, [instanceData.stages]); - - // 获取节点状态 - const getNodeStatus = (nodeId: string): string => { - const stage = nodeStatusMap.get(nodeId); - return stage?.status || 'NOT_STARTED'; - }; - - // 获取节点颜色 - const getNodeColor = (status: string) => { - const colorMap: Record = { - COMPLETED: '#52c41a', - RUNNING: '#1890ff', - FAILED: '#ff4d4f', - TERMINATED: '#faad14', - NOT_STARTED: '#d9d9d9' - }; - return colorMap[status] || '#d9d9d9'; - }; - - // 转换为React Flow节点 - const flowNodes: Node[] = useMemo(() => { - if (!instanceData.graph?.nodes) return []; - - return instanceData.graph.nodes.map(node => { - const status = getNodeStatus(node.id); - const stage = nodeStatusMap.get(node.id); - - return { - id: node.id, - type: 'default', - position: { x: node.position.x + 400, y: node.position.y + 200 }, - data: { - label: ( -
-
- {node.nodeName} -
-
- {getNodeTypeText(node.nodeType)} -
- {stage && ( -
- {getStatusBadge(status)} -
- )} -
- ) - }, - style: { - background: getNodeColor(status), - color: status === 'NOT_STARTED' ? '#333' : '#fff', - border: `2px solid ${getNodeColor(status)}`, - borderRadius: '8px', - padding: '10px', - minWidth: '120px', - boxShadow: status === 'RUNNING' ? '0 0 10px rgba(24, 144, 255, 0.5)' : undefined - } - }; - }); - }, [instanceData.graph, nodeStatusMap]); - - // 判断连线是否已执行 - const isEdgeExecuted = (fromNodeId: string, _toNodeId: string): boolean => { - const fromStage = nodeStatusMap.get(fromNodeId); - - // 如果起始节点已完成,则连线已执行 - return fromStage?.status === 'COMPLETED' || fromStage?.status === 'RUNNING'; - }; - - // 转换为React Flow边 - const flowEdges: Edge[] = useMemo(() => { - if (!instanceData.graph?.edges) return []; - - return instanceData.graph.edges.map(edge => { - const executed = isEdgeExecuted(edge.from, edge.to); - - return { - id: edge.id, - source: edge.from, - target: edge.to, - label: edge.name || undefined, - type: 'smoothstep', - animated: isEdgeExecuted(edge.from, edge.to) && getNodeStatus(edge.to) === 'RUNNING', - style: { - stroke: executed ? '#52c41a' : '#d9d9d9', - strokeWidth: 2 - }, - labelStyle: { - fontSize: 10, - fill: '#666' - } - }; - }); - }, [instanceData.graph, nodeStatusMap]); - - // 描述信息组件 - const DescriptionItem = ({ label, value }: { label: string; value: React.ReactNode }) => ( -
-
{label}
-
{value}
-
- ); - return ( !open && onCancel()}> - + - - + + Close + - 流程执行详情 + + 流程执行详情 + - - 流程图 - 执行时间线 - 详细信息 + + + 📊 流程图 + + + ⏱️ 执行时间线 + + + 📋 详细信息 + {/* 流程图标签页 */} - - - -
- - - - -
-
-
- - {instanceData.graph ? ( - - - 流程执行图 - - -
- - - - { - const status = getNodeStatus(node.id); - return getNodeColor(status); - }} - /> - -
-
-
-
- 已完成 -
-
-
- 运行中 -
-
-
- 失败 -
-
-
- 未执行 -
-
- - - ) : ( - - -
- 暂无流程图数据 -
-
-
- )} + + {/* 执行时间线标签页 */} - - - -
- {instanceData.stages.map((stage, index) => ( -
- {/* 时间线图标 */} -
-
- {getStatusIcon(stage.status)} -
- {index < instanceData.stages.length - 1 && ( -
- )} -
- - {/* 时间线内容 */} -
-
- {stage.nodeName} - - {getNodeTypeText(stage.nodeType)} - -
-
- {stage.startTime && dayjs(stage.startTime).format('YYYY-MM-DD HH:mm:ss')} - {stage.endTime && ` → ${dayjs(stage.endTime).format('HH:mm:ss')}`} - {stage.startTime && stage.endTime && ( - - (耗时: {dayjs(stage.endTime).diff(dayjs(stage.startTime), 'second')}秒) - - )} -
-
{getStatusBadge(stage.status)}
-
-
- ))} -
- - + + {/* 详细信息标签页 */} - - - -
- - - - - - - {instanceData.startTime && instanceData.endTime && ( - - )} - -
-
-
+ + diff --git a/frontend/src/pages/Workflow/Instance/components/tabs/DetailInfoTab.tsx b/frontend/src/pages/Workflow/Instance/components/tabs/DetailInfoTab.tsx new file mode 100644 index 00000000..f62bb255 --- /dev/null +++ b/frontend/src/pages/Workflow/Instance/components/tabs/DetailInfoTab.tsx @@ -0,0 +1,219 @@ +import React from 'react'; +import { Card, CardContent } from '@/components/ui/card'; +import { + FileText, Hash, Key, Calendar, Clock, Activity, + CheckCircle, List, Timer +} from 'lucide-react'; +import dayjs from 'dayjs'; +import { WorkflowHistoricalInstance } from '../../types'; + +interface DetailInfoTabProps { + instanceData: WorkflowHistoricalInstance; + getStatusBadge: (status: string) => React.ReactNode; +} + +const DetailInfoTab: React.FC = ({ + instanceData, + getStatusBadge +}) => { + // 信息项组件 + const InfoItem = ({ + icon: Icon, + label, + value, + valueClass = '' + }: { + icon: any; + label: string; + value: React.ReactNode; + valueClass?: string; + }) => ( +
+
+ + {label} +
+
+ {value} +
+
+ ); + + // 计算总耗时 + const totalDuration = instanceData.startTime && instanceData.endTime + ? dayjs(instanceData.endTime).diff(dayjs(instanceData.startTime), 'second') + : null; + + // 格式化耗时 + const formatDuration = (seconds: number) => { + if (seconds < 60) return `${seconds}秒`; + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + if (minutes < 60) return `${minutes}分${remainingSeconds}秒`; + const hours = Math.floor(minutes / 60); + const remainingMinutes = minutes % 60; + return `${hours}小时${remainingMinutes}分${remainingSeconds}秒`; + }; + + return ( +
+ {/* 基本信息 */} + + +
+ +

基本信息

+
+
+ + {instanceData.processInstanceId} + + } + /> + + {instanceData.processDefinitionId} + + } + /> + + {instanceData.businessKey} + + } + /> + +
+
+
+ + {/* 时间信息 */} + + +
+ +

时间信息

+
+
+ + {dayjs(instanceData.startTime).format('YYYY-MM-DD HH:mm:ss')} + + } + /> + + {dayjs(instanceData.endTime).format('YYYY-MM-DD HH:mm:ss')} + + ) : ( + + + 进行中... + + ) + } + /> + {totalDuration !== null && ( + + {formatDuration(totalDuration)} + + } + /> + )} +
+
+
+ + {/* 执行统计 */} + + +
+ +

执行统计

+
+
+ {/* 总节点数 */} +
+
+
+

执行节点数

+

+ {instanceData.stages.length} +

+
+ +
+
+ + {/* 已完成节点 */} +
+
+
+

已完成节点

+

+ {instanceData.stages.filter(s => s.status === 'COMPLETED').length} +

+
+ +
+
+ + {/* 运行中节点 */} +
+
+
+

运行中节点

+

+ {instanceData.stages.filter(s => s.status === 'RUNNING').length} +

+
+ +
+
+ + {/* 失败节点 */} +
+
+
+

失败节点

+

+ {instanceData.stages.filter(s => s.status === 'FAILED').length} +

+
+ +
+
+
+
+
+
+ ); +}; + +export default DetailInfoTab; + diff --git a/frontend/src/pages/Workflow/Instance/components/tabs/FlowGraphTab.tsx b/frontend/src/pages/Workflow/Instance/components/tabs/FlowGraphTab.tsx new file mode 100644 index 00000000..c286a7e7 --- /dev/null +++ b/frontend/src/pages/Workflow/Instance/components/tabs/FlowGraphTab.tsx @@ -0,0 +1,334 @@ +import React, { useMemo } from 'react'; +import { ReactFlow, Background, Controls, MiniMap, Node, Edge, Handle, Position, BackgroundVariant } from '@xyflow/react'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import dayjs from 'dayjs'; +import '@xyflow/react/dist/style.css'; +import { WorkflowHistoricalInstance, WorkflowInstanceStage } from '../../types'; +import { NodeType } from '../../../Design/nodes/types'; + +interface FlowGraphTabProps { + instanceData: WorkflowHistoricalInstance; + getStatusBadge: (status: string) => React.ReactNode; + getNodeTypeText: (nodeType: string) => string; +} + +// 自定义节点组件 - 增强版 +const EnhancedCustomNode: React.FC = ({ data, selected }) => { + const nodeType = data.nodeType as NodeType; + const status = data.status; + + // 根据状态判断是否需要虚线边框 + const isNotStarted = status === 'NOT_STARTED'; + + return ( +
+ {/* 输入连接点 */} + {nodeType !== NodeType.START_EVENT && ( + + )} + + {/* 节点内容 */} + + + {data.label} + + + + {/* 输出连接点 */} + {nodeType !== NodeType.END_EVENT && ( + + )} +
+ ); +}; + +const nodeTypes = { + enhanced: EnhancedCustomNode +}; + +const FlowGraphTab: React.FC = ({ + instanceData, + getStatusBadge, + getNodeTypeText +}) => { + // 构建节点状态映射 + const nodeStatusMap = useMemo(() => { + const map = new Map(); + instanceData.stages.forEach(stage => { + map.set(stage.nodeId, stage); + }); + return map; + }, [instanceData.stages]); + + // 获取节点状态 + const getNodeStatus = (nodeId: string): string => { + const stage = nodeStatusMap.get(nodeId); + return stage?.status || 'NOT_STARTED'; + }; + + // 获取节点颜色 + const getNodeColor = (status: string) => { + const colorMap: Record = { + COMPLETED: { + bg: 'linear-gradient(135deg, #52c41a 0%, #73d13d 100%)', + border: '#52c41a', + text: '#fff' + }, + RUNNING: { + bg: 'linear-gradient(135deg, #1890ff 0%, #40a9ff 100%)', + border: '#1890ff', + text: '#fff' + }, + FAILED: { + bg: 'linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%)', + border: '#ff4d4f', + text: '#fff' + }, + TERMINATED: { + bg: 'linear-gradient(135deg, #faad14 0%, #ffc53d 100%)', + border: '#faad14', + text: '#fff' + }, + NOT_STARTED: { + bg: '#fafafa', + border: '#d9d9d9', + text: '#666' + } + }; + return colorMap[status] || colorMap.NOT_STARTED; + }; + + // 转换为React Flow节点 + const flowNodes: Node[] = useMemo(() => { + if (!instanceData.graph?.nodes) return []; + + return instanceData.graph.nodes.map(node => { + const status = getNodeStatus(node.id); + const stage = nodeStatusMap.get(node.id); + const colors = getNodeColor(status); + + return { + id: node.id, + type: 'enhanced', + position: { x: node.position.x, y: node.position.y }, + data: { + nodeType: node.nodeType, + status: status, + handleColor: colors.border, + background: colors.bg, + borderColor: colors.border, + textColor: colors.text, + label: ( +
+
+ {node.nodeName} +
+
+ {getNodeTypeText(node.nodeType)} +
+ {stage && ( +
+ {getStatusBadge(status)} +
+ )} + {stage?.startTime && ( +
+ {dayjs(stage.startTime).format('HH:mm:ss')} +
+ )} +
+ ) + } + }; + }); + }, [instanceData.graph, nodeStatusMap]); + + // 判断连线是否已执行 + const isEdgeExecuted = (fromNodeId: string): boolean => { + const fromStage = nodeStatusMap.get(fromNodeId); + return fromStage?.status === 'COMPLETED' || fromStage?.status === 'RUNNING'; + }; + + // 转换为React Flow边 + const flowEdges: Edge[] = useMemo(() => { + if (!instanceData.graph?.edges) return []; + + return instanceData.graph.edges.map(edge => { + const executed = isEdgeExecuted(edge.from); + const fromStatus = getNodeStatus(edge.from); + const toStatus = getNodeStatus(edge.to); + + // 判断是否应该使用虚线 + const isDashed = !executed || toStatus === 'NOT_STARTED'; + + return { + id: edge.id, + source: edge.from, + target: edge.to, + sourceHandle: edge.sourceHandle, + targetHandle: edge.targetHandle, + label: edge.name || undefined, + type: 'smoothstep', + animated: executed && toStatus === 'RUNNING', + style: { + stroke: executed ? '#52c41a' : '#d9d9d9', + strokeWidth: executed ? 3 : 2, + strokeDasharray: isDashed ? '5,5' : undefined, // 虚线效果 + }, + markerEnd: { + type: 'arrowclosed' as const, + color: executed ? '#52c41a' : '#d9d9d9', + }, + labelStyle: { + fontSize: 11, + fontWeight: 500, + fill: executed ? '#52c41a' : '#999', + }, + labelBgStyle: { + fill: '#fff', + fillOpacity: 0.9, + } + }; + }); + }, [instanceData.graph, nodeStatusMap]); + + return ( +
+ {/* 基本信息卡片 */} + + +
+
+
业务标识
+
{instanceData.businessKey}
+
+
+
状态
+
{getStatusBadge(instanceData.status)}
+
+
+
开始时间
+
{dayjs(instanceData.startTime).format('YYYY-MM-DD HH:mm:ss')}
+
+
+
结束时间
+
+ {instanceData.endTime ? dayjs(instanceData.endTime).format('YYYY-MM-DD HH:mm:ss') : '进行中...'} +
+
+
+
+
+ + {/* 流程图卡片 */} + {instanceData.graph ? ( + + + + 流程执行图 + + 实时展示流程执行状态 + + + + +
+ + + + { + const status = getNodeStatus(node.id); + const colors = getNodeColor(status); + return colors.border; + }} + className="!shadow-lg !border !border-border" + maskColor="rgba(0, 0, 0, 0.05)" + /> + +
+ + {/* 图例 */} +
+
+
+ 已完成 +
+
+
+ 运行中 +
+
+
+ 失败 +
+
+
+ 未执行 +
+
+
+ 已执行路径 +
+
+
+ 未执行路径 +
+
+ + + ) : ( + + +
+ 暂无流程图数据 +
+
+
+ )} +
+ ); +}; + +export default FlowGraphTab; + diff --git a/frontend/src/pages/Workflow/Instance/components/tabs/TimelineTab.tsx b/frontend/src/pages/Workflow/Instance/components/tabs/TimelineTab.tsx new file mode 100644 index 00000000..f38ae4ae --- /dev/null +++ b/frontend/src/pages/Workflow/Instance/components/tabs/TimelineTab.tsx @@ -0,0 +1,167 @@ +import React from 'react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Clock, CheckCircle2, XCircle, AlertCircle, Play } from 'lucide-react'; +import dayjs from 'dayjs'; +import { WorkflowHistoricalInstance } from '../../types'; + +interface TimelineTabProps { + instanceData: WorkflowHistoricalInstance; + getStatusBadge: (status: string) => React.ReactNode; + getNodeTypeText: (nodeType: string) => string; +} + +const TimelineTab: React.FC = ({ + instanceData, + getStatusBadge, + getNodeTypeText +}) => { + const getStatusIcon = (status: string) => { + switch (status) { + case 'COMPLETED': + return ; + case 'RUNNING': + return ; + case 'FAILED': + return ; + default: + return ; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'COMPLETED': + return { + border: 'border-green-500', + bg: 'bg-green-50', + line: 'bg-green-200' + }; + case 'RUNNING': + return { + border: 'border-blue-500', + bg: 'bg-blue-50', + line: 'bg-blue-200' + }; + case 'FAILED': + return { + border: 'border-red-500', + bg: 'bg-red-50', + line: 'bg-red-200' + }; + default: + return { + border: 'border-gray-300', + bg: 'bg-gray-50', + line: 'bg-gray-200' + }; + } + }; + + return ( + + +
+ {instanceData.stages.map((stage, index) => { + const colors = getStatusColor(stage.status); + const duration = stage.startTime && stage.endTime + ? dayjs(stage.endTime).diff(dayjs(stage.startTime), 'second') + : null; + + return ( +
+ {/* 时间线左侧 */} +
+ {/* 时间轴圆点 */} +
+ {getStatusIcon(stage.status)} +
+ + {/* 连接线 */} + {index < instanceData.stages.length - 1 && ( +
+ )} +
+ + {/* 时间线内容 */} +
+ + + {/* 节点名称和类型 */} +
+
+

+ {stage.nodeName} +

+

+ {getNodeTypeText(stage.nodeType)} +

+
+
+ {getStatusBadge(stage.status)} +
+
+ + {/* 时间信息 */} +
+ {stage.startTime && ( +
+ + 开始: + + {dayjs(stage.startTime).format('YYYY-MM-DD HH:mm:ss')} + +
+ )} + + {stage.endTime && ( +
+ + 结束: + + {dayjs(stage.endTime).format('YYYY-MM-DD HH:mm:ss')} + +
+ )} + + {duration !== null && ( +
+ 耗时: + + {duration < 60 + ? `${duration}秒` + : `${Math.floor(duration / 60)}分${duration % 60}秒` + } + +
+ )} +
+
+
+
+
+ ); + })} +
+ + {/* 空状态 */} + {instanceData.stages.length === 0 && ( +
+ +

暂无执行记录

+
+ )} + + + ); +}; + +export default TimelineTab; + diff --git a/frontend/src/pages/Workflow/Instance/types.ts b/frontend/src/pages/Workflow/Instance/types.ts index 843fc4af..a9161c12 100644 --- a/frontend/src/pages/Workflow/Instance/types.ts +++ b/frontend/src/pages/Workflow/Instance/types.ts @@ -65,6 +65,8 @@ export interface WorkflowGraphEdge { }; }; vertices: any[]; + sourceHandle?: string; // 源节点连接点 + targetHandle?: string; // 目标节点连接点 } /**