149 lines
5.7 KiB
TypeScript
149 lines
5.7 KiB
TypeScript
import {useEffect, useMemo, useState} from 'react';
|
|
import {Thermometer} from 'lucide-react';
|
|
import {CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts';
|
|
import {ChartPlaceholder, CustomTooltip} from '@/components/common';
|
|
import {useMetricsQuery} from '@/hooks/server/queries';
|
|
import {TEMPERATURE_COLORS} from '@/constants/server';
|
|
import {ChartContainer} from './ChartContainer';
|
|
|
|
interface TemperatureChartProps {
|
|
agentId: string;
|
|
timeRange: string;
|
|
}
|
|
|
|
/**
|
|
* 系统温度图表组件
|
|
* 支持温度类型切换
|
|
*/
|
|
export const TemperatureChart = ({agentId, timeRange}: TemperatureChartProps) => {
|
|
const [selectedTempType, setSelectedTempType] = useState<string>('all');
|
|
|
|
// 数据查询
|
|
const {data: metricsResponse, isLoading} = useMetricsQuery({
|
|
agentId,
|
|
type: 'temperature',
|
|
range: timeRange,
|
|
});
|
|
|
|
// 数据转换
|
|
const chartData = useMemo(() => {
|
|
if (!metricsResponse?.data.series || metricsResponse.data.series.length === 0) return [];
|
|
|
|
// 按时间戳聚合所有温度系列
|
|
const timeMap = new Map<number, any>();
|
|
|
|
metricsResponse.data.series.forEach(series => {
|
|
const sensorName = series.name; // 使用系列名称作为传感器标识
|
|
series.data.forEach(point => {
|
|
const time = new Date(point.timestamp).toLocaleTimeString('zh-CN', {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
|
|
if (!timeMap.has(point.timestamp)) {
|
|
timeMap.set(point.timestamp, {time, timestamp: point.timestamp});
|
|
}
|
|
|
|
const existing = timeMap.get(point.timestamp)!;
|
|
existing[sensorName] = Number(point.value.toFixed(2));
|
|
});
|
|
});
|
|
|
|
return Array.from(timeMap.values());
|
|
}, [metricsResponse]);
|
|
|
|
// 提取所有唯一的温度类型
|
|
const temperatureTypes = useMemo(() => {
|
|
return metricsResponse?.data.series.map(s => s.name).sort() || [];
|
|
}, [metricsResponse]);
|
|
|
|
// 根据选中的类型过滤温度数据
|
|
const filteredTemperatureTypes = useMemo(() => {
|
|
if (selectedTempType === 'all') {
|
|
return temperatureTypes;
|
|
}
|
|
return temperatureTypes.filter(type => type === selectedTempType);
|
|
}, [temperatureTypes, selectedTempType]);
|
|
|
|
// 当温度类型列表变化时,如果当前选中的类型不在列表中,重置为 'all'
|
|
useEffect(() => {
|
|
if (selectedTempType !== 'all' && temperatureTypes.length > 0) {
|
|
if (!temperatureTypes.includes(selectedTempType)) {
|
|
setSelectedTempType('all');
|
|
}
|
|
}
|
|
}, [temperatureTypes, selectedTempType]);
|
|
|
|
// 温度类型选择器
|
|
const tempTypeSelector = temperatureTypes.length > 1 && (
|
|
<select
|
|
value={selectedTempType}
|
|
onChange={(e) => setSelectedTempType(e.target.value)}
|
|
className="rounded-lg border border-cyan-900/50 bg-black/40 px-3 py-1.5 text-xs font-mono text-cyan-300 hover:border-cyan-700 focus:border-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/20"
|
|
>
|
|
<option value="all">所有类型</option>
|
|
{temperatureTypes.map((type) => (
|
|
<option key={type} value={type}>
|
|
{type}
|
|
</option>
|
|
))}
|
|
</select>
|
|
);
|
|
|
|
// 渲染
|
|
if (isLoading) {
|
|
return (
|
|
<ChartContainer title="系统温度" icon={Thermometer} action={tempTypeSelector}>
|
|
<ChartPlaceholder variant="dark"/>
|
|
</ChartContainer>
|
|
);
|
|
}
|
|
|
|
// 如果没有温度数据,不渲染组件
|
|
if (chartData.length === 0 || temperatureTypes.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<ChartContainer title="系统温度" icon={Thermometer} action={tempTypeSelector}>
|
|
<ResponsiveContainer width="100%" height={220}>
|
|
<LineChart data={chartData}>
|
|
<CartesianGrid stroke="currentColor" strokeDasharray="4 4" className="stroke-cyan-900/30"/>
|
|
<XAxis
|
|
dataKey="time"
|
|
stroke="currentColor"
|
|
className="stroke-cyan-600"
|
|
style={{fontSize: '12px'}}
|
|
/>
|
|
<YAxis
|
|
stroke="currentColor"
|
|
className="stroke-cyan-600"
|
|
style={{fontSize: '12px'}}
|
|
tickFormatter={(value) => `${value}°C`}
|
|
/>
|
|
<Tooltip content={<CustomTooltip unit="°C" variant="dark"/>}/>
|
|
<Legend/>
|
|
{/* 为选中的温度类型渲染线条 */}
|
|
{filteredTemperatureTypes.map((type, index) => {
|
|
// 使用预定义颜色,如果没有则使用默认颜色
|
|
const color = TEMPERATURE_COLORS[type] || `hsl(${(index * 60) % 360}, 70%, 50%)`;
|
|
return (
|
|
<Line
|
|
key={type}
|
|
type="monotone"
|
|
dataKey={type}
|
|
name={type}
|
|
stroke={color}
|
|
strokeWidth={2}
|
|
dot={false}
|
|
activeDot={{r: 3}}
|
|
connectNulls
|
|
/>
|
|
);
|
|
})}
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</ChartContainer>
|
|
);
|
|
};
|