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.
 
 
 
 
 

2673 lines
72 KiB

<template>
<!-- 以地图为绝对定位背景所有组件浮动其上 -->
<div class="mission-planning-container">
<!-- 地图背景 -->
<div
id="gis-map-background"
class="map-background"
@click="handleMapClick"
@contextmenu.prevent="finishDrawing"
:style="{ cursor: isDrawing ? 'crosshair' : 'default' }"
>
<!-- cesiummap组件 -->
<cesiumMap :drawDomClick="drawDom"/>
<div class="map-overlay-text">
<i class="el-icon-location-outline text-3xl mb-2 block"></i>
<p>二维GIS地图区域</p>
<p class="text-sm mt-1">支持标绘/航线/空域/实时态势</p>
</div>
<div v-if="isDrawing" style="position: absolute; top: 20px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.8); padding: 10px 20px; border-radius: 4px; border: 1px solid #409EFF; color: #fff; z-index: 2000; display: flex; align-items: center; box-shadow: 0 4px 12px rgba(0,0,0,0.5);">
<i class="el-icon-edit-outline" style="margin-right: 8px; color: #409EFF;"></i>
<span>正在绘制: <b>{{ pendingRoute.name }}</b></span>
<span style="margin: 0 15px; color: #666;">|</span>
<span>已添加航点: {{ pendingRoute.points.length }}</span>
<span style="margin-left: 20px; font-size: 12px; color: #aaa; background: #333; padding: 2px 6px; border-radius: 2px;">左键点击添加航点,右键结束</span>
</div>
<el-dialog
title="新建航线计划"
:visible.sync="showCreateRouteDialog"
width="400px"
:append-to-body="true"
:close-on-click-modal="false"
>
<el-form label-width="80px" size="small">
<el-form-item label="航线名称">
<el-input v-model="pendingRoute.name" placeholder="请输入航线名称"></el-input>
</el-form-item>
<el-form-item label="任务时间">
<el-date-picker
v-model="pendingRoute.startTime"
type="datetime"
placeholder="选择开始时间"
style="width: 100%;">
</el-date-picker>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="showCreateRouteDialog = false">取 消</el-button>
<el-button type="primary" size="small" @click="confirmCreateRoute">开始绘制</el-button>
</span>
</el-dialog>
<el-dialog
title="录入航点信息"
:visible.sync="showWaypointDialog"
width="400px"
:append-to-body="true"
:close-on-click-modal="false"
>
<el-form :model="waypointForm" label-width="80px" size="small">
<el-form-item label="航点名称">
<el-input v-model="waypointForm.name"></el-input>
</el-form-item>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="高度(m)">
<el-input-number v-model="waypointForm.altitude" :controls="false" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="速度(km/h)">
<el-input-number v-model="waypointForm.speed" :controls="false" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="到达时间">
<el-time-picker v-model="waypointForm.time" value-format="HH:mm:ss" placeholder="00:00:00" style="width: 100%"></el-time-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="转弯半径">
<el-input-number v-model="waypointForm.radius" :controls="false" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="showWaypointDialog = false">取消该点</el-button>
<el-button type="primary" size="small" @click="confirmWaypoint">确 定</el-button>
</span>
</el-dialog>
<!-- 地图中间的浮动红点(触发左侧菜单) -->
<div
class="floating-red-dot left-red-dot"
:class="{ hidden: !isMenuHidden }"
@click="showMenu"
title="显示左侧菜单"
>
<div class="red-dot"></div>
<i class="el-icon-s-unfold icon-inside"></i>
</div>
<!-- 地图中间的浮动红点(触发右侧面板) -->
<div
class="floating-red-dot right-red-dot"
:class="{ hidden: !isRightPanelHidden }"
@click="showRightPanel"
title="显示右侧面板"
>
<div class="red-dot"></div>
<i class="el-icon-s-fold icon-inside"></i>
</div>
</div>
<!-- 顶部浮动导航栏 - 最终优化设计 -->
<div class="floating-header">
<div class="header-left">
<div class="system-title">
<i class="el-icon-s-promotion mr-2 logo-icon"></i>
<span class="title-text blue-title">联合任务筹划系统</span>
</div>
<!-- 顶部导航菜单 -->
<div class="top-nav-menu">
<div
v-for="item in topNavItems"
:key="item.id"
class="top-nav-item"
:class="{ active: activeTopNav === item.id }"
@click="selectTopNav(item)"
>
<i :class="item.icon" class="nav-icon"></i>
<span class="nav-text">{{ item.name }}</span>
<!-- 文件下拉菜单 -->
<el-dropdown
v-if="item.id === 'file'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
>
<div class="dropdown-trigger"></div>
<el-dropdown-menu slot="dropdown" class="file-dropdown-menu">
<el-dropdown-item @click.native="newPlan">新建计划</el-dropdown-item>
<el-dropdown-item @click.native="openPlan">打开</el-dropdown-item>
<el-dropdown-item @click.native="savePlan">保存</el-dropdown-item>
<!-- 导入二级菜单 -->
<el-dropdown-item class="submenu-item">
<span>导入</span>
<el-dropdown
trigger="hover"
placement="right-start"
class="submenu-dropdown"
>
<div class="submenu-trigger"></div>
<el-dropdown-menu slot="dropdown" class="submenu">
<el-dropdown-item @click.native="importPlanFile">导入计划</el-dropdown-item>
<el-dropdown-item @click.native="importACD">导入ACD</el-dropdown-item>
<el-dropdown-item @click.native="importATO">导入ATO</el-dropdown-item>
<el-dropdown-item @click.native="importLayer">导入图层</el-dropdown-item>
<el-dropdown-item @click.native="importRoute">导入航线</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-dropdown-item>
<el-dropdown-item @click.native="exportPlan">导出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 编辑下拉菜单 -->
<el-dropdown
v-if="item.id === 'edit'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
>
<div class="dropdown-trigger"></div>
<el-dropdown-menu slot="dropdown" class="file-dropdown-menu">
<el-dropdown-item @click.native="routeEdit">航线编辑</el-dropdown-item>
<el-dropdown-item @click.native="militaryMarking">军事标绘</el-dropdown-item>
<el-dropdown-item @click.native="iconEdit">图标编辑</el-dropdown-item>
<el-dropdown-item @click.native="attributeEdit">属性修改</el-dropdown-item>
<!-- 推演编辑二级菜单 -->
<el-dropdown-item class="submenu-item">
<span>推演编辑</span>
<el-dropdown
trigger="hover"
placement="right-start"
class="submenu-dropdown"
>
<div class="submenu-trigger"></div>
<el-dropdown-menu slot="dropdown" class="submenu">
<el-dropdown-item @click.native="timeSettings">时间设置</el-dropdown-item>
<el-dropdown-item @click.native="aircraftSettings">机型设置</el-dropdown-item>
<el-dropdown-item @click.native="keyEventEdit">关键事件编辑</el-dropdown-item>
<el-dropdown-item @click.native="missileLaunch">导弹发射</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 视图下拉菜单 -->
<el-dropdown
v-if="item.id === 'view'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
>
<div class="dropdown-trigger"></div>
<el-dropdown-menu slot="dropdown" class="file-dropdown-menu">
<el-dropdown-item @click.native="toggle2D3D">2D/3D切换</el-dropdown-item>
<el-dropdown-item @click.native="toggleRuler">显示/隐藏标尺</el-dropdown-item>
<el-dropdown-item @click.native="toggleGrid">网格</el-dropdown-item>
<el-dropdown-item @click.native="toggleScale">比例尺</el-dropdown-item>
<el-dropdown-item @click.native="toggleCompass">指北针</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 地图下拉菜单 -->
<el-dropdown
v-if="item.id === 'map'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
>
<div class="dropdown-trigger"></div>
<el-dropdown-menu slot="dropdown" class="file-dropdown-menu">
<el-dropdown-item @click.native="loadTerrain">加载/切换地形</el-dropdown-item>
<el-dropdown-item @click.native="changeProjection">投影</el-dropdown-item>
<el-dropdown-item @click.native="loadAeroChart">航空图</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 工具下拉菜单 -->
<el-dropdown
v-if="item.id === 'tools'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
>
<div class="dropdown-trigger"></div>
<el-dropdown-menu slot="dropdown" class="file-dropdown-menu">
<el-dropdown-item @click.native="routeCalculation">航线计算</el-dropdown-item>
<el-dropdown-item @click.native="conflictDisplay">冲突显示</el-dropdown-item>
<el-dropdown-item @click.native="dataMaterials">数据资料</el-dropdown-item>
<el-dropdown-item @click.native="coordinateConversion">坐标换算</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 选项下拉菜单 -->
<el-dropdown
v-if="item.id === 'options'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
>
<div class="dropdown-trigger"></div>
<el-dropdown-menu slot="dropdown" class="file-dropdown-menu">
<el-dropdown-item @click.native="settings">设置</el-dropdown-item>
<el-dropdown-item @click.native="systemDescription">系统说明</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 收藏下拉菜单 -->
<el-dropdown
v-if="item.id === 'favorites'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
>
<div class="dropdown-trigger"></div>
<el-dropdown-menu slot="dropdown" class="file-dropdown-menu">
<el-dropdown-item @click.native="layerFavorites">图层收藏</el-dropdown-item>
<el-dropdown-item @click.native="routeFavorites">航线收藏</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
</div>
<div class="header-right">
<!-- 作战信息区域 -->
<div class="combat-info-group">
<!-- 房间编号 -->
<div class="info-box">
<i class="el-icon-office-building info-icon"></i>
<div class="info-content">
<div class="info-label">房间编号</div>
<div class="info-value">{{ roomCode }}</div>
</div>
</div>
<!-- 在线人数 -->
<div class="info-box" @click="showOnlineMembersDialog">
<i class="el-icon-user info-icon"></i>
<div class="info-content">
<div class="info-label">在线人数</div>
<div class="info-value">{{ onlineCount }}人</div>
</div>
</div>
<!-- 作战时间 -->
<div class="info-box">
<i class="el-icon-timer info-icon"></i>
<div class="info-content">
<div class="info-label">作战时间</div>
<div class="info-value">{{ combatTime }}</div>
</div>
</div>
<!-- 天文时间 -->
<div class="info-box">
<i class="el-icon-sunny info-icon"></i>
<div class="info-content">
<div class="info-label">天文时间</div>
<div class="info-value">{{ astroTime }}</div>
</div>
</div>
</div>
<!-- 用户状态区域 -->
<div class="user-status-area">
<!-- 用户头像 -->
<el-avatar :size="32" :src="userAvatar" class="user-avatar" />
</div>
</div>
</div>
<!-- 左侧折叠菜单栏 - 蓝色主题 -->
<div
class="floating-left-menu"
:class="{ 'hidden': isMenuHidden }"
@mouseenter="showMenuTooltip = true"
@mouseleave="showMenuTooltip = false"
>
<!-- 隐藏按钮(>箭头) -->
<div class="hide-btn" @click="hideMenu" 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="selectMenu(item)"
@mouseenter="showTooltip(item)"
@mouseleave="hideTooltip"
>
<i :class="item.icon"></i>
<!-- 悬浮提示 -->
<div v-if="showMenuTooltip && hoverMenu === item.id" class="menu-tooltip">
{{ item.name }}
</div>
</div>
</div>
<!-- 菜单工具提示层(解决遮挡) -->
<div v-if="showMenuTooltip && hoverMenu" class="menu-tooltip-layer">
<div class="tooltip-content">{{ getMenuName(hoverMenu) }}</div>
</div>
</div>
<!-- 右侧外部隐藏按钮 -->
<div
class="right-external-hide-btn"
:class="{ hidden: isRightPanelHidden }"
@click="hideRightPanel"
title="隐藏右侧面板"
>
<i class="el-icon-arrow-right"></i>
</div>
<!-- 右侧实体列表(浮动)- 蓝色主题 -->
<div
class="floating-right-panel blue-theme"
:class="{ 'hidden': isRightPanelHidden }"
>
<!-- 移除面板内部的隐藏按钮 -->
<div class="right-tabs">
<!-- 方案 -->
<div
class="right-tab-item"
:class="{ active: activeRightTab === 'plan' }"
@click="activeRightTab = 'plan'"
>
<i class="el-icon-s-operation"></i>
<span>方案</span>
</div>
<!-- 冲突 -->
<div
class="right-tab-item"
:class="{ active: activeRightTab === 'conflict' }"
@click="activeRightTab = 'conflict'"
>
<i class="el-icon-warning-outline"></i>
<span>冲突</span>
<span v-if="conflictCount > 0" class="badge blue-badge">{{ conflictCount }}</span>
</div>
<!-- 平台 -->
<div
class="right-tab-item"
:class="{ active: activeRightTab === 'platform' }"
@click="activeRightTab = 'platform'"
>
<i class="el-icon-ship"></i>
<span>平台</span>
</div>
</div>
<!-- 方案内容 -->
<div v-if="activeRightTab === 'plan'" class="tab-content plan-content">
<div class="section">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div class="section-title" style="margin-bottom: 0;">航线列表</div>
<el-button
type="primary"
size="mini"
icon="el-icon-plus"
class="blue-btn"
style="padding: 4px 10px; font-size: 12px; height: 24px;"
@click="addRoute"
>
添加航线
</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="selectRoute(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="openRouteDialog(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="openWaypointDialog(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="openWaypointDialog(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="addWaypoint">
添加航点
</el-button>
<el-button size="mini" class="blue-btn" @click="cancelRoute">
取消
</el-button>
</div>
</div>
<!-- 冲突内容 -->
<div v-if="activeRightTab === '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="viewConflict(conflict)">
查看详情
</el-button>
<el-button type="text" size="mini" class="blue-text-btn" @click="resolveConflict(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="runConflictCheck">
重新检测
</el-button>
</div>
</div>
<!-- 平台内容 -->
<div v-if="activeRightTab === '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="openPlatformDialog(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="openPlatformDialog(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="openPlatformDialog(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
class="floating-timeline blue-theme"
:class="{ 'show': showKTimePopup }"
>
<!-- 隐藏按钮(向下箭头) -->
<div class="popup-hide-btn" @click="hideKTimePopup" title="隐藏K时">
<i class="el-icon-arrow-down"></i>
</div>
<div class="timeline-controls">
<div class="current-time blue-time">
<i class="el-icon-time"></i>
<span class="time-text">{{ currentTime }}</span>
</div>
<div class="timeline-slider">
<el-slider
v-model="timeProgress"
:max="100"
:format-tooltip="formatTimeTooltip"
class="compact-slider blue-slider"
/>
</div>
<div class="timeline-marks">
<span class="mark">K-02:00</span>
<span class="mark">K-01:00</span>
<span class="mark current blue-mark">K时</span>
<span class="mark">K+01:00</span>
<span class="mark">K+02:00</span>
</div>
</div>
<div class="system-status">
<div class="status-item">
<span class="status-label">网络延迟:</span>
<span class="status-value success blue-success">< 200ms</span>
</div>
<div class="status-item">
<span class="status-label">数据同步:</span>
<span class="status-value success blue-success">正常</span>
</div>
<div class="status-item">
<span class="status-label">检测告警:</span>
<span class="status-value warning blue-warning">2处</span>
</div>
</div>
</div>
<!-- 在线成员弹窗 -->
<online-members-dialog
v-model="showOnlineMembers"
/>
<!-- 平台编辑弹窗 -->
<platform-edit-dialog
v-model="showPlatformDialog"
:platform="selectedPlatform"
@save="updatePlatform"
/>
<!-- 航线编辑弹窗 -->
<route-edit-dialog
v-model="showRouteDialog"
:route="selectedRoute"
@save="updateRoute"
/>
<!-- 航点编辑弹窗 -->
<waypoint-edit-dialog
v-model="showWaypointDialog"
:waypoint="selectedWaypoint"
@save="updateWaypoint"
/>
<el-dialog title="新建航线" :visible.sync="showCreateRouteDialog" width="30%">
<el-form label-width="80px">
<el-form-item label="航线名称">
<el-input v-model="pendingRoute.name"></el-input>
</el-form-item>
<el-form-item label="起始时间">
<el-time-picker v-model="pendingRoute.startTime" placeholder="选择时间"></el-time-picker>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="showCreateRouteDialog = false">取 消</el-button>
<el-button type="primary" @click="confirmCreateRoute">开始绘制</el-button>
</span>
</el-dialog>
<el-dialog title="添加航点信息" :visible.sync="showWaypointDialog" width="30%" :close-on-click-modal="false">
<el-form :model="waypointForm" label-width="80px">
<el-form-item label="航点名称">
<el-input v-model="waypointForm.name"></el-input>
</el-form-item>
<el-col :span="12">
<el-form-item label="高度(m)">
<el-input-number v-model="waypointForm.altitude" :controls="false" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="速度(km/h)">
<el-input-number v-model="waypointForm.speed" :controls="false" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="到达时间">
<el-time-picker v-model="waypointForm.time" value-format="HH:mm:ss" style="width: 100%"></el-time-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="转弯半径">
<el-input-number v-model="waypointForm.radius" :controls="false" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="showWaypointDialog = false">取消该点</el-button>
<el-button type="primary" @click="confirmWaypoint"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import cesiumMap from '@/views/cesiumMap'
import OnlineMembersDialog from '@/views/dialogs/OnlineMembersDialog'
import PlatformEditDialog from '@/views/dialogs/PlatformEditDialog'
import RouteEditDialog from '@/views/dialogs/RouteEditDialog'
import WaypointEditDialog from '@/views/dialogs/WaypointEditDialog'
export default {
name: 'MissionPlanningView',
components: {
cesiumMap,
OnlineMembersDialog,
PlatformEditDialog,
RouteEditDialog,
WaypointEditDialog
},
data() {
return {
drawDom:false,
// 在线成员弹窗
showOnlineMembers: false,
// 编辑弹窗控制
showPlatformDialog: false,
selectedPlatform: null,
showRouteDialog: false,
selectedRoute: null,
showWaypointDialog: false,
selectedWaypoint: null,
//航线绘制相关数据
isDrawing: false, // 是否处于地图绘制模式
showCreateRouteDialog: false, // 是否显示"新建航线"弹窗
showWaypointDialog: false, // 是否显示"航点信息"弹窗
// 暂存正在创建的航线对象
pendingRoute: {
id: null,
name: '',
startTime: '',
points: []
},
// 暂存当前点击地图生成的临时航点坐标
tempClickPosition: null,
// 航点表单数据
waypointForm: {
name: '',
altitude: 0,
speed: 0,
time: '',
radius: 0
},
// 航点表单校验规则(可选)
waypointRules: {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
altitude: [{ required: true, message: '请输入高度', trigger: 'blur' }]
},
// 顶部导航
activeTopNav: 'file',
topNavItems: [
{ id: 'file', name: '文件', icon: 'el-icon-document' },
{ id: 'edit', name: '编辑', icon: 'el-icon-edit' },
{ id: 'view', name: '视图', icon: 'el-icon-view' },
{ id: 'map', name: '地图', icon: 'el-icon-map-location' },
{ id: 'airspace', name: '空域', icon: 'el-icon-s-grid' },
{ id: 'tools', name: '工具', icon: 'el-icon-setting' },
{ id: 'options', name: '选项', icon: 'el-icon-s-tools' },
{ id: 'favorites', name: '收藏', icon: 'el-icon-star-on' }
],
// 作战信息
roomCode: 'JTF-7-ALPHA',
onlineCount: 30,
combatTime: 'K+01:30:45',
astroTime: '',
// 左侧菜单栏
isMenuHidden: true, // 是否完全隐藏左侧菜单
activeMenu: 'file',
hoverMenu: '',
showMenuTooltip: false,
// 右侧面板控制
isRightPanelHidden: true, // 是否完全隐藏右侧面板
// K时弹出框控制
showKTimePopup: false,
menuItems: [
{ id: 'file', name: '文件', icon: 'el-icon-document' },
{ id: 'start', name: '开始', icon: 'el-icon-caret-right' },
{ id: 'insert', name: '插入', icon: 'el-icon-plus' },
{ id: 'pattern', name: '图案', icon: 'el-icon-picture-outline' },
{ id: 'deduction', name: '推演', icon: 'el-icon-video-play' },
{ id: 'modify', name: '修改', icon: 'el-icon-edit' },
{ id: 'refresh', name: '刷新', icon: 'el-icon-refresh' },
{ id: 'basemap', name: '底图', icon: 'el-icon-map-location' },
{ id: 'import', name: '导入', icon: 'el-icon-upload2' },
{ id: 'export', name: '导出', icon: 'el-icon-download' }
],
// 右侧面板
activeRightTab: 'plan',
selectedRouteId: 101,
selectedRouteDetails: null,
// 冲突数据
conflictCount: 2,
conflicts: [
{
id: 1,
title: '航线空间冲突',
routes: ['Alpha进场航线', 'Beta巡逻航线'],
time: 'K+01:20:00',
position: 'E116° N39°',
severity: 'high'
},
{
id: 2,
title: '时间窗口重叠',
routes: ['侦察覆盖区', '无人机巡逻'],
time: 'K+02:15:00',
position: 'E117° N38°',
severity: 'medium'
}
],
// 平台数据
activePlatformTab: 'air',
airPlatforms: [
{ id: 1, name: 'J-20 歼击机', type: '战斗机', icon: 'el-icon-fighter', color: '#52c41a', status: 'ready' },
{ id: 2, name: 'KJ-2000 预警机', type: '预警机', icon: 'el-icon-s-promotion', color: '#1890ff', status: 'flying' },
{ id: 3, name: 'UAV-01 无人机', type: '侦察无人机', icon: 'el-icon-drone', color: '#fa8c16', status: 'scouting' },
{ id: 4, name: 'H-6K 轰炸机', type: '轰炸机', icon: 'el-icon-bomb', color: '#722ed1', status: 'ready' },
],
seaPlatforms: [
{ id: 5, name: '辽宁舰', type: '航空母舰', icon: 'el-icon-ship', color: '#1890ff', status: 'sailing' },
{ id: 6, name: '055型驱逐舰', type: '驱逐舰', icon: 'el-icon-ship', color: '#52c41a', status: 'patrol' },
{ id: 7, name: '093型潜艇', type: '核潜艇', icon: 'el-icon-ship', color: '#333', status: 'hidden' },
],
groundPlatforms: [
{ id: 8, name: 'HQ-9防空系统', type: '防空导弹', icon: 'el-icon-mic', color: '#f5222d', status: 'alert' },
{ id: 9, name: 'PLZ-05自行火炮', type: '自行火炮', icon: 'el-icon-aim', color: '#fa8c16', status: 'ready' },
{ id: 10, name: '指挥控制车', type: '指挥车', icon: 'el-icon-monitor', color: '#1890ff', status: 'operating' },
],
// 航线数据
routes: [
{ id: 101, name: 'Alpha进场航线', points: 8, conflict: true },
{ id: 102, name: 'Beta巡逻航线', points: 6, conflict: false },
{ id: 103, name: '侦察覆盖区', points: 4, conflict: false },
],
// 时间控制
timeProgress: 45,
currentTime: 'K+01:15:30',
// 用户
userAvatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
};
},
mounted() {
// 初始化时左侧菜单隐藏
this.isMenuHidden = true;
// 初始化时右侧面板隐藏
this.isRightPanelHidden = true;
// 更新时间
this.updateTime();
setInterval(this.updateTime, 1000);
// 作战时间也需要实时更新
setInterval(this.updateCombatTime, 1000);
},
methods: {
// 添加新航线
addRoute() {
this.pendingRoute = {
id: Date.now(), // 生成唯一ID
name: '新建计划航线',
startTime: '',
points: []
};
this.showCreateRouteDialog = true;
},
// 2. 确认航线基本信息,进入“绘图模式”
confirmCreateRoute() {
if (!this.pendingRoute.name) {
this.$message.warning('请输入航线名称');
return;
}
this.showCreateRouteDialog = false;
this.isDrawing = true;
this.$message.info('进入绘制模式:左键点击地图添加航点,右键结束绘制');
},
// 3. 【核心】地图点击事件处理
// 注意:这个方法需要绑定到地图容器的 @click 上,或者在你的 cesiumMap 组件回调里调用
handleMapClick(event) {
if (!this.isDrawing) return;
// 模拟获取点击的经纬度,实际项目中请从 cesium 事件中获取
// const position = event.position || { lat: 30, lng: 120 };
// 暂存位置,打开航点录入弹窗
// this.tempClickPosition = position;
// 重置表单,自动生成默认值
const nextIndex = this.pendingRoute.points.length + 1;
this.waypointForm = {
name: `WP-${nextIndex}`,
altitude: 5000,
speed: 800,
time: '',
radius: 5
};
this.showWaypointDialog = true;
},
// 4. 确认添加一个航点
confirmWaypoint() {
// 将表单数据和位置信息合并
const newPoint = {
...this.waypointForm,
// ...this.tempClickPosition // 这里存入真实的经纬度
};
this.pendingRoute.points.push(newPoint);
this.showWaypointDialog = false;
this.$message.success(`已添加第 ${this.pendingRoute.points.length} 个航点`);
},
// 5. 右键结束绘制
finishDrawing() {
if (!this.isDrawing) return;
if (this.pendingRoute.points.length < 2) {
this.$message.warning('航线至少需要2个航点');
return;
}
// 构造最终数据格式,推入主数组
const finalRoute = {
id: this.pendingRoute.id,
name: this.pendingRoute.name,
points: this.pendingRoute.points.length, // 显示航点数量
conflict: false,
detailData: this.pendingRoute // 保存完整数据以便后续使用
};
this.routes.push(finalRoute);
this.isDrawing = false;
this.selectRoute(finalRoute); // 选中新航线
this.$message.success('航线绘制完成!');
},
// 显示在线成员弹窗
showOnlineMembersDialog() {
this.showOnlineMembers = true;
},
// 平台编辑弹窗相关方法
openPlatformDialog(platform) {
this.selectedPlatform = platform;
this.showPlatformDialog = true;
},
updatePlatform(updatedPlatform) {
// 这里可以添加实际的更新逻辑
this.$message.success('平台更新成功');
},
// 航线编辑弹窗相关方法
openRouteDialog(route) {
this.selectedRoute = route;
this.showRouteDialog = true;
},
updateRoute(updatedRoute) {
// 这里可以添加实际的更新逻辑
this.$message.success('航线更新成功');
},
// 航点编辑弹窗相关方法
openWaypointDialog(waypoint) {
this.selectedWaypoint = waypoint;
this.showWaypointDialog = true;
},
updateWaypoint(updatedWaypoint) {
// 这里可以添加实际的更新逻辑
this.$message.success('航点更新成功');
},
updateTime() {
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
this.astroTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
},
updateCombatTime() {
// 模拟作战时间(K时)的更新
// 这里简单模拟,实际应该根据业务逻辑计算
const now = new Date();
const baseSeconds = 5400; // 1小时30分钟 = 5400秒
const currentSeconds = now.getSeconds() + now.getMinutes() * 60 + now.getHours() * 3600;
const combatSeconds = baseSeconds + (currentSeconds % 86400);
const hours = Math.floor(combatSeconds / 3600);
const minutes = Math.floor((combatSeconds % 3600) / 60);
const seconds = combatSeconds % 60;
this.combatTime = `K+${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
},
// 顶部导航菜单操作
selectTopNav(item) {
this.activeTopNav = item.id;
this.$message.info(`选择菜单: ${item.name}`);
},
// 左侧菜单栏操作
showMenu() {
this.isMenuHidden = false;
this.$message.info('显示左侧菜单');
},
hideMenu() {
this.isMenuHidden = true;
this.$message.info('隐藏左侧菜单');
},
// 右侧面板操作
showRightPanel() {
this.isRightPanelHidden = false;
this.$message.info('显示右侧面板');
},
// 文件下拉菜单方法
newPlan() {
this.$message.success('新建计划');
// 这里可以添加新建计划的逻辑
},
openPlan() {
this.$message.success('打开计划');
// 这里可以添加打开计划的逻辑
},
savePlan() {
this.$message.success('保存计划');
// 这里可以添加保存计划的逻辑
},
importPlan() {
this.$message.success('导入计划');
// 这里可以添加导入计划的逻辑
},
// 导入二级菜单方法
importPlanFile() {
this.$message.success('导入计划');
// 这里可以添加导入计划文件的逻辑
},
importACD() {
this.$message.success('导入ACD');
// 这里可以添加导入ACD文件的逻辑
},
importATO() {
this.$message.success('导入ATO');
// 这里可以添加导入ATO文件的逻辑
},
importLayer() {
this.$message.success('导入图层');
// 这里可以添加导入图层的逻辑
},
importRoute() {
this.$message.success('导入航线');
// 这里可以添加导入航线的逻辑
},
exportPlan() {
this.$message.success('导出计划');
// 这里可以添加导出计划的逻辑
},
// 编辑下拉菜单方法
routeEdit() {
this.$message.success('航线编辑');
// 这里可以添加航线编辑的逻辑
},
militaryMarking() {
this.$message.success('军事标绘');
// 这里可以添加军事标绘的逻辑
},
iconEdit() {
this.$message.success('图标编辑');
// 这里可以添加图标编辑的逻辑
},
attributeEdit() {
this.$message.success('属性修改');
// 这里可以添加属性修改的逻辑
},
deductionEdit() {
this.$message.success('推演编辑');
// 这里可以添加推演编辑的逻辑
},
// 推演编辑二级菜单方法
timeSettings() {
this.$message.success('时间设置');
// 这里可以添加时间设置的逻辑
},
aircraftSettings() {
this.$message.success('机型设置');
// 这里可以添加机型设置的逻辑
},
keyEventEdit() {
this.$message.success('关键事件编辑');
// 这里可以添加关键事件编辑的逻辑
},
missileLaunch() {
this.$message.success('导弹发射');
// 这里可以添加导弹发射的逻辑
},
// 视图下拉菜单方法
toggle2D3D() {
this.$message.success('2D/3D切换');
// 这里可以添加2D/3D切换的逻辑
},
toggleRuler() {
this.$message.success('显示/隐藏标尺');
// 这里可以添加标尺显示/隐藏的逻辑
},
toggleGrid() {
this.$message.success('显示/隐藏网格');
// 这里可以添加网格显示/隐藏的逻辑
},
toggleScale() {
this.$message.success('显示/隐藏比例尺');
// 这里可以添加比例尺显示/隐藏的逻辑
},
toggleCompass() {
this.$message.success('显示/隐藏指北针');
// 这里可以添加指北针显示/隐藏的逻辑
},
// 地图下拉菜单方法
loadTerrain() {
this.$message.success('加载/切换地形');
// 这里可以添加地形加载/切换的逻辑
},
changeProjection() {
this.$message.success('投影');
// 这里可以添加投影切换的逻辑
},
loadAeroChart() {
this.$message.success('航空图');
// 这里可以添加航空图加载的逻辑
},
// 工具下拉菜单方法
routeCalculation() {
this.$message.success('航线计算');
// 这里可以添加航线计算的逻辑
},
conflictDisplay() {
this.$message.success('冲突显示');
// 这里可以添加冲突显示的逻辑
},
dataMaterials() {
this.$message.success('数据资料');
// 这里可以添加数据资料管理的逻辑
},
coordinateConversion() {
this.$message.success('坐标换算');
// 这里可以添加坐标换算的逻辑
},
// 选项下拉菜单方法
settings() {
this.$message.success('设置');
// 这里可以添加系统设置的逻辑
},
systemDescription() {
this.$message.success('系统说明');
// 这里可以添加系统说明的逻辑
},
// 收藏下拉菜单方法
layerFavorites() {
this.$message.success('图层收藏');
// 这里可以添加图层收藏的逻辑
},
routeFavorites() {
this.$message.success('航线收藏');
// 这里可以添加航线收藏的逻辑
},
hideRightPanel() {
this.isRightPanelHidden = true;
this.$message.info('隐藏右侧面板');
},
selectMenu(item) {
this.activeMenu = item.id;
if(item.id === 'modify'){
this.drawDom = !this.drawDom
console.log(this.drawDom,999999)
}
if (item.id === 'deduction') {
// 点击推演按钮,显示/隐藏K时弹出框
this.showKTimePopup = !this.showKTimePopup;
if (this.showKTimePopup) {
this.$message.info('显示推演时钟控制');
} else {
this.$message.info('隐藏推演时钟控制');
}
} else {
// 其他菜单的原有逻辑
this.$message.info(`选择菜单: ${item.name}`);
}
},
showTooltip(item) {
this.hoverMenu = item.id;
},
hideTooltip() {
this.hoverMenu = '';
},
getMenuName(id) {
const menu = this.menuItems.find(item => item.id === id);
return menu ? menu.name : '';
},
// K时弹出框操作
hideKTimePopup() {
this.showKTimePopup = false;
this.$message.info('隐藏推演时钟控制');
},
// 时间控制(保留用于底部时间轴)
play() {
this.$message.success('推演开始');
},
pause() {
this.$message.info('推演暂停');
},
reset() {
this.timeProgress = 0;
this.currentTime = 'K+00:00:00';
this.$message.info('推演已重置');
},
formatTimeTooltip(val) {
const hours = Math.floor(val / 4);
const minutes = (val % 4) * 15;
return `K+${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`;
},
// 航线操作
selectRoute(route) {
this.selectedRouteId = route.id;
// 模拟获取航点数据
this.selectedRouteDetails = {
name: route.name,
waypoints: [
{ name: 'WP1', altitude: 5000, speed: '800km/h', eta: 'K+00:40:00' },
{ name: 'WP2', altitude: 6000, speed: '850km/h', eta: 'K+00:55:00' },
{ name: 'WP3', altitude: 5500, speed: '820km/h', eta: 'K+01:10:00' },
{ name: 'WP4', altitude: 5800, speed: '830km/h', eta: 'K+01:25:00' },
]
};
// 打开航线编辑弹窗
this.openRouteDialog(route);
},
addWaypoint() {
if (this.selectedRouteDetails) {
const count = this.selectedRouteDetails.waypoints.length + 1;
this.selectedRouteDetails.waypoints.push({
name: `WP${count}`,
altitude: 5000,
speed: '800km/h',
eta: `K+01:${(count * 15).toString().padStart(2, '0')}:00`
});
this.$message.success('添加航点成功');
}
},
cancelRoute() {
this.selectedRouteId = null;
this.selectedRouteDetails = null;
this.$message.info('已取消选中');
},
// 冲突操作
runConflictCheck() {
this.conflictCount = 2;
this.$message.warning('检测到2处航线冲突');
},
viewConflict(conflict) {
this.$message.info(`查看冲突:${conflict.title}`);
},
resolveConflict(conflict) {
this.$message.success(`解决冲突:${conflict.title}`);
// 移除已解决的冲突
this.conflicts = this.conflicts.filter(c => c.id !== conflict.id);
this.conflictCount = this.conflicts.length;
},
// 系统功能
exportReport() {
this.$message.success('作战报表导出成功');
},
// 新增导入功能
importData() {
this.$message.success('导入数据成功');
}
}
};
</script>
<style scoped>
.mission-planning-container {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
/* 地图背景 - 保持不变 */
.map-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #1a2f4b 0%, #2c3e50 100%);
/* 正确的写法,直接复制这行替换 */
background: url('~@/assets/map-background.png');
background-size: cover;
background-position: center;
z-index: 1;
}
.map-overlay-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: rgba(255, 255, 255, 0.3);
text-align: center;
font-size: 18px;
pointer-events: none;
}
/* 地图中间的浮动红点 - 通用样式 */
.floating-red-dot {
position: absolute;
top: 50%;
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(5px);
border: 2px solid rgba(255, 0, 0, 0.8);
box-shadow: 0 0 20px rgba(255, 0, 0, 0.6);
cursor: pointer;
z-index: 80;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
animation: pulse-red 2s infinite;
}
.floating-red-dot:hover {
transform: translateY(-50%) scale(1.1);
box-shadow: 0 0 25px rgba(255, 0, 0, 0.8);
}
.floating-red-dot.hidden {
display: none;
}
/* 左侧红点 */
.left-red-dot {
left: 20px;
transform: translateY(-50%);
}
/* 右侧红点 */
.right-red-dot {
right: 20px;
transform: translateY(-50%);
}
.red-dot {
width: 20px;
height: 20px;
border-radius: 50%;
background: radial-gradient(circle at 30% 30%, #ff4444, #cc0000);
position: relative;
}
.icon-inside {
position: absolute;
color: white;
font-size: 16px;
}
@keyframes pulse-red {
0% {
box-shadow: 0 0 20px rgba(255, 0, 0, 0.6);
}
50% {
box-shadow: 0 0 25px rgba(255, 0, 0, 0.8);
}
100% {
box-shadow: 0 0 20px rgba(255, 0, 0, 0.6);
}
}
/* 蓝色主题通用类 */
.blue-theme {
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 138, 255, 0.1);
box-shadow: 0 4px 12px rgba(0, 138, 255, 0.2);
}
.blue-btn {
background: rgba(0, 138, 255, 0.8);
border: 1px solid rgba(0, 138, 255, 0.9);
color: white;
transition: all 0.3s;
}
.blue-btn:hover {
background: rgba(0, 138, 255, 0.9);
border-color: rgba(0, 138, 255, 1);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.3);
}
.blue-text-btn {
color: #008aff;
}
.blue-text-btn:hover {
color: #0066cc;
}
.blue-badge {
background: rgba(0, 138, 255, 0.8);
color: white;
}
.blue-tag {
background: rgba(0, 138, 255, 0.2);
color: #008aff;
border: 1px solid rgba(0, 138, 255, 0.3);
}
.blue-time {
background: rgba(0, 138, 255, 0.1);
border: 1px solid rgba(0, 138, 255, 0.3);
color: #008aff;
}
.blue-success {
color: #008aff;
}
.blue-warning {
color: #ff9900;
}
.blue-mark {
color: #008aff;
}
/* 顶部导航栏 - 最终优化设计 */
.floating-header {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 60px;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 100;
backdrop-filter: blur(15px);
background: rgba(255, 255, 255, 0.85);
border-bottom: 1px solid rgba(0, 138, 255, 0.2);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.header-left {
display: flex;
align-items: center;
gap: 25px;
flex: 1;
}
.system-title {
display: flex;
align-items: center;
font-size: 18px;
font-weight: bold;
min-width: 180px;
}
.system-title i {
font-size: 24px;
color: #008aff;
}
.blue-title {
color: #008aff !important;
}
/* 顶部导航菜单 - 优化为简洁文字效果 */
.top-nav-menu {
display: flex;
gap: 0;
flex: 1;
overflow-x: auto;
max-width: 800px;
padding: 5px 0;
scrollbar-width: thin;
}
.top-nav-menu::-webkit-scrollbar {
height: 3px;
}
.top-nav-menu::-webkit-scrollbar-track {
background: rgba(0, 138, 255, 0.1);
border-radius: 2px;
}
.top-nav-menu::-webkit-scrollbar-thumb {
background: rgba(0, 138, 255, 0.3);
border-radius: 2px;
}
.top-nav-item {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
color: #333;
transition: all 0.3s;
border-radius: 4px;
white-space: nowrap;
min-width: 60px;
justify-content: center;
margin: 0 1px;
position: relative;
flex-shrink: 0;
}
.top-nav-item:hover {
color: #008aff;
}
.top-nav-item.active {
color: #008aff;
font-weight: 700;
}
/* 移除了蓝色指示条 */
.top-nav-item.active::after {
display: none;
}
.nav-icon {
font-size: 14px;
color: #333;
transition: all 0.3s;
}
.top-nav-item:hover .nav-icon,
.top-nav-item.active .nav-icon {
color: #008aff;
}
.nav-text {
display: block;
}
/* 右侧区域 */
.header-right {
display: flex;
align-items: center;
gap: 15px;
}
/* 作战信息组 */
.combat-info-group {
display: flex;
gap: 12px;
}
.info-box {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border-radius: 6px;
transition: all 0.3s;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 138, 255, 0.2);
box-shadow: 0 2px 6px rgba(0, 138, 255, 0.1);
min-width: 120px;
}
.info-box:hover {
background: rgba(255, 255, 255, 0.9);
transform: translateY(-1px);
box-shadow: 0 4px 10px rgba(0, 138, 255, 0.2);
border-color: rgba(0, 138, 255, 0.3);
}
.info-icon {
font-size: 16px;
color: #008aff;
min-width: 20px;
text-align: center;
}
.info-content {
display: flex;
flex-direction: column;
flex: 1;
}
.info-label {
font-size: 11px;
color: #666;
font-weight: 500;
margin-bottom: 2px;
white-space: nowrap;
}
.info-value {
font-size: 13px;
font-weight: 700;
color: #333;
white-space: nowrap;
font-family: 'Courier New', monospace;
letter-spacing: 0.5px;
}
/* 作战时间和天文时间的特殊样式 */
.combat-info-group .info-box:nth-child(3) .info-value {
color: #008aff;
}
.combat-info-group .info-box:nth-child(4) .info-value {
color: #52c41a;
}
/* 用户状态区域 */
.user-status-area {
display: flex;
align-items: center;
}
.user-avatar {
border: 2px solid rgba(0, 138, 255, 0.3);
box-shadow: 0 2px 6px rgba(0, 138, 255, 0.15);
transition: all 0.3s;
background: rgba(255, 255, 255, 0.9);
}
.user-avatar:hover {
transform: scale(1.1);
box-shadow: 0 4px 10px rgba(0, 138, 255, 0.25);
border-color: rgba(0, 138, 255, 0.6);
}
/* 左侧菜单栏 - 蓝色主题 */
.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;
}
.hide-btn:hover {
color: #0066cc;
background: rgba(0, 138, 255, 0.1);
transform: scale(1.1);
}
.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: linear-gradient(135deg, rgba(0, 138, 255, 0.2), rgba(0, 138, 255, 0.3));
color: #008aff;
box-shadow: 0 4px 12px rgba(0, 138, 255, 0.25);
}
.menu-tooltip {
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
background: linear-gradient(135deg, rgba(0, 138, 255, 0.95), rgba(0, 102, 204, 0.95));
color: white;
padding: 8px 14px;
border-radius: 6px;
font-size: 12px;
white-space: nowrap;
z-index: 1000;
margin-left: 10px;
box-shadow: 0 4px 12px rgba(0, 138, 255, 0.3);
pointer-events: none;
backdrop-filter: blur(5px);
}
.menu-tooltip-layer {
position: absolute;
top: 0;
left: 100%;
width: 200px;
height: 100%;
z-index: 999;
pointer-events: none;
}
.tooltip-content {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
background: linear-gradient(135deg, rgba(0, 138, 255, 0.95), rgba(0, 102, 204, 0.95));
color: white;
padding: 8px 14px;
border-radius: 6px;
font-size: 12px;
white-space: nowrap;
margin-left: 10px;
box-shadow: 0 4px 12px rgba(0, 138, 255, 0.3);
backdrop-filter: blur(5px);
}
/* 右侧外部隐藏按钮 */
.right-external-hide-btn {
position: absolute;
top: 70px;
right: 320px; /* 面板宽度300px + 外边距20px */
width: 28px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
color: white;
font-size: 16px;
transition: all 0.3s;
background: rgba(0, 138, 255, 0.9);
border-radius: 8px 0 0 8px;
z-index: 91;
border: 1px solid rgba(0, 138, 255, 1);
border-right: none;
box-shadow: -2px 0 8px rgba(0, 138, 255, 0.3);
opacity: 1;
transform: translateX(0);
}
.right-external-hide-btn:hover {
background: rgba(0, 138, 255, 1);
transform: translateX(-2px);
box-shadow: -4px 0 12px rgba(0, 138, 255, 0.5);
}
.right-external-hide-btn.hidden {
opacity: 0;
pointer-events: none;
transform: translateX(20px);
}
/* 右侧浮动面板 - 蓝色主题 */
.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;
}
.right-tabs {
display: flex;
border-bottom: 1px solid rgba(0, 138, 255, 0.3);
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(5px);
}
.right-tab-item {
flex: 1;
text-align: center;
padding: 12px 0;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
transition: all 0.3s;
position: relative;
border-bottom: 3px solid transparent;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(5px);
}
.right-tab-item i {
font-size: 18px;
color: #666;
transition: all 0.3s;
}
.right-tab-item span {
font-size: 12px;
color: #666;
transition: all 0.3s;
}
.right-tab-item:hover {
background: rgba(0, 138, 255, 0.1);
box-shadow: 0 2px 6px rgba(0, 138, 255, 0.15);
}
.right-tab-item.active {
background: rgba(255, 255, 255, 0.95);
border-bottom: 3px solid rgba(0, 138, 255, 0.8);
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.2);
}
.right-tab-item.active i,
.right-tab-item.active span {
color: #008aff;
font-weight: 600;
text-shadow: 0 0 2px rgba(0, 138, 255, 0.3);
}
.badge {
position: absolute;
top: 8px;
right: 20px;
border-radius: 10px;
padding: 2px 8px;
font-size: 10px;
min-width: 16px;
text-align: center;
backdrop-filter: blur(5px);
box-shadow: 0 2px 4px rgba(0, 138, 255, 0.3);
}
.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: 12px;
color: #666;
margin-bottom: 10px;
padding-bottom: 6px;
border-bottom: 1px solid rgba(0, 138, 255, 0.3);
font-weight: 600;
}
.route-list {
margin-bottom: 20px;
}
.route-item {
display: flex;
align-items: center;
padding: 10px;
border-radius: 6px;
margin-bottom: 6px;
cursor: pointer;
transition: all 0.3s;
border: 1px solid transparent;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(5px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.route-item:hover {
background: rgba(0, 138, 255, 0.1);
border-color: rgba(0, 138, 255, 0.3);
transform: translateY(-1px);
box-shadow: 0 4px 10px rgba(0, 138, 255, 0.15);
}
.route-item.selected {
background: linear-gradient(135deg, rgba(0, 138, 255, 0.15), rgba(0, 138, 255, 0.25));
border-color: rgba(0, 138, 255, 0.4);
box-shadow: 0 4px 12px rgba(0, 138, 255, 0.2);
}
.route-item i {
font-size: 18px;
color: #008aff;
margin-right: 10px;
text-shadow: 0 0 4px rgba(0, 138, 255, 0.3);
}
.route-info {
flex: 1;
}
.route-name {
font-size: 13px;
color: #333;
margin-bottom: 3px;
font-weight: 600;
}
.route-meta {
font-size: 11px;
color: #666;
}
.conflict-tag {
background: linear-gradient(135deg, #f56c6c, #c45656);
border: none;
color: white;
backdrop-filter: blur(5px);
box-shadow: 0 2px 4px rgba(245, 108, 108, 0.3);
}
.waypoint-list {
margin-bottom: 15px;
}
.waypoint-item {
display: flex;
align-items: center;
padding: 10px;
border-radius: 6px;
margin-bottom: 6px;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(0, 138, 255, 0.3);
backdrop-filter: blur(5px);
box-shadow: 0 2px 4px rgba(0, 138, 255, 0.1);
transition: all 0.3s;
}
.waypoint-item:hover {
transform: translateY(-1px);
box-shadow: 0 4px 10px rgba(0, 138, 255, 0.15);
}
.waypoint-item i {
font-size: 18px;
color: #52c41a;
margin-right: 10px;
text-shadow: 0 0 4px rgba(82, 196, 26, 0.3);
}
.waypoint-info {
flex: 1;
}
.waypoint-name {
font-size: 13px;
color: #333;
margin-bottom: 3px;
font-weight: 600;
}
.waypoint-meta {
font-size: 11px;
color: #666;
}
.waypoint-actions {
display: flex;
gap: 10px;
color: #666;
}
.waypoint-actions i {
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
padding: 4px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(5px);
}
.waypoint-actions i:hover {
color: #008aff;
transform: scale(1.1);
box-shadow: 0 2px 6px rgba(0, 138, 255, 0.2);
}
.action-buttons {
display: flex;
gap: 10px;
justify-content: center;
}
/* 按钮样式调整 */
.el-button {
backdrop-filter: blur(5px);
transition: all 0.3s;
}
/* 冲突内容样式 */
.conflict-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.conflict-item {
background: linear-gradient(135deg, rgba(245, 108, 108, 0.1), rgba(245, 108, 108, 0.05));
border: 1px solid rgba(245, 108, 108, 0.3);
border-radius: 8px;
padding: 14px;
backdrop-filter: blur(5px);
box-shadow: 0 4px 10px rgba(245, 108, 108, 0.15);
transition: all 0.3s;
}
.conflict-item:hover {
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(245, 108, 108, 0.2);
}
.conflict-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.conflict-title {
flex: 1;
font-size: 13px;
color: #333;
font-weight: 700;
}
.conflict-details {
background: rgba(255, 255, 255, 0.8);
border-radius: 6px;
padding: 10px;
margin-bottom: 10px;
border: 1px solid rgba(255, 255, 255, 0.4);
backdrop-filter: blur(5px);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.detail-item {
display: flex;
margin-bottom: 5px;
font-size: 12px;
}
.detail-item:last-child {
margin-bottom: 0;
}
.detail-item .label {
color: #666;
min-width: 70px;
font-weight: 500;
}
.detail-item .value {
color: #333;
flex: 1;
font-weight: 600;
}
.conflict-actions {
display: flex;
justify-content: flex-end;
gap: 15px;
}
.no-conflict {
text-align: center;
padding: 40px 20px;
color: #666;
}
.no-conflict p {
margin: 10px 0 15px;
}
/* 平台内容样式 */
.platform-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.platform-item {
display: flex;
align-items: center;
padding: 12px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(0, 138, 255, 0.3);
transition: all 0.3s;
backdrop-filter: blur(5px);
box-shadow: 0 2px 6px rgba(0, 138, 255, 0.1);
}
.platform-item:hover {
background: rgba(0, 138, 255, 0.1);
border-color: rgba(0, 138, 255, 0.4);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 138, 255, 0.2);
}
.platform-icon {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
margin-right: 12px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(5px);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.platform-info {
flex: 1;
}
.platform-name {
font-size: 13px;
color: #333;
margin-bottom: 3px;
font-weight: 600;
}
.platform-type {
font-size: 11px;
color: #666;
}
.platform-status {
margin-left: 10px;
}
.status-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
box-shadow: 0 0 6px currentColor;
}
.status-dot.ready {
background: #52c41a;
box-shadow: 0 0 8px rgba(82, 196, 26, 0.8);
}
.status-dot.flying {
background: #008aff;
animation: pulse 2s infinite;
box-shadow: 0 0 10px rgba(0, 138, 255, 0.8);
}
.status-dot.scouting {
background: #fa8c16;
animation: pulse 1.5s infinite;
box-shadow: 0 0 10px rgba(250, 140, 22, 0.8);
}
.status-dot.sailing {
background: #008aff;
box-shadow: 0 0 8px rgba(0, 138, 255, 0.8);
}
.status-dot.patrol {
background: #52c41a;
animation: pulse 2s infinite;
box-shadow: 0 0 10px rgba(82, 196, 26, 0.8);
}
.status-dot.hidden {
background: #333;
box-shadow: 0 0 6px rgba(51, 51, 51, 0.8);
}
.status-dot.alert {
background: #f5222d;
animation: pulse 1s infinite;
box-shadow: 0 0 10px rgba(245, 34, 45, 0.8);
}
/* 二级菜单样式 */
.submenu-item {
position: relative;
}
.submenu-dropdown {
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
}
.submenu-trigger {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.submenu {
margin-left: 5px;
margin-bottom: 0;
border: none;
border-radius: 6px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(15px);
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(0, 138, 255, 0.2);
padding: 0;
min-width: auto;
width: fit-content;
}
.submenu .el-dropdown-menu__item {
padding: 8px 16px;
font-size: 14px;
color: #333;
transition: all 0.2s ease;
margin: 0;
}
.submenu .el-dropdown-menu__item:hover {
background: rgba(0, 138, 255, 0.1);
color: #008aff;
}
.submenu .el-dropdown-menu__item:not(:last-child) {
border-bottom: 1px solid rgba(0, 138, 255, 0.1);
}
.submenu .el-dropdown-menu__item:last-child {
border-bottom: none;
}
/* 文件下拉菜单样式 */
.file-dropdown {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.dropdown-trigger {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
cursor: pointer;
}
.file-dropdown-menu {
margin-top: 5px;
margin-bottom: 0;
border: none;
border-radius: 6px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(15px);
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(0, 138, 255, 0.2);
padding: 0;
min-width: auto;
width: fit-content;
}
.file-dropdown-menu .el-dropdown-menu__item {
padding: 8px 16px;
font-size: 14px;
color: #333;
transition: all 0.2s ease;
margin: 0;
}
.file-dropdown-menu .el-dropdown-menu__item:hover {
background: rgba(0, 138, 255, 0.1);
color: #008aff;
}
.file-dropdown-menu .el-dropdown-menu__item:not(:last-child) {
border-bottom: 1px solid rgba(0, 138, 255, 0.1);
}
.file-dropdown-menu .el-dropdown-menu__item:last-child {
border-bottom: none;
}
/* 顶部导航菜单调整,为下拉菜单留出空间 */
.top-nav-item {
position: relative;
}
.status-dot.operating {
background: #008aff;
animation: pulse 2s infinite;
box-shadow: 0 0 10px rgba(0, 138, 255, 0.8);
}
/* 蓝色主题标签页 */
.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);
}
/* 底部时间轴(最初版本的样式)- 蓝色主题 */
.floating-timeline {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%) translateY(100%);
width: 80%;
border-radius: 12px;
z-index: 95;
padding: 20px;
color: #333;
transition: all 0.3s ease;
opacity: 0;
pointer-events: none;
backdrop-filter: blur(15px);
background: rgba(255, 255, 255, 0.95);
box-shadow: 0 8px 32px rgba(0, 138, 255, 0.25);
border: 1px solid rgba(0, 138, 255, 0.3);
}
.floating-timeline.show {
transform: translateX(-50%) translateY(0);
opacity: 1;
pointer-events: auto;
}
.popup-hide-btn {
position: absolute;
top: -28px;
left: 50%;
transform: translateX(-50%);
width: 56px;
height: 28px;
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 138, 255, 0.4);
border-bottom: none;
border-radius: 12px 12px 0 0;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #008aff;
font-size: 18px;
transition: all 0.3s;
z-index: 10;
}
.popup-hide-btn:hover {
background: rgba(0, 138, 255, 0.2);
color: #0066cc;
height: 32px;
top: -32px;
}
.timeline-controls {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.current-time {
display: flex;
align-items: center;
gap: 8px;
font-family: monospace;
font-size: 14px;
font-weight: 500;
padding: 4px 12px;
border-radius: 4px;
}
.timeline-slider {
flex: 1;
margin: 0 20px;
}
.compact-slider {
width: 100%;
}
.blue-slider >>> .el-slider__bar {
background-color: rgba(0, 138, 255, 0.8);
}
.blue-slider >>> .el-slider__button {
border-color: rgba(0, 138, 255, 0.8);
background-color: rgba(0, 138, 255, 0.8);
}
.timeline-marks {
display: flex;
justify-content: space-between;
font-size: 11px;
color: #666;
margin-top: 5px;
}
.mark.current {
font-weight: bold;
}
.system-status {
display: flex;
justify-content: center;
gap: 30px;
font-size: 12px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid rgba(0, 138, 255, 0.4);
}
.status-item {
display: flex;
align-items: center;
gap: 5px;
}
.status-label {
color: #666;
}
.status-value.success {
font-weight: 500;
}
.status-value.warning {
font-weight: 500;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: rgba(0, 138, 255, 0.1);
border-radius: 3px;
backdrop-filter: blur(5px);
}
::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, rgba(0, 138, 255, 0.5), rgba(0, 138, 255, 0.7));
border-radius: 3px;
box-shadow: 0 0 6px rgba(0, 138, 255, 0.4);
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, rgba(0, 138, 255, 0.7), rgba(0, 138, 255, 0.9));
}
.ml-3 {
margin-left: 10px;
}
</style>