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.
 
 
 
 
 

747 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;
/* 移除 cursor: pointer 因为不再整行可点 */
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>