无人机航线
This commit is contained in:
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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]}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user