|
|
@ -1,5 +1,12 @@ |
|
|
|
import React, { useMemo, useEffect, useRef, useState, useCallback } from "react"; |
|
|
|
import { Typography, Badge, Button, Space } from "antd"; |
|
|
|
import React, { |
|
|
|
useMemo, |
|
|
|
useEffect, |
|
|
|
useRef, |
|
|
|
useState, |
|
|
|
useCallback, |
|
|
|
} from "react"; |
|
|
|
import { Typography, Badge, Button, Space,InputNumber } from "antd"; |
|
|
|
import { DownloadOutlined } from "@ant-design/icons"; |
|
|
|
import { |
|
|
|
Chart as ChartJS, |
|
|
|
CategoryScale, |
|
|
@ -25,7 +32,7 @@ ChartJS.register( |
|
|
|
|
|
|
|
const { Title } = Typography; |
|
|
|
|
|
|
|
const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
const RealtimeCharts = ({ tableData, lastUpdateTime, onDataExport,exportCount = 200, setExportCount }) => { |
|
|
|
const xChartRef = useRef(null); |
|
|
|
const yChartRef = useRef(null); |
|
|
|
|
|
|
@ -119,7 +126,7 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
// 获取 deviceId 到 desc 的映射 |
|
|
|
const deviceDescMap = useMemo(() => { |
|
|
|
const map = {}; |
|
|
|
tableData.forEach(item => { |
|
|
|
tableData.forEach((item) => { |
|
|
|
if (item.deviceId && item.desc) { |
|
|
|
map[item.deviceId] = item.desc; |
|
|
|
} |
|
|
@ -130,18 +137,18 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
useEffect(() => { |
|
|
|
if (deviceIds.length > 0) { |
|
|
|
const initialVisible = {}; |
|
|
|
deviceIds.forEach(deviceId => { |
|
|
|
deviceIds.forEach((deviceId) => { |
|
|
|
initialVisible[deviceId] = true; // 默认所有标靶都显示 |
|
|
|
}); |
|
|
|
setVisibleTargets(prev => ({ ...initialVisible, ...prev })); |
|
|
|
setVisibleTargets((prev) => ({ ...initialVisible, ...prev })); |
|
|
|
} |
|
|
|
}, [deviceIds.join(',')]); |
|
|
|
}, [deviceIds.join(",")]); |
|
|
|
|
|
|
|
// 切换标靶显示状态的函数 |
|
|
|
const toggleTargetVisibility = useCallback((deviceId) => { |
|
|
|
setVisibleTargets(prev => ({ |
|
|
|
setVisibleTargets((prev) => ({ |
|
|
|
...prev, |
|
|
|
[deviceId]: !prev[deviceId] |
|
|
|
[deviceId]: !prev[deviceId], |
|
|
|
})); |
|
|
|
|
|
|
|
// 同时控制两个图表的显示状态 |
|
|
@ -149,8 +156,12 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
const yChart = yChartRef.current; |
|
|
|
|
|
|
|
if (xChart && yChart) { |
|
|
|
const xDatasetIndex = xChart.data.datasets.findIndex(dataset => dataset.label === deviceId); |
|
|
|
const yDatasetIndex = yChart.data.datasets.findIndex(dataset => dataset.label === deviceId); |
|
|
|
const xDatasetIndex = xChart.data.datasets.findIndex( |
|
|
|
(dataset) => dataset.label === deviceId |
|
|
|
); |
|
|
|
const yDatasetIndex = yChart.data.datasets.findIndex( |
|
|
|
(dataset) => dataset.label === deviceId |
|
|
|
); |
|
|
|
|
|
|
|
if (xDatasetIndex !== -1 && yDatasetIndex !== -1) { |
|
|
|
const isVisible = xChart.isDatasetVisible(xDatasetIndex); |
|
|
@ -167,9 +178,10 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
}, []); |
|
|
|
|
|
|
|
// 全部显示/隐藏的函数 |
|
|
|
const toggleAllTargets = useCallback((visible) => { |
|
|
|
const toggleAllTargets = useCallback( |
|
|
|
(visible) => { |
|
|
|
const newVisibleTargets = {}; |
|
|
|
deviceIds.forEach(deviceId => { |
|
|
|
deviceIds.forEach((deviceId) => { |
|
|
|
newVisibleTargets[deviceId] = visible; |
|
|
|
}); |
|
|
|
setVisibleTargets(newVisibleTargets); |
|
|
@ -189,7 +201,9 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
xChart.update(); |
|
|
|
yChart.update(); |
|
|
|
} |
|
|
|
}, [deviceIds]); |
|
|
|
}, |
|
|
|
[deviceIds] |
|
|
|
); |
|
|
|
|
|
|
|
// 准备X轴图表数据 |
|
|
|
const xChartData = useMemo(() => { |
|
|
@ -229,7 +243,10 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
|
|
|
|
return { labels, datasets }; |
|
|
|
} catch (error) { |
|
|
|
console.error(`${new Date().toLocaleString()} - 准备X轴图表数据出错:`, error); |
|
|
|
console.error( |
|
|
|
`${new Date().toLocaleString()} - 准备X轴图表数据出错:`, |
|
|
|
error |
|
|
|
); |
|
|
|
return { labels: [], datasets: [] }; |
|
|
|
} |
|
|
|
}, [tableData, visibleTargets]); |
|
|
@ -272,13 +289,17 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
|
|
|
|
return { labels, datasets }; |
|
|
|
} catch (error) { |
|
|
|
console.error(`${new Date().toLocaleString()} - 准备Y轴图表数据出错:`, error); |
|
|
|
console.error( |
|
|
|
`${new Date().toLocaleString()} - 准备Y轴图表数据出错:`, |
|
|
|
error |
|
|
|
); |
|
|
|
return { labels: [], datasets: [] }; |
|
|
|
} |
|
|
|
}, [tableData, visibleTargets]); |
|
|
|
|
|
|
|
// Chart.js配置选项 - 移到组件内部以访问 deviceDescMap |
|
|
|
const chartOptions = useMemo(() => ({ |
|
|
|
const chartOptions = useMemo( |
|
|
|
() => ({ |
|
|
|
responsive: true, |
|
|
|
maintainAspectRatio: false, |
|
|
|
interaction: { |
|
|
@ -294,16 +315,16 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
return tooltipItem.parsed.y !== null; |
|
|
|
}, |
|
|
|
callbacks: { |
|
|
|
title: function(context) { |
|
|
|
title: function (context) { |
|
|
|
return context[0].label; // 显示时间 |
|
|
|
}, |
|
|
|
label: function(context) { |
|
|
|
label: function (context) { |
|
|
|
const deviceId = context.dataset.label; |
|
|
|
const deviceDesc = deviceDescMap[deviceId] || deviceId; |
|
|
|
const value = context.parsed.y; |
|
|
|
return `${deviceDesc}: ${value}`; |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
scales: { |
|
|
@ -345,10 +366,13 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
tension: 0, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}), [deviceDescMap]); |
|
|
|
}), |
|
|
|
[deviceDescMap] |
|
|
|
); |
|
|
|
|
|
|
|
// X轴图表配置 |
|
|
|
const xChartOptions = useMemo(() => ({ |
|
|
|
const xChartOptions = useMemo( |
|
|
|
() => ({ |
|
|
|
...chartOptions, |
|
|
|
plugins: { |
|
|
|
...chartOptions.plugins, |
|
|
@ -370,10 +394,13 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}), [chartOptions]); |
|
|
|
}), |
|
|
|
[chartOptions] |
|
|
|
); |
|
|
|
|
|
|
|
// Y轴图表配置 |
|
|
|
const yChartOptions = useMemo(() => ({ |
|
|
|
const yChartOptions = useMemo( |
|
|
|
() => ({ |
|
|
|
...chartOptions, |
|
|
|
plugins: { |
|
|
|
...chartOptions.plugins, |
|
|
@ -395,7 +422,9 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}), [chartOptions]); |
|
|
|
}), |
|
|
|
[chartOptions] |
|
|
|
); |
|
|
|
|
|
|
|
// 如果没有数据,显示空状态 |
|
|
|
if (!tableData || tableData.length === 0) { |
|
|
@ -442,7 +471,14 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
flexDirection: "column", |
|
|
|
}} |
|
|
|
> |
|
|
|
<Title level={4} style={{ marginBottom: "16px" }}> |
|
|
|
<Title |
|
|
|
level={4} |
|
|
|
style={{ |
|
|
|
marginBottom: "16px", |
|
|
|
display: "flex", |
|
|
|
alignItems: "center", |
|
|
|
}} |
|
|
|
> |
|
|
|
实时数据图 |
|
|
|
<Badge |
|
|
|
status="processing" |
|
|
@ -450,15 +486,50 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
style={{ marginLeft: "16px", fontSize: "12px" }} |
|
|
|
/> |
|
|
|
<Badge |
|
|
|
status="default" |
|
|
|
status="success" |
|
|
|
text={`图表数据采样频率:1Hz`} |
|
|
|
style={{ marginLeft: "16px", fontSize: "12px" }} |
|
|
|
/> |
|
|
|
<Badge |
|
|
|
status="default" |
|
|
|
text={`导出数据条数配置`} |
|
|
|
style={{ marginLeft: "16px", fontSize: "12px" }} |
|
|
|
/> |
|
|
|
<InputNumber |
|
|
|
min={1} |
|
|
|
max={1000} |
|
|
|
value={exportCount} |
|
|
|
onChange={setExportCount} |
|
|
|
style={{ marginLeft: 16, width: 80 }} |
|
|
|
step={1} |
|
|
|
/> |
|
|
|
<Button |
|
|
|
style={{ marginLeft: 8 }} |
|
|
|
icon={<DownloadOutlined />} |
|
|
|
onClick={() => onDataExport(exportCount)} |
|
|
|
> |
|
|
|
导出 |
|
|
|
</Button> |
|
|
|
</Title> |
|
|
|
|
|
|
|
{/* 统一的图例控制器 */} |
|
|
|
<div style={{ marginBottom: "16px", padding: "12px", backgroundColor: "#fafafa", borderRadius: "6px" }}> |
|
|
|
<div style={{ marginBottom: "8px", fontSize: "14px", fontWeight: "500" }}>显示控制</div> |
|
|
|
<div |
|
|
|
style={{ |
|
|
|
marginBottom: "16px", |
|
|
|
padding: "12px", |
|
|
|
backgroundColor: "#fafafa", |
|
|
|
borderRadius: "6px", |
|
|
|
}} |
|
|
|
> |
|
|
|
<div |
|
|
|
style={{ |
|
|
|
marginBottom: "8px", |
|
|
|
fontSize: "14px", |
|
|
|
fontWeight: "500", |
|
|
|
}} |
|
|
|
> |
|
|
|
显示控制 |
|
|
|
</div> |
|
|
|
<Space wrap> |
|
|
|
<Button |
|
|
|
size="small" |
|
|
@ -467,10 +538,7 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
> |
|
|
|
全部显示 |
|
|
|
</Button> |
|
|
|
<Button |
|
|
|
size="small" |
|
|
|
onClick={() => toggleAllTargets(false)} |
|
|
|
> |
|
|
|
<Button size="small" onClick={() => toggleAllTargets(false)}> |
|
|
|
全部隐藏 |
|
|
|
</Button> |
|
|
|
{deviceIds.map((deviceId, index) => ( |
|
|
@ -480,8 +548,12 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => { |
|
|
|
type={visibleTargets[deviceId] ? "primary" : "default"} |
|
|
|
style={{ |
|
|
|
borderColor: getDeviceColor(index), |
|
|
|
color: visibleTargets[deviceId] ? "#fff" : getDeviceColor(index), |
|
|
|
backgroundColor: visibleTargets[deviceId] ? getDeviceColor(index) : "#fff" |
|
|
|
color: visibleTargets[deviceId] |
|
|
|
? "#fff" |
|
|
|
: getDeviceColor(index), |
|
|
|
backgroundColor: visibleTargets[deviceId] |
|
|
|
? getDeviceColor(index) |
|
|
|
: "#fff", |
|
|
|
}} |
|
|
|
onClick={() => toggleTargetVisibility(deviceId)} |
|
|
|
> |
|
|
|