全景视频 根据 王勇的 修改

This commit is contained in:
mmc
2026-07-23 14:04:59 +08:00
parent 6f0b9ff66a
commit b11f218fb2
2 changed files with 169 additions and 220 deletions

View File

@@ -1007,12 +1007,68 @@ export default function DeviceStatusPage({
style={{ borderRadius: 12, marginBottom: 8 }}
bodyStyle={{ padding: 12, height: 'calc(100vh - 220px)', minHeight: 500 }}
>
<Space style={{ marginBottom: 8 }}>
<Space style={{ marginBottom: 8, }} align="start" >
<Button type={mowerView === 'front' ? 'primary' : 'default'} onClick={() => setMowerView('front')}>前视</Button>
<Button type={mowerView === 'back' ? 'primary' : 'default'} onClick={() => setMowerView('back')}>后视</Button>
<Button type={mowerView === 'left' ? 'primary' : 'default'} onClick={() => setMowerView('left')}>左视</Button>
<Button type={mowerView === 'right' ? 'primary' : 'default'} onClick={() => setMowerView('right')}>右视</Button>
<Button type={mowerView === 'all' ? 'primary' : 'default'} onClick={() => setMowerView('all')}>全景</Button>
{mowerView == "all" ? <> <div style={{
background: 'rgba(0,0,0,0.7)',
color: '#fff',
padding: '10px 12px',
borderRadius: 8,
fontSize: 11,
zIndex: 10,
backdropFilter: 'blur(4px)'
}}>
<div style={{ marginBottom: 6, fontWeight: 600, color: '#1890ff' }}>机器人状态</div>
<div style={{ display: 'flex', gap: '3px 10px', flexWrap: 'wrap' }}>
<span>电量:</span>
<span style={{ color: (runningStatus.battery || 0) < 20 ? '#ff4d4f' : '#52c41a' }}>
{runningStatus.battery !== undefined ? `${runningStatus.battery}%` : '-'}
</span>
<span>电压:</span>
<span>{runningStatus.voltage !== undefined ? `${Number(runningStatus.voltage).toFixed(2)}V` : '-'}</span>
<span>左电机:</span>
<span>{runningStatus.leftCurrent !== undefined ? `${Number(runningStatus.leftCurrent).toFixed(2)}A` : '-'}</span>
<span>右电机:</span>
<span>{runningStatus.rightCurrent !== undefined ? `${Number(runningStatus.rightCurrent).toFixed(2)}A` : '-'}</span>
<span>左速度:</span>
<span>{runningStatus.leftMeasureSpeed !== undefined ? `${Number(runningStatus.leftMeasureSpeed).toFixed(1)}rpm` : '-'}</span>
<span>右速度:</span>
<span>{runningStatus.rightMeasureSpeed !== undefined ? `${Number(runningStatus.rightMeasureSpeed).toFixed(1)}rpm` : '-'}</span>
<span>左温度:</span>
<span>{runningStatus.leftMotorTemp !== undefined ? `${Number(runningStatus.leftMotorTemp).toFixed(1)}℃` : '-'}</span>
<span>右温度:</span>
<span>{runningStatus.rightMotorTemp !== undefined ? `${Number(runningStatus.rightMotorTemp).toFixed(1)}℃` : '-'}</span>
<span>主控温度:</span>
<span>{runningStatus.chipTemp !== undefined ? `${Number(runningStatus.chipTemp).toFixed(1)}℃` : '-'}</span>
<span>航向角:</span>
<span>{runningStatus.yaw !== undefined ? `${Number(runningStatus.yaw).toFixed(1)}°` : '-'}</span>
<span>俯仰角:</span>
<span>{runningStatus.pitch !== undefined ? `${Number(runningStatus.pitch).toFixed(1)}°` : '-'}</span>
<span>航向角状态:</span>
<span>{runningStatus.headingStatus !== undefined ? `${runningStatus.headingStatus == 1 ? '初始化' : '未初始化'}` : '-'}</span>
<span>定位:</span>
<span>
{runningStatus.qual == 0 ? '无效' :
runningStatus.qual == 1 ? 'GPS单点' :
runningStatus.qual == 2 ? 'DGPS差分' :
runningStatus.qual == 4 ? 'RTK固定' :
runningStatus.qual == 5 ? 'RTK浮点' : '未知'}
</span>
<span>卫星:</span>
<span>{runningStatus.satelliteCnt || 0}颗</span>
<span>模式:</span>
<span>
{runningStatus.controlMode == 1 ? '本地' :
runningStatus.controlMode == 3 ? '远程' : '未知'}
</span>
</div>
</div>
</> : null}
</Space>
<div style={{
@@ -1029,7 +1085,6 @@ export default function DeviceStatusPage({
robotVideo={robotVideo}
error={error}
ready={ready}
statusData={runningStatus}
/>
) : (
<div style={{ position: 'relative', width: '100%', height: '100%' }}>

View File

@@ -1,229 +1,123 @@
import React, { useState, useRef, useEffect, memo } from 'react';
import VideoPlayer from '../VideoPlayer.tsx';
import DraggablePanel from './DraggablePanel.jsx';
// 视频子面板,使用 memo 避免不必要的重渲染
const VideoSubPanel = memo(({
idx,
label,
x,
y,
width,
height,
bounds,
serialNumber,
robotVideo,
error,
ready
}) => {
const hasVideo = ready && !error;
if (!hasVideo) return null;
export function MowerAllView({ serialNumber, robotVideo, error, ready }) {
return (
<DraggablePanel
initialX={x}
initialY={y}
width={width}
height={height}
bounds={bounds}
handleContent={
<div style={{
width: '60px',
height: '100%',
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
borderRadius: '8px',
}}>
<span style={{ color: '#e9f0eb', fontSize: 16, fontWeight: 'bold' }}>
⋮⋮ {label}
</span>
</div>
}
>
<div style={{
display: 'flex',
width: '100%',
height: '100%',
gap: 8, // 三列之间间距
padding: 8,
boxSizing: 'border-box',
overflow: 'auto'
}}>
{/* ========== 左列:左视 ========== */}
<div style={{
width: '100%',
height: '100%',
border: '1px solid #fff3',
borderRadius: 8,
overflow: 'hidden',
flex: 1,
display: 'flex',
flexDirection: 'column',
gap: 8,
}}>
<VideoPlayer
deviceId={serialNumber}
idx={idx}
className="w-full h-full object-contain"
video={robotVideo}
error={error}
ready={ready}
/>
</div>
</DraggablePanel>
);
});
export function MowerAllView({ serialNumber, robotVideo, error, ready, statusData }) {
const containerRef = useRef(null);
const [panelSize, setPanelSize] = useState(null);
const [initialPositions, setInitialPositions] = useState(null);
const [containerBounds, setContainerBounds] = useState(null);
// 初始化尺寸、窗口缩放重新计算面板尺寸
useEffect(() => {
const calcSize = () => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const w = rect.width * 0.30;
const h = Math.floor(rect.height * 0.35);
setPanelSize({ width: w, height: h });
setContainerBounds({ width: rect.width, height: rect.height });
setInitialPositions({
left: { x: 8, y: 8 },
right: { x: rect.width - w - 8, y: 8 },
back: { x: (rect.width - w) / 2, y: rect.height - h - 8 }
});
};
calcSize();
const timer = setTimeout(calcSize, 100);
window.addEventListener('resize', calcSize);
return () => {
clearTimeout(timer);
window.removeEventListener('resize', calcSize);
};
}, []);
return (
<div
ref={containerRef}
style={{
position: 'relative',
width: '100%',
height: '100%',
overflow: 'hidden'
}}
>
{/* 前视主画面 - 铺满 */}
<div style={{ position: 'absolute', inset: 0, zIndex: 1 }}>
<VideoPlayer
deviceId={serialNumber}
idx={1}
className="w-full h-full object-contain"
video={robotVideo}
error={error}
ready={ready}
/>
</div>
{/* 三个可拖拽子面板 */}
{panelSize && initialPositions && containerBounds && (
<>
<VideoSubPanel
idx={3}
label="左视"
x={initialPositions.left.x}
y={initialPositions.left.y}
width={panelSize.width}
height={panelSize.height}
bounds={containerBounds}
serialNumber={serialNumber}
robotVideo={robotVideo}
error={error}
ready={ready}
/>
<VideoSubPanel
idx={4}
label="右视"
x={initialPositions.right.x}
y={initialPositions.right.y}
width={panelSize.width}
height={panelSize.height}
bounds={containerBounds}
serialNumber={serialNumber}
robotVideo={robotVideo}
error={error}
ready={ready}
/>
<VideoSubPanel
idx={2}
label="后视"
x={initialPositions.back.x}
y={initialPositions.back.y}
width={panelSize.width}
height={panelSize.height}
bounds={containerBounds}
serialNumber={serialNumber}
robotVideo={robotVideo}
error={error}
ready={ready}
/>
</>
)}
{/* 状态详情面板 */}
{statusData && (
<div style={{
position: 'absolute',
bottom: 8,
left: 8,
background: 'rgba(0,0,0,0.7)',
color: '#fff',
padding: '10px 12px',
flex: 1,
position: 'relative',
border: '1px solid #eae4e4',
flexShrink: 0,
borderRadius: 8,
fontSize: 11,
zIndex: 20,
backdropFilter: 'blur(4px)'
overflow: "hidden",
maxHeight: 240,
transform: 'translateY(50%)'
}}>
<div style={{ marginBottom: 6, fontWeight: 600, color: '#1890ff' }}>机器人状态</div>
<div style={{ display: 'grid', gridTemplateColumns: 'auto auto', gap: '3px 10px' }}>
<span>电量:</span>
<span style={{ color: (statusData.battery || 0) < 20 ? '#ff4d4f' : '#52c41a' }}>
{statusData.battery !== undefined ? `${statusData.battery}%` : '-'}
</span>
<span>电压:</span>
<span>{statusData.voltage !== undefined ? `${Number(statusData.voltage).toFixed(2)}V` : '-'}</span>
<span>左电机:</span>
<span>{statusData.leftCurrent !== undefined ? `${Number(statusData.leftCurrent).toFixed(2)}A` : '-'}</span>
<span>右电机:</span>
<span>{statusData.rightCurrent !== undefined ? `${Number(statusData.rightCurrent).toFixed(2)}A` : '-'}</span>
<span>左速度:</span>
<span>{statusData.leftMeasureSpeed !== undefined ? `${Number(statusData.leftMeasureSpeed).toFixed(1)}rpm` : '-'}</span>
<span>右速度:</span>
<span>{statusData.rightMeasureSpeed !== undefined ? `${Number(statusData.rightMeasureSpeed).toFixed(1)}rpm` : '-'}</span>
<span>左温度:</span>
<span>{statusData.leftMotorTemp !== undefined ? `${Number(statusData.leftMotorTemp).toFixed(1)}℃` : '-'}</span>
<span>右温度:</span>
<span>{statusData.rightMotorTemp !== undefined ? `${Number(statusData.rightMotorTemp).toFixed(1)}℃` : '-'}</span>
<span>主控温度:</span>
<span>{statusData.chipTemp !== undefined ? `${Number(statusData.chipTemp).toFixed(1)}℃` : '-'}</span>
<span>航向角:</span>
<span>{statusData.yaw !== undefined ? `${Number(statusData.yaw).toFixed(1)}°` : '-'}</span>
<span>俯仰角:</span>
<span>{statusData.pitch !== undefined ? `${Number(statusData.pitch).toFixed(1)}°` : '-'}</span>
<span>航向角状态:</span>
<span>{statusData.headingStatus !== undefined ? `${statusData.headingStatus == 1 ? '初始化' : '未初始化'}` : '-'}</span>
<span>定位:</span>
<span>
{statusData.qual == 0 ? '无效' :
statusData.qual == 1 ? 'GPS单点' :
statusData.qual == 2 ? 'DGPS差分' :
statusData.qual == 4 ? 'RTK固定' :
statusData.qual == 5 ? 'RTK浮点' : '未知'}
</span>
<span>卫星:</span>
<span>{statusData.satelliteCnt || 0}颗</span>
<span>模式:</span>
<span>
{statusData.controlMode == 1 ? '本地' :
statusData.controlMode == 3 ? '远程' : '未知'}
</span>
</div>
<VideoPlayer
deviceId={serialNumber}
idx={3}
className="w-full h-full object-contain"
video={robotVideo}
error={error}
ready={ready}
/>
<div style={{ position: 'absolute', top: 4, left: 8, color: '#fff', fontSize: 12, background: '#0006', padding: '2px 6px', borderRadius: 4 }}>左视</div>
</div>
)}
</div>
{/* ========== 中列:上-前视 下-后视 ========== */}
<div style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
gap: 8,
}}>
{/* 前视 */}
<div style={{
position: 'relative',
border: '1px solid #eae4e4',
flexShrink: 0,
borderRadius: 8,
overflow: "hidden",
height: 240
}}>
<VideoPlayer
deviceId={serialNumber}
idx={1}
className="w-full h-full object-contain"
video={robotVideo}
error={error}
ready={ready}
/>
<div style={{ position: 'absolute', top: 4, left: 8, color: '#fff', fontSize: 12, background: '#0006', padding: '2px 6px', borderRadius: 4 }}>前视</div>
</div>
{/* 后视 */}
<div style={{
height: 240,
position: 'relative',
border: '1px solid #eae4e4',
flexShrink: 0,
borderRadius: 8,
overflow: "hidden"
}}>
<VideoPlayer
deviceId={serialNumber}
idx={2}
className="w-full h-full object-contain"
video={robotVideo}
error={error}
ready={ready}
/>
<div style={{ position: 'absolute', top: 4, left: 8, color: '#fff', fontSize: 12, background: '#0006', padding: '2px 6px', borderRadius: 4 }}>后视</div>
</div>
</div>
<div style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
gap: 8,
}}>
<div style={{
flex: 1,
position: 'relative',
border: '1px solid #eae4e4',
flexShrink: 0,
borderRadius: 8,
overflow: "hidden",
maxHeight: 240,
transform: 'translateY(50%)'
}}>
<VideoPlayer
deviceId={serialNumber}
idx={4}
className="w-full h-full object-contain"
video={robotVideo}
error={error}
ready={ready}
/>
<div style={{ position: 'absolute', top: 4, left: 8, color: '#fff', fontSize: 12, background: '#0006', padding: '2px 6px', borderRadius: 4 }}>右视</div>
</div>
</div>
</div>
);
)
}