|
|
|
@ -26,98 +26,79 @@ |
|
|
|
新建方案 |
|
|
|
</el-button> |
|
|
|
</div> |
|
|
|
<div class="route-list"> |
|
|
|
<div class="tree-list"> |
|
|
|
<!-- 方案列表 --> |
|
|
|
<div |
|
|
|
v-for="plan in plans" |
|
|
|
:key="plan.id" |
|
|
|
class="route-item" |
|
|
|
class="tree-item plan-item" |
|
|
|
:class="{ selected: selectedPlanId === plan.id }" |
|
|
|
@click="handleSelectPlan(plan)" |
|
|
|
> |
|
|
|
<i class="el-icon-folder-opened"></i> |
|
|
|
<div class="route-info"> |
|
|
|
<div class="route-name">{{ plan.name }}</div> |
|
|
|
<div class="route-meta">{{ plan.routes.length }}个航线</div> |
|
|
|
</div> |
|
|
|
<div class="route-actions"> |
|
|
|
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenPlanDialog(plan)"></i> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-if="selectedPlanDetails" class="section"> |
|
|
|
<div class="section-header"> |
|
|
|
<div class="section-title">航线列表</div> |
|
|
|
<el-button |
|
|
|
type="primary" |
|
|
|
size="mini" |
|
|
|
@click="handleCreateRoute" |
|
|
|
class="create-route-btn-new" |
|
|
|
> |
|
|
|
新建航线 |
|
|
|
</el-button> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="route-list"> |
|
|
|
<div |
|
|
|
v-for="route in routes" |
|
|
|
:key="route.id" |
|
|
|
class="route-item" |
|
|
|
:class="{ 'active': activeRouteIds.includes(route.id) }" |
|
|
|
@click="$emit('select-route', route)" |
|
|
|
> |
|
|
|
<i class="el-icon-map-location"></i> |
|
|
|
<div class="route-info"> |
|
|
|
<div class="route-name">{{ route.name }}</div> |
|
|
|
<div class="route-meta">{{ route.points }}个航点</div> |
|
|
|
</div> |
|
|
|
<el-tag |
|
|
|
v-if="route.conflict" |
|
|
|
size="mini" |
|
|
|
type="danger" |
|
|
|
class="conflict-tag" |
|
|
|
> |
|
|
|
冲突 |
|
|
|
</el-tag> |
|
|
|
<div class="route-actions"> |
|
|
|
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenRouteDialog(route)"></i> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-if="selectedRouteDetails" class="section"> |
|
|
|
<div class="section-title">航点列表</div> |
|
|
|
<div class="waypoint-list"> |
|
|
|
<div |
|
|
|
v-for="point in selectedRouteDetails.waypoints" |
|
|
|
:key="point.name" |
|
|
|
class="waypoint-item" |
|
|
|
> |
|
|
|
<i class="el-icon-location"></i> |
|
|
|
<div class="waypoint-info"> |
|
|
|
<div class="waypoint-name">{{ point.name }}</div> |
|
|
|
<div class="waypoint-meta">高度: {{ point.altitude }}m | 速度: {{ point.speed }}</div> |
|
|
|
<div class="tree-item-header" @click="togglePlan(plan.id)"> |
|
|
|
<i :class="expandedPlans.includes(plan.id) ? 'el-icon-folder-opened' : 'el-icon-folder'" class="tree-icon"></i> |
|
|
|
<div class="tree-item-info"> |
|
|
|
<div class="tree-item-name">{{ plan.name }}</div> |
|
|
|
<div class="tree-item-meta">{{ routes.filter(r => r.scenarioId === plan.id).length }}个航线</div> |
|
|
|
</div> |
|
|
|
<div class="tree-item-actions"> |
|
|
|
<i class="el-icon-plus" title="新建航线" @click.stop="handleCreateRouteForPlan(plan)"></i> |
|
|
|
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenPlanDialog(plan)"></i> |
|
|
|
<i class="el-icon-delete" title="删除" @click.stop="handleDeletePlan(plan)"></i> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="waypoint-actions"> |
|
|
|
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenWaypointDialog(point)"></i> |
|
|
|
<i class="el-icon-delete" title="删除"></i> |
|
|
|
<!-- 航线列表 --> |
|
|
|
<div v-if="expandedPlans.includes(plan.id)" class="tree-children route-children"> |
|
|
|
<div |
|
|
|
v-for="route in routes.filter(r => r.scenarioId === plan.id)" |
|
|
|
:key="route.id" |
|
|
|
class="tree-item route-item" |
|
|
|
:class="getRouteClasses(route.id)" |
|
|
|
> |
|
|
|
<div class="tree-item-header" @click="toggleRoute(route.id)"> |
|
|
|
<i :class="expandedRoutes.includes(route.id) ? 'el-icon-map-location' : 'el-icon-map-location'" class="tree-icon"></i> |
|
|
|
<div class="tree-item-info"> |
|
|
|
<div class="tree-item-name">{{ route.name }}</div> |
|
|
|
<div class="tree-item-meta">{{ route.points }}个航点</div> |
|
|
|
</div> |
|
|
|
<el-tag |
|
|
|
v-if="route.conflict" |
|
|
|
size="mini" |
|
|
|
type="danger" |
|
|
|
class="conflict-tag" |
|
|
|
> |
|
|
|
冲突 |
|
|
|
</el-tag> |
|
|
|
<div class="tree-item-actions"> |
|
|
|
<i class="el-icon-view" title="显示/隐藏" @click.stop="handleToggleRouteVisibility(route)"></i> |
|
|
|
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenRouteDialog(route)"></i> |
|
|
|
<i class="el-icon-delete" title="删除" @click.stop="handleDeleteRoute(route)"></i> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<!-- 航点列表 --> |
|
|
|
<div v-if="expandedRoutes.includes(route.id)" class="tree-children waypoint-children"> |
|
|
|
<div |
|
|
|
v-for="point in (expandedRoutes.includes(route.id) && route.waypoints ? route.waypoints : [])" |
|
|
|
:key="point.name" |
|
|
|
class="tree-item waypoint-item" |
|
|
|
> |
|
|
|
<div class="tree-item-header"> |
|
|
|
<i class="el-icon-location tree-icon"></i> |
|
|
|
<div class="tree-item-info"> |
|
|
|
<div class="tree-item-name">{{ point.name }}</div> |
|
|
|
<div class="tree-item-meta">高度: {{ point.altitude }}m | 速度: {{ point.speed }}</div> |
|
|
|
</div> |
|
|
|
<div class="tree-item-actions"> |
|
|
|
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenWaypointDialog(point)"></i> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="action-buttons"> |
|
|
|
<el-button type="primary" size="mini" icon="el-icon-circle-plus" class="blue-btn" @click="handleAddWaypoint"> |
|
|
|
添加航点 |
|
|
|
</el-button> |
|
|
|
<el-button size="mini" class="blue-btn" @click="handleCancelRoute"> |
|
|
|
取消 |
|
|
|
</el-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-if="activeTab === 'conflict'" class="tab-content conflict-content"> |
|
|
|
<div v-if="conflicts.length > 0" class="conflict-list"> |
|
|
|
<div |
|
|
|
@ -162,7 +143,6 @@ |
|
|
|
</el-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-if="activeTab === 'platform'" class="tab-content platform-content"> |
|
|
|
<div class="platform-categories"> |
|
|
|
<el-tabs v-model="activePlatformTab" type="card" size="mini" class="blue-tabs"> |
|
|
|
@ -187,7 +167,6 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</el-tab-pane> |
|
|
|
|
|
|
|
<el-tab-pane label="海上" name="sea"> |
|
|
|
<div class="platform-list"> |
|
|
|
<div |
|
|
|
@ -209,7 +188,6 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</el-tab-pane> |
|
|
|
|
|
|
|
<el-tab-pane label="地面" name="ground"> |
|
|
|
<div class="platform-list"> |
|
|
|
<div |
|
|
|
@ -301,10 +279,82 @@ export default { |
|
|
|
}, |
|
|
|
data() { |
|
|
|
return { |
|
|
|
activePlatformTab: 'air' |
|
|
|
activePlatformTab: 'air', |
|
|
|
expandedPlans: [], // 展开的方案列表 |
|
|
|
expandedRoutes: [] // 展开的航线列表 |
|
|
|
} |
|
|
|
}, |
|
|
|
watch: { |
|
|
|
activeRouteIds: { |
|
|
|
handler(newVal) { |
|
|
|
console.log('activeRouteIds updated:', newVal); |
|
|
|
}, |
|
|
|
deep: true |
|
|
|
} |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
// 切换方案展开/折叠 |
|
|
|
togglePlan(planId) { |
|
|
|
const index = this.expandedPlans.indexOf(planId) |
|
|
|
if (index > -1) { |
|
|
|
// 折叠方案时,取消选中该方案的所有航线 |
|
|
|
this.expandedPlans.splice(index, 1) |
|
|
|
const planRoutes = this.routes.filter(r => r.scenarioId === planId) |
|
|
|
planRoutes.forEach(route => { |
|
|
|
if (this.activeRouteIds.includes(route.id)) { |
|
|
|
// 触发航线隐藏 |
|
|
|
this.handleToggleRouteVisibility(route) |
|
|
|
} |
|
|
|
}) |
|
|
|
// 折叠方案时,取消选中方案 |
|
|
|
this.$emit('select-plan', { id: null }) |
|
|
|
} else { |
|
|
|
// 展开方案时,选中该方案 |
|
|
|
this.expandedPlans.push(planId) |
|
|
|
const plan = this.plans.find(p => p.id === planId) |
|
|
|
if (plan) { |
|
|
|
this.$emit('select-plan', plan) |
|
|
|
} |
|
|
|
// 选中该方案的所有航线 |
|
|
|
const planRoutes = this.routes.filter(r => r.scenarioId === planId) |
|
|
|
planRoutes.forEach(route => { |
|
|
|
if (!this.activeRouteIds.includes(route.id)) { |
|
|
|
this.handleSelectRoute(route) |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 切换航线展开/折叠 |
|
|
|
toggleRoute(routeId) { |
|
|
|
const route = this.routes.find(r => r.id === routeId) |
|
|
|
if (!route) return |
|
|
|
|
|
|
|
const isRouteSelected = this.activeRouteIds.includes(routeId) |
|
|
|
const isRouteExpanded = this.expandedRoutes.includes(routeId) |
|
|
|
|
|
|
|
if (isRouteSelected) { |
|
|
|
// 航线已选中 |
|
|
|
if (isRouteExpanded) { |
|
|
|
// 航线已展开,点击则收回航点 |
|
|
|
const index = this.expandedRoutes.indexOf(routeId) |
|
|
|
this.expandedRoutes.splice(index, 1) |
|
|
|
} else { |
|
|
|
// 航线未展开,点击则展开航点 |
|
|
|
this.expandedRoutes.push(routeId) |
|
|
|
} |
|
|
|
} else { |
|
|
|
// 航线未选中,点击则选中并显示航线和航点 |
|
|
|
this.handleSelectRoute(route) |
|
|
|
// 选中后自动展开航点 |
|
|
|
this.$nextTick(() => { |
|
|
|
if (!this.expandedRoutes.includes(routeId)) { |
|
|
|
this.expandedRoutes.push(routeId) |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
handleHide() { |
|
|
|
this.$emit('hide') |
|
|
|
}, |
|
|
|
@ -314,6 +364,7 @@ export default { |
|
|
|
}, |
|
|
|
|
|
|
|
handleSelectRoute(route) { |
|
|
|
// 确保航线有航点数据 |
|
|
|
this.$emit('select-route', route) |
|
|
|
}, |
|
|
|
|
|
|
|
@ -325,6 +376,14 @@ export default { |
|
|
|
this.$emit('create-route') |
|
|
|
}, |
|
|
|
|
|
|
|
handleCreateRouteForPlan(plan) { |
|
|
|
this.$emit('create-route', plan) |
|
|
|
}, |
|
|
|
|
|
|
|
handleDeletePlan(plan) { |
|
|
|
// 暂时留空,后续实现 |
|
|
|
}, |
|
|
|
|
|
|
|
handleOpenPlanDialog(plan) { |
|
|
|
this.$emit('open-plan-dialog', plan) |
|
|
|
}, |
|
|
|
@ -333,16 +392,27 @@ export default { |
|
|
|
this.$emit('open-route-dialog', route) |
|
|
|
}, |
|
|
|
|
|
|
|
handleOpenWaypointDialog(point) { |
|
|
|
this.$emit('open-waypoint-dialog', point) |
|
|
|
handleToggleRouteVisibility(route) { |
|
|
|
this.$emit('toggle-route-visibility', route) |
|
|
|
// 当隐藏航线时,自动收回航点列表 |
|
|
|
const routeIndex = this.expandedRoutes.indexOf(route.id) |
|
|
|
if (routeIndex > -1) { |
|
|
|
this.expandedRoutes.splice(routeIndex, 1) |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
handleAddWaypoint() { |
|
|
|
this.$emit('add-waypoint') |
|
|
|
getRouteClasses(routeId) { |
|
|
|
return { |
|
|
|
active: this.activeRouteIds.includes(routeId) |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
handleCancelRoute() { |
|
|
|
this.$emit('cancel-route') |
|
|
|
handleDeleteRoute(route) { |
|
|
|
// 暂时留空,后续实现 |
|
|
|
}, |
|
|
|
|
|
|
|
handleOpenWaypointDialog(point) { |
|
|
|
this.$emit('open-waypoint-dialog', point) |
|
|
|
}, |
|
|
|
|
|
|
|
handleViewConflict(conflict) { |
|
|
|
@ -466,115 +536,90 @@ export default { |
|
|
|
opacity: 0.9; |
|
|
|
} |
|
|
|
|
|
|
|
.route-list { |
|
|
|
.tree-list { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
gap: 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.route-item { |
|
|
|
.tree-item { |
|
|
|
border-radius: 6px; |
|
|
|
transition: all 0.3s; |
|
|
|
border: 1px solid rgba(0, 138, 255, 0.1); |
|
|
|
} |
|
|
|
|
|
|
|
.tree-item-header { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
gap: 10px; |
|
|
|
padding: 10px; |
|
|
|
background: rgba(255, 255, 255, 0.8); |
|
|
|
border-radius: 6px; |
|
|
|
cursor: pointer; |
|
|
|
transition: all 0.3s; |
|
|
|
border: 1px solid rgba(0, 138, 255, 0.1); |
|
|
|
position: relative; |
|
|
|
border-radius: 6px; |
|
|
|
} |
|
|
|
|
|
|
|
.route-item:hover { |
|
|
|
.tree-item-header:hover { |
|
|
|
background: rgba(0, 138, 255, 0.1); |
|
|
|
transform: translateX(-2px); |
|
|
|
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15); |
|
|
|
} |
|
|
|
|
|
|
|
.route-item.active { |
|
|
|
background: rgba(0, 138, 255, 0.15); |
|
|
|
border-color: rgba(0, 138, 255, 0.3); |
|
|
|
box-shadow: 0 2px 10px rgba(0, 138, 255, 0.25); |
|
|
|
.tree-item.plan-item .tree-item-header { |
|
|
|
background: rgba(255, 255, 255, 0.9) !important; |
|
|
|
} |
|
|
|
|
|
|
|
.route-info { |
|
|
|
flex: 1; |
|
|
|
.tree-item.route-item .tree-item-header { |
|
|
|
background: rgba(255, 255, 255, 0.8) !important; |
|
|
|
} |
|
|
|
|
|
|
|
.route-name { |
|
|
|
font-size: 14px; |
|
|
|
font-weight: 500; |
|
|
|
color: #333; |
|
|
|
.tree-item.route-item:not(.active) .tree-item-header { |
|
|
|
background: rgba(255, 255, 255, 0.8) !important; |
|
|
|
} |
|
|
|
|
|
|
|
.route-meta { |
|
|
|
font-size: 12px; |
|
|
|
color: #999; |
|
|
|
} |
|
|
|
|
|
|
|
.route-actions { |
|
|
|
display: flex; |
|
|
|
gap: 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.route-actions i { |
|
|
|
cursor: pointer; |
|
|
|
color: #008aff; |
|
|
|
font-size: 14px; |
|
|
|
padding: 4px; |
|
|
|
border-radius: 4px; |
|
|
|
transition: all 0.2s; |
|
|
|
.tree-item.waypoint-item .tree-item-header { |
|
|
|
background: rgba(224, 238, 255, 0.8); |
|
|
|
} |
|
|
|
|
|
|
|
.route-actions i:hover { |
|
|
|
background: rgba(0, 138, 255, 0.1); |
|
|
|
transform: scale(1.2); |
|
|
|
} |
|
|
|
|
|
|
|
.waypoint-list { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
gap: 8px; |
|
|
|
.tree-item.active .tree-item-header { |
|
|
|
background: rgba(0, 138, 255, 0.15) !important; |
|
|
|
border-color: rgba(0, 138, 255, 0.3); |
|
|
|
box-shadow: 0 2px 10px rgba(0, 138, 255, 0.25); |
|
|
|
} |
|
|
|
|
|
|
|
.waypoint-item { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
gap: 10px; |
|
|
|
padding: 10px; |
|
|
|
background: rgba(255, 255, 255, 0.8); |
|
|
|
border-radius: 6px; |
|
|
|
transition: all 0.3s; |
|
|
|
border: 1px solid rgba(0, 138, 255, 0.1); |
|
|
|
.tree-item.selected .tree-item-header { |
|
|
|
background: rgba(0, 138, 255, 0.1) !important; |
|
|
|
border-color: rgba(0, 138, 255, 0.2); |
|
|
|
} |
|
|
|
|
|
|
|
.waypoint-item:hover { |
|
|
|
background: rgba(0, 138, 255, 0.1); |
|
|
|
transform: translateX(-2px); |
|
|
|
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15); |
|
|
|
.tree-icon { |
|
|
|
font-size: 16px; |
|
|
|
color: #008aff; |
|
|
|
flex-shrink: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.waypoint-info { |
|
|
|
.tree-item-info { |
|
|
|
flex: 1; |
|
|
|
} |
|
|
|
|
|
|
|
.waypoint-name { |
|
|
|
.tree-item-name { |
|
|
|
font-size: 14px; |
|
|
|
font-weight: 500; |
|
|
|
color: #333; |
|
|
|
} |
|
|
|
|
|
|
|
.waypoint-meta { |
|
|
|
.tree-item-meta { |
|
|
|
font-size: 12px; |
|
|
|
color: #999; |
|
|
|
} |
|
|
|
|
|
|
|
.waypoint-actions { |
|
|
|
.tree-item-actions { |
|
|
|
display: flex; |
|
|
|
gap: 8px; |
|
|
|
flex-shrink: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.waypoint-actions i { |
|
|
|
.tree-item-actions i { |
|
|
|
cursor: pointer; |
|
|
|
color: #008aff; |
|
|
|
font-size: 14px; |
|
|
|
@ -583,11 +628,27 @@ export default { |
|
|
|
transition: all 0.2s; |
|
|
|
} |
|
|
|
|
|
|
|
.waypoint-actions i:hover { |
|
|
|
.tree-item-actions i:hover { |
|
|
|
background: rgba(0, 138, 255, 0.1); |
|
|
|
transform: scale(1.2); |
|
|
|
} |
|
|
|
|
|
|
|
.tree-children { |
|
|
|
margin-left: 20px; |
|
|
|
margin-top: 4px; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
gap: 4px; |
|
|
|
} |
|
|
|
|
|
|
|
.route-children { |
|
|
|
margin-left: 25px; |
|
|
|
} |
|
|
|
|
|
|
|
.waypoint-children { |
|
|
|
margin-left: 50px; |
|
|
|
} |
|
|
|
|
|
|
|
.action-buttons { |
|
|
|
display: flex; |
|
|
|
gap: 10px; |
|
|
|
|