任务执行成功

This commit is contained in:
mmc
2026-05-27 13:48:17 +08:00
parent 3fc6dd2352
commit 6e4baa9bb7

View File

@@ -10,14 +10,16 @@ import {
BranchesOutlined, CloudOutlined, ThunderboltOutlined, CheckCircleOutlined,
InfoCircleOutlined, ClockCircleOutlined,
SettingOutlined, WarningOutlined, ArrowRightOutlined,
SearchOutlined, MoreOutlined, AimOutlined
SearchOutlined, MoreOutlined, AimOutlined, PlayCircleOutlined
} from '@ant-design/icons';
import {
Plane, Navigation, Wind, Thermometer, Droplets, Satellite,
LayoutGrid, Clock, CheckCircle2, AlertCircle, Zap, Waves,
Calendar, User, TrendingUp, Maximize2, Play, Square, X, MapPin, RotateCw
} from 'lucide-react';
import { getsinglePlan, getsinglePlanTrack, updateFlightTaskStatus, postFlightCommmand, getWayLine, createWaylineTask, getFlightTaskDetail } from '@/api/device';
import {
getsinglePlan, getsinglePlanTrack, updateFlightTaskStatus, postFlightCommmand, getWayLine, createWaylineTask, getFlightTaskDetail
} from '@/api/device';
import dayjs from 'dayjs';
import utils from '@/lib/utils';
import CesiumMap from '../Map';
@@ -43,8 +45,17 @@ export default function WayLinePage({ planedevices }) {
const [boardDateRange, setBoardDateRange] = useState(null);
const [boardTaskList, setBoardTaskList] = useState([]);
// ✅ 航线列表 独立无人机筛选(新增)
const [waylineDrone, setWaylineDrone] = useState(null);
const [points, setPoints] = useState([]);
// 航线列表相关状态
const [selectedWayline, setSelectedWayline] = useState(null);
const [rthModalVisible, setRthModalVisible] = useState(false);
const [tempRthAltitude, setTempRthAltitude] = useState(100);
const [waylineSearch, setWaylineSearch] = useState('');
// 任务控制相关状态
const [selectedTask, setSelectedTask] = useState(null);
const [taskstatus, setTaskStatus] = useState('suspended');
@@ -117,7 +128,7 @@ export default function WayLinePage({ planedevices }) {
value: item.id
}
})
setWaylineList(_list || []); // 取list数组
setWaylineList(_list || []);
}
} catch (err) {
message.error('获取航线列表失败');
@@ -131,7 +142,7 @@ export default function WayLinePage({ planedevices }) {
setWaylineUuid('');
setTaskForm({
name: '',
sn: listDrone?.gateway_sn || '',
sn: waylineDrone?.gateway_sn || '',
landing_dock_sn: '',
rth_altitude: 100,
rth_mode: 'optimal',
@@ -159,9 +170,7 @@ export default function WayLinePage({ planedevices }) {
const payload = {
...taskForm,
wayline_uuid: waylineUuid,
//task_type: 'recurring',
repeat_type: taskForm.repeat_type,
// ✅ 直接传入处理好的对象
repeat_option: buildRepeatOption(),
recurring_task_start_time_list: taskForm.recurring_task_start_time_list.map(t => dayjs(t).unix()),
};
@@ -170,7 +179,7 @@ export default function WayLinePage({ planedevices }) {
if (res.code === 200 || res.code === 0) {
utils.message.success('创建任务成功');
resetModals();
getListTask(); // 刷新列表
getListTask();
} else {
utils.message.error(res.msg || '创建失败');
}
@@ -181,12 +190,15 @@ export default function WayLinePage({ planedevices }) {
}
};
// 选中无人机时自动填充机场SN
// 航线列表无人机变更时同步到表单
useEffect(() => {
if (listDrone) {
setTaskForm(prev => ({ ...prev, sn: listDrone.gateway_sn || '' }));
if (waylineDrone) {
setTaskForm(prev => ({
...prev,
sn: waylineDrone.gateway_sn || ''
}));
}
}, [listDrone]);
}, [waylineDrone]);
// 顶部统计
const [todayCount, setTodayCount] = useState(0);
@@ -239,12 +251,12 @@ export default function WayLinePage({ planedevices }) {
}, [taskList]);
// 获取任务列表数据 【时间戳已改为秒级】
// 获取任务列表数据
const getListTask = async () => {
if (!planedevices?.length) return;
try {
const defaultStart = dayjs().startOf('day').unix(); // 秒级
const defaultEnd = dayjs().endOf('day').unix(); // 秒级
const defaultStart = dayjs().startOf('day').unix();
const defaultEnd = dayjs().endOf('day').unix();
const [beginAt, endAt] = listDateRange?.length === 2
? [dayjs(listDateRange[0]).unix(), dayjs(listDateRange[1]).unix()]
@@ -253,7 +265,6 @@ export default function WayLinePage({ planedevices }) {
const sns = listDrone ? [listDrone.gateway_sn] : planedevices.map(item => item.gateway_sn);
const res = await getsinglePlan({ sns, beginAt, endAt });
if (res?.code === 200) {
// 处理嵌套数据结构:data: { [sn]: { list: [] } }
let combinedList = [];
if (res.data) {
Object.values(res.data).forEach((item: any) => {
@@ -267,12 +278,12 @@ export default function WayLinePage({ planedevices }) {
}
};
// 获取看板任务数据 【时间戳已改为秒级】
// 获取看板任务数据
const getBoardTask = async () => {
if (!planedevices?.length) return;
try {
const defaultStart = dayjs().startOf('day').unix(); // 秒级
const defaultEnd = dayjs().endOf('day').unix(); // 秒级
const defaultStart = dayjs().startOf('day').unix();
const defaultEnd = dayjs().endOf('day').unix();
const [beginAt, endAt] = boardDateRange?.length === 2
? [dayjs(boardDateRange[0]).unix(), dayjs(boardDateRange[1]).unix()]
@@ -281,7 +292,6 @@ export default function WayLinePage({ planedevices }) {
const sns = boardDrone ? [boardDrone.gateway_sn] : planedevices.map(item => item.gateway_sn);
const res = await getsinglePlan({ sns, beginAt, endAt });
if (res?.code === 200) {
// 处理嵌套数据结构
let combinedList = [];
if (res.data) {
Object.values(res.data).forEach((item: any) => {
@@ -294,19 +304,20 @@ export default function WayLinePage({ planedevices }) {
message.error('任务看板数据获取失败');
}
};
useEffect(() => {
fetchWaylineList();
}, [])
// 初始化默认全部设备
// 初始化默认设备
useEffect(() => {
if (planedevices?.length) {
setListDrone(planedevices[0]); // 默认显示第一个无人机
setBoardDrone(null); // 默认显示所有
setListDrone(planedevices[0]);
setWaylineDrone(planedevices[0]); // 航线列表默认选中第一个
setBoardDrone(null);
}
}, [planedevices]);
// 各自筛选变更独立请求
useEffect(() => {
getListTask();
}, [listDrone, listDateRange]);
@@ -337,29 +348,23 @@ export default function WayLinePage({ planedevices }) {
// 列表表格列
const taskColumns = [
{ title: '任务名称', dataIndex: 'name', width: 260, ellipsis: true, fixed: "left" },
//{ title: '任务UUID', dataIndex: 'uuid', width: 260, ellipsis: true },
{ title: '设备SN', dataIndex: 'sn', width: 140 },
{ title: '状态', dataIndex: 'status', width: 80, render: getStatusTag },
{
title: '开始时间',
dataIndex: 'begin_at',
width: 170,
title: '开始时间', dataIndex: 'begin_at', width: 170,
render: t => t ? dayjs(t).format('YYYY-MM-DD HH:mm:ss') : '-'
},
{
title: '结束时间',
dataIndex: 'end_at',
width: 170,
title: '结束时间', dataIndex: 'end_at', width: 170,
render: t => t ? dayjs(t).format('YYYY-MM-DD HH:mm:ss') : '-'
},
];
// 表格行点击查看轨迹
const handleRowClick = async (record) => {
setSelectedTask(record); // 记录当前选中的任务
setSelectedTask(record);
try {
getTaskDetail(record.uuid); // 获取任务详情
getTaskDetail(record.uuid);
const res = await getsinglePlanTrack(record);
if (res.code === 0 && res.data.track?.points?.length) {
const points = res.data.track.points.map(item => ({
@@ -373,21 +378,83 @@ export default function WayLinePage({ planedevices }) {
}
} catch (err) {
message.error('请求失败');
console.error(err);
}
};
// 航线列表点击处理
const handleWaylineClick = (wayline) => {
setSelectedWayline(wayline);
if (wayline.points && wayline.points.length > 0) {
const mapPoints = wayline.points.map(p => ({
lon: p.longitude || p.lon,
lat: p.latitude || p.lat
}));
setPoints(mapPoints);
} else if (wayline.track?.points?.length > 0) {
const mapPoints = wayline.track.points.map(p => ({
lon: p.longitude || p.lon,
lat: p.latitude || p.lat
}));
setPoints(mapPoints);
}
};
// 执行航线按钮点击(改为图标)
const handleExecuteClick = (e, wayline) => {
e.stopPropagation();
// ✅ 使用航线列表自己的无人机
if (!waylineDrone?.gateway_sn) {
utils.message.warning('请先在航线列表上方选择执行无人机');
return;
}
setSelectedWayline(wayline);
setTempRthAltitude(100);
setRthModalVisible(true);
};
// 确认执行航线任务
const confirmExecuteWayline = async () => {
if (!selectedWayline || !waylineDrone?.gateway_sn) return;
setLoading(true);
try {
const payload = {
name: `${selectedWayline.name}_${dayjs().format('MMDDHHmm')}`,
sn: waylineDrone.gateway_sn, // ✅ 使用航线列表无人机
wayline_uuid: selectedWayline.id || selectedWayline.uuid,
rth_altitude: tempRthAltitude,
task_type: 'immediate',
rth_mode: 'optimal',
wayline_precision_type: 'gps',
resumable_status: 'auto',
min_battery_capacity: 50,
repeat_type: 'nonrepeating',
time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,
};
const res: any = await createWaylineTask(payload);
if (res.code === 200 || res.code === 0) {
utils.message.success('航线执行任务创建成功');
setRthModalVisible(false);
getListTask();
} else {
utils.message.error(res.msg || '执行失败');
}
} catch (err) {
utils.message.error('创建任务异常');
} finally {
setLoading(false);
}
};
// 看板数据分组处理
const groupBoardData = () => {
// 待执行 = 准备中 + 待开始 + 已挂起 + 已终止
const waitList = boardTaskList.filter(item =>
['preparing', 'waiting', 'suspended', 'terminated'].includes(item.status)
);
// 执行中 = 运行中 + 执行中 + 恢复执行
const runList = boardTaskList.filter(item =>
['running', 'executing', 'restored'].includes(item.status)
);
// 已完成 = 执行成功
const finishList = boardTaskList.filter(item =>
['success'].includes(item.status)
);
@@ -397,13 +464,8 @@ export default function WayLinePage({ planedevices }) {
const topCards = [
{
title: '今日航线计划',
value: todayCount,
unit: '条',
trend: trend,
trendColor: trendColor,
icon: <LayoutGrid size={22} />,
color: '#165DFF'
title: '今日航线计划', value: todayCount, unit: '条', trend: trend, trendColor: trendColor,
icon: <LayoutGrid size={22} />, color: '#165DFF'
},
{ title: '执行中任务', value: runList.length, unit: '个', desc: '进行中', icon: <Plane size={22} />, color: '#165DFF' },
{ title: '待审批任务', value: '4', unit: '个', trend: '较昨日 -1', trendColor: '#F53F3F', icon: <Calendar size={22} />, color: '#FF7D00' },
@@ -412,7 +474,6 @@ export default function WayLinePage({ planedevices }) {
{ title: '覆盖装备', value: '4 类 / 9 台', unit: '', desc: '全部在线', icon: <Satellite size={22} />, color: '#165DFF' },
];
// 指令下发逻辑
const sendFlightCommand = async (command: 'return_home' | 'return_home_cancel' | 'flighttask_pause' | 'flighttask_recovery') => {
if (!listDrone?.device_sn) {
@@ -429,7 +490,6 @@ export default function WayLinePage({ planedevices }) {
const res: any = await postFlightCommmand(data);
if (res.code === 200 || res.code === 0) {
utils.message.success('指令下发成功');
// 切换状态
if (command === 'return_home') setIsReturningHome(true);
if (command === 'return_home_cancel') setIsReturningHome(false);
if (command === 'flighttask_pause') setIsTaskPaused(true);
@@ -443,8 +503,8 @@ export default function WayLinePage({ planedevices }) {
setFlightCommandLoading(false);
}
};
const getTaskDetail = async (taskId) => {
console.log('获取任务详情,taskId=', taskId);
if (!taskId) return;
setTaskDetail(null);
setLoading(true);
@@ -452,14 +512,12 @@ export default function WayLinePage({ planedevices }) {
const res = await getFlightTaskDetail(taskId);
if (res.code === 0) {
setTaskDetail(res.data);
} else {
setTaskDetail(null);
utils.message.error('暂无详情');
}
} catch (err) {
setTaskDetail(null);
utils.message.error('获取详情接口异常');
} finally {
setLoading(false);
@@ -489,7 +547,6 @@ export default function WayLinePage({ planedevices }) {
setTaskStatus('restored');
}
utils.message.success(statusText);
// 刷新任务列表
getListTask();
} else {
utils.message.error(res.msg || '操作失败');
@@ -585,7 +642,6 @@ export default function WayLinePage({ planedevices }) {
options={planedevices?.map(item => ({
value: item.device_sn,
label: item.drone_callsign || item.device_sn || item.callsign
}))}
value={listDrone?.device_sn}
onChange={(val) => {
@@ -737,7 +793,6 @@ export default function WayLinePage({ planedevices }) {
</Space>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '8px' }}>
{/* 执行/挂起/继续 逻辑 */}
{selectedTask?.status === 'executing' || selectedTask?.status === 'running' || selectedTask?.status === 'restored' ? (
<Button
type="primary"
@@ -766,7 +821,6 @@ export default function WayLinePage({ planedevices }) {
{selectedTask?.status === 'suspended' ? '继续任务' : '执行任务'}
</Button>
)}
{/* 返航逻辑 */}
<Button
type="primary"
danger={isReturningHome}
@@ -778,7 +832,6 @@ export default function WayLinePage({ planedevices }) {
{isReturningHome ? '取消返航' : '返航'}
</Button>
{/* 暂停/恢复逻辑 */}
<Button
type="default"
icon={isTaskPaused ? <RotateCw size={16} /> : <Square size={16} />}
@@ -797,9 +850,7 @@ export default function WayLinePage({ planedevices }) {
bodyStyle={{ padding: '16px' }}
style={{ ...cardStyle, flex: 1 }}
>
{/* 顶部参数卡片区域,改成和截图一致的横向布局 */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '8px', marginBottom: 16 }}>
{/* 适宜性 */}
<div style={{ background: '#F7F8FA', borderRadius: 10, padding: '12px', display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 40, height: 40, borderRadius: '50%', background: 'rgba(22, 93, 255, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<CloudOutlined size={20} style={{ color: '#165DFF' }} />
@@ -810,7 +861,6 @@ export default function WayLinePage({ planedevices }) {
</div>
</div>
{/* 风速 */}
<div style={{ background: '#F7F8FA', borderRadius: 10, padding: '12px', display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 40, height: 40, borderRadius: '50%', background: 'rgba(22, 93, 255, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Wind size={20} style={{ color: '#165DFF' }} />
@@ -822,7 +872,6 @@ export default function WayLinePage({ planedevices }) {
</div>
</div>
{/* 温度 */}
<div style={{ background: '#F7F8FA', borderRadius: 10, padding: '12px', display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 40, height: 40, borderRadius: '50%', background: 'rgba(255, 125, 0, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Thermometer size={20} style={{ color: '#FF7D00' }} />
@@ -834,7 +883,6 @@ export default function WayLinePage({ planedevices }) {
</div>
</div>
{/* 降水概率 */}
<div style={{ background: '#F7F8FA', borderRadius: 10, padding: '12px', display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 40, height: 40, borderRadius: '50%', background: 'rgba(255, 125, 0, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Droplets size={20} style={{ color: '#FF7D00' }} />
@@ -846,7 +894,6 @@ export default function WayLinePage({ planedevices }) {
</div>
</div>
{/* GNSS质量 */}
<div style={{ background: '#F7F8FA', borderRadius: 10, padding: '12px', display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 40, height: 40, borderRadius: '50%', background: 'rgba(0, 180, 42, 0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Satellite size={20} style={{ color: '#00B42A' }} />
@@ -859,7 +906,6 @@ export default function WayLinePage({ planedevices }) {
</div>
</div>
{/* 底部提示区域 */}
<div style={{ display: 'flex', gap: '12px' }}>
<div style={{ flex: 1, background: 'rgba(255, 125, 0, 0.1)', borderRadius: 8, padding: '10px 12px', display: 'flex', alignItems: 'center', gap: 8 }}>
<AlertCircle size={16} style={{ color: '#FF7D00' }} />
@@ -876,7 +922,92 @@ export default function WayLinePage({ planedevices }) {
{/* 底部内容区 */}
<Row gutter={16}>
<Col xs={24} xl={14}>
<Col xs={24} xl={4}>
<Card
title={
<div className="flex justify-between items-center w-full">
<span style={{ fontSize: fontTitle, fontWeight: 700 }}>航线列表</span>
{/* ✅ 航线列表独立无人机筛选 */}
<Select
size="small"
style={{ width: 120 }}
placeholder="选择无人机"
showSearch
allowClear
options={planedevices?.map(item => ({
value: item.device_sn,
label: item.drone_callsign || item.device_sn || item.callsign
}))}
value={waylineDrone?.device_sn}
onChange={(val) => {
const find = planedevices.find(i => i.device_sn === val);
setWaylineDrone(find || null);
}}
/>
</div>
}
bordered={false}
bodyStyle={{ padding: '12px', display: 'flex', flexDirection: 'column', height: 480 }}
style={{ ...cardStyle, height: '100%' }}
>
<Input
placeholder="搜索航线..."
prefix={<SearchOutlined style={{ color: '#86909C' }} />}
style={{ marginBottom: 12, borderRadius: 6 }}
size="small"
value={waylineSearch}
onChange={e => setWaylineSearch(e.target.value)}
/>
<div style={{ flex: 1, overflowY: 'auto', paddingRight: 4 }} className="custom-scrollbar">
{waylineList
.filter(item => item.name?.toLowerCase().includes(waylineSearch.toLowerCase()))
.map((item) => (
<div
key={item.id || item.uuid}
onClick={() => handleWaylineClick(item)}
style={{
padding: '10px 12px',
borderRadius: 8,
marginBottom: 8,
cursor: 'pointer',
background: selectedWayline?.id === item.id ? '#E8F3FF' : '#F7F8FA',
border: selectedWayline?.id === item.id ? '1px solid #165DFF' : '1px solid transparent',
transition: 'all 0.2s',
position: 'relative'
}}
>
<div style={{ fontSize: 13, fontWeight: 600, color: '#1D2129', marginBottom: 4, paddingRight: 40 }}>
{item.name}
</div>
<div style={{ fontSize: 11, color: '#86909C', display: 'flex', alignItems: 'center', gap: 4 }}>
<Clock size={12} /> {item.update_time ? dayjs(item.update_time).format('MM-DD HH:mm') : '未知时间'}
</div>
{/* ✅ 执行按钮改为图标 */}
<Tooltip title="执行航线">
<Button
type="text"
icon={<PlayCircleOutlined style={{ color: '#165DFF', fontSize: 18 }} />}
style={{
position: 'absolute',
right: 8,
top: '50%',
transform: 'translateY(-50%)',
}}
onClick={(e) => handleExecuteClick(e, item)}
/>
</Tooltip>
</div>
))}
{waylineList.length === 0 && (
<div style={{ textAlign: 'center', padding: '20px 0', color: '#86909C', fontSize: 12 }}>
暂无航线数据
</div>
)}
</div>
</Card>
</Col>
<Col xs={24} xl={12}>
<Card
title={<span style={{ fontSize: fontTitle, fontWeight: 700 }}>任务甘特视图 / 排班视图 <Text type="secondary" style={{ fontSize: 11, fontWeight: 400, marginLeft: 12 }}>今日 (2025-05-24)</Text></span>}
extra={<MoreOutlined style={{ cursor: 'pointer', fontSize: 18 }} />}
@@ -926,7 +1057,7 @@ export default function WayLinePage({ planedevices }) {
</Card>
</Col>
<Col xs={24} xl={10}>
<Col xs={24} xl={8}>
<Card
title={
<div className="flex justify-between items-center w-full">
@@ -1016,14 +1147,11 @@ export default function WayLinePage({ planedevices }) {
</Col>
))}
</Row>
{/*<div style={{ textAlign: 'center', marginTop: 12 }}>
<Button type="link" size="small" style={{ fontSize: 12, color: '#86909C' }}>查看全部 <ArrowRightOutlined style={{ fontSize: 10 }} /></Button>
</div>*/}
</Card>
</Col>
</Row>
{/* ============================================== */}
{/* 创建任务弹窗 */}
<Modal
open={createTaskModal}
title="创建航线任务"
@@ -1033,7 +1161,6 @@ export default function WayLinePage({ planedevices }) {
width={650}
>
<div className="py-4 space-y-4">
{/* 基础信息 */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm block mb-1">航线名称 <span className="text-red-500">*</span></label>
@@ -1069,7 +1196,6 @@ export default function WayLinePage({ planedevices }) {
</div>
</div>
{/* 飞行参数 */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm block mb-1">返航高度(m)</label>
@@ -1130,9 +1256,6 @@ export default function WayLinePage({ planedevices }) {
onChange={(v) => setTaskForm({ ...taskForm, task_type: v })}
options={[
{ label: '立即', value: 'immediate' },
//{ label: '单次定时', value: 'timed' },
//{ label: '重复', value: 'recurring' },
//{ label: '连续', value: 'continuous' },
]}
style={{ width: '100%' }}
/>
@@ -1303,6 +1426,42 @@ export default function WayLinePage({ planedevices }) {
</div>
</Modal>
</div >
{/* 执行航线设置返航高度 Modal */}
<Modal
title="执行航线任务"
open={rthModalVisible}
onOk={confirmExecuteWayline}
onCancel={() => setRthModalVisible(false)}
confirmLoading={loading}
okText="立即执行"
cancelText="取消"
centered
width={400}
>
<div style={{ padding: '10px 0' }}>
<div style={{ marginBottom: 16 }}>
<Text strong>所选航线:</Text>
<Text>{selectedWayline?.name}</Text>
</div>
<div style={{ marginBottom: 16 }}>
<Text strong>执行设备:</Text>
<Text>{waylineDrone?.drone_callsign || waylineDrone?.device_sn || '未选择设备'}</Text>
</div>
<div>
<div style={{ marginBottom: 8 }}><Text strong>返航高度 (m):</Text></div>
<InputNumber
min={20}
max={500}
value={tempRthAltitude}
onChange={val => setTempRthAltitude(val || 100)}
style={{ width: '100%' }}
/>
<div style={{ marginTop: 4, fontSize: 12, color: '#86909C' }}>
建议高度:80m - 150m,请确保高于周围障碍物。
</div>
</div>
</div>
</Modal>
</div>
);
}