You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
756 lines
17 KiB
756 lines
17 KiB
<template>
|
|
<div>
|
|
<!-- 右侧外部隐藏按钮。 -->
|
|
<div
|
|
class="right-external-hide-btn"
|
|
:class="{ hidden: isHidden }"
|
|
@click="handleHide"
|
|
title="隐藏右侧面板"
|
|
>
|
|
<i class="el-icon-arrow-right"></i>
|
|
</div>
|
|
|
|
<!-- 右侧实体列表(浮动)- 蓝色主题 -->
|
|
<div
|
|
class="floating-right-panel blue-theme"
|
|
:class="{ 'hidden': isHidden }"
|
|
>
|
|
<!-- 方案内容 -->
|
|
<div v-if="activeTab === 'plan'" class="tab-content plan-content">
|
|
<div class="section">
|
|
<div class="section-header">
|
|
<div class="section-title">航线列表</div>
|
|
<el-button
|
|
type="text"
|
|
icon="el-icon-plus"
|
|
size="mini"
|
|
class="header-action-btn"
|
|
@click="handleCreateRoute"
|
|
>
|
|
新建
|
|
</el-button>
|
|
</div>
|
|
|
|
<div class="route-list">
|
|
<div
|
|
v-for="route in routes"
|
|
:key="route.id"
|
|
class="route-item"
|
|
:class="{ selected: selectedRouteId === route.id }"
|
|
@click="handleSelectRoute(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>
|
|
<div class="waypoint-actions">
|
|
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenWaypointDialog(point)"></i>
|
|
<i class="el-icon-delete" title="删除"></i>
|
|
</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
|
|
v-for="conflict in conflicts"
|
|
:key="conflict.id"
|
|
class="conflict-item"
|
|
>
|
|
<div class="conflict-header">
|
|
<i class="el-icon-warning" style="color: #f56c6c;"></i>
|
|
<span class="conflict-title">{{ conflict.title }}</span>
|
|
<el-tag size="mini" type="danger">严重</el-tag>
|
|
</div>
|
|
<div class="conflict-details">
|
|
<div class="detail-item">
|
|
<span class="label">涉及航线:</span>
|
|
<span class="value">{{ conflict.routes.join('、') }}</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="label">冲突时间:</span>
|
|
<span class="value">{{ conflict.time }}</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="label">冲突位置:</span>
|
|
<span class="value">{{ conflict.position }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="conflict-actions">
|
|
<el-button type="text" size="mini" class="blue-text-btn" @click="handleViewConflict(conflict)">
|
|
查看详情
|
|
</el-button>
|
|
<el-button type="text" size="mini" class="blue-text-btn" @click="handleResolveConflict(conflict)">
|
|
解决冲突
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="no-conflict">
|
|
<i class="el-icon-success" style="color: #67c23a; font-size: 24px;"></i>
|
|
<p>暂无冲突</p>
|
|
<el-button size="mini" class="blue-btn" @click="handleRunConflictCheck">
|
|
重新检测
|
|
</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">
|
|
<el-tab-pane label="空中" name="air">
|
|
<div class="platform-list">
|
|
<div
|
|
v-for="platform in airPlatforms"
|
|
:key="platform.id"
|
|
class="platform-item"
|
|
@click="handleOpenPlatformDialog(platform)"
|
|
>
|
|
<div class="platform-icon" :style="{ color: platform.color }">
|
|
<i :class="platform.icon"></i>
|
|
</div>
|
|
<div class="platform-info">
|
|
<div class="platform-name">{{ platform.name }}</div>
|
|
<div class="platform-type">{{ platform.type }}</div>
|
|
</div>
|
|
<div class="platform-status">
|
|
<span class="status-dot" :class="platform.status"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane label="海上" name="sea">
|
|
<div class="platform-list">
|
|
<div
|
|
v-for="platform in seaPlatforms"
|
|
:key="platform.id"
|
|
class="platform-item"
|
|
@click="handleOpenPlatformDialog(platform)"
|
|
>
|
|
<div class="platform-icon" :style="{ color: platform.color }">
|
|
<i :class="platform.icon"></i>
|
|
</div>
|
|
<div class="platform-info">
|
|
<div class="platform-name">{{ platform.name }}</div>
|
|
<div class="platform-type">{{ platform.type }}</div>
|
|
</div>
|
|
<div class="platform-status">
|
|
<span class="status-dot" :class="platform.status"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane label="地面" name="ground">
|
|
<div class="platform-list">
|
|
<div
|
|
v-for="platform in groundPlatforms"
|
|
:key="platform.id"
|
|
class="platform-item"
|
|
@click="handleOpenPlatformDialog(platform)"
|
|
>
|
|
<div class="platform-icon" :style="{ color: platform.color }">
|
|
<i :class="platform.icon"></i>
|
|
</div>
|
|
<div class="platform-info">
|
|
<div class="platform-name">{{ platform.name }}</div>
|
|
<div class="platform-type">{{ platform.type }}</div>
|
|
</div>
|
|
<div class="platform-status">
|
|
<span class="status-dot" :class="platform.status"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'RightPanel',
|
|
props: {
|
|
isHidden: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
activeTab: {
|
|
type: String,
|
|
default: 'plan'
|
|
},
|
|
routes: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
selectedRouteId: {
|
|
type: [String, Number],
|
|
default: null
|
|
},
|
|
selectedRouteDetails: {
|
|
type: Object,
|
|
default: null
|
|
},
|
|
conflicts: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
conflictCount: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
airPlatforms: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
seaPlatforms: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
groundPlatforms: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
activePlatformTab: 'air'
|
|
}
|
|
},
|
|
methods: {
|
|
handleHide() {
|
|
this.$emit('hide')
|
|
},
|
|
|
|
handleSelectRoute(route) {
|
|
this.$emit('select-route', route)
|
|
},
|
|
|
|
// 新增:新建航线事件
|
|
handleCreateRoute() {
|
|
this.$emit('create-route')
|
|
},
|
|
|
|
handleOpenRouteDialog(route) {
|
|
this.$emit('open-route-dialog', route)
|
|
},
|
|
|
|
handleOpenWaypointDialog(point) {
|
|
this.$emit('open-waypoint-dialog', point)
|
|
},
|
|
|
|
handleAddWaypoint() {
|
|
this.$emit('add-waypoint')
|
|
},
|
|
|
|
handleCancelRoute() {
|
|
this.$emit('cancel-route')
|
|
},
|
|
|
|
handleViewConflict(conflict) {
|
|
this.$emit('view-conflict', conflict)
|
|
},
|
|
|
|
handleResolveConflict(conflict) {
|
|
this.$emit('resolve-conflict', conflict)
|
|
},
|
|
|
|
handleRunConflictCheck() {
|
|
this.$emit('run-conflict-check')
|
|
},
|
|
|
|
handleOpenPlatformDialog(platform) {
|
|
this.$emit('open-platform-dialog', platform)
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* 右侧外部隐藏按钮 */
|
|
.right-external-hide-btn {
|
|
position: absolute;
|
|
top: 80px;
|
|
right: 20px;
|
|
width: 30px;
|
|
height: 30px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
color: #008aff;
|
|
font-size: 18px;
|
|
background: rgba(255, 255, 255, 0.5);
|
|
border-radius: 50%;
|
|
z-index: 85;
|
|
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.3);
|
|
transition: all 0.3s;
|
|
backdrop-filter: blur(5px);
|
|
}
|
|
|
|
.right-external-hide-btn:hover {
|
|
color: #0066cc;
|
|
background: rgba(0, 138, 255, 0.2);
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.right-external-hide-btn.hidden {
|
|
opacity: 0;
|
|
transform: translateX(100%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* 右侧浮动面板 - 蓝色主题 */
|
|
.floating-right-panel {
|
|
position: absolute;
|
|
top: 70px;
|
|
right: 20px;
|
|
width: 300px;
|
|
border-radius: 0 8px 8px 8px;
|
|
z-index: 90;
|
|
color: #333;
|
|
overflow: hidden;
|
|
box-shadow: 0 4px 20px rgba(0, 138, 255, 0.2);
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(15px);
|
|
transition: all 0.3s ease;
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.floating-right-panel.hidden {
|
|
opacity: 0;
|
|
transform: translateX(100%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.tab-content {
|
|
padding: 15px;
|
|
max-height: 500px;
|
|
overflow-y: auto;
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.section {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
/* 新增:标题栏容器样式 */
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
border-bottom: 2px solid rgba(0, 138, 255, 0.2);
|
|
margin-bottom: 10px;
|
|
padding-bottom: 8px;
|
|
}
|
|
|
|
/* 修改:移除了原有的 border-bottom,改在 section-header 中统一定义 */
|
|
.section-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: #008aff;
|
|
}
|
|
|
|
/* 新增:头部按钮样式 */
|
|
.header-action-btn {
|
|
padding: 0;
|
|
color: #008aff;
|
|
}
|
|
|
|
.header-action-btn:hover {
|
|
color: #0066cc;
|
|
}
|
|
|
|
.route-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.route-item {
|
|
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;
|
|
}
|
|
|
|
.route-item:hover {
|
|
background: rgba(0, 138, 255, 0.1);
|
|
transform: translateX(-2px);
|
|
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15);
|
|
}
|
|
|
|
.route-item.selected {
|
|
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);
|
|
}
|
|
|
|
.route-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.route-name {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: #333;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.route-actions i:hover {
|
|
background: rgba(0, 138, 255, 0.1);
|
|
transform: scale(1.2);
|
|
}
|
|
|
|
.waypoint-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.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);
|
|
}
|
|
|
|
.waypoint-item:hover {
|
|
background: rgba(0, 138, 255, 0.1);
|
|
transform: translateX(-2px);
|
|
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15);
|
|
}
|
|
|
|
.waypoint-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.waypoint-name {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: #333;
|
|
}
|
|
|
|
.waypoint-meta {
|
|
font-size: 12px;
|
|
color: #999;
|
|
}
|
|
|
|
.waypoint-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.waypoint-actions i {
|
|
cursor: pointer;
|
|
color: #008aff;
|
|
font-size: 14px;
|
|
padding: 4px;
|
|
border-radius: 4px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.waypoint-actions i:hover {
|
|
background: rgba(0, 138, 255, 0.1);
|
|
transform: scale(1.2);
|
|
}
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 10px;
|
|
padding: 10px 0;
|
|
}
|
|
|
|
.conflict-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.conflict-item {
|
|
background: rgba(255, 255, 255, 0.8);
|
|
border-radius: 6px;
|
|
padding: 12px;
|
|
border: 1px solid rgba(245, 108, 108, 0.2);
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.conflict-item:hover {
|
|
background: rgba(245, 108, 108, 0.1);
|
|
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.15);
|
|
}
|
|
|
|
.conflict-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.conflict-title {
|
|
flex: 1;
|
|
font-weight: 600;
|
|
color: #f56c6c;
|
|
}
|
|
|
|
.conflict-details {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.detail-item {
|
|
display: flex;
|
|
gap: 8px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.detail-item .label {
|
|
color: #999;
|
|
min-width: 70px;
|
|
}
|
|
|
|
.detail-item .value {
|
|
color: #333;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.conflict-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.no-conflict {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 15px;
|
|
padding: 40px 20px;
|
|
color: #999;
|
|
}
|
|
|
|
.platform-categories {
|
|
height: 100%;
|
|
}
|
|
|
|
.platform-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
max-height: 450px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.platform-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 12px;
|
|
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);
|
|
}
|
|
|
|
.platform-item:hover {
|
|
background: rgba(0, 138, 255, 0.1);
|
|
transform: translateX(-2px);
|
|
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15);
|
|
}
|
|
|
|
.platform-icon {
|
|
width: 40px;
|
|
height: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 20px;
|
|
background: rgba(255, 255, 255, 0.9);
|
|
border-radius: 50%;
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.platform-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.platform-name {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: #333;
|
|
}
|
|
|
|
.platform-type {
|
|
font-size: 12px;
|
|
color: #999;
|
|
}
|
|
|
|
.platform-status {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.status-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
}
|
|
|
|
.status-dot.online {
|
|
background: #67c23a;
|
|
box-shadow: 0 0 6px rgba(103, 194, 58, 0.6);
|
|
}
|
|
|
|
.status-dot.offline {
|
|
background: #999;
|
|
}
|
|
|
|
.status-dot.operating {
|
|
background: #008aff;
|
|
animation: pulse 2s infinite;
|
|
box-shadow: 0 0 10px rgba(0, 138, 255, 0.8);
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
.blue-btn {
|
|
background: rgba(0, 138, 255, 0.1);
|
|
color: #008aff;
|
|
border: 1px solid rgba(0, 138, 255, 0.3);
|
|
}
|
|
|
|
.blue-btn:hover {
|
|
background: rgba(0, 138, 255, 0.2);
|
|
border-color: rgba(0, 138, 255, 0.5);
|
|
}
|
|
|
|
.blue-text-btn {
|
|
color: #008aff;
|
|
}
|
|
|
|
.blue-text-btn:hover {
|
|
color: #0066cc;
|
|
}
|
|
|
|
.blue-badge {
|
|
background: rgba(245, 108, 108, 0.1);
|
|
color: #f56c6c;
|
|
border: 1px solid rgba(245, 108, 108, 0.3);
|
|
}
|
|
|
|
.blue-tabs >>> .el-tabs__item {
|
|
color: #666;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.blue-tabs >>> .el-tabs__item:hover {
|
|
color: #008aff;
|
|
}
|
|
|
|
.blue-tabs >>> .el-tabs__item.is-active {
|
|
color: #008aff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.blue-tabs >>> .el-tabs__active-bar {
|
|
background-color: #008aff;
|
|
box-shadow: 0 0 6px rgba(0, 138, 255, 0.5);
|
|
}
|
|
|
|
.blue-tabs >>> .el-tabs__nav-wrap::after {
|
|
background-color: rgba(0, 138, 255, 0.3);
|
|
}
|
|
|
|
.blue-success {
|
|
color: #67c23a;
|
|
}
|
|
|
|
.blue-warning {
|
|
color: #e6a23c;
|
|
}
|
|
</style>
|
|
|