|
@ -16,8 +16,8 @@ const CameraView = ({ |
|
|
const [scale, setScale] = useState(1.0); |
|
|
const [scale, setScale] = useState(1.0); |
|
|
const [translateX, setTranslateX] = useState(0); |
|
|
const [translateX, setTranslateX] = useState(0); |
|
|
const [translateY, setTranslateY] = useState(0); |
|
|
const [translateY, setTranslateY] = useState(0); |
|
|
const [videoNaturalWidth, setVideoNaturalWidth] = useState(1920); |
|
|
const [videoNaturalWidth, setVideoNaturalWidth] = useState(0); |
|
|
const [videoNaturalHeight, setVideoNaturalHeight] = useState(1080); |
|
|
const [videoNaturalHeight, setVideoNaturalHeight] = useState(0); |
|
|
const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); |
|
|
const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); |
|
|
|
|
|
|
|
|
// 交互状态 |
|
|
// 交互状态 |
|
@ -37,6 +37,7 @@ const CameraView = ({ |
|
|
const [currentDrawingRect, setCurrentDrawingRect] = useState(null); |
|
|
const [currentDrawingRect, setCurrentDrawingRect] = useState(null); |
|
|
const [hoveredRectIndex, setHoveredRectIndex] = useState(-1); |
|
|
const [hoveredRectIndex, setHoveredRectIndex] = useState(-1); |
|
|
const [isSaving, setIsSaving] = useState(false); // 保存状态管理 |
|
|
const [isSaving, setIsSaving] = useState(false); // 保存状态管理 |
|
|
|
|
|
const [streamError, setStreamError] = useState(false); // 视频流断开状态 |
|
|
|
|
|
|
|
|
// 使用WebSocket连接 |
|
|
// 使用WebSocket连接 |
|
|
const { isConnected, sendMessage } = useWebSocket(); |
|
|
const { isConnected, sendMessage } = useWebSocket(); |
|
@ -76,7 +77,7 @@ const CameraView = ({ |
|
|
canvas.style.width = rect.width + "px"; |
|
|
canvas.style.width = rect.width + "px"; |
|
|
canvas.style.height = rect.height + "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,确保画布已经完全设置好 |
|
|
// 延迟调用applyTransform,确保画布已经完全设置好 |
|
|
setTimeout(() => { |
|
|
setTimeout(() => { |
|
@ -87,12 +88,13 @@ const CameraView = ({ |
|
|
|
|
|
|
|
|
// 图片加载完成 |
|
|
// 图片加载完成 |
|
|
const handleImageLoad = () => { |
|
|
const handleImageLoad = () => { |
|
|
|
|
|
setStreamError(false); // 恢复正常 |
|
|
if (imgRef.current) { |
|
|
if (imgRef.current) { |
|
|
const img = imgRef.current; |
|
|
const img = imgRef.current; |
|
|
if (img.naturalWidth && img.naturalHeight) { |
|
|
if (img.naturalWidth && img.naturalHeight) { |
|
|
setVideoNaturalWidth(img.naturalWidth); |
|
|
setVideoNaturalWidth(img.naturalWidth); |
|
|
setVideoNaturalHeight(img.naturalHeight); |
|
|
setVideoNaturalHeight(img.naturalHeight); |
|
|
// console.log(`handleImageLoad: 视频原始分辨率: ${img.naturalWidth}x${img.naturalHeight}`); |
|
|
// console.log(`handleImageLoad: 视频原始分辨率: ${img.naturalWidth}x${img.naturalHeight}`); |
|
|
} |
|
|
} |
|
|
resizeCanvas(); |
|
|
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 handleMouseDown = (e) => { |
|
|
const container = containerRef.current; |
|
|
const container = containerRef.current; |
|
@ -224,7 +238,7 @@ const CameraView = ({ |
|
|
(target) => target.id === clickedRectangle.id |
|
|
(target) => target.id === clickedRectangle.id |
|
|
); |
|
|
); |
|
|
if (targetData) { |
|
|
if (targetData) { |
|
|
// console.log("点击矩形框,弹出标靶详情:", targetData); |
|
|
// console.log("点击矩形框,弹出标靶详情:", targetData); |
|
|
onRectangleClick(targetData); |
|
|
onRectangleClick(targetData); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -272,7 +286,8 @@ const CameraView = ({ |
|
|
const templateParams = selectedTemplate |
|
|
const templateParams = selectedTemplate |
|
|
? { |
|
|
? { |
|
|
// 从模板中获取参数 |
|
|
// 从模板中获取参数 |
|
|
name: selectedTemplate.name || `target${rectangles.length + 1}`, |
|
|
name: |
|
|
|
|
|
selectedTemplate.name || `target${rectangles.length + 1}`, |
|
|
radius: selectedTemplate.physicalRadius || 40.0, |
|
|
radius: selectedTemplate.physicalRadius || 40.0, |
|
|
isReferencePoint: selectedTemplate.isBaseline || false, |
|
|
isReferencePoint: selectedTemplate.isBaseline || false, |
|
|
gradientThreshold: |
|
|
gradientThreshold: |
|
@ -312,13 +327,13 @@ const CameraView = ({ |
|
|
|
|
|
|
|
|
setRectangles((prev) => [...prev, newRect]); |
|
|
setRectangles((prev) => [...prev, newRect]); |
|
|
|
|
|
|
|
|
// console.log("新建矩形:", newRect); |
|
|
// console.log("新建矩形:", newRect); |
|
|
if (selectedTemplate) { |
|
|
if (selectedTemplate) { |
|
|
// console.log("使用模板参数:", selectedTemplate); |
|
|
// console.log("使用模板参数:", selectedTemplate); |
|
|
} else { |
|
|
} else { |
|
|
// console.log("使用默认参数"); |
|
|
// console.log("使用默认参数"); |
|
|
} |
|
|
} |
|
|
// logRectangleData(); |
|
|
// logRectangleData(); |
|
|
} |
|
|
} |
|
|
}; // 拖拽已存在的矩形 |
|
|
}; // 拖拽已存在的矩形 |
|
|
const dragExistingRectangle = (mouseX, mouseY) => { |
|
|
const dragExistingRectangle = (mouseX, mouseY) => { |
|
@ -542,11 +557,15 @@ const CameraView = ({ |
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
|
|
|
// 如果视频没有加载完成,直接返回 |
|
|
// 如果视频没有加载完成,直接返回 |
|
|
if (!videoNaturalWidth || !videoNaturalHeight) { |
|
|
if ( |
|
|
console.log("redrawAllRectangles: 视频尺寸未准备好", { |
|
|
!videoNaturalWidth || |
|
|
videoNaturalWidth, |
|
|
!videoNaturalHeight || |
|
|
videoNaturalHeight, |
|
|
videoNaturalWidth < 100 || |
|
|
}); |
|
|
videoNaturalHeight < 100 |
|
|
|
|
|
) { |
|
|
|
|
|
// 清空画布,防止残影 |
|
|
|
|
|
const ctx = canvas.getContext("2d"); |
|
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -790,7 +809,14 @@ const CameraView = ({ |
|
|
setIsSaving(false); |
|
|
setIsSaving(false); |
|
|
}, 1000); |
|
|
}, 1000); |
|
|
} |
|
|
} |
|
|
}, [rectangles, isConnected, sendMessage, refreshTargets, onClearSelection, isSaving]); |
|
|
}, [ |
|
|
|
|
|
rectangles, |
|
|
|
|
|
isConnected, |
|
|
|
|
|
sendMessage, |
|
|
|
|
|
refreshTargets, |
|
|
|
|
|
onClearSelection, |
|
|
|
|
|
isSaving, |
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
// 全局滚轮事件处理,防止alt+滚轮触发页面滚动 |
|
|
// 全局滚轮事件处理,防止alt+滚轮触发页面滚动 |
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
@ -843,7 +869,6 @@ const CameraView = ({ |
|
|
return () => cancelAnimationFrame(rafId); |
|
|
return () => cancelAnimationFrame(rafId); |
|
|
}, [rectangles]); |
|
|
}, [rectangles]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 从标靶数据初始化矩形框 |
|
|
// 从标靶数据初始化矩形框 |
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
|
// console.log("targets 或 loading 状态变化:", { |
|
|
// console.log("targets 或 loading 状态变化:", { |
|
@ -852,7 +877,13 @@ const CameraView = ({ |
|
|
// targetsLength: targets?.length, |
|
|
// targetsLength: targets?.length, |
|
|
// }); |
|
|
// }); |
|
|
|
|
|
|
|
|
if (!loading && targets && targets.length > 0) { |
|
|
if ( |
|
|
|
|
|
!loading && |
|
|
|
|
|
targets && |
|
|
|
|
|
targets.length > 0 && |
|
|
|
|
|
videoNaturalWidth > 0 && |
|
|
|
|
|
videoNaturalHeight > 0 |
|
|
|
|
|
) { |
|
|
// console.log("从标靶数据初始化矩形框:", targets); |
|
|
// console.log("从标靶数据初始化矩形框:", targets); |
|
|
|
|
|
|
|
|
const initialRectangles = targets |
|
|
const initialRectangles = targets |
|
@ -912,7 +943,7 @@ const CameraView = ({ |
|
|
setRectangles([]); |
|
|
setRectangles([]); |
|
|
// console.log("标靶数据为空,已清空矩形框"); |
|
|
// console.log("标靶数据为空,已清空矩形框"); |
|
|
} |
|
|
} |
|
|
}, [targets, loading]); // 只依赖targets和loading状态 |
|
|
}, [targets, loading, videoNaturalWidth, videoNaturalHeight]); // 只依赖targets和loading状态 |
|
|
|
|
|
|
|
|
// 专门监听rectangles变化并重绘 |
|
|
// 专门监听rectangles变化并重绘 |
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
@ -1058,7 +1089,7 @@ const CameraView = ({ |
|
|
src={streamUrl} |
|
|
src={streamUrl} |
|
|
alt="MJPEG 视频流" |
|
|
alt="MJPEG 视频流" |
|
|
onLoad={handleImageLoad} |
|
|
onLoad={handleImageLoad} |
|
|
onError={() => console.error("视频流加载失败")} |
|
|
onError={handleImageError} |
|
|
style={{ |
|
|
style={{ |
|
|
position: "absolute", |
|
|
position: "absolute", |
|
|
left: 0, |
|
|
left: 0, |
|
@ -1070,6 +1101,27 @@ const CameraView = ({ |
|
|
pointerEvents: "none", |
|
|
pointerEvents: "none", |
|
|
}} |
|
|
}} |
|
|
/> |
|
|
/> |
|
|
|
|
|
{/* 视频流断开提示 */} |
|
|
|
|
|
{streamError && ( |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
position: "absolute", |
|
|
|
|
|
left: 0, |
|
|
|
|
|
top: 0, |
|
|
|
|
|
width: "100%", |
|
|
|
|
|
height: "100%", |
|
|
|
|
|
background: "rgba(0,0,0,0.7)", |
|
|
|
|
|
color: "#fff", |
|
|
|
|
|
display: "flex", |
|
|
|
|
|
alignItems: "center", |
|
|
|
|
|
justifyContent: "center", |
|
|
|
|
|
fontSize: 24, |
|
|
|
|
|
zIndex: 100, |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
视频流断开,正在尝试重连... |
|
|
|
|
|
</div> |
|
|
|
|
|
)} |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
{/* 绘制画布 */} |
|
|
{/* 绘制画布 */} |
|
@ -1121,7 +1173,9 @@ const CameraView = ({ |
|
|
position: "absolute", |
|
|
position: "absolute", |
|
|
top: "10px", |
|
|
top: "10px", |
|
|
right: "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", |
|
|
color: "white", |
|
|
border: "1px solid rgba(255, 255, 255, 0.3)", |
|
|
border: "1px solid rgba(255, 255, 255, 0.3)", |
|
|
borderRadius: "4px", |
|
|
borderRadius: "4px", |
|
@ -1162,7 +1216,7 @@ const CameraView = ({ |
|
|
}} |
|
|
}} |
|
|
> |
|
|
> |
|
|
<div> |
|
|
<div> |
|
|
分辨率: {videoNaturalWidth} × {videoNaturalHeight} |
|
|
摄像头分辨率: {videoNaturalWidth} × {videoNaturalHeight} |
|
|
</div> |
|
|
</div> |
|
|
<div>缩放: {Math.round(scale * 100)}%</div> |
|
|
<div>缩放: {Math.round(scale * 100)}%</div> |
|
|
<div> |
|
|
<div> |
|
|