Browse Source

侧边栏

lbj
sd 3 months ago
parent
commit
c34b9666fa
  1. 139
      ruoyi-ui/src/views/childRoom/LeftMenu.vue
  2. 723
      ruoyi-ui/src/views/childRoom/RightPanel.vue

139
ruoyi-ui/src/views/childRoom/LeftMenu.vue

@ -0,0 +1,139 @@
<template>
<div
class="floating-left-menu"
:class="{ 'hidden': isHidden }"
@mouseenter="showTooltip = true"
@mouseleave="showTooltip = false"
>
<!-- 隐藏按钮>箭头 -->
<div class="hide-btn" @click="handleHide" title="隐藏菜单">
<i class="el-icon-arrow-left"></i>
</div>
<!-- 一级菜单 -->
<div class="menu-icons">
<div
v-for="item in menuItems"
:key="item.id"
class="menu-item"
:class="{ active: activeMenu === item.id }"
@click="handleSelectMenu(item)"
:title="item.name"
>
<i :class="item.icon"></i>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'LeftMenu',
props: {
isHidden: {
type: Boolean,
default: false
},
menuItems: {
type: Array,
default: () => []
},
activeMenu: {
type: String,
default: ''
}
},
methods: {
handleHide() {
this.$emit('hide')
},
handleSelectMenu(item) {
this.$emit('select', item)
}
}
}
</script>
<style scoped>
/* 左侧菜单栏 - 蓝色主题 。*/
.floating-left-menu {
position: absolute;
top: 70px;
left: 20px;
width: 40px;
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 138, 255, 0.1);
border-radius: 8px;
z-index: 90;
box-shadow: 0 4px 12px rgba(0, 138, 255, 0.2);
padding: 15px 5px;
transition: all 0.3s ease;
overflow: hidden;
opacity: 1;
transform: translateX(0);
}
.floating-left-menu.hidden {
opacity: 0;
transform: translateX(-100%);
pointer-events: none;
}
.hide-btn {
position: absolute;
top: 15px;
left: 50%;
transform: translateX(-50%);
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: #008aff;
font-size: 16px;
transition: all 0.3s;
background: rgba(255, 255, 255, 0.5);
border-radius: 4px;
z-index: 10;
}
.menu-icons {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 30px;
}
.menu-item {
display: flex;
justify-content: center;
align-items: center;
height: 40px;
cursor: pointer;
color: #555;
font-size: 20px;
position: relative;
transition: all 0.3s;
border-radius: 4px;
padding: 0 5px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(5px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.menu-item:hover {
background: rgba(0, 138, 255, 0.1);
color: #008aff;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0, 138, 255, 0.2);
}
.menu-item.active {
background: rgba(0, 138, 255, 0.15);
color: #008aff;
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.3);
}
</style>

723
ruoyi-ui/src/views/childRoom/RightPanel.vue

@ -0,0 +1,723 @@
<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-title">航线列表</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"
@click="handleOpenWaypointDialog(point)"
>
<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)
},
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-title {
font-size: 14px;
font-weight: 600;
color: #008aff;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 2px solid rgba(0, 138, 255, 0.2);
}
.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>
Loading…
Cancel
Save