'use client'; import React, { useState, useEffect } from 'react'; interface TaskTimelineData { timestamp: string; completed: number; failed: number; in_progress: number; total: number; } interface TaskTimelineChartProps { projectIds: string[]; timeRange: number; // 小时 } export default function TaskTimelineChart({ projectIds, timeRange }: TaskTimelineChartProps) { const [timelineData, setTimelineData] = useState([]); const [loading, setLoading] = useState(false); const [selectedMetric, setSelectedMetric] = useState<'total' | 'completed' | 'failed' | 'success_rate'>('total'); // 模拟生成时间线数据 const generateMockData = () => { const data: TaskTimelineData[] = []; const now = new Date(); const intervalHours = timeRange <= 24 ? 1 : timeRange <= 72 ? 3 : 6; const points = Math.floor(timeRange / intervalHours); for (let i = points; i >= 0; i--) { const timestamp = new Date(now.getTime() - i * intervalHours * 60 * 60 * 1000); // 模拟数据波动 const baseCompleted = 50 + Math.random() * 30; const baseFailed = 5 + Math.random() * 10; const baseInProgress = 10 + Math.random() * 15; // 根据项目数量调整 const multiplier = projectIds.length; data.push({ timestamp: timestamp.toISOString(), completed: Math.floor(baseCompleted * multiplier), failed: Math.floor(baseFailed * multiplier), in_progress: Math.floor(baseInProgress * multiplier), total: Math.floor((baseCompleted + baseFailed + baseInProgress) * multiplier), }); } return data; }; useEffect(() => { setLoading(true); // 模拟API调用 setTimeout(() => { setTimelineData(generateMockData()); setLoading(false); }, 500); }, [projectIds, timeRange]); const getMetricValue = (data: TaskTimelineData, metric: string) => { switch (metric) { case 'total': return data.total; case 'completed': return data.completed; case 'failed': return data.failed; case 'success_rate': return data.total > 0 ? (data.completed / data.total) * 100 : 0; default: return 0; } }; const getMetricColor = (metric: string) => { switch (metric) { case 'total': return '#3b82f6'; case 'completed': return '#10b981'; case 'failed': return '#ef4444'; case 'success_rate': return '#8b5cf6'; default: return '#6b7280'; } }; const formatTimestamp = (timestamp: string) => { const date = new Date(timestamp); if (timeRange <= 24) { return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); } else { return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' }); } }; const LineChart = () => { if (timelineData.length === 0) return null; const values = timelineData.map(d => getMetricValue(d, selectedMetric)); const maxValue = Math.max(...values); const minValue = Math.min(...values); const range = maxValue - minValue || 1; const width = 600; const height = 200; const padding = 40; const points = timelineData.map((data, index) => { const x = padding + (index / (timelineData.length - 1)) * (width - 2 * padding); const value = getMetricValue(data, selectedMetric); const y = height - padding - ((value - minValue) / range) * (height - 2 * padding); return { x, y, value, data }; }); const pathData = points.map((point, index) => `${index === 0 ? 'M' : 'L'} ${point.x} ${point.y}` ).join(' '); return (
{/* 网格线 */} {/* Y轴标签 */} {[0, 0.25, 0.5, 0.75, 1].map((ratio, index) => { const y = height - padding - ratio * (height - 2 * padding); const value = minValue + ratio * range; return ( {selectedMetric === 'success_rate' ? `${value.toFixed(0)}%` : Math.round(value)} ); })} {/* 主线条 */} {/* 填充区域 */} {/* 数据点 */} {points.map((point, index) => ( {formatTimestamp(point.data.timestamp)}: { selectedMetric === 'success_rate' ? `${point.value.toFixed(1)}%` : Math.round(point.value) } ))} {/* X轴标签 */} {points.filter((_, index) => index % Math.ceil(points.length / 6) === 0).map((point, index) => ( {formatTimestamp(point.data.timestamp)} ))}
); }; const metrics = [ { key: 'total', label: '总任务数', color: '#3b82f6' }, { key: 'completed', label: '完成数', color: '#10b981' }, { key: 'failed', label: '失败数', color: '#ef4444' }, { key: 'success_rate', label: '成功率', color: '#8b5cf6' }, ]; return (

任务趋势分析

{metrics.map((metric) => ( ))}
{loading ? (
加载趋势数据...
) : timelineData.length === 0 ? (
📈
暂无趋势数据
) : (
{/* 统计信息 */}
{metrics.map((metric) => { const values = timelineData.map(d => getMetricValue(d, metric.key)); const latest = values[values.length - 1] || 0; const previous = values[values.length - 2] || 0; const change = previous !== 0 ? ((latest - previous) / previous) * 100 : 0; return (
{metric.label}
{metric.key === 'success_rate' ? `${latest.toFixed(1)}%` : Math.round(latest)}
= 0 ? 'text-emerald-400' : 'text-red-400'}`}> {change >= 0 ? '↗' : '↘'} {Math.abs(change).toFixed(1)}%
); })}
)}
); }