Browse Source

refactor:将图表库从 ECharts 迁移到 Chart.js,以提升图表功能和性能

master
qinjian 4 days ago
parent
commit
1f8c45a797
  1. 495
      client/src/sections/wuyuanbiaoba/components/RealtimeCharts.jsx
  2. 12
      client/src/sections/wuyuanbiaoba/components/RealtimeDataTable.jsx
  3. 98
      client/src/sections/wuyuanbiaoba/container/index.jsx
  4. 99
      package-lock.json
  5. 4
      package.json
  6. 6
      server/tcpProxy/index.js

495
client/src/sections/wuyuanbiaoba/components/RealtimeCharts.jsx

@ -1,233 +1,344 @@
import React from 'react'; import React, { useMemo, useEffect, useRef } from "react";
import { Typography, Badge } from 'antd'; import { Typography, Badge } from "antd";
import ReactECharts from 'echarts-for-react'; import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title as ChartTitle,
Tooltip,
Legend,
} from "chart.js";
import { Line } from "react-chartjs-2";
// Chart.js
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
ChartTitle,
Tooltip,
Legend
);
const { Title } = Typography; const { Title } = Typography;
const RealtimeCharts = ({ tableData, lastUpdateTime }) => { const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
const xChartRef = useRef(null);
const yChartRef = useRef(null);
//
if (!Array.isArray(tableData)) {
console.warn('tableData is not an array:', tableData);
return <div>数据格式错误</div>;
}
// //
const getDeviceColor = (deviceId) => { const getDeviceColor = (deviceId) => {
const colorMap = { const colorMap = {
'DEV000': "#1890ff", // target1: "#52c41a", // 绿
'DEV001': "#52c41a", // 绿 target2: "#faad14", //
'DEV002': "#faad14", // target3: "#f5222d", //
'DEV003': "#f5222d", // target4: "#722ed1", //
'DEV004': "#722ed1", // target5: "#fa8c16", //
'DEV005': "#fa8c16", // target6: "#13c2c2", //
'DEV006': "#13c2c2", // target7: "#eb2f96", //
'DEV007': "#eb2f96", // target8: "#2f54eb", //
'DEV008': "#2f54eb", // target9: "#fa541c", //
'DEV009': "#fa541c", // target10: "#1890ff", //
}; };
// ID使 return colorMap[deviceId] || "#1890ff";
if (colorMap[deviceId]) { };
return colorMap[deviceId];
}
// // - 25
const deviceNumber = deviceId.replace(/\D/g, ''); // const sampleData = (data) => {
const colors = [ if (!data || data.length === 0) {
"#1890ff", "#52c41a", "#faad14", "#f5222d", "#722ed1", return {
"#fa8c16", "#13c2c2", "#eb2f96", "#2f54eb", "#fa541c" labels: [],
]; deviceIds: [],
return colors[parseInt(deviceNumber) % colors.length]; timeGroups: {},
sortedTimes: []
}; };
}
try {
//
const timeGroups = {};
data.forEach((item) => {
if (!item || !item.updateTime) return;
const timeKey = Math.floor(new Date(item.updateTime).getTime() / 1000); //
if (!timeGroups[timeKey]) {
timeGroups[timeKey] = [];
}
timeGroups[timeKey].push(item);
});
// // 25
const prepareChartData = () => { const sortedTimes = Object.keys(timeGroups)
// ID .sort((a, b) => Number(b) - Number(a))
const deviceIds = [...new Set(tableData.map((item) => item.deviceId))].sort(); .slice(0, 25)
.reverse();
// // ID
const allTimes = [ const deviceIds = [...new Set(data.map((item) => item?.deviceId).filter(Boolean))].sort();
...new Set(tableData.map((item) => item.updateTime)),
].sort((a, b) => new Date(a) - new Date(b)); //
const timeLabels = allTimes.map((time) => { const labels = sortedTimes.map((timeKey) => {
const date = new Date(time); return new Date(Number(timeKey) * 1000).toLocaleTimeString("zh-CN", {
return date.toLocaleTimeString("zh-CN", {
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
second: "2-digit", second: "2-digit",
}); });
}); });
// return { labels, deviceIds, timeGroups, sortedTimes };
const deviceData = deviceIds.map((deviceId) => { } catch (error) {
const deviceItems = tableData.filter( console.error('数据采样出错:', error);
return {
labels: [],
deviceIds: [],
timeGroups: {},
sortedTimes: []
};
}
};
// X
const xChartData = useMemo(() => {
try {
const { labels, deviceIds, timeGroups, sortedTimes } = sampleData(tableData);
if (!deviceIds || deviceIds.length === 0) {
return { labels: [], datasets: [] };
}
const datasets = deviceIds.map((deviceId) => {
const data = sortedTimes.map((timeKey) => {
const timeData = timeGroups[timeKey] || [];
const deviceData = timeData.find(
(item) => item.deviceId === deviceId (item) => item.deviceId === deviceId
); );
return deviceData && deviceData.xValue !== undefined ? parseFloat(deviceData.xValue) : null;
// XYnull
const xData = allTimes.map((time) => {
const item = deviceItems.find((d) => d.updateTime === time);
return item ? parseFloat(item.xValue) : null;
}); });
const yData = allTimes.map((time) => { return {
const item = deviceItems.find((d) => d.updateTime === time); label: deviceId,
return item ? parseFloat(item.yValue) : null; data: data,
borderColor: getDeviceColor(deviceId),
backgroundColor: getDeviceColor(deviceId) + "20",
borderWidth: 2,
pointRadius: 3,
pointHoverRadius: 5,
tension: 0,
connectNulls: false,
};
}); });
// 使 return { labels, datasets };
const color = getDeviceColor(deviceId); } catch (error) {
console.error('准备X轴图表数据出错:', error);
return { labels: [], datasets: [] };
}
}, [tableData]);
// Y
const yChartData = useMemo(() => {
try {
const { labels, deviceIds, timeGroups, sortedTimes } = sampleData(tableData);
if (!deviceIds || deviceIds.length === 0) {
return { labels: [], datasets: [] };
}
const datasets = deviceIds.map((deviceId) => {
const data = sortedTimes.map((timeKey) => {
const timeData = timeGroups[timeKey] || [];
const deviceData = timeData.find(
(item) => item.deviceId === deviceId
);
return deviceData && deviceData.yValue !== undefined ? parseFloat(deviceData.yValue) : null;
});
return { return {
deviceId, label: deviceId,
xData, data: data,
yData, borderColor: getDeviceColor(deviceId),
color, backgroundColor: getDeviceColor(deviceId) + "20",
borderWidth: 2,
pointRadius: 3,
pointHoverRadius: 5,
tension: 0,
connectNulls: false,
}; };
}); });
return { timeLabels, deviceData }; return { labels, datasets };
}; } catch (error) {
console.error('准备Y轴图表数据出错:', error);
return { labels: [], datasets: [] };
}
}, [tableData]);
const { timeLabels, deviceData } = prepareChartData(); // Chart.js
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: "index",
intersect: false,
},
plugins: {
legend: {
position: "bottom",
labels: {
usePointStyle: true,
padding: 20,
font: {
size: 12,
},
},
},
tooltip: {
filter: function (tooltipItem) {
return tooltipItem.parsed.y !== null;
},
},
},
scales: {
x: {
display: true,
title: {
display: true,
text: "时间",
},
ticks: {
maxRotation: 45,
minRotation: 0,
font: {
size: 10,
},
},
},
y: {
display: true,
title: {
display: true,
font: {
size: 12,
},
},
ticks: {
font: {
size: 10,
},
},
},
},
elements: {
point: {
radius: 3,
hoverRadius: 5,
},
line: {
tension: 0,
},
},
};
// X线 // X
const getXChartOption = () => ({ const xChartOptions = {
...chartOptions,
plugins: {
...chartOptions.plugins,
title: { title: {
display: true,
text: "X轴位移数据", text: "X轴位移数据",
left: "center", font: {
textStyle: { size: 16,
fontSize: 16,
fontWeight: "normal",
}, },
}, },
tooltip: {
trigger: "axis",
formatter: function (params) {
let result = `时间: ${params[0].axisValue}<br/>`;
params.forEach((param) => {
if (param.value !== null) {
result += `${param.seriesName}: ${param.value} mm<br/>`;
}
});
return result;
}, },
scales: {
...chartOptions.scales,
y: {
...chartOptions.scales.y,
title: {
...chartOptions.scales.y.title,
text: "X值(mm)",
}, },
legend: {
orient: "horizontal",
bottom: "5%",
textStyle: {
fontSize: 12,
},
},
grid: {
left: "3%",
right: "4%",
bottom: "15%",
top: "15%",
containLabel: true,
},
xAxis: {
type: "category",
data: timeLabels,
axisLabel: {
rotate: 45,
fontSize: 11,
},
},
yAxis: {
type: "value",
name: "X值(mm)",
nameTextStyle: {
fontSize: 13,
},
axisLabel: {
fontSize: 11,
},
},
series: deviceData.map((device) => ({
name: device.deviceId,
type: "line",
data: device.xData,
smooth: false,
connectNulls: false,
lineStyle: {
color: device.color,
width: 2,
}, },
itemStyle: {
color: device.color,
}, },
symbol: "circle", };
symbolSize: 4,
})),
});
// Y线 // Y
const getYChartOption = () => ({ const yChartOptions = {
...chartOptions,
plugins: {
...chartOptions.plugins,
title: { title: {
display: true,
text: "Y轴位移数据", text: "Y轴位移数据",
left: "center", font: {
textStyle: { size: 16,
fontSize: 16,
fontWeight: "normal",
}, },
}, },
tooltip: {
trigger: "axis",
formatter: function (params) {
let result = `时间: ${params[0].axisValue}<br/>`;
params.forEach((param) => {
if (param.value !== null) {
result += `${param.seriesName}: ${param.value} mm<br/>`;
}
});
return result;
}, },
scales: {
...chartOptions.scales,
y: {
...chartOptions.scales.y,
title: {
...chartOptions.scales.y.title,
text: "Y值(mm)",
}, },
legend: {
orient: "horizontal",
bottom: "5%",
textStyle: {
fontSize: 12,
},
},
grid: {
left: "3%",
right: "4%",
bottom: "15%",
top: "15%",
containLabel: true,
},
xAxis: {
type: "category",
data: timeLabels,
axisLabel: {
rotate: 45,
fontSize: 11,
},
},
yAxis: {
type: "value",
name: "Y值(mm)",
nameTextStyle: {
fontSize: 13,
},
axisLabel: {
fontSize: 11,
},
},
series: deviceData.map((device) => ({
name: device.deviceId,
type: "line",
data: device.yData,
smooth: false,
connectNulls: false,
lineStyle: {
color: device.color,
width: 2,
}, },
itemStyle: {
color: device.color,
}, },
symbol: "circle", };
symbolSize: 4,
})), //
}); useEffect(() => {
console.log('RealtimeCharts - tableData:', tableData);
console.log('RealtimeCharts - xChartData:', xChartData);
console.log('RealtimeCharts - yChartData:', yChartData);
}, [tableData, xChartData, yChartData]);
//
if (!tableData || tableData.length === 0) {
return (
<div
style={{
flex: 2,
padding: "16px",
display: "flex",
flexDirection: "column",
}}
>
<Title level={4} style={{ marginBottom: "16px" }}>
实时数据图
<Badge
status="default"
text="等待数据..."
style={{ marginLeft: "16px", fontSize: "12px" }}
/>
</Title>
<div style={{
flex: 1,
display: "flex",
alignItems: "center",
justifyContent: "center",
minHeight: "500px"
}}>
<div style={{ textAlign: "center", color: "#999" }}>
<p>等待实时数据...</p>
</div>
</div>
</div>
);
}
return ( return (
<div <div
@ -262,13 +373,14 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
backgroundColor: "white", backgroundColor: "white",
borderRadius: "8px", borderRadius: "8px",
border: "1px solid #e8e8e8", border: "1px solid #e8e8e8",
minHeight: "250px", minHeight: "280px",
padding: "16px",
}} }}
> >
<ReactECharts <Line
option={getXChartOption()} ref={xChartRef}
style={{ height: "100%", width: "100%" }} data={xChartData}
opts={{ renderer: "canvas" }} options={xChartOptions}
/> />
</div> </div>
@ -279,13 +391,14 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
backgroundColor: "white", backgroundColor: "white",
borderRadius: "8px", borderRadius: "8px",
border: "1px solid #e8e8e8", border: "1px solid #e8e8e8",
minHeight: "250px", minHeight: "280px",
padding: "16px",
}} }}
> >
<ReactECharts <Line
option={getYChartOption()} ref={yChartRef}
style={{ height: "100%", width: "100%" }} data={yChartData}
opts={{ renderer: "canvas" }} options={yChartOptions}
/> />
</div> </div>
</div> </div>

12
client/src/sections/wuyuanbiaoba/components/RealtimeDataTable.jsx

@ -10,15 +10,21 @@ const RealtimeDataTable = ({ realtimeData }) => {
title: "X值(mm)", title: "X值(mm)",
dataIndex: "xValue", dataIndex: "xValue",
key: "xValue", key: "xValue",
render: (text) => Number(text), render: (text) => Number(text),
}, },
{ {
title: "Y值(mm)", title: "Y值(mm)",
dataIndex: "yValue", dataIndex: "yValue",
key: "yValue", key: "yValue",
render: (text) => Number(text), render: (text) => Number(text),
}, },
{ title: "更新时间", dataIndex: "updateTime", key: "updateTime", width: 180 }, {
title: "更新时间",
dataIndex: "updateTime",
key: "updateTime",
},
]; ];
return ( return (
@ -48,10 +54,6 @@ const RealtimeDataTable = ({ realtimeData }) => {
columns={tableColumns} columns={tableColumns}
pagination={false} pagination={false}
size="small" size="small"
scroll={{
y: 508,
}}
virtual
/> />
</div> </div>
</div> </div>

98
client/src/sections/wuyuanbiaoba/container/index.jsx

@ -10,7 +10,11 @@ import {
TemplateModal, TemplateModal,
TargetDetailModal, TargetDetailModal,
} from "../components"; } from "../components";
import { WebSocketProvider, useWebSocket, useWebSocketSubscription } from "../actions/websocket.jsx"; import {
WebSocketProvider,
useWebSocket,
useWebSocketSubscription,
} from "../actions/websocket.jsx";
import { useTemplateStorage } from "../hooks/useTemplateStorage.js"; import { useTemplateStorage } from "../hooks/useTemplateStorage.js";
import { useTargetStorage } from "../hooks/useTargetStorage.js"; import { useTargetStorage } from "../hooks/useTargetStorage.js";
@ -21,7 +25,7 @@ const WuyuanbiaobaContent = () => {
const { isConnected, sendMessage } = useWebSocket(); const { isConnected, sendMessage } = useWebSocket();
// //
const realtimeDataSubscription = useWebSocketSubscription('dev', 'data'); const realtimeDataSubscription = useWebSocketSubscription("dev", "data");
const { const {
templates: tempListData, templates: tempListData,
@ -45,6 +49,9 @@ const WuyuanbiaobaContent = () => {
const [realtimeData, setRealtimeData] = useState([]); const [realtimeData, setRealtimeData] = useState([]);
const [lastUpdateTime, setLastUpdateTime] = useState(new Date()); const [lastUpdateTime, setLastUpdateTime] = useState(new Date());
//
const [lastSampleTime, setLastSampleTime] = useState(0);
// //
const [templateModalVisible, setTemplateModalVisible] = useState(false); const [templateModalVisible, setTemplateModalVisible] = useState(false);
const [templateModalMode, setTemplateModalMode] = useState("add"); // 'add' | 'edit' const [templateModalMode, setTemplateModalMode] = useState("add"); // 'add' | 'edit'
@ -65,7 +72,7 @@ const WuyuanbiaobaContent = () => {
return []; return [];
} }
return data.data.map(item => ({ return data.data.map((item) => ({
key: item.pos, key: item.pos,
deviceId: `target${Number(item.pos) + 1}`, deviceId: `target${Number(item.pos) + 1}`,
xValue: item.x, xValue: item.x,
@ -79,22 +86,23 @@ const WuyuanbiaobaContent = () => {
// //
setRealtimeData([]); setRealtimeData([]);
setTableData([]); setTableData([]);
console.log('数据已初始化,等待实时数据...',import.meta.env.MODE); console.log("数据已初始化,等待实时数据...", import.meta.env.MODE);
}, []); }, []);
// //
useEffect(() => { useEffect(() => {
if (!templatesLoading && tempListData.length > 0 && !selectedTemplate) { if (!templatesLoading && tempListData.length > 0 && !selectedTemplate) {
// //
const builtinTemplate = tempListData.find(template => template.key === 'builtin_1'); const builtinTemplate = tempListData.find(
(template) => template.key === "builtin_1"
);
if (builtinTemplate) { if (builtinTemplate) {
setSelectedTemplate('builtin_1'); setSelectedTemplate("builtin_1");
console.log('默认选中内置模板:', builtinTemplate.name); console.log("默认选中内置模板:", builtinTemplate.name);
} else { } else {
// //
setSelectedTemplate(tempListData[0].key); setSelectedTemplate(tempListData[0].key);
console.log('默认选中第一个模板:', tempListData[0].name); console.log("默认选中第一个模板:", tempListData[0].name);
} }
} }
}, [templatesLoading, tempListData, selectedTemplate]); }, [templatesLoading, tempListData, selectedTemplate]);
@ -110,59 +118,59 @@ const WuyuanbiaobaContent = () => {
// //
useEffect(() => { useEffect(() => {
console.log('实时数据订阅状态:', { // console.log(':', {
hasData: !!realtimeDataSubscription.latest, // hasData: !!realtimeDataSubscription.latest,
dataCount: realtimeDataSubscription.data?.length || 0, // dataCount: realtimeDataSubscription.data?.length || 0,
latestTimestamp: realtimeDataSubscription.latest?.timestamp, // latestTimestamp: realtimeDataSubscription.latest?.timestamp,
}); // });
}, [realtimeDataSubscription]); }, [realtimeDataSubscription]);
// //
useEffect(() => { useEffect(() => {
if (realtimeDataSubscription.latest && realtimeDataSubscription.latest.values) { if (
const newRealtimeData = processRealtimeData(realtimeDataSubscription.latest.values); realtimeDataSubscription.latest &&
realtimeDataSubscription.latest.values
) {
const newRealtimeData = processRealtimeData(
realtimeDataSubscription.latest.values
);
if (newRealtimeData.length > 0) { if (newRealtimeData.length > 0) {
console.log('收到实时数据:', newRealtimeData); const currentTime = Date.now();
const currentSecond = Math.floor(currentTime / 1000);
// -
setRealtimeData(prevRealtimeData => {
// ID
const deviceDataMap = new Map();
// //
prevRealtimeData.forEach(data => { if (currentSecond > lastSampleTime) {
deviceDataMap.set(data.deviceId, data); setLastSampleTime(currentSecond);
});
// //
newRealtimeData.forEach(data => { setTableData((prevData) => {
deviceDataMap.set(data.deviceId, data);
});
// ID
return Array.from(deviceDataMap.values()).sort((a, b) =>
a.deviceId.localeCompare(b.deviceId)
);
});
//
setTableData(prevData => {
const updatedData = [ const updatedData = [
...prevData, ...prevData,
...newRealtimeData.map((point, index) => ({ ...newRealtimeData.map((point) => ({
...point, ...point,
key: `${Date.now()}_${point.key}`, // key key: `${currentTime}_${point.key}`,
timestamp: currentTime,
updateTime: new Date(currentTime).toLocaleString(),
})), })),
]; ];
// 100
return updatedData.slice(-100); // 2525
return updatedData.slice(-75); // 3 * 25
}); });
// 使
setRealtimeData(newRealtimeData.map((point) => ({
...point,
key: `realtime_${point.key}`,
updateTime: new Date(currentTime).toLocaleString(),
})));
}
setLastUpdateTime(new Date()); setLastUpdateTime(new Date());
} }
} }
}, [realtimeDataSubscription.latest]); }, [realtimeDataSubscription.latest, lastSampleTime]);
// //
const handleEditTarget = (target) => { const handleEditTarget = (target) => {
@ -436,7 +444,7 @@ const WuyuanbiaobaContent = () => {
lastUpdateTime={lastUpdateTime} lastUpdateTime={lastUpdateTime}
/> />
{/* Table 区域 */} {/* Table 区域 - 使用采样数据显示 */}
<RealtimeDataTable realtimeData={realtimeData} /> <RealtimeDataTable realtimeData={realtimeData} />
</div> </div>

99
package-lock.json

@ -17,9 +17,9 @@
"devDependencies": { "devDependencies": {
"@ant-design/icons": "^5.4.0", "@ant-design/icons": "^5.4.0",
"antd": "^5.19.0", "antd": "^5.19.0",
"echarts": "^5.6.0", "chart.js": "^4.5.0",
"echarts-for-react": "^3.0.2",
"react": "18.x", "react": "18.x",
"react-chartjs-2": "^5.3.0",
"react-dom": "18.x", "react-dom": "18.x",
"react-redux": "^9.1.2", "react-redux": "^9.1.2",
"react-router-dom": "^6.26.0" "react-router-dom": "^6.26.0"
@ -862,6 +862,13 @@
"node": ">= 18" "node": ">= 18"
} }
}, },
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmmirror.com/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"dev": true,
"license": "MIT"
},
"node_modules/@noble/hashes": { "node_modules/@noble/hashes": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/@noble/hashes/-/hashes-1.8.0.tgz", "resolved": "https://nexus.ngaiot.com/repository/fs-npm/@noble/hashes/-/hashes-1.8.0.tgz",
@ -1771,6 +1778,19 @@
"tslib": "^2.0.3" "tslib": "^2.0.3"
} }
}, },
"node_modules/chart.js": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-4.5.0.tgz",
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/classnames": { "node_modules/classnames": {
"version": "2.5.1", "version": "2.5.1",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/classnames/-/classnames-2.5.1.tgz", "resolved": "https://nexus.ngaiot.com/repository/fs-npm/classnames/-/classnames-2.5.1.tgz",
@ -2112,39 +2132,6 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/echarts": {
"version": "5.6.0",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/echarts/-/echarts-5.6.0.tgz",
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.6.1"
}
},
"node_modules/echarts-for-react": {
"version": "3.0.3",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/echarts-for-react/-/echarts-for-react-3.0.3.tgz",
"integrity": "sha512-KdvZGkCwmx5DTHl7vjo0CBodSaPY31hPWRC4NZ5B+utQfoW+M54OTBvkoCmktR0kJ+1Bj6rP7pIJJnxPdySyug==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"size-sensor": "^1.0.1"
},
"peerDependencies": {
"echarts": "^3.0.0 || ^4.0.0 || ^5.0.0",
"react": "^15.0.0 || >=16.0.0"
}
},
"node_modules/echarts/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"dev": true,
"license": "0BSD"
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://nexus.ngaiot.com/repository/fs-npm/ee-first/-/ee-first-1.1.1.tgz",
@ -2289,13 +2276,6 @@
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=",
"dev": true,
"license": "MIT"
},
"node_modules/fast-safe-stringify": { "node_modules/fast-safe-stringify": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "resolved": "https://nexus.ngaiot.com/repository/fs-npm/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
@ -4123,6 +4103,17 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-chartjs-2": {
"version": "5.3.0",
"resolved": "https://registry.npmmirror.com/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz",
"integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"chart.js": "^4.1.1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://nexus.ngaiot.com/repository/fs-npm/react-dom/-/react-dom-18.3.1.tgz",
@ -4503,13 +4494,6 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/size-sensor": {
"version": "1.0.2",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/size-sensor/-/size-sensor-1.0.2.tgz",
"integrity": "sha512-2NCmWxY7A9pYKGXNBfteo4hy14gWu47rg5692peVMst6lQLPKrVjhY+UTEsPI5ceFRJSl3gVgMYaUi/hKuaiKw==",
"dev": true,
"license": "ISC"
},
"node_modules/snake-case": { "node_modules/snake-case": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/snake-case/-/snake-case-3.0.4.tgz", "resolved": "https://nexus.ngaiot.com/repository/fs-npm/snake-case/-/snake-case-3.0.4.tgz",
@ -5004,23 +4988,6 @@
"engines": { "engines": {
"node": ">= 4.0.0" "node": ">= 4.0.0"
} }
},
"node_modules/zrender": {
"version": "5.6.1",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/zrender/-/zrender-5.6.1.tgz",
"integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"tslib": "2.3.0"
}
},
"node_modules/zrender/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"dev": true,
"license": "0BSD"
} }
} }
} }

4
package.json

@ -16,11 +16,11 @@
"devDependencies": { "devDependencies": {
"@ant-design/icons": "^5.4.0", "@ant-design/icons": "^5.4.0",
"antd": "^5.19.0", "antd": "^5.19.0",
"chart.js": "^4.5.0",
"react": "18.x", "react": "18.x",
"react-chartjs-2": "^5.3.0",
"react-dom": "18.x", "react-dom": "18.x",
"react-redux": "^9.1.2", "react-redux": "^9.1.2",
"echarts": "^5.6.0",
"echarts-for-react": "^3.0.2",
"react-router-dom": "^6.26.0" "react-router-dom": "^6.26.0"
}, },
"dependencies": { "dependencies": {

6
server/tcpProxy/index.js

@ -38,7 +38,7 @@ function setupTcpProxy(conf) {
// 尝试解析为文本 // 尝试解析为文本
try { try {
textData = data.toString('utf8'); textData = data.toString('utf8');
console.log('收到TCP数据片段:', textData.length, '字节'); // console.log('收到TCP数据片段:', textData.length, '字节');
} catch (e) { } catch (e) {
console.log('TCP数据无法解析为文本'); console.log('TCP数据无法解析为文本');
return; return;
@ -59,8 +59,8 @@ function setupTcpProxy(conf) {
// 转发完整消息到WebSocket // 转发完整消息到WebSocket
if (ws.readyState === WebSocket.OPEN) { if (ws.readyState === WebSocket.OPEN) {
console.log('准备发送完整消息到WebSocket:', completeMessage.length, '字节'); // console.log('准备发送完整消息到WebSocket:', completeMessage.length, '字节');
console.log('消息内容:', completeMessage); // console.log('消息内容:', completeMessage);
ws.send(completeMessage, (err) => { ws.send(completeMessage, (err) => {
if (err) { if (err) {
console.error('WebSocket发送数据错误:', err); console.error('WebSocket发送数据错误:', err);

Loading…
Cancel
Save