|
|
|
|
@ -99,6 +99,15 @@ const UIModule = (() => {
|
|
|
|
|
fetchZones();
|
|
|
|
|
setInterval(fetchZones, 5000);
|
|
|
|
|
|
|
|
|
|
// 区域标注管理
|
|
|
|
|
document.getElementById('btn-add-zone').addEventListener('click', openZoneModal);
|
|
|
|
|
document.getElementById('modal-zone-close').addEventListener('click', closeZoneModal);
|
|
|
|
|
document.getElementById('modal-zone-cancel').addEventListener('click', closeZoneModal);
|
|
|
|
|
document.getElementById('modal-zone-confirm').addEventListener('click', addZone);
|
|
|
|
|
document.getElementById('modal-zone').addEventListener('click', function(e) {
|
|
|
|
|
if (e.target === e.currentTarget) closeZoneModal();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
addLog('info', '系统就绪 — 实机连接模式');
|
|
|
|
|
addLog('info', '请确保电脑已连接 P600 的 WiFi 数传,rosbridge 需在机载电脑上运行');
|
|
|
|
|
}
|
|
|
|
|
@ -590,12 +599,16 @@ const UIModule = (() => {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
container.innerHTML = dangerZones.map(function(dz) {
|
|
|
|
|
return '<div class="danger-item">' +
|
|
|
|
|
'<span class="danger-icon">⚠</span>' +
|
|
|
|
|
'<div class="danger-info">' +
|
|
|
|
|
'<div class="danger-desc">' + (dz.description || '危险区域') + '</div>' +
|
|
|
|
|
'<div class="danger-coord">(' + Number(dz.lat).toFixed(4) + ', ' + Number(dz.lng).toFixed(4) + ') R' + (dz.radius || 50) + 'm</div>' +
|
|
|
|
|
var zid = dz.id || '';
|
|
|
|
|
return '<div class="danger-item" style="justify-content:space-between;">' +
|
|
|
|
|
'<div style="display:flex;align-items:center;gap:10px;min-width:0;flex:1;">' +
|
|
|
|
|
'<span class="danger-icon">⚠</span>' +
|
|
|
|
|
'<div class="danger-info">' +
|
|
|
|
|
'<div class="danger-desc">' + (dz.description || '危险区域') + '</div>' +
|
|
|
|
|
'<div class="danger-coord">(' + Number(dz.lat).toFixed(4) + ', ' + Number(dz.lng).toFixed(4) + ') R' + (dz.radius || 50) + 'm</div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
(zid ? '<span style="color:#ff4d4f;font-size:12px;cursor:pointer;padding:4px;flex-shrink:0;" onclick="UIModule.deleteZone(' + zid + ')">🗑</span>' : '') +
|
|
|
|
|
'</div>';
|
|
|
|
|
}).join('');
|
|
|
|
|
}
|
|
|
|
|
@ -611,16 +624,104 @@ const UIModule = (() => {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
container.innerHTML = safeZones.map(function(sz) {
|
|
|
|
|
return '<div class="safe-item">' +
|
|
|
|
|
'<span class="safe-icon">✔</span>' +
|
|
|
|
|
'<div class="danger-info">' +
|
|
|
|
|
'<div class="danger-desc">' + (sz.description || '安全区域') + '</div>' +
|
|
|
|
|
'<div class="danger-coord">(' + Number(sz.lat).toFixed(4) + ', ' + Number(sz.lng).toFixed(4) + ') R' + (sz.radius || 50) + 'm</div>' +
|
|
|
|
|
var zid = sz.id || '';
|
|
|
|
|
return '<div class="safe-item" style="justify-content:space-between;">' +
|
|
|
|
|
'<div style="display:flex;align-items:center;gap:10px;min-width:0;flex:1;">' +
|
|
|
|
|
'<span class="safe-icon">✔</span>' +
|
|
|
|
|
'<div class="danger-info">' +
|
|
|
|
|
'<div class="danger-desc">' + (sz.description || '安全区域') + '</div>' +
|
|
|
|
|
'<div class="danger-coord">(' + Number(sz.lat).toFixed(4) + ', ' + Number(sz.lng).toFixed(4) + ') R' + (sz.radius || 50) + 'm</div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
'</div>' +
|
|
|
|
|
(zid ? '<span style="color:#ff4d4f;font-size:12px;cursor:pointer;padding:4px;flex-shrink:0;" onclick="UIModule.deleteZone(' + zid + ')">🗑</span>' : '') +
|
|
|
|
|
'</div>';
|
|
|
|
|
}).join('');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============== 区域标注管理 ===============
|
|
|
|
|
|
|
|
|
|
function openZoneModal(keepValues) {
|
|
|
|
|
if (!keepValues) {
|
|
|
|
|
document.getElementById('zone-name').value = '';
|
|
|
|
|
document.getElementById('zone-coords').value = '';
|
|
|
|
|
document.getElementById('zone-lat').value = '';
|
|
|
|
|
document.getElementById('zone-lng').value = '';
|
|
|
|
|
document.getElementById('zone-radius').value = '100';
|
|
|
|
|
}
|
|
|
|
|
MapModule.stopZonePick();
|
|
|
|
|
var radios = document.querySelectorAll('#modal-zone input[name="ztype"]');
|
|
|
|
|
if (radios.length > 0) radios[0].checked = true;
|
|
|
|
|
document.getElementById('modal-zone').style.display = 'flex';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeZoneModal() {
|
|
|
|
|
MapModule.stopZonePick();
|
|
|
|
|
document.getElementById('modal-zone').style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function pickZoneFromMap() {
|
|
|
|
|
if (!mapInited) {
|
|
|
|
|
addLog('warning', '请先进入无人机监控页面加载地图');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
closeZoneModal();
|
|
|
|
|
addLog('info', '请在地图上点击选择区域位置');
|
|
|
|
|
MapModule.startZonePick(function(lat, lng) {
|
|
|
|
|
document.getElementById('zone-coords').value = lat.toFixed(6) + ', ' + lng.toFixed(6);
|
|
|
|
|
document.getElementById('zone-lat').value = lat.toFixed(6);
|
|
|
|
|
document.getElementById('zone-lng').value = lng.toFixed(6);
|
|
|
|
|
openZoneModal(true); // 传入 true 保留已填入的坐标
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function addZone() {
|
|
|
|
|
var name = document.getElementById('zone-name').value.trim();
|
|
|
|
|
var lat = parseFloat(document.getElementById('zone-lat').value);
|
|
|
|
|
var lng = parseFloat(document.getElementById('zone-lng').value);
|
|
|
|
|
var radius = parseInt(document.getElementById('zone-radius').value) || 100;
|
|
|
|
|
var typeEl = document.querySelector('#modal-zone input[name="ztype"]:checked');
|
|
|
|
|
var type = typeEl ? typeEl.value : 'danger';
|
|
|
|
|
|
|
|
|
|
if (!name) { addLog('warning', '请输入区域名称'); return; }
|
|
|
|
|
if (isNaN(lat) || isNaN(lng)) { addLog('warning', '请输入有效的经纬度'); return; }
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
var resp = await fetch('/api/zones', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
description: name,
|
|
|
|
|
lat: lat, lng: lng,
|
|
|
|
|
radius: radius,
|
|
|
|
|
zone_type: type
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
if (resp.ok) {
|
|
|
|
|
addLog('success', '已添加' + (type === 'danger' ? '危险区域' : '安全区域') + ': ' + name);
|
|
|
|
|
closeZoneModal();
|
|
|
|
|
fetchZones();
|
|
|
|
|
if (mapInited) {
|
|
|
|
|
if (type === 'danger') MapModule.addDangerZone(lat, lng, radius, name);
|
|
|
|
|
else MapModule.addSafeZone(lat, lng, radius, name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
addLog('error', '添加区域失败: 网络错误');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function deleteZone(id) {
|
|
|
|
|
try {
|
|
|
|
|
var resp = await fetch('/api/zones/' + id, { method: 'DELETE' });
|
|
|
|
|
if (resp.ok) {
|
|
|
|
|
addLog('info', '已删除区域 #' + id);
|
|
|
|
|
fetchZones();
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
addLog('error', '删除失败: 网络错误');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============== 闪光检测功能 ===============
|
|
|
|
|
|
|
|
|
|
function onEnableFlashDetection() {
|
|
|
|
|
@ -861,6 +962,8 @@ const UIModule = (() => {
|
|
|
|
|
onTelemetry,
|
|
|
|
|
onStatus,
|
|
|
|
|
onDroneDisconnected,
|
|
|
|
|
deleteZone,
|
|
|
|
|
pickZoneFromMap,
|
|
|
|
|
updateWaypointList,
|
|
|
|
|
updateButtons,
|
|
|
|
|
addLog
|
|
|
|
|
|