deploy-ease-platform/backend/docs/frontend/部署流程图弹窗开发指南.md
2025-11-11 14:06:52 +08:00

29 KiB
Raw Blame History

部署流程图弹窗开发指南

1. 概述

本指南用于指导前端开发部署流程图可视化弹窗,展示部署工作流的执行状态、节点详情和统计信息。


2. API 接口

2.1 接口信息

GET /api/v1/deploy-records/{deployRecordId}/flow-graph

2.2 请求参数

参数名 类型 必填 说明
deployRecordId Long 部署记录ID

2.3 响应数据结构

interface DeployRecordFlowGraphDTO {
  // ============ 部署记录基本信息 ============
  deployRecordId: number;          // 部署记录ID
  businessKey: string;              // 业务标识UUID
  deployStatus: DeployStatus;       // 部署状态
  deployBy: string;                 // 部署人
  deployRemark: string;             // 部署备注
  deployStartTime: string;          // 部署开始时间ISO格式
  deployEndTime: string;            // 部署结束时间ISO格式
  deployDuration: number;           // 部署总时长(秒)

  // ============ 业务上下文信息 ============
  applicationName: string;          // 应用名称
  applicationCode: string;          // 应用编码
  environmentName: string;          // 环境名称
  teamName: string;                 // 团队名称

  // ============ 工作流实例信息 ============
  workflowInstanceId: number;       // 工作流实例ID
  processInstanceId: string;        // 流程实例IDFlowable
  workflowStatus: WorkflowStatus;   // 工作流实例状态
  workflowStartTime: string;        // 工作流开始时间
  workflowEndTime: string;          // 工作流结束时间
  workflowDuration: number;         // 工作流总时长(秒)

  // ============ 执行统计信息 ============
  totalNodeCount: number;           // 总节点数
  executedNodeCount: number;        // 已执行节点数
  successNodeCount: number;         // 成功节点数
  failedNodeCount: number;          // 失败节点数
  runningNodeCount: number;         // 运行中节点数

  // ============ 流程图数据 ============
  graph: WorkflowDefinitionGraph;   // 流程图结构
  nodeInstances: NodeInstanceDTO[]; // 节点执行状态列表
}

// 节点实例DTO
interface NodeInstanceDTO {
  id: number | null;                // 节点实例ID未执行节点为null
  nodeId: string;                   // 节点ID
  nodeName: string;                 // 节点名称
  nodeType: string;                 // 节点类型
  status: NodeStatus;               // 节点状态
  startTime: string | null;         // 开始时间
  endTime: string | null;           // 结束时间
  duration: number | null;          // 执行时长(秒)
  errorMessage: string | null;      // 错误信息(失败时)
  processInstanceId: string;        // 流程实例ID
  createTime: string | null;        // 创建时间
  updateTime: string | null;        // 更新时间
}

// 流程图结构
interface WorkflowDefinitionGraph {
  nodes: GraphNode[];               // 节点列表
  edges: GraphEdge[];               // 边列表
}

interface GraphNode {
  id: string;                       // 节点ID
  nodeCode: string;                 // 节点编码
  nodeType: string;                 // 节点类型
  nodeName: string;                 // 节点名称
  position: { x: number; y: number }; // 节点位置
  configs: Record<string, any>;     // 节点配置
  inputMapping: Record<string, any>; // 输入映射
  outputs: OutputField[];           // 输出字段定义
}

interface GraphEdge {
  id: string;                       // 边ID
  from: string;                     // 源节点ID
  to: string;                       // 目标节点ID
  name: string;                     // 边名称
  config: {
    type: string;                   // 边类型
    condition: {
      type: string;                 // 条件类型
      priority: number;             // 优先级
      expression?: string;          // 条件表达式
    };
  };
}

// 枚举类型
enum DeployStatus {
  CREATED = 'CREATED',              // 已创建
  PENDING_APPROVAL = 'PENDING_APPROVAL', // 待审批
  RUNNING = 'RUNNING',              // 运行中
  SUCCESS = 'SUCCESS',              // 成功
  FAILED = 'FAILED',                // 失败
  REJECTED = 'REJECTED',            // 审批拒绝
  CANCELLED = 'CANCELLED',          // 已取消
  TERMINATED = 'TERMINATED',        // 已终止
  PARTIAL_SUCCESS = 'PARTIAL_SUCCESS' // 部分成功
}

enum NodeStatus {
  NOT_STARTED = 'NOT_STARTED',      // 未开始
  RUNNING = 'RUNNING',              // 运行中
  SUSPENDED = 'SUSPENDED',          // 已暂停
  COMPLETED = 'COMPLETED',          // 已完成
  TERMINATED = 'TERMINATED',        // 已终止
  FAILED = 'FAILED'                 // 执行失败
}

3. UI 设计建议

3.1 整体布局

建议使用全屏或大尺寸弹窗1200px+),分为三个主要区域:

┌─────────────────────────────────────────────────────────────┐
│  📋 头部信息区(部署基本信息 + 统计卡片)                    │
├─────────────────────────────────────────────────────────────┤
│                                                               │
│  🎨 流程图可视化区Canvas绘制                             │
│     - 节点按状态着色                                          │
│     - 支持缩放、拖拽                                          │
│     - 点击节点查看详情                                        │
│                                                               │
├─────────────────────────────────────────────────────────────┤
│  📊 底部详情区(选中节点的详细信息)                          │
└─────────────────────────────────────────────────────────────┘

3.2 头部信息区

显示关键信息和统计数据:

// 第一行:基本信息
┌───────────────────────────────────────────────────────────┐
 🚀 部署流程详情                              [关闭 ]     
├───────────────────────────────────────────────────────────┤
 应用:应用名称 (应用编码) | 环境:生产环境 | 团队:运维组  
 部署人:张三 | 开始时间:2025-11-05 14:00:00              
 状态:🟢 运行中 | 总时长:530                          
└───────────────────────────────────────────────────────────┘

// 第二行:统计卡片
┌──────────┬──────────┬──────────┬──────────┬──────────┐
 总节点    已执行    成功      失败      运行中   
    8         5         4         0         1     
  100%       62%       50%        0%       12%    
└──────────┴──────────┴──────────┴──────────┴──────────┘

3.3 流程图可视化区

节点状态着色方案

const nodeColorMap = {
  NOT_STARTED: '#d9d9d9',    // 未开始:灰色
  RUNNING: '#1890ff',         // 运行中:蓝色(闪烁动画)
  COMPLETED: '#52c41a',       // 已完成:绿色
  FAILED: '#ff4d4f',          // 失败:红色
  SUSPENDED: '#faad14',       // 暂停:橙色
  TERMINATED: '#8c8c8c'       // 终止:深灰
};

const nodeIconMap = {
  START_EVENT: '▶️',
  END_EVENT: '⏹️',
  USER_TASK: '👤',
  SERVICE_TASK: '⚙️',
  APPROVAL: '✓',
  SCRIPT_TASK: '📝',
  JENKINS_BUILD: '🔨',
  NOTIFICATION: '🔔'
};

节点显示内容

┌─────────────────┐
│  🔨 Jenkins构建  │  ← 节点图标 + 名称
├─────────────────┤
│  ✓ 已完成        │  ← 状态(带图标)
│  ⏱ 2分30秒      │  ← 执行时长
└─────────────────┘

失败节点特殊标记:

┌─────────────────┐
│  ⚙️ 部署服务     │
├─────────────────┤
│  ✗ 执行失败      │  ← 红色状态
│  ⚠️ 点击查看错误 │  ← 错误提示
└─────────────────┘

3.4 底部详情区

选中节点后显示完整信息:

┌─────────────────────────────────────────────────────────┐
│ 📌 节点详情                                             │
├─────────────────────────────────────────────────────────┤
│ 节点名称Jenkins构建                                    │
│ 节点类型SERVICE_TASK (服务任务)                       │
│ 执行状态:✓ 已完成                                      │
│                                                          │
│ ⏱️ 时间信息                                             │
│   开始时间2025-11-05 14:02:30                         │
│   结束时间2025-11-05 14:05:00                         │
│   执行时长2分30秒                                      │
│                                                          │
│ 📊 执行结果(如果有)                                    │
│   构建编号:#123                                         │
│   构建状态SUCCESS                                      │
│   构建产物app-1.0.0.jar                               │
│                                                          │
│ ⚠️ 错误信息(失败时显示)                               │
│   连接超时无法连接到Jenkins服务器 (timeout: 30s)      │
└─────────────────────────────────────────────────────────┘

4. 组件划分建议

4.1 组件层次结构

DeployFlowGraphModal (弹窗容器)
├── FlowGraphHeader (头部信息)
│   ├── DeployBasicInfo (基本信息)
│   └── ExecutionStatistics (统计卡片)
├── FlowGraphCanvas (流程图画布)
│   ├── GraphNode (节点组件)
│   └── GraphEdge (边组件)
└── NodeDetailPanel (节点详情面板)

4.2 核心组件代码示例

4.2.1 弹窗容器组件

// DeployFlowGraphModal.tsx
import React, { useState, useEffect } from 'react';
import { Modal, Spin, message } from 'antd';
import { getDeployFlowGraph } from '@/services/deploy';
import type { DeployRecordFlowGraphDTO, NodeInstanceDTO } from '@/types/deploy';

interface Props {
  visible: boolean;
  deployRecordId: number;
  onClose: () => void;
}

export const DeployFlowGraphModal: React.FC<Props> = ({
  visible,
  deployRecordId,
  onClose
}) => {
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState<DeployRecordFlowGraphDTO | null>(null);
  const [selectedNode, setSelectedNode] = useState<NodeInstanceDTO | null>(null);

  useEffect(() => {
    if (visible && deployRecordId) {
      loadFlowGraph();
    }
  }, [visible, deployRecordId]);

  const loadFlowGraph = async () => {
    setLoading(true);
    try {
      const response = await getDeployFlowGraph(deployRecordId);
      setData(response.data);
    } catch (error) {
      message.error('加载流程图失败');
    } finally {
      setLoading(false);
    }
  };

  return (
    <Modal
      title="部署流程详情"
      open={visible}
      onCancel={onClose}
      width={1200}
      footer={null}
      destroyOnClose
    >
      <Spin spinning={loading}>
        {data && (
          <div className="deploy-flow-graph">
            {/* 头部信息 */}
            <FlowGraphHeader data={data} />
            
            {/* 流程图画布 */}
            <FlowGraphCanvas
              graph={data.graph}
              nodeInstances={data.nodeInstances}
              selectedNode={selectedNode}
              onNodeClick={setSelectedNode}
            />
            
            {/* 节点详情面板 */}
            {selectedNode && (
              <NodeDetailPanel
                node={selectedNode}
                onClose={() => setSelectedNode(null)}
              />
            )}
          </div>
        )}
      </Spin>
    </Modal>
  );
};

4.2.2 头部信息组件

// FlowGraphHeader.tsx
import React from 'react';
import { Tag, Statistic, Card, Row, Col, Descriptions } from 'antd';
import type { DeployRecordFlowGraphDTO } from '@/types/deploy';
import { formatDuration, getStatusTag } from '@/utils/deploy';

interface Props {
  data: DeployRecordFlowGraphDTO;
}

export const FlowGraphHeader: React.FC<Props> = ({ data }) => {
  return (
    <div className="flow-graph-header">
      {/* 基本信息 */}
      <Descriptions column={3} size="small" bordered style={{ marginBottom: 16 }}>
        <Descriptions.Item label="应用">
          {data.applicationName} ({data.applicationCode})
        </Descriptions.Item>
        <Descriptions.Item label="环境">
          {data.environmentName}
        </Descriptions.Item>
        <Descriptions.Item label="团队">
          {data.teamName}
        </Descriptions.Item>
        <Descriptions.Item label="部署人">
          {data.deployBy}
        </Descriptions.Item>
        <Descriptions.Item label="开始时间">
          {data.deployStartTime}
        </Descriptions.Item>
        <Descriptions.Item label="状态">
          {getStatusTag(data.deployStatus)}
        </Descriptions.Item>
        <Descriptions.Item label="总时长" span={3}>
          {formatDuration(data.deployDuration)}
        </Descriptions.Item>
        {data.deployRemark && (
          <Descriptions.Item label="备注" span={3}>
            {data.deployRemark}
          </Descriptions.Item>
        )}
      </Descriptions>

      {/* 统计卡片 */}
      <Row gutter={16}>
        <Col span={4}>
          <Card>
            <Statistic
              title="总节点"
              value={data.totalNodeCount}
              suffix="个"
            />
          </Card>
        </Col>
        <Col span={5}>
          <Card>
            <Statistic
              title="已执行"
              value={data.executedNodeCount}
              suffix={`/ ${data.totalNodeCount}`}
              valueStyle={{ color: '#1890ff' }}
            />
            <div style={{ marginTop: 8, fontSize: 12, color: '#8c8c8c' }}>
              {((data.executedNodeCount / data.totalNodeCount) * 100).toFixed(0)}%
            </div>
          </Card>
        </Col>
        <Col span={5}>
          <Card>
            <Statistic
              title="成功"
              value={data.successNodeCount}
              valueStyle={{ color: '#52c41a' }}
            />
          </Card>
        </Col>
        <Col span={5}>
          <Card>
            <Statistic
              title="失败"
              value={data.failedNodeCount}
              valueStyle={{ color: data.failedNodeCount > 0 ? '#ff4d4f' : undefined }}
            />
          </Card>
        </Col>
        <Col span={5}>
          <Card>
            <Statistic
              title="运行中"
              value={data.runningNodeCount}
              valueStyle={{ color: data.runningNodeCount > 0 ? '#1890ff' : undefined }}
            />
          </Card>
        </Col>
      </Row>
    </div>
  );
};

4.2.3 流程图画布组件(使用 AntV X6

// FlowGraphCanvas.tsx
import React, { useEffect, useRef } from 'react';
import { Graph } from '@antv/x6';
import type { WorkflowDefinitionGraph, NodeInstanceDTO } from '@/types/deploy';
import { getNodeColor, getNodeIcon } from '@/utils/workflow';

interface Props {
  graph: WorkflowDefinitionGraph;
  nodeInstances: NodeInstanceDTO[];
  selectedNode: NodeInstanceDTO | null;
  onNodeClick: (node: NodeInstanceDTO) => void;
}

export const FlowGraphCanvas: React.FC<Props> = ({
  graph,
  nodeInstances,
  selectedNode,
  onNodeClick
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const graphRef = useRef<Graph | null>(null);

  useEffect(() => {
    if (!containerRef.current) return;

    // 创建画布
    const graphInstance = new Graph({
      container: containerRef.current,
      width: 1100,
      height: 500,
      grid: true,
      panning: true,
      mousewheel: {
        enabled: true,
        modifiers: ['ctrl', 'meta'],
      },
    });

    graphRef.current = graphInstance;

    // 渲染节点
    renderNodes(graphInstance);

    // 渲染边
    renderEdges(graphInstance);

    // 监听节点点击
    graphInstance.on('node:click', ({ node }) => {
      const nodeId = node.id;
      const nodeInstance = nodeInstances.find(n => n.nodeId === nodeId);
      if (nodeInstance) {
        onNodeClick(nodeInstance);
      }
    });

    return () => {
      graphInstance.dispose();
    };
  }, [graph, nodeInstances]);

  const renderNodes = (graphInstance: Graph) => {
    graph.nodes.forEach(node => {
      const nodeInstance = nodeInstances.find(n => n.nodeId === node.id);
      const status = nodeInstance?.status || 'NOT_STARTED';
      const color = getNodeColor(status);
      const icon = getNodeIcon(node.nodeType);

      graphInstance.addNode({
        id: node.id,
        x: node.position.x,
        y: node.position.y,
        width: 120,
        height: 80,
        label: `${icon} ${node.nodeName}`,
        attrs: {
          body: {
            fill: color,
            stroke: status === selectedNode?.nodeId ? '#1890ff' : '#d9d9d9',
            strokeWidth: status === selectedNode?.nodeId ? 3 : 1,
            rx: 6,
            ry: 6,
          },
          label: {
            fill: '#ffffff',
            fontSize: 12,
          },
        },
        data: {
          status,
          duration: nodeInstance?.duration,
          hasError: !!nodeInstance?.errorMessage,
        },
      });

      // 添加状态标签
      if (nodeInstance) {
        graphInstance.addNode({
          id: `${node.id}-status`,
          x: node.position.x + 5,
          y: node.position.y + 60,
          width: 110,
          height: 18,
          label: getStatusLabel(status, nodeInstance.duration),
          attrs: {
            body: {
              fill: 'rgba(255, 255, 255, 0.9)',
              stroke: 'none',
              rx: 3,
              ry: 3,
            },
            label: {
              fill: '#000000',
              fontSize: 10,
            },
          },
        });
      }
    });
  };

  const renderEdges = (graphInstance: Graph) => {
    graph.edges.forEach(edge => {
      graphInstance.addEdge({
        id: edge.id,
        source: edge.from,
        target: edge.to,
        label: edge.name,
        attrs: {
          line: {
            stroke: '#8c8c8c',
            strokeWidth: 2,
            targetMarker: {
              name: 'classic',
              size: 8,
            },
          },
          label: {
            fill: '#595959',
            fontSize: 11,
          },
        },
      });
    });
  };

  return (
    <div
      ref={containerRef}
      className="flow-graph-canvas"
      style={{ border: '1px solid #d9d9d9', marginTop: 16 }}
    />
  );
};

4.2.4 节点详情面板

// NodeDetailPanel.tsx
import React from 'react';
import { Card, Descriptions, Tag, Alert } from 'antd';
import type { NodeInstanceDTO } from '@/types/deploy';
import { formatDuration, getStatusTag } from '@/utils/deploy';

interface Props {
  node: NodeInstanceDTO;
  onClose: () => void;
}

export const NodeDetailPanel: React.FC<Props> = ({ node, onClose }) => {
  return (
    <Card
      title="📌 节点详情"
      extra={<a onClick={onClose}>关闭</a>}
      style={{ marginTop: 16 }}
    >
      <Descriptions column={2} bordered size="small">
        <Descriptions.Item label="节点名称" span={2}>
          {node.nodeName}
        </Descriptions.Item>
        <Descriptions.Item label="节点类型">
          {node.nodeType}
        </Descriptions.Item>
        <Descriptions.Item label="执行状态">
          {getStatusTag(node.status)}
        </Descriptions.Item>
        
        {node.startTime && (
          <>
            <Descriptions.Item label="开始时间">
              {node.startTime}
            </Descriptions.Item>
            <Descriptions.Item label="结束时间">
              {node.endTime || '执行中...'}
            </Descriptions.Item>
            <Descriptions.Item label="执行时长" span={2}>
              {node.duration ? formatDuration(node.duration) : '计算中...'}
            </Descriptions.Item>
          </>
        )}

        {node.status === 'NOT_STARTED' && (
          <Descriptions.Item label="说明" span={2}>
            <Tag color="default">该节点尚未执行</Tag>
          </Descriptions.Item>
        )}
      </Descriptions>

      {/* 错误信息 */}
      {node.errorMessage && (
        <Alert
          message="执行错误"
          description={node.errorMessage}
          type="error"
          showIcon
          style={{ marginTop: 16 }}
        />
      )}

      {/* 执行结果outputs*/}
      {node.outputs && Object.keys(node.outputs).length > 0 && (
        <div style={{ marginTop: 16 }}>
          <h4>📊 执行结果</h4>
          <Descriptions column={1} bordered size="small">
            {Object.entries(node.outputs).map(([key, value]) => (
              <Descriptions.Item key={key} label={key}>
                {typeof value === 'object' ? JSON.stringify(value) : String(value)}
              </Descriptions.Item>
            ))}
          </Descriptions>
        </div>
      )}
    </Card>
  );
};

5. 工具函数

// utils/deploy.ts

/**
 * 格式化时长
 */
export function formatDuration(seconds: number | null): string {
  if (seconds === null || seconds === undefined) {
    return '-';
  }
  
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  const secs = seconds % 60;
  
  if (hours > 0) {
    return `${hours}小时${minutes}${secs}秒`;
  } else if (minutes > 0) {
    return `${minutes}${secs}秒`;
  } else {
    return `${secs}秒`;
  }
}

/**
 * 获取状态标签
 */
export function getStatusTag(status: string): React.ReactNode {
  const statusConfig = {
    NOT_STARTED: { color: 'default', text: '未开始' },
    RUNNING: { color: 'processing', text: '运行中' },
    COMPLETED: { color: 'success', text: '已完成' },
    FAILED: { color: 'error', text: '执行失败' },
    SUSPENDED: { color: 'warning', text: '已暂停' },
    TERMINATED: { color: 'default', text: '已终止' },
    
    CREATED: { color: 'default', text: '已创建' },
    PENDING_APPROVAL: { color: 'warning', text: '待审批' },
    SUCCESS: { color: 'success', text: '成功' },
    REJECTED: { color: 'error', text: '审批拒绝' },
    CANCELLED: { color: 'default', text: '已取消' },
    PARTIAL_SUCCESS: { color: 'warning', text: '部分成功' },
  };
  
  const config = statusConfig[status] || { color: 'default', text: status };
  return <Tag color={config.color}>{config.text}</Tag>;
}

/**
 * 获取节点颜色
 */
export function getNodeColor(status: string): string {
  const colorMap = {
    NOT_STARTED: '#d9d9d9',
    RUNNING: '#1890ff',
    COMPLETED: '#52c41a',
    FAILED: '#ff4d4f',
    SUSPENDED: '#faad14',
    TERMINATED: '#8c8c8c',
  };
  return colorMap[status] || '#d9d9d9';
}

/**
 * 获取节点图标
 */
export function getNodeIcon(nodeType: string): string {
  const iconMap = {
    START_EVENT: '▶️',
    END_EVENT: '⏹️',
    USER_TASK: '👤',
    SERVICE_TASK: '⚙️',
    APPROVAL: '✓',
    SCRIPT_TASK: '📝',
    JENKINS_BUILD: '🔨',
    NOTIFICATION: '🔔',
  };
  return iconMap[nodeType] || '⚙️';
}

/**
 * 获取状态文本
 */
function getStatusLabel(status: string, duration: number | null): string {
  const statusText = {
    NOT_STARTED: '未开始',
    RUNNING: '运行中',
    COMPLETED: '已完成',
    FAILED: '执行失败',
    SUSPENDED: '已暂停',
    TERMINATED: '已终止',
  };
  
  const text = statusText[status] || status;
  const timeText = duration ? ` | ${formatDuration(duration)}` : '';
  
  return `${text}${timeText}`;
}

6. 高级功能建议

6.1 实时刷新

对于运行中的部署,建议实现定时刷新:

useEffect(() => {
  if (data?.runningNodeCount > 0) {
    const timer = setInterval(() => {
      loadFlowGraph(); // 每5秒刷新一次
    }, 5000);
    
    return () => clearInterval(timer);
  }
}, [data?.runningNodeCount]);

6.2 节点动画

运行中的节点添加闪烁动画:

@keyframes pulse {
  0% { opacity: 1; }
  50% { opacity: 0.6; }
  100% { opacity: 1; }
}

.node-running {
  animation: pulse 2s infinite;
}

6.3 进度条展示

在头部添加整体进度条:

<Progress
  percent={(data.executedNodeCount / data.totalNodeCount) * 100}
  status={data.failedNodeCount > 0 ? 'exception' : 'active'}
  format={(percent) => `${data.executedNodeCount} / ${data.totalNodeCount}`}
/>

6.4 节点搜索/过滤

添加节点名称搜索功能:

<Input
  placeholder="搜索节点"
  prefix={<SearchOutlined />}
  onChange={(e) => handleSearchNode(e.target.value)}
/>

6.5 导出功能

支持导出流程图为图片或PDF

import html2canvas from 'html2canvas';

const exportFlowGraph = async () => {
  const canvas = await html2canvas(containerRef.current);
  const link = document.createElement('a');
  link.download = `deploy-flow-${deployRecordId}.png`;
  link.href = canvas.toDataURL();
  link.click();
};

7. 注意事项

7.1 性能优化

  1. 虚拟滚动节点数量超过50个时使用虚拟滚动
  2. 懒加载:详情面板内容按需加载
  3. 防抖节流:缩放、拖拽等操作使用防抖

7.2 异常处理

  1. 空数据处理:显示"暂无流程图数据"提示
  2. 加载失败:提供重试按钮
  3. 超时处理超过30秒显示超时提示

7.3 用户体验

  1. 加载状态:使用骨架屏或加载动画
  2. 操作提示:提供操作引导(缩放、拖拽说明)
  3. 快捷键支持ESC关闭弹窗Ctrl+滚轮缩放

7.4 移动端适配

如需移动端支持,建议:

  • 使用响应式布局
  • 触摸手势支持
  • 简化节点显示信息

8. 测试用例

8.1 基本功能测试

  • 弹窗正常打开和关闭
  • 数据正确加载和显示
  • 节点正确渲染(位置、颜色、图标)
  • 边正确连接
  • 点击节点显示详情

8.2 状态测试

  • 未开始节点显示为灰色
  • 运行中节点显示蓝色并闪烁
  • 成功节点显示绿色
  • 失败节点显示红色并显示错误信息

8.3 交互测试

  • 画布拖拽功能正常
  • 画布缩放功能正常
  • 节点选中高亮正常
  • 统计数据准确

9. 参考资源

9.1 推荐库

9.2 设计参考

  • Jenkins 构建详情页
  • GitLab CI/CD Pipeline 可视化
  • GitHub Actions 工作流可视化

10. 常见问题

Q1: 节点太多时如何优化显示? A: 使用缩略图导航 + 局部放大超过100个节点时考虑分页或折叠显示。

Q2: 如何处理长时间运行的部署? A: 实现自动刷新5-10秒间隔+ WebSocket推送更新。

Q3: 失败节点如何快速定位? A: 在头部添加"快速定位失败节点"按钮,点击自动滚动并高亮。

Q4: 如何显示审批节点的审批人信息? A: 在节点 outputs 中包含 approverapprovalTime 等信息,详情面板中展示。


11. 迭代计划

V1.0(基础版)

  • 流程图基本渲染
  • 节点状态显示
  • 统计信息展示
  • 节点详情查看

V2.0(增强版)

  • 📋 实时刷新
  • 📋 节点搜索/过滤
  • 📋 导出功能
  • 📋 操作历史记录

V3.0(高级版)

  • 📋 时间轴视图
  • 📋 性能分析(节点耗时对比)
  • 📋 历史部署对比
  • 📋 智能建议(性能优化建议)

联系方式

如有问题请联系后端开发团队或查阅API文档。