diff --git a/client/src/sections/wuyuanbiaoba/components/CameraView.jsx b/client/src/sections/wuyuanbiaoba/components/CameraView.jsx
index 9b26fb8..fdf8434 100644
--- a/client/src/sections/wuyuanbiaoba/components/CameraView.jsx
+++ b/client/src/sections/wuyuanbiaoba/components/CameraView.jsx
@@ -16,8 +16,8 @@ const CameraView = ({
const [scale, setScale] = useState(1.0);
const [translateX, setTranslateX] = useState(0);
const [translateY, setTranslateY] = useState(0);
- const [videoNaturalWidth, setVideoNaturalWidth] = useState(1920);
- const [videoNaturalHeight, setVideoNaturalHeight] = useState(1080);
+ const [videoNaturalWidth, setVideoNaturalWidth] = useState(0);
+ const [videoNaturalHeight, setVideoNaturalHeight] = useState(0);
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
// 交互状态
@@ -37,6 +37,7 @@ const CameraView = ({
const [currentDrawingRect, setCurrentDrawingRect] = useState(null);
const [hoveredRectIndex, setHoveredRectIndex] = useState(-1);
const [isSaving, setIsSaving] = useState(false); // 保存状态管理
+ const [streamError, setStreamError] = useState(false); // 视频流断开状态
// 使用WebSocket连接
const { isConnected, sendMessage } = useWebSocket();
@@ -76,7 +77,7 @@ const CameraView = ({
canvas.style.width = rect.width + "px";
canvas.style.height = rect.height + "px";
- // console.log("resizeCanvas: 画布大小已调整", { width: rect.width, height: rect.height });
+ // console.log("resizeCanvas: 画布大小已调整", { width: rect.width, height: rect.height });
// 延迟调用applyTransform,确保画布已经完全设置好
setTimeout(() => {
@@ -87,12 +88,13 @@ const CameraView = ({
// 图片加载完成
const handleImageLoad = () => {
+ setStreamError(false); // 恢复正常
if (imgRef.current) {
const img = imgRef.current;
if (img.naturalWidth && img.naturalHeight) {
setVideoNaturalWidth(img.naturalWidth);
setVideoNaturalHeight(img.naturalHeight);
- // console.log(`handleImageLoad: 视频原始分辨率: ${img.naturalWidth}x${img.naturalHeight}`);
+ // console.log(`handleImageLoad: 视频原始分辨率: ${img.naturalWidth}x${img.naturalHeight}`);
}
resizeCanvas();
// 视频加载完成后,确保重新绘制矩形框
@@ -103,6 +105,18 @@ const CameraView = ({
}
};
+ // 图片加载失败
+ const handleImageError = () => {
+ setStreamError(true);
+ // 自动重连,3秒后尝试刷新图片src
+ setTimeout(() => {
+ setStreamError(false);
+ if (imgRef.current) {
+ imgRef.current.src = streamUrl + "?t=" + Date.now();
+ }
+ }, 3000);
+ };
+
// 鼠标按下
const handleMouseDown = (e) => {
const container = containerRef.current;
@@ -224,7 +238,7 @@ const CameraView = ({
(target) => target.id === clickedRectangle.id
);
if (targetData) {
- // console.log("点击矩形框,弹出标靶详情:", targetData);
+ // console.log("点击矩形框,弹出标靶详情:", targetData);
onRectangleClick(targetData);
}
}
@@ -272,7 +286,8 @@ const CameraView = ({
const templateParams = selectedTemplate
? {
// 从模板中获取参数
- name: selectedTemplate.name || `target${rectangles.length + 1}`,
+ name:
+ selectedTemplate.name || `target${rectangles.length + 1}`,
radius: selectedTemplate.physicalRadius || 40.0,
isReferencePoint: selectedTemplate.isBaseline || false,
gradientThreshold:
@@ -312,13 +327,13 @@ const CameraView = ({
setRectangles((prev) => [...prev, newRect]);
- // console.log("新建矩形:", newRect);
+ // console.log("新建矩形:", newRect);
if (selectedTemplate) {
- // console.log("使用模板参数:", selectedTemplate);
+ // console.log("使用模板参数:", selectedTemplate);
} else {
- // console.log("使用默认参数");
+ // console.log("使用默认参数");
}
- // logRectangleData();
+ // logRectangleData();
}
}; // 拖拽已存在的矩形
const dragExistingRectangle = (mouseX, mouseY) => {
@@ -542,11 +557,15 @@ const CameraView = ({
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 如果视频没有加载完成,直接返回
- if (!videoNaturalWidth || !videoNaturalHeight) {
- console.log("redrawAllRectangles: 视频尺寸未准备好", {
- videoNaturalWidth,
- videoNaturalHeight,
- });
+ if (
+ !videoNaturalWidth ||
+ !videoNaturalHeight ||
+ videoNaturalWidth < 100 ||
+ videoNaturalHeight < 100
+ ) {
+ // 清空画布,防止残影
+ const ctx = canvas.getContext("2d");
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
return;
}
@@ -790,7 +809,14 @@ const CameraView = ({
setIsSaving(false);
}, 1000);
}
- }, [rectangles, isConnected, sendMessage, refreshTargets, onClearSelection, isSaving]);
+ }, [
+ rectangles,
+ isConnected,
+ sendMessage,
+ refreshTargets,
+ onClearSelection,
+ isSaving,
+ ]);
// 全局滚轮事件处理,防止alt+滚轮触发页面滚动
useEffect(() => {
@@ -843,7 +869,6 @@ const CameraView = ({
return () => cancelAnimationFrame(rafId);
}, [rectangles]);
-
// 从标靶数据初始化矩形框
useEffect(() => {
// console.log("targets 或 loading 状态变化:", {
@@ -852,7 +877,13 @@ const CameraView = ({
// targetsLength: targets?.length,
// });
- if (!loading && targets && targets.length > 0) {
+ if (
+ !loading &&
+ targets &&
+ targets.length > 0 &&
+ videoNaturalWidth > 0 &&
+ videoNaturalHeight > 0
+ ) {
// console.log("从标靶数据初始化矩形框:", targets);
const initialRectangles = targets
@@ -912,7 +943,7 @@ const CameraView = ({
setRectangles([]);
// console.log("标靶数据为空,已清空矩形框");
}
- }, [targets, loading]); // 只依赖targets和loading状态
+ }, [targets, loading, videoNaturalWidth, videoNaturalHeight]); // 只依赖targets和loading状态
// 专门监听rectangles变化并重绘
useEffect(() => {
@@ -1058,7 +1089,7 @@ const CameraView = ({
src={streamUrl}
alt="MJPEG 视频流"
onLoad={handleImageLoad}
- onError={() => console.error("视频流加载失败")}
+ onError={handleImageError}
style={{
position: "absolute",
left: 0,
@@ -1070,6 +1101,27 @@ const CameraView = ({
pointerEvents: "none",
}}
/>
+ {/* 视频流断开提示 */}
+ {streamError && (
+
+ 视频流断开,正在尝试重连...
+
+ )}
{/* 绘制画布 */}
@@ -1121,7 +1173,9 @@ const CameraView = ({
position: "absolute",
top: "10px",
right: "10px",
- backgroundColor: isSaving ? "rgba(128, 128, 128, 0.7)" : "rgba(0, 100, 0, 0.7)",
+ backgroundColor: isSaving
+ ? "rgba(128, 128, 128, 0.7)"
+ : "rgba(0, 100, 0, 0.7)",
color: "white",
border: "1px solid rgba(255, 255, 255, 0.3)",
borderRadius: "4px",
@@ -1162,7 +1216,7 @@ const CameraView = ({
}}
>
- 分辨率: {videoNaturalWidth} × {videoNaturalHeight}
+ 摄像头分辨率: {videoNaturalWidth} × {videoNaturalHeight}
缩放: {Math.round(scale * 100)}%
diff --git a/client/src/sections/wuyuanbiaoba/components/RealtimeCharts.jsx b/client/src/sections/wuyuanbiaoba/components/RealtimeCharts.jsx
index 324e4d2..10520bf 100644
--- a/client/src/sections/wuyuanbiaoba/components/RealtimeCharts.jsx
+++ b/client/src/sections/wuyuanbiaoba/components/RealtimeCharts.jsx
@@ -60,7 +60,7 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
labels: [],
deviceIds: [],
timeGroups: {},
- sortedTimes: []
+ sortedTimes: [],
};
}
@@ -70,7 +70,9 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
data.forEach((item) => {
if (!item || !item.updateTime) return;
- const timeKey = Math.floor(new Date(item.updateTime).getTime() / 1000); // 按秒分组
+ const timeKey = Math.floor(
+ new Date(item.updateTime).getTime() / 1000
+ ); // 按秒分组
if (!timeGroups[timeKey]) {
timeGroups[timeKey] = [];
}
@@ -84,25 +86,30 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
.reverse();
// 获取所有设备ID
- const deviceIds = [...new Set(data.map((item) => item?.deviceId).filter(Boolean))].sort();
+ const deviceIds = [
+ ...new Set(data.map((item) => item?.deviceId).filter(Boolean)),
+ ].sort();
// 生成时间标签
const labels = sortedTimes.map((timeKey) => {
- return new Date(Number(timeKey) * 1000).toLocaleTimeString("zh-CN", {
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- });
+ return new Date(Number(timeKey) * 1000).toLocaleTimeString(
+ "zh-CN",
+ {
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ }
+ );
});
return { labels, deviceIds, timeGroups, sortedTimes };
} catch (error) {
- console.error('数据采样出错:', error);
+ console.error("数据采样出错:", error);
return {
labels: [],
deviceIds: [],
timeGroups: {},
- sortedTimes: []
+ sortedTimes: [],
};
}
};
@@ -110,7 +117,8 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
// 准备X轴图表数据
const xChartData = useMemo(() => {
try {
- const { labels, deviceIds, timeGroups, sortedTimes } = sampleData(tableData);
+ const { labels, deviceIds, timeGroups, sortedTimes } =
+ sampleData(tableData);
if (!deviceIds || deviceIds.length === 0) {
return { labels: [], datasets: [] };
@@ -122,7 +130,9 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
const deviceData = timeData.find(
(item) => item.deviceId === deviceId
);
- return deviceData && deviceData.xValue !== undefined ? parseFloat(deviceData.xValue) : null;
+ return deviceData && deviceData.xValue !== undefined
+ ? parseFloat(deviceData.xValue)
+ : null;
});
return {
@@ -140,7 +150,7 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
return { labels, datasets };
} catch (error) {
- console.error('准备X轴图表数据出错:', error);
+ console.error("准备X轴图表数据出错:", error);
return { labels: [], datasets: [] };
}
}, [tableData]);
@@ -148,7 +158,8 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
// 准备Y轴图表数据
const yChartData = useMemo(() => {
try {
- const { labels, deviceIds, timeGroups, sortedTimes } = sampleData(tableData);
+ const { labels, deviceIds, timeGroups, sortedTimes } =
+ sampleData(tableData);
if (!deviceIds || deviceIds.length === 0) {
return { labels: [], datasets: [] };
@@ -160,7 +171,9 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
const deviceData = timeData.find(
(item) => item.deviceId === deviceId
);
- return deviceData && deviceData.yValue !== undefined ? parseFloat(deviceData.yValue) : null;
+ return deviceData && deviceData.yValue !== undefined
+ ? parseFloat(deviceData.yValue)
+ : null;
});
return {
@@ -178,7 +191,7 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
return { labels, datasets };
} catch (error) {
- console.error('准备Y轴图表数据出错:', error);
+ console.error("准备Y轴图表数据出错:", error);
return { labels: [], datasets: [] };
}
}, [tableData]);
@@ -301,9 +314,9 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
// 添加调试信息
// useEffect(() => {
- // console.log('RealtimeCharts - tableData:', tableData);
- // console.log('RealtimeCharts - xChartData:', xChartData);
- // console.log('RealtimeCharts - yChartData:', yChartData);
+ // console.log('RealtimeCharts - tableData:', tableData);
+ // console.log('RealtimeCharts - xChartData:', xChartData);
+ // console.log('RealtimeCharts - yChartData:', yChartData);
// }, [tableData, xChartData, yChartData]);
// 如果没有数据,显示空状态
@@ -325,13 +338,15 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
style={{ marginLeft: "16px", fontSize: "12px" }}
/>
-
+
@@ -356,7 +371,13 @@ const RealtimeCharts = ({ tableData, lastUpdateTime }) => {
text={`实时更新 - 最后更新: ${lastUpdateTime.toLocaleTimeString()}`}
style={{ marginLeft: "16px", fontSize: "12px" }}
/>
+
+
{
if (targetDataResponse && targetDataResponse.values) {
try {
const responseValues = targetDataResponse.values;
- console.log('收到标靶数据:', responseValues);
+ // console.log('收到标靶数据:', responseValues);
// 检查是否为空对象
if (!responseValues || Object.keys(responseValues).length === 0) {
@@ -101,7 +101,7 @@ export const useTargetStorage = () => {
setTargets(formattedTargets);
setError(null);
- console.log('解析到标靶数据:', formattedTargets);
+ // console.log('解析到标靶数据:', formattedTargets);
} catch (err) {
console.error('处理标靶数据失败:', err);
setError('处理标靶数据失败');