无人机航线

This commit is contained in:
mmc
2026-05-21 17:04:21 +08:00
parent 0e6de5566e
commit 2fcc5a13ea
3 changed files with 186 additions and 44 deletions

View File

@@ -46,8 +46,9 @@ export function getJurisdiction(data) {
export function getsinglePlan(data) {
return request({
url: `/iot/UAV/getFlightTask?sn=${data.gateway_sn}`,
method: 'get',
url: `/iot/UAV/getFlightTask`,
method: 'post',
data
})
}

View File

@@ -257,28 +257,31 @@ export default function DeviceStatusPage({
onChange={(val) => onDeviceChange?.(val)}
showSearch
optionFilterProp="label"
options={devicesAll?.map((item) => ({
value: item.device_sn || item.serialNumber,
label:
item.deviceAlias ||
item.drone_callsign ||
item.callsign ||
item.deviceName ||
'未知',
}))}
options={[
{
label: <span>机器人</span>,
title: 'manager',
options: devicesAll.filter(item => item.serialNumber).map(item => ({
value: item.device_sn || item.serialNumber,
label: item.deviceAlias || item.deviceName || '未知设备'
})),
},
{
label: <span>无人机</span>,
title: 'engineer',
options: devicesAll.filter(item => item.device_sn).map(item => ({
value: item.device_sn,
label: item.drone_callsign || item.callsign || '未知设备'
})),
},
]}
/>
<Tag color="blue">
{isUAV ? '无人机' : '机器人'}
</Tag>
{wsStatus === 'connected' && (
<Badge
status="processing"
color="#52c41a"
text="实时连接中"
/>
)}
</div>
<Row gutter={[16, 16]}>

View File

@@ -16,8 +16,10 @@ import {
LayoutGrid, Clock, CheckCircle2, AlertCircle, Zap, Waves,
Calendar, User, TrendingUp, Maximize2
} from 'lucide-react';
import { getsinglePlan } from '@/api/device';
import { getsinglePlan, getsinglePlanTrack } from '@/api/device';
import dayjs from 'dayjs';
import utils from '@/lib/utils';
import CesiumMap from '../Map';
const { Text, Title } = Typography;
@@ -30,6 +32,7 @@ export default function WayLinePage({ planedevices }) {
const cardStyle = { borderRadius: 12, border: 'none', boxShadow: '0 2px 12px rgba(0,0,0,0.04)' };
const [selectedDrone, setSelectedDrone] = useState(null);
const [taskList, setTaskList] = useState([]); // 真实任务列表
const [points, setPoints] = useState([]); // 航线轨迹点
// 顶部统计
const topCards = [
@@ -45,7 +48,10 @@ export default function WayLinePage({ planedevices }) {
const getTaskList = async (drone) => {
if (!drone || !drone.device_sn) return;
try {
const res = await getsinglePlan(drone);
const data = {
sn: [drone.gateway_sn]
}
const res = await getsinglePlan(data);
if (res?.code === 0) {
setTaskList(res.data?.list || []);
}
@@ -58,6 +64,8 @@ export default function WayLinePage({ planedevices }) {
useEffect(() => {
if (planedevices?.length) {
setSelectedDrone(planedevices[0]);
const data = planedevices.map(i => i.gateway_sn);
getTaskList(data);
}
}, [planedevices]);
@@ -68,12 +76,21 @@ export default function WayLinePage({ planedevices }) {
// 任务状态颜色
const getStatusTag = (status) => {
const map = {
success: { color: 'green', text: '成功' },
running: { color: 'blue', text: '执行中' },
failed: { color: 'red', text: '失败' },
const statusMap = {
// 基础状态
success: { color: 'success', text: '执行成功' },
running: { color: 'processing', text: '执行中' },
failed: { color: 'error', text: '执行失败' },
waiting: { color: 'default', text: '待开始' },
starting_failure: { color: 'error', text: '启动失败' },
executing: { color: 'processing', text: '执行中' },
suspended: { color: 'warning', text: '已挂起' },
restored: { color: 'processing', text: '恢复执行' },
terminated: { color: 'default', text: '已终止' },
timeout: { color: 'error', text: '执行超时' },
};
const item = map[status] || { color: 'default', text: status };
const item = statusMap[status] || { color: 'default', text: status };
return <Tag color={item.color}>{item.text}</Tag>;
};
@@ -97,7 +114,37 @@ export default function WayLinePage({ planedevices }) {
render: t => t ? dayjs(t).format('YYYY-MM-DD HH:mm:ss') : '-'
},
];
// 表格行点击事件
const handleRowClick = async (record) => {
try {
getsinglePlanTrack(record).then(res => {
if (res.code == 0 && res.data.track?.points?.length) {
const points = res.data.track.points.map(item => ({
lon: item.longitude || item.lon,
lat: item.latitude || item.lat
}));
setPoints(points);
} else {
utils.message.warning("暂无航线数据");
setPoints([]);
}
})
// ======================
// 在这里写你的接口请求
// ======================
// 示例:
// const res = await getTaskDetail(record.uuid);
// if (res.code === 0) {
// message.success('获取成功');
// console.log('任务详情:', res.data);
// }
} catch (err) {
message.error('请求失败');
console.error(err);
}
};
return (
<div className="normal-spacing" style={{ background: '#f5f7fa', minHeight: '100vh', padding: '16px' }}>
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
@@ -144,14 +191,19 @@ export default function WayLinePage({ planedevices }) {
style={{ ...cardStyle, marginBottom: 10 }}
>
<div style={{ width: '100%', height: 420, background: '#e8f3ff url(https://picsum.photos/seed/solar_map_v2/1600/1000) center/cover', position: 'relative' }}>
<svg style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
{/*<svg style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
<path d="M100,120 L200,80 L350,150 L500,100 L650,180" fill="none" stroke="#165DFF" strokeWidth="3" strokeDasharray="8,4" />
<path d="M220,300 Q350,220 480,350 T700,280" fill="none" stroke="#FF7D00" strokeWidth="3" />
<path d="M400,80 L550,140 L700,100" fill="none" stroke="#00B42A" strokeWidth="3" strokeDasharray="4,2" />
<circle cx="100" cy="120" r="6" fill="#165DFF" stroke="#fff" strokeWidth="2" />
<circle cx="200" cy="80" r="6" fill="#165DFF" stroke="#fff" strokeWidth="2" />
<circle cx="350" cy="150" r="6" fill="#165DFF" stroke="#fff" strokeWidth="2" />
</svg>
</svg>*/}
<CesiumMap
points={points}
from="device-control"
/>
<div style={{ position: 'absolute', top: 16, left: 16, background: 'rgba(255,255,255,0.92)', backdropFilter: 'blur(8px)', padding: '14px 16px', borderRadius: 10, width: 150, boxShadow: '0 4px 12px rgba(0,0,0,0.1)' }}>
<div style={{ fontSize: fontSmall, display: 'flex', flexDirection: 'column', gap: 10 }}>
@@ -206,6 +258,12 @@ export default function WayLinePage({ planedevices }) {
pagination={false}
scroll={{ y: 260 }}
style={{ height: '100%' }}
// 关键:行点击事件
onRow={(record) => ({
onClick: () => handleRowClick(record),
style: { cursor: 'pointer' } // 👈 就是这一行,显示小手
})}
/>
</Card>
</Col>
@@ -309,25 +367,105 @@ export default function WayLinePage({ planedevices }) {
</Col>
<Col span={18}>
<Row gutter={[16, 16]}>
{[
{ label: '风速', value: '2.8 m/s', desc: '适宜', icon: <Wind size={16} />, color: '#165DFF' },
{ label: '温度', value: '28℃', desc: '适宜', icon: <Thermometer size={16} />, color: '#FF7D00' },
{ label: '降水概率', value: '10%', desc: '极低', icon: <Droplets size={16} />, color: '#165DFF' },
{ label: 'GNSS 质量', value: '良好', desc: '适宜', icon: <Satellite size={16} />, color: '#00B42A' },
].map((item, idx) => (
<Col span={12} key={idx}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 32, height: 32, borderRadius: 8, background: '#F7F8FA', display: 'flex', justifyContent: 'center', alignItems: 'center', color: item.color }}>
{item.icon}
{
label: '风速',
value: `${selectedDrone?.wind_speed ?? '-'} m/s`,
icon: <Wind size={16} />,
color: '#165DFF',
// 飞行条件:≤ 5 m/s 适宜
judge: (val) => {
const speed = parseFloat(val);
if (isNaN(speed)) return { desc: '未知', color: '#86909C' };
if (speed <= 5) return { desc: '适宜', color: '#52C41A' };
if (speed <= 8) return { desc: '谨慎', color: '#FF7D00' };
return { desc: '不适宜', color: '#F53F3F' };
}
},
{
label: '温度',
value: `${selectedDrone?.environment_temperature ?? '-'}℃`,
icon: <Thermometer size={16} />,
color: '#FF7D00',
// 飞行条件:0℃ ~ 40℃ 适宜
judge: (val) => {
const temp = parseFloat(val);
if (isNaN(temp)) return { desc: '未知', color: '#86909C' };
if (temp >= 0 && temp <= 40) return { desc: '适宜', color: '#52C41A' };
return { desc: '不适宜', color: '#F53F3F' };
}
},
{
label: '降雨',
value: `${selectedDrone?.rainfall} `,
icon: <ThunderboltOutlined size={16} />,
color: '#FF7D00',
judge: (val) => {
if (val?.trim() === "no_rain") {
console.log('✅ 匹配到无雨 → 适宜');
return { desc: '适宜', color: '#52C41A' };
} else {
console.log('❌ 未匹配 → 不适宜');
return { desc: '不适宜', color: '#F53F3F' };
}
}
},
{
label: '信号质量',
value: (() => {
const q = selectedDrone?.network_state?.quality;
if (q === 'good') return '优';
if (q === 'better') return '良';
if (q === 'moderate') return '中';
if (q === 'poor') return '差';
if (q === 'too_pool') return '极差';
return '正常';
})(),
icon: <Satellite size={16} />,
color: '#00B42A',
// 信号质量:优 / 良 → 适宜,中 → 谨慎,差/极差 → 不适宜
judge: (val) => {
if (val === '优' || val === '良' || val === '正常') return { desc: '适宜', color: '#52C41A' };
if (val === '中') return { desc: '谨慎', color: '#FF7D00' };
return { desc: '不适宜', color: '#F53F3F' };
}
},
].map((item, idx) => {
// 自动计算状态文案和颜色
const status = item.judge(item.value);
return (
<Col span={12} key={idx}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div
style={{
width: 32, height: 32, borderRadius: 8,
background: '#F7F8FA', display: 'flex',
justifyContent: 'center', alignItems: 'center',
color: item.color
}}
>
{item.icon}
</div>
<div>
<div style={{ fontSize: 10, color: '#86909C' }}>{item.label}</div>
<div style={{ fontSize: fontNormal, fontWeight: 700, color: '#1D2129' }}>
{item.value}
</div>
<div
style={{
fontSize: 10,
color: status.color,
fontWeight: 500
}}
>
{status.desc}
</div>
</div>
</div>
<div>
<div style={{ fontSize: 10, color: '#86909C' }}>{item.label}</div>
<div style={{ fontSize: fontNormal, fontWeight: 700, color: '#1D2129' }}>{item.value}</div>
<div style={{ fontSize: 10, color: '#52C41A', fontWeight: 500 }}>{item.desc}</div>
</div>
</div>
</Col>
))}
</Col>
);
})}
</Row>
</Col>
</Row>