实现A*路径规划 #21

Merged
p5iufsk8w merged 1 commits from dmz_branch into main 6 months ago

@ -0,0 +1,83 @@
const db = require('./src/config/database');
const testThreatZones = [
{
type: 'aircraft',
level: 'high',
description: '长沙高危空域 - 测试区域',
geometry_type: 'circle',
geometry_data: {
center: { lng: 112.940, lat: 28.180 },
radius: 1500
},
status: 'active'
},
{
type: 'radar',
level: 'medium',
description: '长沙雷达监控区 - 测试区域',
geometry_type: 'circle',
geometry_data: {
center: { lng: 112.970, lat: 28.200 },
radius: 1000
},
status: 'active'
},
{
type: 'missile',
level: 'critical',
description: '长沙禁飞区 - 测试区域',
geometry_type: 'circle',
geometry_data: {
center: { lng: 112.920, lat: 28.220 },
radius: 800
},
status: 'active'
}
];
(async () => {
try {
console.log('开始插入测试威胁区数据...');
// 清除现有威胁区数据
await db.execute('DELETE FROM threat_zones WHERE description LIKE "%测试区域%"');
console.log('已清除现有测试数据');
// 插入新的测试威胁区
for (const zone of testThreatZones) {
const sql = `
INSERT INTO threat_zones
(type, level, description, geometry_type, geometry_data, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())
`;
const params = [
zone.type,
zone.level,
zone.description,
zone.geometry_type,
JSON.stringify(zone.geometry_data),
zone.status
];
const [result] = await db.execute(sql, params);
console.log(`插入威胁区: ${zone.description}, ID: ${result.insertId}`);
}
// 验证插入的数据
const [rows] = await db.execute('SELECT * FROM threat_zones WHERE description LIKE "%测试区域%"');
console.log('\n插入的威胁区数据:');
rows.forEach(row => {
console.log(`- ID: ${row.id}, 类型: ${row.type}, 等级: ${row.level}, 几何数据: ${row.geometry_data}`);
});
console.log('\n测试威胁区数据插入完成');
await db.end();
process.exit(0);
} catch (error) {
console.error('插入测试数据失败:', error);
process.exit(1);
}
})();

@ -9,8 +9,8 @@ let dronesData = [
name: '侦察无人机-001',
type: '侦察型',
status: 'active',
latitude: 39.9042,
longitude: 116.4074,
latitude: 28.1941,
longitude: 112.9823,
battery: 85,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
@ -20,8 +20,8 @@ let dronesData = [
name: '运输无人机-002',
type: '运输型',
status: 'idle',
latitude: 39.9122,
longitude: 116.4134,
latitude: 28.2022,
longitude: 112.9903,
battery: 92,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
@ -31,8 +31,8 @@ let dronesData = [
name: '巡逻无人机-003',
type: '巡逻型',
status: 'active',
latitude: 39.8965,
longitude: 116.3972,
latitude: 28.1865,
longitude: 112.9722,
battery: 67,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
@ -124,8 +124,8 @@ router.post('/', authenticateToken, async (req, res) => {
name: name || `无人机-${nextId - 1}`,
type: type || '通用型',
status: status || 'idle',
latitude: latitude ? parseFloat(latitude) : 39.9042,
longitude: longitude ? parseFloat(longitude) : 116.4074,
latitude: latitude ? parseFloat(latitude) : 28.1941,
longitude: longitude ? parseFloat(longitude) : 112.9823,
battery: 100,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()

@ -106,12 +106,13 @@ let operationLogs = [
id: 3,
name: '自动规划路径-003'
},
description: '创建新的路径规划',
description: '执行路径规划算法',
details: {
algorithm: 'astar',
waypoints: 5,
distance: 2500,
estimatedTime: 15
estimatedTime: 15,
location: { lng: 112.982279, lat: 28.19409 }
},
success: true,
duration: 2800,

@ -5,19 +5,19 @@ const router = express.Router()
let tasks = [
{
id: 1,
title: '北京CBD区域侦察任务',
description: '对北京CBD核心区域进行空中侦察,收集地面目标信息',
title: '长沙市中心区域侦察任务',
description: '对长沙市中心核心区域进行空中侦察,收集地面目标信息',
type: 'reconnaissance',
priority: 'high',
status: 'executing',
assignedDrones: [1, 2],
area: {
name: '北京CBD',
name: '长沙市中心',
boundary: [
[116.42, 39.93],
[116.47, 39.93],
[116.47, 39.89],
[116.42, 39.89]
[112.97, 28.22],
[113.02, 28.22],
[113.02, 28.18],
[112.97, 28.18]
]
},
objectives: [
@ -43,7 +43,7 @@ let tasks = [
assignedDrones: [3],
targetLocation: {
name: '救援点A',
coordinates: [116.35, 39.88],
coordinates: [112.90, 28.13],
description: '临时救援营地'
},
payload: {
@ -68,10 +68,10 @@ let tasks = [
status: 'completed',
assignedDrones: [2],
patrolRoute: [
{ name: '检查点1', coordinates: [116.38, 39.90] },
{ name: '检查点2', coordinates: [116.40, 39.91] },
{ name: '检查点3', coordinates: [116.39, 39.92] },
{ name: '检查点4', coordinates: [116.37, 39.91] }
{ name: '检查点1', coordinates: [112.93, 28.15] },
{ name: '检查点2', coordinates: [112.95, 28.16] },
{ name: '检查点3', coordinates: [112.94, 28.17] },
{ name: '检查点4', coordinates: [112.92, 28.16] }
],
startTime: new Date('2024-01-01T06:00:00'),
endTime: new Date('2024-01-01T08:00:00'),

@ -106,22 +106,36 @@ router.get('/', async (req, res) => {
const [rows] = await db.execute(sql, params)
// 转换数据格式,兼容前端
const threatZones = rows.map(row => ({
id: row.id,
type: row.type,
level: row.level,
description: row.description,
geometry: {
type: row.geometry_type,
...row.geometry_data
},
timeRange: [row.time_start, row.time_end],
status: row.status,
createdAt: row.created_at,
updatedAt: row.updated_at,
typeConfig: threatTypes[row.type],
levelConfig: threatLevels[row.level]
}))
const threatZones = rows.map(row => {
let geometryData = null
try {
if (row.geometry_data) {
geometryData = typeof row.geometry_data === 'string' ? JSON.parse(row.geometry_data) : row.geometry_data
}
} catch (error) {
console.error('解析威胁区几何数据失败:', error, row.geometry_data)
geometryData = null
}
return {
id: row.id,
type: row.type,
level: row.level,
description: row.description,
geometry_type: row.geometry_type,
geometry_data: geometryData,
geometry: geometryData ? {
type: row.geometry_type,
...geometryData
} : null,
timeRange: [row.time_start, row.time_end],
status: row.status,
createdAt: row.created_at,
updatedAt: row.updated_at,
typeConfig: threatTypes[row.type],
levelConfig: threatLevels[row.level]
}
})
res.json({
success: true,

@ -1,7 +1,21 @@
-- ============================================
-- 无人机指挥中心数据库初始化脚本
-- 包含用户认证、威胁区域管理、路径规划等核心功能
-- ============================================
-- 创建数据库
CREATE DATABASE IF NOT EXISTS command_center CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE command_center;
-- 设置字符集
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
SET character_set_connection=utf8mb4;
-- ============================================
-- 1. 用户认证系统
-- ============================================
-- 创建用户表
CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT,
@ -12,33 +26,9 @@ CREATE TABLE IF NOT EXISTS users (
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建无人机表
CREATE TABLE IF NOT EXISTS drones (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
type VARCHAR(50) NOT NULL,
status ENUM('active', 'idle', 'error') NOT NULL DEFAULT 'idle',
latitude DECIMAL(10, 8),
longitude DECIMAL(11, 8),
battery INT DEFAULT 100,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 创建路径规划记录表
CREATE TABLE IF NOT EXISTS path_plans (
id INT PRIMARY KEY AUTO_INCREMENT,
drone_id INT NOT NULL,
start_latitude DECIMAL(10, 8) NOT NULL,
start_longitude DECIMAL(11, 8) NOT NULL,
end_latitude DECIMAL(10, 8) NOT NULL,
end_longitude DECIMAL(11, 8) NOT NULL,
path_points JSON,
status ENUM('pending', 'executing', 'completed', 'failed') NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (drone_id) REFERENCES drones(id)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- ============================================
-- 2. 威胁区域管理系统
-- ============================================
-- 删除现有威胁区表(如果存在)以确保结构正确
DROP TABLE IF EXISTS threat_zones;
@ -57,12 +47,50 @@ CREATE TABLE threat_zones (
created_by INT DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (created_by) REFERENCES users(id),
INDEX idx_type (type),
INDEX idx_level (level),
INDEX idx_status (status)
INDEX idx_status (status),
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- ============================================
-- 3. 路径规划系统
-- ============================================
-- 删除现有路径规划表(如果存在)以确保结构正确
DROP TABLE IF EXISTS path_plans;
-- 创建路径规划表基于最新API结构
CREATE TABLE path_plans (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) DEFAULT NULL COMMENT '路径规划名称',
start_latitude DECIMAL(10, 7) NOT NULL COMMENT '起点纬度',
start_longitude DECIMAL(10, 7) NOT NULL COMMENT '起点经度',
end_latitude DECIMAL(10, 7) NOT NULL COMMENT '终点纬度',
end_longitude DECIMAL(10, 7) NOT NULL COMMENT '终点经度',
path_points JSON NOT NULL COMMENT '路径点数据包含lng, lat, altitude',
algorithm VARCHAR(20) NOT NULL DEFAULT 'astar' COMMENT '使用的规划算法',
distance DECIMAL(10, 2) DEFAULT NULL COMMENT '路径总距离(米)',
estimated_time INT DEFAULT NULL COMMENT '预计时间(分钟)',
flight_altitude INT DEFAULT 100 COMMENT '飞行高度(米)',
flight_speed DECIMAL(5, 2) DEFAULT 10.00 COMMENT '飞行速度(米/秒)',
threat_zones_avoided JSON DEFAULT NULL COMMENT '避开的威胁区ID列表',
threat_zones_passed JSON DEFAULT NULL COMMENT '穿过的威胁区ID列表',
target_order JSON DEFAULT NULL COMMENT '目标点访问顺序',
status ENUM('planned', 'executing', 'completed', 'failed', 'cancelled') NOT NULL DEFAULT 'planned',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
executed_at TIMESTAMP NULL COMMENT '开始执行时间',
completed_at TIMESTAMP NULL COMMENT '完成时间',
INDEX idx_status (status),
INDEX idx_algorithm (algorithm),
INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='路径规划表';
-- ============================================
-- 4. 操作日志系统
-- ============================================
-- 创建操作日志表
CREATE TABLE IF NOT EXISTS operation_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
@ -72,28 +100,120 @@ CREATE TABLE IF NOT EXISTS operation_logs (
target_id INT NOT NULL,
details JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_user_id (user_id),
INDEX idx_action (action),
INDEX idx_target_type (target_type),
INDEX idx_created_at (created_at)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- ============================================
-- 5. 插入初始数据
-- ============================================
-- 插入默认管理员用户
INSERT INTO users (username, password, role)
VALUES ('admin', '$2a$10$X7UrH5QxX5QxX5QxX5QxX.5QxX5QxX5QxX5QxX5QxX5QxX5QxX5Qx', 'admin')
ON DUPLICATE KEY UPDATE username = username;
-- 插入示例威胁区数据
INSERT INTO threat_zones (type, level, description, geometry_type, geometry_data, time_start, time_end, status)
INSERT INTO threat_zones (type, level, description, geometry_type, geometry_data, time_start, time_end, status, created_by)
VALUES
('radar', 'high', 'Enemy radar station', 'circle',
JSON_OBJECT('center', JSON_ARRAY(116.397428, 39.91), 'radius', 2000),
'2024-01-01 00:00:00', '2024-12-31 23:59:59', 'active'),
('missile', 'critical', 'Missile launch site', 'circle',
JSON_OBJECT('center', JSON_ARRAY(116.42, 39.89), 'radius', 3000),
'2024-01-01 00:00:00', '2024-12-31 23:59:59', 'active'),
('aircraft', 'medium', 'Air patrol zone', 'polygon',
JSON_OBJECT('path', JSON_ARRAY(
JSON_ARRAY(116.38, 39.92),
JSON_ARRAY(116.40, 39.92),
JSON_ARRAY(116.40, 39.90),
JSON_ARRAY(116.38, 39.90)
('radar', 'high', 'Radar Station', 'circle',
JSON_OBJECT('center', JSON_OBJECT('lng', 112.982279, 'lat', 28.19409), 'radius', 2000),
'2024-01-01 00:00:00', '2024-12-31 23:59:59', 'active', 1),
('missile', 'critical', 'Missile Site', 'circle',
JSON_OBJECT('center', JSON_OBJECT('lng', 112.992279, 'lat', 28.18409), 'radius', 3000),
'2024-01-01 00:00:00', '2024-12-31 23:59:59', 'active', 1),
('aircraft', 'medium', 'Air Patrol Zone', 'polygon',
JSON_OBJECT('coordinates', JSON_ARRAY(
JSON_OBJECT('lng', 112.970000, 'lat', 28.200000),
JSON_OBJECT('lng', 112.990000, 'lat', 28.200000),
JSON_OBJECT('lng', 112.990000, 'lat', 28.180000),
JSON_OBJECT('lng', 112.970000, 'lat', 28.180000)
)),
'2024-01-01 06:00:00', '2024-01-01 18:00:00', 'active');
'2024-01-01 06:00:00', '2024-01-01 18:00:00', 'active', 1),
('weather', 'low', 'Weather Zone', 'rectangle',
JSON_OBJECT('southwest', JSON_OBJECT('lng', 112.960000, 'lat', 28.170000), 'northeast', JSON_OBJECT('lng', 112.980000, 'lat', 28.190000)),
'2024-01-01 00:00:00', '2024-12-31 23:59:59', 'active', 1),
('ground', 'medium', 'Military Base', 'polygon',
JSON_OBJECT('coordinates', JSON_ARRAY(
JSON_OBJECT('lng', 112.975000, 'lat', 28.195000),
JSON_OBJECT('lng', 112.980000, 'lat', 28.195000),
JSON_OBJECT('lng', 112.980000, 'lat', 28.190000),
JSON_OBJECT('lng', 112.975000, 'lat', 28.190000)
)),
'2024-01-01 00:00:00', '2024-12-31 23:59:59', 'active', 1);
-- 插入示例路径规划数据
INSERT INTO path_plans (
name, start_latitude, start_longitude, end_latitude, end_longitude,
path_points, algorithm, distance, estimated_time, flight_altitude, flight_speed,
threat_zones_avoided, status
) VALUES
(
'长沙市区巡逻路径', 28.194090, 112.982279, 28.200000, 112.990000,
JSON_ARRAY(
JSON_OBJECT('lng', 112.982279, 'lat', 28.194090, 'altitude', 100),
JSON_OBJECT('lng', 112.985000, 'lat', 28.197000, 'altitude', 100),
JSON_OBJECT('lng', 112.990000, 'lat', 28.200000, 'altitude', 100)
),
'astar', 1200.50, 8, 100, 10.0,
JSON_ARRAY(1, 2), 'completed'
),
(
'湘江沿岸监控路径', 28.196000, 112.985000, 28.205000, 112.995000,
JSON_ARRAY(
JSON_OBJECT('lng', 112.985000, 'lat', 28.196000, 'altitude', 120),
JSON_OBJECT('lng', 112.988000, 'lat', 28.199000, 'altitude', 120),
JSON_OBJECT('lng', 112.992000, 'lat', 28.202000, 'altitude', 120),
JSON_OBJECT('lng', 112.995000, 'lat', 28.205000, 'altitude', 120)
),
'astar', 1800.75, 12, 120, 10.0,
JSON_ARRAY(1, 3), 'executing'
),
(
'橘子洲头勘察路径', 28.180000, 112.970000, 28.185000, 112.975000,
JSON_ARRAY(
JSON_OBJECT('lng', 112.970000, 'lat', 28.180000, 'altitude', 50),
JSON_OBJECT('lng', 112.972000, 'lat', 28.182000, 'altitude', 60),
JSON_OBJECT('lng', 112.975000, 'lat', 28.185000, 'altitude', 50)
),
'straight', 800.25, 5, 50, 8.0,
JSON_ARRAY(), 'planned'
);
-- ============================================
-- 6. 数据验证和显示
-- ============================================
-- 验证数据插入
SELECT '=== 数据库初始化完成 ===' as status;
SELECT 'Users count:' as info, COUNT(*) as count FROM users;
SELECT 'Threat zones count:' as info, COUNT(*) as count FROM threat_zones;
SELECT 'Path plans count:' as info, COUNT(*) as count FROM path_plans;
SELECT 'Operation logs count:' as info, COUNT(*) as count FROM operation_logs;
-- 显示威胁区域统计
SELECT
level as '威胁级别',
COUNT(*) as '数量',
GROUP_CONCAT(type SEPARATOR ', ') as '类型'
FROM threat_zones
WHERE status = 'active'
GROUP BY level
ORDER BY FIELD(level, 'low', 'medium', 'high', 'critical');
-- 显示路径规划统计
SELECT
algorithm as '算法',
status as '状态',
COUNT(*) as '数量',
ROUND(AVG(distance), 2) as '平均距离(米)'
FROM path_plans
GROUP BY algorithm, status
ORDER BY algorithm, status;
-- ============================================
-- 初始化完成
-- ============================================

@ -62,7 +62,7 @@ export default {
const store = useStore()
const mapInstance = ref(null)
const mapLoaded = ref(false)
const markers = ref({})
const viewMode = ref('3D')
const currentMapStyle = ref('normal')
const overlayLayers = ref(['buildings'])
@ -128,7 +128,7 @@ export default {
//
mapInstance.value = new AMap.Map('sharedMap', {
zoom: 11,
center: [116.397428, 39.90923],
center: [112.982279, 28.19409], //
viewMode: viewMode.value,
pitch: viewMode.value === '3D' ? 45 : 0,
rotation: 0,
@ -226,8 +226,7 @@ export default {
console.error('图层初始化失败:', layerError);
}
//
fetchDrones()
console.log('地图初始化完成')
})
} catch (error) {
@ -237,98 +236,7 @@ export default {
}
//
const fetchDrones = async () => {
try {
console.log('获取无人机数据...')
await store.dispatch('fetchDrones')
//
console.log('Store状态:', store.state)
//
const drones = store.state.drones
console.log('无人机数据状态:', drones)
if (Array.isArray(drones)) {
initDroneMarkers()
} else {
console.warn('无人机数据格式不正确,从模块中获取')
const dronesFromModule = store.state.drones?.list
console.log('无人机模块数据:', dronesFromModule)
if (Array.isArray(dronesFromModule)) {
// 便
store.commit('SET_DRONES', dronesFromModule)
initDroneMarkers()
}
}
} catch (error) {
console.error('获取无人机数据失败:', error)
}
}
//
const initDroneMarkers = () => {
if (!mapInstance.value || !window.AMap) return
// store
const drones = store.state.drones || []
// drones
if (!Array.isArray(drones)) {
console.warn('无人机数据不是数组格式:', drones)
return //
}
console.log('无人机数据:', drones)
//
drones.forEach(drone => {
if (drone && drone.latitude && drone.longitude) {
addDroneMarker(drone)
}
})
}
//
const addDroneMarker = (drone) => {
if (!mapInstance.value || !window.AMap) return
//
if (markers.value[drone.id]) {
mapInstance.value.remove(markers.value[drone.id])
}
//
let statusColor = '#2ecc71' // 绿 - active
if (drone.status === 'idle') {
statusColor = '#3498db' //
} else if (drone.status === 'error') {
statusColor = '#e74c3c' //
}
//
const marker = new window.AMap.Marker({
position: [drone.longitude, drone.latitude],
icon: new window.AMap.Icon({
size: new window.AMap.Size(40, 40),
image: 'data:image/svg+xml;base64,' + safeBase64Encode(`
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
<circle cx="20" cy="20" r="16" fill="${statusColor}" stroke="#fff" stroke-width="2"/>
<text x="20" y="24" text-anchor="middle" fill="white" font-size="12">${drone.id}</text>
</svg>
`),
imageSize: new window.AMap.Size(40, 40)
}),
anchor: 'center',
zIndex: 100,
angle: drone.heading || 0,
})
//
markers.value[drone.id] = marker
mapInstance.value.add(marker)
}
//
const changeMapStyle = (style) => {

@ -10,8 +10,8 @@
</div>
</template>
<!-- 无人机选择 -->
<div class="section">
<!-- 无人机选择 - 暂时隐藏使用默认无人机 -->
<!-- <div class="section">
<h4>选择无人机</h4>
<el-select
v-model="droneId"
@ -31,11 +31,28 @@
</span>
</el-option>
</el-select>
</div>
</div> -->
<!-- 路径点列表 -->
<div class="section">
<h4>路径点 ({{ pathPoints.length }})</h4>
<h4>路径规划 (1对多)</h4>
<div class="planning-info">
<el-alert
type="info"
:closable="false"
show-icon
style="margin-bottom: 12px;"
>
<template #default>
<div style="font-size: 12px;">
<p> 第1个点为起点其余为目标点</p>
<p> 系统将按最优顺序访问所有目标点</p>
<p> 优先访问安全区域的目标点</p>
</div>
</template>
</el-alert>
</div>
<div class="mode-buttons">
<el-button
:type="addMode ? 'success' : 'default'"
@ -50,7 +67,7 @@
@click="$emit('plan-path')"
:disabled="pathPoints.length < 2"
>
规划路径
A*规划路径
</el-button>
</div>
@ -59,11 +76,19 @@
v-for="(point, index) in pathPoints"
:key="index"
class="path-point-item"
:class="{ 'start-point': index === 0, 'target-point': index > 0 }"
>
<span class="point-index">{{ index + 1 }}</span>
<span class="point-coords">
{{ point.lng.toFixed(4) }}, {{ point.lat.toFixed(4) }}
<span class="point-index" :class="{ 'start': index === 0 }">
{{ index === 0 ? '起' : index }}
</span>
<div class="point-info">
<div class="point-coords">
{{ point.lng.toFixed(4) }}, {{ point.lat.toFixed(4) }}
</div>
<div class="point-type">
{{ index === 0 ? '起点' : `目标点${index}` }}
</div>
</div>
<el-button
type="danger"
size="small"
@ -73,19 +98,32 @@
style="margin-left: auto;"
/>
</div>
<div v-if="pathPoints.length === 0" class="empty-hint">
点击地图添加路径点
</div>
<div v-if="pathPoints.length === 1" class="hint">
再添加至少1个目标点
</div>
<div v-if="pathPoints.length > 1" class="summary">
总计: 1个起点 + {{ pathPoints.length - 1 }}个目标点
</div>
</div>
</div>
<!-- 规划参数 -->
<div class="section">
<h4>规划参数</h4>
<h4>A*算法参数</h4>
<el-form label-width="80px" size="small">
<el-form-item label="算法">
<el-select v-model="algorithm" style="width: 100%" @change="onAlgorithmChange">
<el-option label="A*算法" value="astar" />
<el-option label="RRT算法" value="rrt" />
<el-option label="直线规划" value="straight" />
<el-select v-model="algorithm" style="width: 100%" @change="onAlgorithmChange" disabled>
<el-option label="A*算法 (威胁区避障)" value="astar" />
</el-select>
<div class="algorithm-desc">
智能避开威胁区域优化访问顺序
</div>
</el-form-item>
<el-form-item label="飞行高度">
<el-input-number
@ -96,6 +134,7 @@
style="width: 100%"
@change="onAltitudeChange"
/>
<span class="unit"></span>
</el-form-item>
<el-form-item label="飞行速度">
<el-input-number
@ -106,20 +145,42 @@
style="width: 100%"
@change="onSpeedChange"
/>
<span class="unit">m/s</span>
</el-form-item>
</el-form>
</div>
<!-- 路径信息 -->
<div v-if="pathInfo" class="section">
<h4>路径信息</h4>
<h4>路径规划结果</h4>
<div class="path-info">
<p><strong>总距离:</strong> {{ pathInfo.distance }}</p>
<p><strong>预计时间:</strong> {{ pathInfo.duration }}分钟</p>
<p><strong>路径点数:</strong> {{ pathInfo.pointCount }}</p>
<div class="info-item">
<span class="label">算法:</span>
<span class="value">{{ getAlgorithmName(pathInfo.algorithm) }}</span>
</div>
<div class="info-item">
<span class="label">总距离:</span>
<span class="value">{{ (pathInfo.distance / 1000).toFixed(2) }} km</span>
</div>
<div class="info-item">
<span class="label">预计时间:</span>
<span class="value">{{ pathInfo.duration }} 分钟</span>
</div>
<div class="info-item">
<span class="label">路径点数:</span>
<span class="value">{{ pathInfo.pointCount }} </span>
</div>
<div v-if="pathInfo.threatZonesAvoided && pathInfo.threatZonesAvoided.length > 0" class="info-item threat-avoided">
<span class="label">避开威胁:</span>
<span class="value">{{ pathInfo.threatZonesAvoided.length }} 个区域</span>
</div>
<div v-if="pathInfo.targetOrder && pathInfo.targetOrder.length > 0" class="info-item">
<span class="label">访问顺序:</span>
<span class="value">已优化</span>
</div>
</div>
<el-button type="success" @click="$emit('execute-path')" style="width: 100%">
执行路径
<el-button type="success" @click="$emit('execute-path')" style="width: 100%; margin-top: 12px;">
执行路径规划
</el-button>
</div>
@ -186,7 +247,7 @@ export default {
},
planningAlgorithm: {
type: String,
default: 'straight'
default: 'astar'
},
flightAltitude: {
type: Number,
@ -251,6 +312,17 @@ export default {
emit('update-flight-speed', value)
}
//
const getAlgorithmName = (algorithm) => {
const algorithmNames = {
astar: 'A*算法',
rrt: 'RRT算法',
genetic: '遗传算法',
straight: '直线规划'
}
return algorithmNames[algorithm] || algorithm
}
return {
droneId,
algorithm,
@ -259,7 +331,8 @@ export default {
onSelectDrone,
onAlgorithmChange,
onAltitudeChange,
onSpeedChange
onSpeedChange,
getAlgorithmName
}
}
}
@ -301,28 +374,67 @@ h4 {
.path-point-item {
display: flex;
align-items: center;
padding: 5px;
margin-bottom: 5px;
background-color: #f5f7fa;
border-radius: 4px;
padding: 8px;
margin-bottom: 6px;
background: #f9f9f9;
border-radius: 6px;
border-left: 3px solid #e6e6e6;
transition: all 0.3s ease;
}
.path-point-item:hover {
background: #f0f0f0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.path-point-item.start-point {
border-left-color: #1890FF;
background: linear-gradient(90deg, #e6f7ff 0%, #f9f9f9 100%);
}
.path-point-item.target-point {
border-left-color: #52C41A;
background: linear-gradient(90deg, #f6ffed 0%, #f9f9f9 100%);
}
.point-index {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background-color: #409EFF;
color: white;
width: 24px;
height: 24px;
border-radius: 50%;
margin-right: 10px;
background: #52C41A;
color: white;
font-size: 12px;
font-weight: bold;
margin-right: 10px;
flex-shrink: 0;
}
.point-index.start {
background: #1890FF;
width: 28px;
height: 28px;
font-size: 14px;
}
.point-info {
flex: 1;
min-width: 0;
}
.point-coords {
flex-grow: 1;
font-size: 12px;
font-size: 13px;
font-family: monospace;
color: #333;
font-weight: 500;
}
.point-type {
font-size: 11px;
color: #666;
margin-top: 2px;
}
.path-info {
@ -341,4 +453,55 @@ h4 {
display: flex;
justify-content: space-between;
}
.planning-info {
margin-bottom: 12px;
}
.algorithm-desc {
font-size: 12px;
color: #909399;
}
.unit {
margin-left: 10px;
font-size: 12px;
color: #909399;
}
.info-item {
margin-bottom: 5px;
}
.label {
font-size: 12px;
color: #909399;
}
.value {
font-size: 14px;
font-weight: 600;
}
.empty-hint {
text-align: center;
color: #909399;
font-size: 12px;
}
.hint {
text-align: center;
color: #909399;
font-size: 12px;
}
.summary {
text-align: center;
color: #909399;
font-size: 12px;
}
.threat-avoided {
color: #f56c6c;
}
</style>

@ -46,100 +46,46 @@ const auth = {
}
}
const drones = {
namespaced: true,
state: () => ({
list: []
}),
mutations: {
setDrones(state, drones) {
state.list = drones
}
},
actions: {
async fetchDrones({ commit, rootState }) {
// 这里应调用后端API获取无人机列表
const token = rootState.auth.token
try {
const res = await axios.get(`${API_URL}/api/drones`, {
headers: { Authorization: `Bearer ${token}` }
})
commit('setDrones', res.data.data || [])
} catch {
commit('setDrones', [])
}
}
}
}
export default createStore({
state: {
// 根级别的无人机状态用于兼容MapView
drones: []
// 威胁区域数据
threatZones: [],
// 路径规划数据
pathPlans: []
},
mutations: {
// 根级别的无人机mutations
SET_DRONES(state, drones) {
state.drones = drones
SET_THREAT_ZONES(state, zones) {
state.threatZones = zones
},
setDrones(state, drones) {
state.drones = drones
SET_PATH_PLANS(state, plans) {
state.pathPlans = plans
}
},
actions: {
// 根级别的无人机actions
async fetchDrones({ commit, state }) {
async fetchThreatZones({ commit }) {
try {
const token = state.auth?.token || localStorage.getItem('token')
const headers = {}
if (token) {
headers['Authorization'] = `Bearer ${token}`
const res = await axios.get(`${API_URL}/api/threat-zones`)
if (res.data && res.data.success) {
commit('SET_THREAT_ZONES', res.data.data)
}
const res = await axios.get(`${API_URL}/api/drones`, { headers })
if (res.data && res.data.success && Array.isArray(res.data.data)) {
commit('SET_DRONES', res.data.data)
} else if (res.data && Array.isArray(res.data)) {
commit('SET_DRONES', res.data)
} else {
throw new Error('无效的数据格式')
} catch (error) {
console.error('获取威胁区域数据失败:', error)
commit('SET_THREAT_ZONES', [])
}
},
async fetchPathPlans({ commit }) {
try {
const res = await axios.get(`${API_URL}/api/path-planning`)
if (res.data && res.data.success) {
commit('SET_PATH_PLANS', res.data.data)
}
} catch (error) {
console.warn('获取无人机数据失败,使用模拟数据:', error)
// 使用模拟数据
const mockDrones = [
{
id: 1,
name: '侦察无人机-01',
latitude: 39.9042,
longitude: 116.4074,
status: 'active',
battery: 85
},
{
id: 2,
name: '运输无人机-02',
latitude: 39.9100,
longitude: 116.4200,
status: 'idle',
battery: 60
},
{
id: 3,
name: '攻击无人机-03',
latitude: 39.8950,
longitude: 116.3900,
status: 'active',
battery: 95
}
]
commit('SET_DRONES', mockDrones)
console.error('获取路径规划数据失败:', error)
commit('SET_PATH_PLANS', [])
}
}
},
modules: {
auth,
drones
auth
}
})

@ -13,10 +13,6 @@
@clear-targets="clearTargets"
:add-mode="addMode"
:target-points="targetPoints"
:show-drones="showDrones"
:show-drone-info="showDroneInfo"
@toggle-drone-visibility="toggleDroneVisibility"
@toggle-drone-info="toggleDroneInfo"
/>
</div>
</el-tab-pane>
@ -55,8 +51,6 @@
<el-tab-pane label="路径规划" name="pathPlanning">
<div class="panel-scroll-container">
<path-planning-panel
:drones="drones"
:selected-drone-id="selectedDroneId"
:path-points="pathPoints"
:add-mode="addMode"
:planning-algorithm="planningAlgorithm"
@ -71,7 +65,6 @@
@center-to-start="centerToStart"
@center-to-end="centerToEnd"
@fit-to-path="fitToPath"
@select-drone="selectDrone"
@update-planning-algorithm="updatePlanningAlgorithm"
@update-flight-altitude="updateFlightAltitude"
@update-flight-speed="updateFlightSpeed"
@ -180,8 +173,6 @@ export default {
//
const addMode = ref(false)
const showDrones = ref(true)
const showDroneInfo = ref(false)
//
const targetPoints = ref([])
@ -209,19 +200,16 @@ export default {
})
//
const drones = computed(() => store.state.drones || [])
const selectedDroneId = ref(null)
const pathPoints = ref([])
const pathMarkers = ref([])
const pathLine = ref(null)
const planningAlgorithm = ref('straight')
const planningAlgorithm = ref('astar')
const flightAltitude = ref(100)
const flightSpeed = ref(10)
const pathInfo = ref(null)
//
const mouseTool = ref(null)
const droneMarkers = ref({})
// base64
const safeBase64Encode = (str) => {
@ -253,7 +241,7 @@ export default {
//
const onMapLoaded = (map) => {
mapInstance.value = map
console.log('统一地图组件加载完成')
console.log('地图加载完成,开始自动加载数据...')
//
const AMap = window.AMap
@ -267,77 +255,22 @@ export default {
}
}
//
initActivePanelFeatures()
//
initDroneMarkers()
//
loadAllData()
}
//
const initActivePanelFeatures = () => {
switch (activePanel.value) {
case 'basicMap':
//
break
case 'threatZone':
//
loadThreatZones()
break
case 'pathPlanning':
//
if (drones.value.length > 0 && !selectedDroneId.value) {
selectedDroneId.value = drones.value[0].id
}
break
}
}
//
const addTestThreatZone = () => {
if (!mapInstance.value) return
//
const loadAllData = async () => {
try {
const AMap = window.AMap
//
const testCircle = new AMap.Circle({
center: [116.397428, 39.90923], //
radius: 1000,
strokeColor: '#FF6B6B',
strokeWeight: 2,
strokeOpacity: 0.8,
fillColor: '#FF6B6B',
fillOpacity: 0.3
})
mapInstance.value.add(testCircle)
console.log('开始自动加载所有数据...')
//
const testZone = {
id: 'test-' + Date.now(),
type: 'radar',
level: 'medium',
description: '测试威胁区',
timeRange: [new Date(), new Date(Date.now() + 24 * 60 * 60 * 1000)],
geometry: {
type: 'circle',
center: [116.397428, 39.90923],
radius: 1000
},
overlay: testCircle
}
threatZones.value.push(testZone)
zoneOverlays[testZone.id] = testCircle
//
await loadThreatZones()
testCircle.on('click', () => selectZone(testZone.id))
console.log('已添加测试威胁区:', testZone)
ElMessage.success('已添加测试威胁区(红色圆形)')
console.log('所有数据加载完成')
} catch (error) {
console.error('添加测试威胁区失败:', error)
console.error('自动加载数据失败:', error)
}
}
@ -355,6 +288,22 @@ export default {
// mouseTool
}
//
const initActivePanelFeatures = () => {
switch (activePanel.value) {
case 'basicMap':
//
break
case 'threatZone':
//
loadThreatZones()
break
case 'pathPlanning':
//
break
}
}
//
const handleTabClick = () => {
//
@ -384,144 +333,9 @@ export default {
}
}
//
const initDroneMarkers = async () => {
if (!mapInstance.value) return
try {
//
await store.dispatch('fetchDrones')
const AMap = window.AMap
if (!AMap || !drones.value || drones.value.length === 0) {
console.log('跳过无人机标记初始化:条件不满足')
return
}
//
Object.values(droneMarkers.value).forEach(marker => {
mapInstance.value.remove(marker)
})
droneMarkers.value = {}
drones.value.forEach(drone => {
if (!drone || typeof drone.longitude !== 'number' || typeof drone.latitude !== 'number') {
console.warn('无效的无人机数据:', drone)
return
}
try {
const marker = new AMap.Marker({
position: [drone.longitude, drone.latitude],
title: drone.name || `无人机-${drone.id}`,
icon: new AMap.Icon({
size: new AMap.Size(32, 32),
image: getDroneIconSvg(drone.status),
imageSize: new AMap.Size(32, 32)
})
})
//
const infoWindow = new AMap.InfoWindow({
isCustom: true,
content: `
<div style="padding: 10px; background: white; border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
<h4 style="margin: 0 0 8px 0; color: #333;">${drone.name || `无人机-${drone.id}`}</h4>
<p style="margin: 4px 0; color: #666;">状态: ${getStatusText(drone.status)}</p>
<p style="margin: 4px 0; color: #666;">电量: ${drone.battery || 0}%</p>
<p style="margin: 4px 0; color: #666;">位置: ${drone.longitude.toFixed(6)}, ${drone.latitude.toFixed(6)}</p>
</div>
`,
offset: new AMap.Pixel(16, -45)
})
marker.on('click', () => {
try {
infoWindow.open(mapInstance.value, marker.getPosition())
} catch (infoError) {
console.warn('信息窗体打开失败:', infoError)
}
})
droneMarkers.value[drone.id] = marker
if (showDrones.value) {
mapInstance.value.add(marker)
}
} catch (markerError) {
console.warn(`无人机 ${drone.id} 标记创建失败:`, markerError)
}
})
} catch (error) {
console.warn('无人机标记初始化出错:', error)
// 使
const sampleDrones = [
{
id: 1,
name: '侦察无人机-01',
latitude: 39.9042,
longitude: 116.4074,
status: 'active',
battery: 85
},
{
id: 2,
name: '运输无人机-02',
latitude: 39.9100,
longitude: 116.4200,
status: 'idle',
battery: 60
}
]
store.commit('setDrones', sampleDrones)
}
}
const getDroneIconSvg = (status) => {
const colors = {
active: '#52C41A',
idle: '#1890FF',
error: '#FF4D4F'
}
const color = colors[status] || colors.idle
const svgString = `
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<circle cx="16" cy="16" r="12" fill="${color}" stroke="#fff" stroke-width="2"/>
<circle cx="16" cy="16" r="6" fill="#fff"/>
</svg>
`
return 'data:image/svg+xml;base64,' + safeBase64Encode(svgString)
}
const getStatusText = (status) => {
const statusMap = {
active: '活动中',
idle: '待机',
error: '故障'
}
return statusMap[status] || status
}
//
//
const toggleDroneVisibility = (value) => {
showDrones.value = value
Object.values(droneMarkers.value).forEach(marker => {
if (value) {
mapInstance.value.add(marker)
} else {
mapInstance.value.remove(marker)
}
})
}
//
const toggleDroneInfo = (value) => {
showDroneInfo.value = value
//
}
//
const getAddModeTip = () => {
@ -1305,7 +1119,7 @@ export default {
pathMarkers.value = []
}
//
// -
const planPath = async () => {
if (pathPoints.value.length < 2) {
ElMessage.warning('至少需要2个路径点')
@ -1313,20 +1127,265 @@ export default {
}
try {
//
const distance = calculateDistance()
const duration = Math.ceil(distance / (flightSpeed.value * 1000 / 60)) //
console.log('开始多目标路径规划...')
console.log('算法:', planningAlgorithm.value)
console.log('路径点数量:', pathPoints.value.length)
console.log('威胁区:', threatZones.value)
const startPoint = pathPoints.value[0]
const targetPoints = pathPoints.value.slice(1) //
// -
const planningData = {
startPoint: {
lng: startPoint.lng,
lat: startPoint.lat
},
targetPoints: targetPoints.map(point => ({
lng: point.lng,
lat: point.lat
})),
algorithm: planningAlgorithm.value || 'astar', // 使A*
flightAltitude: flightAltitude.value,
flightSpeed: flightSpeed.value,
threatZones: threatZones.value && threatZones.value.length > 0 ? threatZones.value.map(zone => ({
id: zone.id,
type: zone.type,
level: zone.level,
geometry_type: zone.geometry?.type || zone.geometry_type,
geometry_data: zone.geometry || zone.geometry_data,
description: zone.description
})) : []
}
console.log('发送多目标路径规划请求:', planningData)
//
const response = await fetch('/api/path-planning', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(planningData)
})
if (!response.ok) {
const errorText = await response.text()
console.error('后端错误响应:', {
status: response.status,
statusText: response.statusText,
error: errorText
})
throw new Error(`路径规划失败: ${response.status} - ${errorText}`)
}
const result = await response.json()
console.log('路径规划结果:', result)
pathInfo.value = {
distance: Math.round(distance),
duration: duration,
pointCount: pathPoints.value.length
if (result.success) {
const { data, details } = result
//
pathInfo.value = {
distance: Math.round(data.distance || details?.totalDistance || 0),
duration: data.estimated_time || details?.estimatedTime || 0,
pointCount: data.path_points?.length || details?.waypointCount || 0,
algorithm: data.algorithm || result.algorithm,
threatZonesAvoided: data.threat_zones_avoided || details?.threatZonesAvoided || [],
targetOrder: details?.targetOrder || []
}
//
clearPathDisplay()
//
const pathData = data.path_points || data.path || []
renderPlannedPathWithTargets(pathData, targetPoints, details?.targetOrder)
ElMessage.success(`${getAlgorithmName(result.algorithm)}多目标路径规划完成!`)
const threatCount = data.threat_zones_avoided?.length || details?.threatZonesAvoided?.length || 0
if (threatCount > 0) {
ElMessage.info(`成功避开 ${threatCount} 个威胁区`)
}
// 访
if (details?.targetOrder && details.targetOrder.length > 0) {
const orderInfo = details.targetOrder.map((target, index) =>
`${index + 1}. ${target.coords.lng.toFixed(4)}, ${target.coords.lat.toFixed(4)}${target.inThreatZone ? ' (威胁区内)' : ''}`
).join('\n')
setTimeout(() => {
ElMessage({
message: `目标点访问顺序:\n${orderInfo}`,
type: 'info',
duration: 10000,
showClose: true
})
}, 1000)
}
} else {
throw new Error(result.message || '路径规划失败')
}
ElMessage.success('路径规划完成')
} catch (error) {
ElMessage.error('路径规划失败')
console.error('路径规划失败:', error)
ElMessage.error('路径规划失败: ' + error.message)
}
}
//
const getAlgorithmName = (algorithm) => {
const algorithmNames = {
astar: 'A*算法',
rrt: 'RRT算法',
genetic: '遗传算法',
straight: '直线规划'
}
return algorithmNames[algorithm] || algorithm
}
// -
const renderPlannedPathWithTargets = (plannedPath, originalTargets, targetOrder) => {
if (!mapInstance.value || !plannedPath || plannedPath.length < 2) return
const AMap = window.AMap
console.log('渲染多目标路径:', {
pathLength: plannedPath.length,
targetCount: originalTargets.length,
targetOrder: targetOrder
})
// 线
const pathCoords = plannedPath.map(point => [point.lng, point.lat])
pathLine.value = new AMap.Polyline({
path: pathCoords,
strokeColor: '#52C41A', // 绿
strokeWeight: 4,
strokeStyle: 'solid',
strokeOpacity: 0.8
})
mapInstance.value.add(pathLine.value)
const markers = []
//
const startMarker = new AMap.Marker({
position: [plannedPath[0].lng, plannedPath[0].lat],
icon: new AMap.Icon({
size: new AMap.Size(35, 35),
image: 'data:image/svg+xml;base64,' + safeBase64Encode(`
<svg xmlns="http://www.w3.org/2000/svg" width="35" height="35" viewBox="0 0 35 35">
<circle cx="17.5" cy="17.5" r="15" fill="#1890FF" stroke="#fff" stroke-width="3"/>
<text x="17.5" y="22" text-anchor="middle" fill="white" font-size="10" font-weight="bold"></text>
</svg>
`),
imageSize: new AMap.Size(35, 35)
}),
title: '起点',
zIndex: 200
})
mapInstance.value.add(startMarker)
markers.push(startMarker)
// 访
if (targetOrder && targetOrder.length > 0) {
targetOrder.forEach((target, index) => {
const visitOrder = index + 1
const isInThreat = target.inThreatZone
const threatLevel = target.threatLevel
//
let color = '#52C41A' // 绿
if (isInThreat) {
switch (threatLevel) {
case 'low':
color = '#FAAD14' //
break
case 'medium':
color = '#FA8C16' //
break
case 'high':
color = '#F5222D' //
break
case 'critical':
color = '#722ED1' //
break
}
}
const targetMarker = new AMap.Marker({
position: [target.coords.lng, target.coords.lat],
icon: new AMap.Icon({
size: new AMap.Size(32, 32),
image: 'data:image/svg+xml;base64,' + safeBase64Encode(`
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<circle cx="16" cy="16" r="13" fill="${color}" stroke="#fff" stroke-width="2"/>
<text x="16" y="20" text-anchor="middle" fill="white" font-size="9" font-weight="bold">${visitOrder}</text>
${isInThreat ? '<circle cx="23" cy="9" r="4" fill="#F5222D" stroke="#fff" stroke-width="1"/>' : ''}
</svg>
`),
imageSize: new AMap.Size(32, 32)
}),
title: `目标点${visitOrder}${isInThreat ? ` (${threatLevel}威胁区)` : ' (安全区域)'}`,
zIndex: 150
})
mapInstance.value.add(targetMarker)
markers.push(targetMarker)
})
} else {
//
originalTargets.forEach((target, index) => {
const targetNumber = index + 1
const isLastTarget = index === originalTargets.length - 1
const targetMarker = new AMap.Marker({
position: [target.lng, target.lat],
icon: new AMap.Icon({
size: new AMap.Size(30, 30),
image: 'data:image/svg+xml;base64,' + safeBase64Encode(`
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30">
<circle cx="15" cy="15" r="12" fill="${isLastTarget ? '#F5222D' : '#52C41A'}" stroke="#fff" stroke-width="2"/>
<text x="15" y="19" text-anchor="middle" fill="white" font-size="8" font-weight="bold">${isLastTarget ? '终' : targetNumber}</text>
</svg>
`),
imageSize: new AMap.Size(30, 30)
}),
title: isLastTarget ? '终点' : `目标点${targetNumber}`,
zIndex: 150
})
mapInstance.value.add(targetMarker)
markers.push(targetMarker)
})
}
pathMarkers.value = markers
//
setTimeout(() => {
if (pathLine.value) {
mapInstance.value.setFitView([pathLine.value], false, [50, 50, 50, 50]) //
}
}, 100)
}
//
const clearPathDisplay = () => {
// 线
if (pathLine.value) {
mapInstance.value.remove(pathLine.value)
pathLine.value = null
}
//
clearPathMarkers()
}
const calculateDistance = () => {
@ -1356,11 +1415,6 @@ export default {
//
const executePath = async () => {
if (!selectedDroneId.value) {
ElMessage.warning('请选择执行路径的无人机')
return
}
try {
await ElMessageBox.confirm('确定要执行此路径吗?', '确认执行', {
confirmButtonText: '确定',
@ -1390,14 +1444,7 @@ export default {
ElMessage.success('已清除所有路径点')
}
//
const selectDrone = (id) => {
selectedDroneId.value = id
const drone = drones.value.find(d => d.id === id)
if (drone) {
ElMessage.success(`已选择无人机: ${drone.name}`)
}
}
//
const updatePlanningAlgorithm = (algorithm) => {
@ -1434,18 +1481,68 @@ export default {
//
const fitToPath = () => {
if (pathPoints.value.length === 0) return
console.log('fitToPath called, pathPoints length:', pathPoints.value.length)
const bounds = new (window.AMap.Bounds)()
pathPoints.value.forEach(point => {
bounds.extend([point.lng, point.lat])
})
if (!pathPoints.value || pathPoints.value.length === 0) {
console.warn('没有路径点数据')
ElMessage.warning('没有路径点可显示')
return
}
mapInstance.value.setBounds(bounds)
try {
//
if (!mapInstance.value) {
console.error('地图实例不存在')
ElMessage.error('地图未初始化')
return
}
// AMap
if (!window.AMap || !window.AMap.Bounds) {
console.error('AMap Bounds API不可用')
ElMessage.error('地图API未加载完成')
return
}
console.log('路径点数据:', pathPoints.value)
//
const bounds = new window.AMap.Bounds()
//
let validPointsCount = 0
pathPoints.value.forEach((point, index) => {
if (point && typeof point.lng === 'number' && typeof point.lat === 'number') {
bounds.extend([point.lng, point.lat])
validPointsCount++
console.log(`添加路径点${index + 1}: [${point.lng}, ${point.lat}]`)
} else {
console.warn(`无效的路径点${index + 1}:`, point)
}
})
if (validPointsCount === 0) {
console.error('没有有效的路径点')
ElMessage.error('路径点数据无效')
return
}
//
mapInstance.value.setBounds(bounds, false, [20, 20, 20, 20]) //
console.log('成功适配路径边界,包含', validPointsCount, '个有效点')
ElMessage.success(`已显示包含${validPointsCount}个点的完整路径`)
} catch (error) {
console.error('fitToPath执行错误:', error)
ElMessage.error('显示完整路径失败: ' + error.message)
}
}
onMounted(() => {
//
console.log('UnifiedMapView mounted, 准备加载数据...')
//
})
return {
@ -1453,8 +1550,6 @@ export default {
activePanel,
addMode,
targetPoints,
showDrones,
showDroneInfo,
threatZones,
selectedZoneId,
currentThreatType,
@ -1466,8 +1561,6 @@ export default {
visibleLayers,
editDialogVisible,
editForm,
drones,
selectedDroneId,
pathPoints,
planningAlgorithm,
flightAltitude,
@ -1479,8 +1572,6 @@ export default {
onMapClick,
toggleAddMode,
clearTargets,
toggleDroneVisibility,
toggleDroneInfo,
getAddModeTip,
getDrawTip,
handleThreatTypeChange,
@ -1500,7 +1591,6 @@ export default {
planPath,
executePath,
clearAll,
selectDrone,
updatePlanningAlgorithm,
updateFlightAltitude,
updateFlightSpeed,

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save