diff --git a/src/components/devices/mowerAllView.jsx b/src/components/devices/mowerAllView.jsx index 68fa405..18553fe 100644 --- a/src/components/devices/mowerAllView.jsx +++ b/src/components/devices/mowerAllView.jsx @@ -1,23 +1,226 @@ - +import React, { useState, useRef, useEffect, useCallback, memo } from 'react'; import VideoPlayer from '../VideoPlayer.tsx'; -export function MowerAllView({ serialNumber, robotVideo, error, ready, statusData }) { +// 单独抽离视频面板,memo 避免无关重渲染 +const VideoPanel = memo(({ + target, + idx, + label, + width, + height, + pos, + isDraggingAny, + dragTarget, + onMouseDown, + serialNumber, + robotVideo, + error, + ready +}) => { + // 空值直接不渲染,防止 pos.x 报错 + if (!pos) return null; + + const isSelfDragging = isDraggingAny && dragTarget === target; + return ( -
- {/* 前视 - 铺满整个区域 */} -
+ width, + height, + zIndex: 10, + border: '1px solid #fff3', + borderRadius: 8, + overflow: 'hidden', + backgroundColor: '#000', + // GPU 加速,不触发页面回流 + transform: `translate(${pos.x}px, ${pos.y}px)`, + willChange: 'transform', + transition: isSelfDragging ? 'none' : 'transform 0.1s ease-out', + pointerEvents: 'auto' + }} + > + {/* 拖拽手柄 */} +
onMouseDown(e, target)} + > + + ⋮⋮ {label} + +
+ +
+ +
+
+ ); +}); + +export function MowerAllView({ serialNumber, robotVideo, error, ready, statusData }) { + const containerRef = useRef(null); + // 拖拽临时状态,仅拖动过程使用 + const dragInfoRef = useRef({ + isDragging: false, + target: null, + startX: 0, + startY: 0, + originX: 0, + originY: 0 + }); + + // 面板尺寸 state(初始化一次) + const [panelSize, setPanelSize] = useState({ + leftWidth: 200, + leftHeight: 150, + rightWidth: 200, + rightHeight: 150, + backWidth: 300, + backHeight: 150 + }); + + // 面板位置 state,拖拽只更新对应一项,局部渲染 + const [panelPositions, setPanelPositions] = useState({ + left: null, + right: null, + back: null + }); + + // 全局鼠标移动统一处理,只维护一份逻辑 + const globalMouseMove = useCallback((e) => { + const drag = dragInfoRef.current; + if (!drag.isDragging || !containerRef.current) return; + + const containerRect = containerRef.current.getBoundingClientRect(); + const deltaX = e.clientX - drag.startX; + const deltaY = e.clientY - drag.startY; + + let newX = drag.originX + deltaX; + let newY = drag.originY + deltaY; + let maxW, maxH; + + switch (drag.target) { + case 'left': + maxW = panelSize.leftWidth; + maxH = panelSize.leftHeight; + break; + case 'right': + maxW = panelSize.rightWidth; + maxH = panelSize.rightHeight; + break; + case 'back': + maxW = panelSize.backWidth; + maxH = panelSize.backHeight; + break; + default: + return; + } + + // 边界约束 + newX = Math.max(0, Math.min(newX, containerRect.width - maxW)); + newY = Math.max(0, Math.min(newY, containerRect.height - maxH)); + + // 仅更新当前拖拽面板位置,不刷新全部组件 + setPanelPositions(prev => ({ + ...prev, + [drag.target]: { x: newX, y: newY } + })); + }, [panelSize]); + + const globalMouseUp = useCallback(() => { + dragInfoRef.current.isDragging = false; + dragInfoRef.current.target = null; + }, []); + + // 全局一次性挂载鼠标事件,组件销毁解绑 + useEffect(() => { + window.addEventListener('mousemove', globalMouseMove); + window.addEventListener('mouseup', globalMouseUp); + return () => { + window.removeEventListener('mousemove', globalMouseMove); + window.removeEventListener('mouseup', globalMouseUp); + }; + }, [globalMouseMove, globalMouseUp]); + + // 初始化尺寸与位置(只执行一次) + useEffect(() => { + if (!containerRef.current || panelPositions.left) return; + const rect = containerRef.current.getBoundingClientRect(); + + // 三块面板统一尺寸,消除后视大小不一致问题 + const baseW = Math.max(200, Math.floor(rect.width * 0.25)); + const baseH = Math.max(150, Math.floor(rect.height * 0.3)); + const size = { + leftWidth: baseW, + leftHeight: baseH, + rightWidth: baseW, + rightHeight: baseH, + backWidth: baseW, + backHeight: baseH + }; + setPanelSize(size); + + // 初始坐标直接赋值,不会为null + setPanelPositions({ + left: { x: 8, y: 8 }, + right: { x: rect.width - baseW - 8, y: 8 }, + back: { x: (rect.width - baseW) / 2, y: rect.height - baseH - 8 } + }); + }, [panelPositions.left]); + + // 鼠标按下,开启拖拽 + const handleDragStart = useCallback((e, target) => { + e.preventDefault(); + e.stopPropagation(); + const pos = panelPositions[target]; + // 二次做空校验,防止初始化未完成拖拽触发 + if (!pos) return; + + dragInfoRef.current = { + isDragging: true, + target, + startX: e.clientX, + startY: e.clientY, + originX: pos.x, + originY: pos.y + }; + }, [panelPositions]); + + const dragState = dragInfoRef.current; + + return ( +
+ {/* 底层前视画面,稳定不重渲染 */} +
- {/* 左视 - 左上角 */} -
- + {/* 左视 */} + -
+ {/* 右视 */} + - {/* 右视 - 右上角 */} -
- + {/* 后视 */} + -
- - {/* 后视 - 底部居中 */} -
- - -
- - - - {/* 状态详情叠加层 */} + {/* 底部状态面板 */} {statusData && (
)}
- ) + ); } diff --git a/vite.config.ts b/vite.config.ts index ff8339b..bbcd8c6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -18,9 +18,13 @@ const ENV = { DEV: 'development', PROD: 'production' }; export default defineConfig(({ mode }) => { const env = loadEnv(mode, '.', ''); return { + define: { + 'process.env': {} + }, plugins: [ react(), tailwindcss(), + cesiumPlugin(), viteStaticCopy({ targets: [ {