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: [
{