天气窗口与执行约束 温度和速度显示

This commit is contained in:
mmc
2026-05-28 16:28:10 +08:00
parent 1ca393472d
commit ef175e7bed
3 changed files with 112 additions and 19 deletions

View File

@@ -706,15 +706,59 @@ export default function CesiumMap({ realtimePosition, pathPoints = [], devices =
};
}, []);
const focusEntityRef = useRef(null);
useEffect(() => {
if (!viewerRef.current || !focusLocation) return;
if (!viewerRef.current || !focusLocation) {
if (focusEntityRef.current) {
viewerRef.current?.entities.remove(focusEntityRef.current);
focusEntityRef.current = null;
}
return;
}
const viewer = viewerRef.current;
const Cesium = window.Cesium;
// 选择图片
let image = carImg;
if (focusLocation.type === "plane" || focusLocation.type === "drone") {
image = planeImg;
} else if (focusLocation.type === "robot") {
image = droneInspectImg;
} else if (focusLocation.type === "mower") {
image = carImg;
}
// 如果已经有 focusEntity,先移除
if (focusEntityRef.current) {
viewer.entities.remove(focusEntityRef.current);
}
// 添加新的 focusEntity
focusEntityRef.current = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(Number(focusLocation.lon), Number(focusLocation.lat), 0),
billboard: {
image: image,
width: 60,
height: 60,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
},
label: {
text: focusLocation.name,
font: "12px sans-serif",
pixelOffset: new Cesium.Cartesian2(0, -80),
fillColor: Cesium.Color.YELLOW,
showBackground: true,
backgroundColor: Cesium.Color.BLACK.withAlpha(0.6)
}
});
viewer.camera.flyTo({
destination: window.Cesium.Cartesian3.fromDegrees(Number(focusLocation.lon), Number(focusLocation.lat), focusLocation.height),
destination: Cesium.Cartesian3.fromDegrees(Number(focusLocation.lon), Number(focusLocation.lat), focusLocation.height || 800),
orientation: {
heading: window.Cesium.Math.toRadians(0.0),
pitch: window.Cesium.Math.toRadians(-90.0),
heading: Cesium.Math.toRadians(0.0),
pitch: Cesium.Math.toRadians(-90.0),
roll: 0.0,
},
duration: 1.5

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import {
Row, Col, Card, Button, Space, Table, Tag, Typography, Progress, Badge,
Tabs, Input, Switch, Divider, Tooltip, Timeline, Select, message, DatePicker,
@@ -24,6 +24,7 @@ import dayjs from 'dayjs';
import utils from '@/lib/utils';
import CesiumMap from '../Map';
import { getUAVState } from '@/api/device';
import { fix2, getVal } from '@/lib/utils';
const { Text, Title } = Typography;
const { RangePicker } = DatePicker;
@@ -38,9 +39,40 @@ export default function WayLinePage({ planedevices, onRefresh }) {
// 任务列表 独立筛选状态
const [listDrone, setListDrone] = useState(null);
const [listDroneRealtime, setListDroneRealtime] = useState(null);
const [listDateRange, setListDateRange] = useState(null);
const [taskList, setTaskList] = useState([]);
const listDroneTimerRef = useRef<any>(null);
// 定时获取任务列表所选无人机的实时状态
useEffect(() => {
const fetchRealtime = async () => {
if (listDrone?.gateway_sn && listDrone?.device_sn) {
try {
const res = await getUAVState({
sn: listDrone.gateway_sn,
droneSn: listDrone.device_sn
});
if (res.code === 200) {
setListDroneRealtime(res.data);
}
} catch (err) {
console.error('获取列表无人机实时状态失败', err);
}
} else {
setListDroneRealtime(null);
}
};
fetchRealtime();
listDroneTimerRef.current = setInterval(fetchRealtime, 3000);
return () => {
if (listDroneTimerRef.current) clearInterval(listDroneTimerRef.current);
};
}, [listDrone]);
// 工作看板 独立筛选状态
const [boardDrone, setBoardDrone] = useState(null);
const [boardDateRange, setBoardDateRange] = useState(null);
@@ -62,7 +94,9 @@ export default function WayLinePage({ planedevices, onRefresh }) {
if (res.code === 200 && res.data) {
setFocusLocation({
lon: res.data.longitude,
lat: res.data.latitude
lat: res.data.latitude,
type: drone.type || 'plane',
name: drone.drone_callsign || drone.name || drone.device_sn
});
}
} catch (err) {
@@ -465,11 +499,11 @@ export default function WayLinePage({ planedevices, onRefresh }) {
utils.message.success('航线执行任务创建成功');
setRthModalVisible(false);
getListTask();
if (onRefresh) {
setTimeout(() => {
onRefresh();
}, 1500)
}
//if (onRefresh) {
// setTimeout(() => {
// onRefresh();
// }, 1500)
//}
//await getTrackFn({
// uuid: res.data.task_uuid
@@ -502,7 +536,7 @@ export default function WayLinePage({ planedevices, onRefresh }) {
const topCards = [
{
title: '今日航线计划', value: todayCount, unit: '条', trend: trend, trendColor: trendColor,
title: '今日航线计划', value: todayCount, unit: '条', desc: '进行中', trend: trend, trendColor: trendColor,
icon: <LayoutGrid size={22} />, color: '#165DFF'
},
{ title: '执行中任务', value: runList.length, unit: '个', desc: '进行中', icon: <Plane size={22} />, color: '#165DFF' },
@@ -906,7 +940,7 @@ export default function WayLinePage({ planedevices, onRefresh }) {
</div>
<div>
<div style={{ fontSize: 12, color: '#86909C' }}>风速</div>
<div style={{ fontSize: 14, fontWeight: 700, color: '#1D2129' }}>{listDrone?.wind_speed ?? '-'} m/s</div>
<div style={{ fontSize: 14, fontWeight: 700, color: '#1D2129' }}>{fix2((listDroneRealtime || listDrone)?.wind_speed)} m/s</div>
<div style={{ fontSize: 12, color: '#00B42A' }}>适宜</div>
</div>
</div>
@@ -917,7 +951,7 @@ export default function WayLinePage({ planedevices, onRefresh }) {
</div>
<div>
<div style={{ fontSize: 12, color: '#86909C' }}>温度</div>
<div style={{ fontSize: 14, fontWeight: 700, color: '#1D2129' }}>{listDrone?.environment_temperature ?? '-'}℃</div>
<div style={{ fontSize: 14, fontWeight: 700, color: '#1D2129' }}>{fix2((listDroneRealtime || listDrone)?.environment_temperature)}℃</div>
<div style={{ fontSize: 12, color: '#00B42A' }}>适宜</div>
</div>
</div>
@@ -927,9 +961,11 @@ export default function WayLinePage({ planedevices, onRefresh }) {
<Droplets size={20} style={{ color: '#FF7D00' }} />
</div>
<div>
<div style={{ fontSize: 12, color: '#86909C' }}>降水概率</div>
<div style={{ fontSize: 14, fontWeight: 700, color: '#1D2129' }}>10%</div>
<div style={{ fontSize: 12, color: '#00B42A' }}>低</div>
<div style={{ fontSize: 12, color: '#86909C' }}>降水状态</div>
<div style={{ fontSize: 14, fontWeight: 700, color: '#1D2129' }}>{getVal((listDroneRealtime || listDrone)?.rainfall)}</div>
<div style={{ fontSize: 12, color: (listDroneRealtime || listDrone)?.rainfall === 'no_rain' ? '#00B42A' : '#F53F3F' }}>
{(listDroneRealtime || listDrone)?.rainfall === 'no_rain' ? '低风险' : '有雨'}
</div>
</div>
</div>
@@ -939,8 +975,12 @@ export default function WayLinePage({ planedevices, onRefresh }) {
</div>
<div>
<div style={{ fontSize: 12, color: '#86909C' }}>GNSS质量</div>
<div style={{ fontSize: 14, fontWeight: 700, color: '#1D2129' }}>良好</div>
<div style={{ fontSize: 12, color: '#00B42A' }}>适宜</div>
<div style={{ fontSize: 14, fontWeight: 700, color: '#1D2129' }}>
{(listDroneRealtime || listDrone)?.position_state?.rtk_number ?? 0} RTK
</div>
<div style={{ fontSize: 12, color: (listDroneRealtime || listDrone)?.position_state?.is_fixed === 'fixing_successful' ? '#00B42A' : '#FF7D00' }}>
{(listDroneRealtime || listDrone)?.position_state?.is_fixed === 'fixing_successful' ? '已固定' : '未固定'}
</div>
</div>
</div>
</div>

View File

@@ -159,6 +159,15 @@ export function getWeatherType(weatherText = "") {
return "cloudy";
}
export const fix2 = (val) => {
if (val === null || val === undefined || isNaN(val)) return '-';
return Number(val).toFixed(2);
};
export const getVal = (val) => val == "no_rain" ? "无" : (val || '-');
// ==============================================
// 通用导出表格为 CSV / Excel(通用版,不写死任何字段)
// ==============================================