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. 100
      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 { Typography, Badge } from 'antd';
import ReactECharts from 'echarts-for-react';
import React, { useMemo, useEffect, useRef } from "react";
import { Typography, Badge } from "antd";
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 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 colorMap = {
'DEV000': "#1890ff", //
'DEV001': "#52c41a", // 绿
'DEV002': "#faad14", //
'DEV003': "#f5222d", //
'DEV004': "#722ed1", //
'DEV005': "#fa8c16", //
'DEV006': "#13c2c2", //
'DEV007': "#eb2f96", //
'DEV008': "#2f54eb", //
'DEV009': "#fa541c", //
target1: "#52c41a", // 绿
target2: "#faad14", //
target3: "#f5222d", //
target4: "#722ed1", //
target5: "#fa8c16", //
target6: "#13c2c2", //
target7: "#eb2f96", //
target8: "#2f54eb", //
target9: "#fa541c", //
target10: "#1890ff", //
};
// ID使
if (colorMap[deviceId]) {
return colorMap[deviceId];
}
return colorMap[deviceId] || "#1890ff";
};
//
const deviceNumber = deviceId.replace(/\D/g, ''); //
const colors = [
"#1890ff", "#52c41a", "#faad14", "#f5222d", "#722ed1",
"#fa8c16", "#13c2c2", "#eb2f96", "#2f54eb", "#fa541c"
];
return colors[parseInt(deviceNumber) % colors.length];
// - 25
const sampleData = (data) => {
if (!data || data.length === 0) {
return {
labels: [],
deviceIds: [],
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 sortedTimes = Object.keys(timeGroups)
.sort((a, b) => Number(b) - Number(a))
.slice(0, 25)
.reverse();
//
const prepareChartData = () => {
// ID
const deviceIds = [...new Set(tableData.map((item) => item.deviceId))].sort();
// ID
const deviceIds = [...new Set(data.map((item) => item?.deviceId).filter(Boolean))].sort();
//
const allTimes = [
...new Set(tableData.map((item) => item.updateTime)),
].sort((a, b) => new Date(a) - new Date(b));
const timeLabels = allTimes.map((time) => {
const date = new Date(time);
return date.toLocaleTimeString("zh-CN", {
//
const labels = sortedTimes.map((timeKey) => {
return new Date(Number(timeKey) * 1000).toLocaleTimeString("zh-CN", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
});
//
const deviceData = deviceIds.map((deviceId) => {
const deviceItems = tableData.filter(
return { labels, deviceIds, timeGroups, sortedTimes };
} catch (error) {
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
);
// XYnull
const xData = allTimes.map((time) => {
const item = deviceItems.find((d) => d.updateTime === time);
return item ? parseFloat(item.xValue) : null;
return deviceData && deviceData.xValue !== undefined ? parseFloat(deviceData.xValue) : null;
});
const yData = allTimes.map((time) => {
const item = deviceItems.find((d) => d.updateTime === time);
return item ? parseFloat(item.yValue) : null;
return {
label: deviceId,
data: data,
borderColor: getDeviceColor(deviceId),
backgroundColor: getDeviceColor(deviceId) + "20",
borderWidth: 2,
pointRadius: 3,
pointHoverRadius: 5,
tension: 0,
connectNulls: false,
};
});
// 使
const color = getDeviceColor(deviceId);
return { labels, datasets };
} 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 {
deviceId,
xData,
yData,
color,
label: deviceId,
data: data,
borderColor: getDeviceColor(deviceId),
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线
const getXChartOption = () => ({
// X
const xChartOptions = {
...chartOptions,
plugins: {
...chartOptions.plugins,
title: {
display: true,
text: "X轴位移数据",
left: "center",
textStyle: {
fontSize: 16,
fontWeight: "normal",
font: {
size: 16,
},
},
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线
const getYChartOption = () => ({
// Y
const yChartOptions = {
...chartOptions,
plugins: {
...chartOptions.plugins,
title: {
display: true,
text: "Y轴位移数据",
left: "center",
textStyle: {
fontSize: 16,
fontWeight: "normal",
font: {
size: 16,
},
},
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 (
<div
@ -262,13 +373,14 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
backgroundColor: "white",
borderRadius: "8px",
border: "1px solid #e8e8e8",
minHeight: "250px",
minHeight: "280px",
padding: "16px",
}}
>
<ReactECharts
option={getXChartOption()}
style={{ height: "100%", width: "100%" }}
opts={{ renderer: "canvas" }}
<Line
ref={xChartRef}
data={xChartData}
options={xChartOptions}
/>
</div>
@ -279,13 +391,14 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
backgroundColor: "white",
borderRadius: "8px",
border: "1px solid #e8e8e8",
minHeight: "250px",
minHeight: "280px",
padding: "16px",
}}
>
<ReactECharts
option={getYChartOption()}
style={{ height: "100%", width: "100%" }}
opts={{ renderer: "canvas" }}
<Line
ref={yChartRef}
data={yChartData}
options={yChartOptions}
/>
</div>
</div>

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

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

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

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

99
package-lock.json

@ -17,9 +17,9 @@
"devDependencies": {
"@ant-design/icons": "^5.4.0",
"antd": "^5.19.0",
"echarts": "^5.6.0",
"echarts-for-react": "^3.0.2",
"chart.js": "^4.5.0",
"react": "18.x",
"react-chartjs-2": "^5.3.0",
"react-dom": "18.x",
"react-redux": "^9.1.2",
"react-router-dom": "^6.26.0"
@ -862,6 +862,13 @@
"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": {
"version": "1.8.0",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/@noble/hashes/-/hashes-1.8.0.tgz",
@ -1771,6 +1778,19 @@
"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": {
"version": "2.5.1",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/classnames/-/classnames-2.5.1.tgz",
@ -2112,39 +2132,6 @@
"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": {
"version": "1.1.1",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/ee-first/-/ee-first-1.1.1.tgz",
@ -2289,13 +2276,6 @@
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"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": {
"version": "2.1.1",
"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_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": {
"version": "18.3.1",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/react-dom/-/react-dom-18.3.1.tgz",
@ -4503,13 +4494,6 @@
"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": {
"version": "3.0.4",
"resolved": "https://nexus.ngaiot.com/repository/fs-npm/snake-case/-/snake-case-3.0.4.tgz",
@ -5004,23 +4988,6 @@
"engines": {
"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": {
"@ant-design/icons": "^5.4.0",
"antd": "^5.19.0",
"chart.js": "^4.5.0",
"react": "18.x",
"react-chartjs-2": "^5.3.0",
"react-dom": "18.x",
"react-redux": "^9.1.2",
"echarts": "^5.6.0",
"echarts-for-react": "^3.0.2",
"react-router-dom": "^6.26.0"
},
"dependencies": {

6
server/tcpProxy/index.js

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

Loading…
Cancel
Save