|
|
|
|
<template>
|
|
|
|
|
<!-- 以地图为绝对定位背景,所有组件浮动其上 -->
|
|
|
|
|
<div class="mission-planning-container">
|
|
|
|
|
<!-- 地图背景 -->
|
|
|
|
|
<div id="gis-map-background" class="map-background">
|
|
|
|
|
<!-- cesiummap组件 -->
|
|
|
|
|
<cesiumMap ref="cesiumMap" :drawDomClick="drawDom" @draw-complete="handleMapDrawComplete"
|
|
|
|
|
@open-waypoint-dialog="handleOpenWaypointEdit" />
|
|
|
|
|
<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
|
|
|
|
|
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>
|
|
|
|
|
<el-dialog title="保存新航线" :visible.sync="showNameDialog" width="30%" :append-to-body="true">
|
|
|
|
|
<el-form label-width="80px">
|
|
|
|
|
<el-form-item label="航线名称">
|
|
|
|
|
<el-input v-model="newRouteName" placeholder="例如:航线一"></el-input>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
<div slot="footer" class="dialog-footer">
|
|
|
|
|
<el-button @click="showNameDialog = false">取 消</el-button>
|
|
|
|
|
<el-button type="primary" @click="confirmSaveNewRoute">确 定</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 顶部导航栏 -->
|
|
|
|
|
<top-header
|
|
|
|
|
:room-code="roomCode"
|
|
|
|
|
:online-count="onlineCount"
|
|
|
|
|
:combat-time="combatTime"
|
|
|
|
|
:astro-time="astroTime"
|
|
|
|
|
:user-avatar="userAvatar"
|
|
|
|
|
@select-nav="selectTopNav"
|
|
|
|
|
@save-plan="savePlan"
|
|
|
|
|
@import-plan-file="importPlanFile"
|
|
|
|
|
@import-acd="importACD"
|
|
|
|
|
@import-ato="importATO"
|
|
|
|
|
@import-layer="importLayer"
|
|
|
|
|
@import-route="importRoute"
|
|
|
|
|
@export-plan="exportPlan"
|
|
|
|
|
@route-edit="routeEdit"
|
|
|
|
|
@military-marking="militaryMarking"
|
|
|
|
|
@icon-edit="iconEdit"
|
|
|
|
|
@toggle-icon-edit="toggleIconEditMode"
|
|
|
|
|
@attribute-edit="attributeEdit"
|
|
|
|
|
@time-settings="timeSettings"
|
|
|
|
|
@aircraft-settings="aircraftSettings"
|
|
|
|
|
@key-event-edit="keyEventEdit"
|
|
|
|
|
@missile-launch="missileLaunch"
|
|
|
|
|
@toggle-2d-3d="toggle2D3D"
|
|
|
|
|
@toggle-ruler="toggleRuler"
|
|
|
|
|
@toggle-grid="toggleGrid"
|
|
|
|
|
@save-scale="saveScale"
|
|
|
|
|
@load-terrain="loadTerrain"
|
|
|
|
|
@change-projection="changeProjection"
|
|
|
|
|
@load-aero-chart="loadAeroChart"
|
|
|
|
|
@save-power-zone="savePowerZone"
|
|
|
|
|
@threat-zone="threatZone"
|
|
|
|
|
@route-calculation="routeCalculation"
|
|
|
|
|
@conflict-display="conflictDisplay"
|
|
|
|
|
@data-materials="dataMaterials"
|
|
|
|
|
@coordinate-conversion="coordinateConversion"
|
|
|
|
|
@page-layout="pageLayout"
|
|
|
|
|
@data-storage-path="dataStoragePath"
|
|
|
|
|
@save-external-params="saveExternalParams"
|
|
|
|
|
@import-airport="importAirport"
|
|
|
|
|
@import-route-data="importRouteData"
|
|
|
|
|
@import-landmark="importLandmark"
|
|
|
|
|
@toggle-airport="toggleAirport"
|
|
|
|
|
@toggle-landmark="toggleLandmark"
|
|
|
|
|
@toggle-route="toggleRoute"
|
|
|
|
|
@system-description="systemDescription"
|
|
|
|
|
@layer-favorites="layerFavorites"
|
|
|
|
|
@route-favorites="routeFavorites"
|
|
|
|
|
@show-online-members="showOnlineMembersDialog"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<!-- 左侧折叠菜单栏 - 蓝色主题 -->
|
|
|
|
|
<left-menu
|
|
|
|
|
:is-hidden="isMenuHidden"
|
|
|
|
|
:menu-items="menuItems"
|
|
|
|
|
:active-menu="activeMenu"
|
|
|
|
|
:is-edit-mode="isIconEditMode"
|
|
|
|
|
:available-icons="topNavItems"
|
|
|
|
|
:position="menuPosition"
|
|
|
|
|
@hide="hideMenu"
|
|
|
|
|
@select="selectMenu"
|
|
|
|
|
@menu-action="handleMenuAction"
|
|
|
|
|
@update:menu-items="updateMenuItems"
|
|
|
|
|
@drag-end="handleMenuDragEnd"
|
|
|
|
|
@add-items="handleAddMenuItems"
|
|
|
|
|
@delete="handleDeleteMenuItem"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<!-- 右侧实体列表(浮动)- 蓝色主题 -->
|
|
|
|
|
<right-panel
|
|
|
|
|
:is-hidden="isRightPanelHidden"
|
|
|
|
|
:active-tab="activeRightTab"
|
|
|
|
|
:plans="plans"
|
|
|
|
|
:selected-plan-id="selectedPlanId"
|
|
|
|
|
:selected-plan-details="selectedPlanDetails"
|
|
|
|
|
:selected-route-id="selectedRouteId"
|
|
|
|
|
:routes="routes"
|
|
|
|
|
:active-route-ids="activeRouteIds"
|
|
|
|
|
:selected-route-details="selectedRouteDetails"
|
|
|
|
|
:conflicts="conflicts"
|
|
|
|
|
:conflict-count="conflictCount"
|
|
|
|
|
:air-platforms="airPlatforms"
|
|
|
|
|
:sea-platforms="seaPlatforms"
|
|
|
|
|
:ground-platforms="groundPlatforms"
|
|
|
|
|
@hide="hideRightPanel"
|
|
|
|
|
@select-plan="selectPlan"
|
|
|
|
|
@select-route="selectRoute"
|
|
|
|
|
@create-plan="createPlan"
|
|
|
|
|
@create-route="createRoute"
|
|
|
|
|
@delete-route="handleDeleteRoute"
|
|
|
|
|
@open-plan-dialog="openPlanDialog"
|
|
|
|
|
@open-route-dialog="openRouteDialog"
|
|
|
|
|
@open-waypoint-dialog="openWaypointDialog"
|
|
|
|
|
@add-waypoint="addWaypoint"
|
|
|
|
|
@cancel-route="cancelRoute"
|
|
|
|
|
@view-conflict="viewConflict"
|
|
|
|
|
@resolve-conflict="resolveConflict"
|
|
|
|
|
@run-conflict-check="runConflictCheck"
|
|
|
|
|
@open-platform-dialog="openPlatformDialog"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<!-- 左下角工具面板 -->
|
|
|
|
|
<bottom-left-panel />
|
|
|
|
|
|
|
|
|
|
<!-- 底部时间轴(最初版本的样式)- 蓝色主题 -->
|
|
|
|
|
<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="playback-controls">
|
|
|
|
|
<button
|
|
|
|
|
class="control-btn blue-control-btn"
|
|
|
|
|
@click="togglePlay"
|
|
|
|
|
:title="isPlaying ? '暂停' : '播放'"
|
|
|
|
|
>
|
|
|
|
|
<i :class="isPlaying ? 'el-icon-video-pause' : 'el-icon-video-play'"></i>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<div class="speed-control">
|
|
|
|
|
<button
|
|
|
|
|
class="control-btn blue-control-btn"
|
|
|
|
|
@click="decreaseSpeed"
|
|
|
|
|
:disabled="playbackSpeed <= 1"
|
|
|
|
|
title="减速"
|
|
|
|
|
>
|
|
|
|
|
<i class="el-icon-arrow-down"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<span class="speed-text">{{ playbackSpeed }}x</span>
|
|
|
|
|
<button
|
|
|
|
|
class="control-btn blue-control-btn"
|
|
|
|
|
@click="increaseSpeed"
|
|
|
|
|
:disabled="playbackSpeed >= 25"
|
|
|
|
|
title="加速"
|
|
|
|
|
>
|
|
|
|
|
<i class="el-icon-arrow-up"></i>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</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"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<!-- 威力区弹窗 -->
|
|
|
|
|
<power-zone-dialog
|
|
|
|
|
v-model="showPowerZoneDialog"
|
|
|
|
|
:power-zone="currentPowerZone"
|
|
|
|
|
@save="savePowerZone"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<!-- 比例尺弹窗 -->
|
|
|
|
|
<scale-dialog
|
|
|
|
|
v-model="showScaleDialog"
|
|
|
|
|
:scale="currentScale"
|
|
|
|
|
@save="saveScale"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<!-- 外部参数弹窗 -->
|
|
|
|
|
<external-params-dialog
|
|
|
|
|
v-model="showExternalParamsDialog"
|
|
|
|
|
:external-params="currentExternalParams"
|
|
|
|
|
@save="saveExternalParams"
|
|
|
|
|
@import-airport="importAirport"
|
|
|
|
|
@import-route-data="importRoute"
|
|
|
|
|
@import-landmark="importLandmark"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<!-- 页面布局弹窗 -->
|
|
|
|
|
<page-layout-dialog
|
|
|
|
|
v-model="showPageLayoutDialog"
|
|
|
|
|
@save="savePageLayout"
|
|
|
|
|
/>
|
|
|
|
|
</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'
|
|
|
|
|
import PowerZoneDialog from '@/views/dialogs/PowerZoneDialog'
|
|
|
|
|
import ScaleDialog from '@/views/dialogs/ScaleDialog'
|
|
|
|
|
import ExternalParamsDialog from '@/views/dialogs/ExternalParamsDialog'
|
|
|
|
|
import PageLayoutDialog from '@/views/dialogs/PageLayoutDialog'
|
|
|
|
|
import LeftMenu from './LeftMenu'
|
|
|
|
|
import RightPanel from './RightPanel'
|
|
|
|
|
import BottomLeftPanel from './BottomLeftPanel'
|
|
|
|
|
import TopHeader from './TopHeader'
|
|
|
|
|
import { listRoutes, getRoutes, addRoutes,delRoutes } from "@/api/system/routes";
|
|
|
|
|
import { updateWaypoints } from "@/api/system/waypoints";
|
|
|
|
|
export default {
|
|
|
|
|
name: 'MissionPlanningView',
|
|
|
|
|
components: {
|
|
|
|
|
cesiumMap,
|
|
|
|
|
OnlineMembersDialog,
|
|
|
|
|
PlatformEditDialog,
|
|
|
|
|
RouteEditDialog,
|
|
|
|
|
WaypointEditDialog,
|
|
|
|
|
PowerZoneDialog,
|
|
|
|
|
ScaleDialog,
|
|
|
|
|
ExternalParamsDialog,
|
|
|
|
|
PageLayoutDialog,
|
|
|
|
|
LeftMenu,
|
|
|
|
|
RightPanel,
|
|
|
|
|
BottomLeftPanel,
|
|
|
|
|
TopHeader
|
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
drawDom:false,
|
|
|
|
|
// 在线成员弹窗
|
|
|
|
|
showOnlineMembers: false,
|
|
|
|
|
// 编辑弹窗控制
|
|
|
|
|
showPlatformDialog: false,
|
|
|
|
|
selectedPlatform: null,
|
|
|
|
|
showRouteDialog: false,
|
|
|
|
|
selectedRoute: null,
|
|
|
|
|
showWaypointDialog: false,
|
|
|
|
|
selectedWaypoint: null,
|
|
|
|
|
// 威力区、比例尺、外部参数弹窗
|
|
|
|
|
showPowerZoneDialog: false,
|
|
|
|
|
currentPowerZone: {},
|
|
|
|
|
showScaleDialog: false,
|
|
|
|
|
currentScale: {},
|
|
|
|
|
showExternalParamsDialog: false,
|
|
|
|
|
currentExternalParams: {},
|
|
|
|
|
showPageLayoutDialog: false,
|
|
|
|
|
menuPosition: 'left',
|
|
|
|
|
showNameDialog: false,
|
|
|
|
|
newRouteName: '',
|
|
|
|
|
tempMapPoints: [],
|
|
|
|
|
|
|
|
|
|
// 作战信息
|
|
|
|
|
roomCode: 'JTF-7-ALPHA',
|
|
|
|
|
onlineCount: 30,
|
|
|
|
|
combatTime: 'K+01:30:45',
|
|
|
|
|
astroTime: '',
|
|
|
|
|
|
|
|
|
|
// 左侧菜单栏
|
|
|
|
|
isMenuHidden: true, // 是否完全隐藏左侧菜单
|
|
|
|
|
activeMenu: 'file',
|
|
|
|
|
isIconEditMode: false, // 图标编辑模式
|
|
|
|
|
|
|
|
|
|
// 方案-航线-航点数据
|
|
|
|
|
selectedPlanId: null,
|
|
|
|
|
selectedPlanDetails: null,
|
|
|
|
|
|
|
|
|
|
// 顶部导航菜单项(用于图标选择)- 只显示指定的菜单项
|
|
|
|
|
topNavItems: [
|
|
|
|
|
{ id: 'routeEdit', name: '航线编辑', icon: 'el-icon-edit-outline' },
|
|
|
|
|
{ id: 'militaryMarking', name: '军事标绘', icon: 'el-icon-crop' },
|
|
|
|
|
{ id: 'attributeEdit', name: '属性修改', icon: 'el-icon-setting' },
|
|
|
|
|
{ id: 'timeSettings', name: '时间设置', icon: 'el-icon-timer' },
|
|
|
|
|
{ id: 'aircraftSettings', name: '机型设置', icon: 'el-icon-s-custom' },
|
|
|
|
|
{ id: 'keyEventEdit', name: '关键事件编辑', icon: 'el-icon-edit' },
|
|
|
|
|
{ id: 'missileLaunch', name: '导弹发射', icon: 'el-icon-s-promotion' },
|
|
|
|
|
{ id: 'toggle2D3D', name: '2D/3D切换', icon: 'el-icon-picture-outline' },
|
|
|
|
|
{ id: 'toggleRuler', name: '显示/隐藏标尺', icon: 'el-icon-set-up' },
|
|
|
|
|
{ id: 'toggleGrid', name: '网格', icon: 'el-icon-s-grid' },
|
|
|
|
|
{ id: 'toggleScale', name: '比例尺', icon: 'el-icon-data-line' },
|
|
|
|
|
{ id: 'loadTerrain', name: '加载/切换地形', icon: 'el-icon-picture-outline-round' },
|
|
|
|
|
{ id: 'changeProjection', name: '投影', icon: 'el-icon-aim' },
|
|
|
|
|
{ id: 'loadAeroChart', name: '航空图', icon: 'el-icon-map-location' },
|
|
|
|
|
{ id: 'powerZone', name: '威力区', icon: 'el-icon-warning-outline' },
|
|
|
|
|
{ id: 'threatZone', name: '威胁区', icon: 'el-icon-warning' },
|
|
|
|
|
{ id: 'routeCalculation', name: '航线计算', icon: 'el-icon-s-data' },
|
|
|
|
|
{ id: 'conflictDisplay', name: '冲突显示', icon: 'el-icon-error' },
|
|
|
|
|
{ id: 'dataMaterials', name: '数据资料', icon: 'el-icon-folder' },
|
|
|
|
|
{ id: 'coordinateConversion', name: '坐标换算', icon: 'el-icon-coordinate' },
|
|
|
|
|
{ id: 'pageLayout', name: '页面布局', icon: 'el-icon-menu' },
|
|
|
|
|
{ id: 'dataStoragePath', name: '数据存储路径', icon: 'el-icon-folder-opened' },
|
|
|
|
|
{ id: 'externalParams', name: '外部参数', icon: 'el-icon-s-tools' },
|
|
|
|
|
{ id: 'toggleAirport', name: '显示/隐藏机场', icon: 'el-icon-s-flag' },
|
|
|
|
|
{ id: 'toggleLandmark', name: '显示/隐藏地标', icon: 'el-icon-location-outline' },
|
|
|
|
|
{ id: 'toggleRoute', name: '显示/隐藏航线', icon: 'el-icon-position' },
|
|
|
|
|
{ id: 'systemDescription', name: '系统说明', icon: 'el-icon-info' },
|
|
|
|
|
{ id: 'layerFavorites', name: '图层收藏', icon: 'el-icon-star-off' },
|
|
|
|
|
{ id: 'routeFavorites', name: '航线收藏', icon: 'el-icon-collection' }
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
// 右侧面板控制
|
|
|
|
|
isRightPanelHidden: true, // 是否完全隐藏右侧面板
|
|
|
|
|
|
|
|
|
|
// K时弹出框控制
|
|
|
|
|
showKTimePopup: false,
|
|
|
|
|
|
|
|
|
|
// 显示/隐藏控制
|
|
|
|
|
showAirport: true,
|
|
|
|
|
showLandmark: true,
|
|
|
|
|
showRoute: true,
|
|
|
|
|
|
|
|
|
|
menuItems: [
|
|
|
|
|
{ id: 'file', name: '方案', icon: 'el-icon-folder-opened' },
|
|
|
|
|
{ id: 'start', name: '冲突', icon: 'el-icon-error' },
|
|
|
|
|
{ id: 'insert', name: '平台', icon: 'el-icon-s-platform' },
|
|
|
|
|
{ id: 'pattern', name: '图案', icon: 'el-icon-picture-outline-round' },
|
|
|
|
|
{ id: 'deduction', name: '推演', icon: 'el-icon-video-play' },
|
|
|
|
|
{ id: 'modify', name: '修改', icon: 'el-icon-edit-outline' },
|
|
|
|
|
{ id: 'refresh', name: '刷新', icon: 'el-icon-refresh' },
|
|
|
|
|
{ id: 'basemap', name: '底图', icon: 'el-icon-picture' },
|
|
|
|
|
{ id: 'save', name: '保存', icon: 'el-icon-document-checked' },
|
|
|
|
|
{ id: 'import', name: '导入', icon: 'el-icon-upload2' },
|
|
|
|
|
{ id: 'export', name: '导出', icon: 'el-icon-download' }
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
// 右侧面板
|
|
|
|
|
activeRightTab: 'plan',
|
|
|
|
|
activeRouteIds: [], // 存储当前所有选中的航线ID
|
|
|
|
|
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-s-cooperation', 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-s-help', 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' },
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
// 航线数据 - 改为方案-航线-航点三级结构
|
|
|
|
|
plans: [
|
|
|
|
|
{
|
|
|
|
|
id: 1,
|
|
|
|
|
name: '方案A',
|
|
|
|
|
routes: [
|
|
|
|
|
{ id: 101, name: 'Alpha进场航线', points: 8, conflict: true },
|
|
|
|
|
{ id: 102, name: 'Beta巡逻航线', points: 6, conflict: false },
|
|
|
|
|
{ id: 103, name: '侦察覆盖区', points: 4, conflict: false },
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 2,
|
|
|
|
|
name: '方案B',
|
|
|
|
|
routes: [
|
|
|
|
|
{ id: 201, name: 'Gamma突击航线', points: 10, conflict: false },
|
|
|
|
|
{ id: 202, name: 'Delta支援航线', points: 5, conflict: true },
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
// 时间控制
|
|
|
|
|
timeProgress: 45,
|
|
|
|
|
currentTime: 'K+01:15:30',
|
|
|
|
|
isPlaying: false,
|
|
|
|
|
playbackSpeed: 1,
|
|
|
|
|
playbackInterval: null,
|
|
|
|
|
|
|
|
|
|
// 用户
|
|
|
|
|
userAvatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
mounted() {
|
|
|
|
|
this.getList();
|
|
|
|
|
// 初始化时左侧菜单隐藏
|
|
|
|
|
this.isMenuHidden = true;
|
|
|
|
|
// 初始化时右侧面板隐藏
|
|
|
|
|
this.isRightPanelHidden = true;
|
|
|
|
|
// 初始化选中的方案
|
|
|
|
|
this.selectPlan(this.plans[0]);
|
|
|
|
|
|
|
|
|
|
// 更新时间
|
|
|
|
|
this.updateTime();
|
|
|
|
|
setInterval(this.updateTime, 1000);
|
|
|
|
|
// 作战时间也需要实时更新
|
|
|
|
|
setInterval(this.updateCombatTime, 1000);
|
|
|
|
|
},
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
// 清除播放定时器
|
|
|
|
|
if (this.playbackInterval) {
|
|
|
|
|
clearInterval(this.playbackInterval);
|
|
|
|
|
this.playbackInterval = null;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
// 处理从地图点击传来的编辑请求
|
|
|
|
|
handleOpenWaypointEdit(wpName) {
|
|
|
|
|
// 1. 确保当前有选中的航线详情
|
|
|
|
|
if (!this.selectedRouteDetails || !this.selectedRouteDetails.waypoints) {
|
|
|
|
|
this.$message.warning('请先在右侧列表选择一条航线');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 2. 根据点击的航点名称在数据列表中查找
|
|
|
|
|
const wpData = this.selectedRouteDetails.waypoints.find(item => item.name === wpName);
|
|
|
|
|
if (wpData) {
|
|
|
|
|
// 3. 深拷贝给编辑弹窗绑定的变量,防止直接修改原始数组
|
|
|
|
|
this.selectedWaypoint = JSON.parse(JSON.stringify(wpData));
|
|
|
|
|
// 4. 打开弹窗
|
|
|
|
|
this.showWaypointDialog = true;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this.$message.info('未找到该航点的业务数据');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 显示在线成员弹窗
|
|
|
|
|
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) {
|
|
|
|
|
const index = this.routes.findIndex(r => r.id === updatedRoute.id);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
// 使用 splice 触发响应式更新
|
|
|
|
|
const newRouteData = { ...this.routes[index], ...updatedRoute };
|
|
|
|
|
this.routes.splice(index, 1, newRouteData);
|
|
|
|
|
|
|
|
|
|
// 如果当前选中的是这条航线,同步更新详情中的名称
|
|
|
|
|
if (this.selectedRouteDetails && this.selectedRouteId === updatedRoute.id) {
|
|
|
|
|
this.selectedRouteDetails.name = updatedRoute.name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.$message.success('航线名称更新成功');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 新建航线
|
|
|
|
|
createRoute() {
|
|
|
|
|
if (this.$refs.cesiumMap) {
|
|
|
|
|
this.$refs.cesiumMap.startMissionRouteDrawing();
|
|
|
|
|
this.$message.success('进入航线规划模式');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async handleDeleteRoute(route) {
|
|
|
|
|
try {
|
|
|
|
|
// 二次确认,防止误删
|
|
|
|
|
await this.$confirm(`确定要彻底删除航线 "${route.name}" 吗?`, '提示', {
|
|
|
|
|
type: 'warning'
|
|
|
|
|
});
|
|
|
|
|
if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) {
|
|
|
|
|
this.selectedRouteDetails = null;
|
|
|
|
|
}
|
|
|
|
|
const res = await delRoutes(route.id);
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
this.$message.success('删除成功');
|
|
|
|
|
// 同步地图:如果该航线正在显示,立即清除
|
|
|
|
|
if (this.$refs.cesiumMap) {
|
|
|
|
|
this.$refs.cesiumMap.removeRouteById(route.id);
|
|
|
|
|
}
|
|
|
|
|
// 同步状态:从选中列表中移除该 ID
|
|
|
|
|
const idx = this.activeRouteIds.indexOf(route.id);
|
|
|
|
|
if (idx > -1) {
|
|
|
|
|
this.activeRouteIds.splice(idx, 1);
|
|
|
|
|
}
|
|
|
|
|
await this.getList();
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
if (e !== 'cancel') {
|
|
|
|
|
console.error("删除航线失败:", e);
|
|
|
|
|
this.$message.error('删除操作失败');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
/** 从数据库拉取最新的航线列表数据 */
|
|
|
|
|
async getList() {
|
|
|
|
|
const query = { scenarioId: 1 };
|
|
|
|
|
try {
|
|
|
|
|
const response = await listRoutes(query);
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
this.routes = response.rows.map(item => ({
|
|
|
|
|
id: item.id,
|
|
|
|
|
name: item.callSign,
|
|
|
|
|
points: item.waypoints ? item.waypoints.length : 0,
|
|
|
|
|
conflict: false
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
this.$message.error("获取航线列表失败");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
handleMapDrawComplete(points) {
|
|
|
|
|
if (!points || points.length < 2) {
|
|
|
|
|
this.$message.error('航点太少,无法生成航线');
|
|
|
|
|
this.drawDom = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.tempMapPoints = points; // 暂存坐标点
|
|
|
|
|
this.showNameDialog = true; // 弹出对话框起名
|
|
|
|
|
},
|
|
|
|
|
/** 弹窗点击“确定”:正式将数据保存到后端数据库 */
|
|
|
|
|
async confirmSaveNewRoute() {
|
|
|
|
|
// 严格校验起名逻辑
|
|
|
|
|
if (!this.newRouteName || this.newRouteName.trim() === '') {
|
|
|
|
|
this.$message.error('新增航线未命名,请输入名称后保存!');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 获取当前选中的方案 ID
|
|
|
|
|
const currentScenarioId = this.selectedPlanId;
|
|
|
|
|
if (!currentScenarioId) {
|
|
|
|
|
this.$message.warning('请先在左侧选择一个方案,再保存航线!');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 构造符合后端 Routes 实体类的数据结构
|
|
|
|
|
const routeData = {
|
|
|
|
|
callSign: this.newRouteName,
|
|
|
|
|
scenarioId: currentScenarioId,
|
|
|
|
|
platformId: 1,
|
|
|
|
|
attributes: "{}",
|
|
|
|
|
waypoints: this.tempMapPoints.map((p, index) => ({
|
|
|
|
|
name: `WP${index + 1}`,
|
|
|
|
|
lat: p.lat,
|
|
|
|
|
lng: p.lng,
|
|
|
|
|
alt: 5000.0,
|
|
|
|
|
speed: 800.0,
|
|
|
|
|
startTime: 'K+00:00:00',
|
|
|
|
|
turnAngle: 0.0
|
|
|
|
|
}))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 调用后端 API 保存到数据库
|
|
|
|
|
const response = await addRoutes(routeData);
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
this.$message.success('航线及其航点已成功保存至当前方案');
|
|
|
|
|
// 获取后端生成的正式 ID
|
|
|
|
|
const savedRoute = response.data;
|
|
|
|
|
const newRouteId = savedRoute ? savedRoute.id : null;
|
|
|
|
|
if (this.$refs.cesiumMap) {
|
|
|
|
|
this.$refs.cesiumMap.clearRoute();
|
|
|
|
|
// 如果拿到了正式 ID,则按照“正式航线”的规则渲染一次
|
|
|
|
|
if (newRouteId) {
|
|
|
|
|
if (!this.activeRouteIds.includes(savedRoute.id)) {
|
|
|
|
|
this.activeRouteIds.push(savedRoute.id);
|
|
|
|
|
}
|
|
|
|
|
// 更新当前详情,确保右侧下方航点列表立刻显示
|
|
|
|
|
this.selectedRouteDetails = {
|
|
|
|
|
id: newRouteId,
|
|
|
|
|
name: this.newRouteName,
|
|
|
|
|
waypoints: routeData.waypoints
|
|
|
|
|
};
|
|
|
|
|
//使用正式 ID 渲染点和线
|
|
|
|
|
this.$refs.cesiumMap.renderRouteWaypoints(routeData.waypoints, newRouteId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 重置 UI 状态
|
|
|
|
|
this.showNameDialog = false;
|
|
|
|
|
this.drawDom = false;
|
|
|
|
|
this.newRouteName = '';
|
|
|
|
|
this.tempMapPoints = [];
|
|
|
|
|
// 重新拉取右侧航线列表以保持同步
|
|
|
|
|
await this.getList();
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("保存航线失败:", error);
|
|
|
|
|
this.$message.error('保存失败,请检查后端服务');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 航点编辑弹窗相关方法
|
|
|
|
|
openWaypointDialog(waypoint) {
|
|
|
|
|
this.selectedWaypoint = waypoint;
|
|
|
|
|
this.showWaypointDialog = true;
|
|
|
|
|
},
|
|
|
|
|
/** 航点编辑保存:更新数据库并同步地图显示 */
|
|
|
|
|
async updateWaypoint(updatedWaypoint) {
|
|
|
|
|
// 确保有选中的航线详情
|
|
|
|
|
if (!this.selectedRouteDetails || !this.selectedRouteDetails.waypoints) return;
|
|
|
|
|
try {
|
|
|
|
|
const response = await updateWaypoints(updatedWaypoint);
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
// 找到当前编辑点在本地数组中的位置
|
|
|
|
|
const index = this.selectedRouteDetails.waypoints.findIndex(p => p.id === updatedWaypoint.id);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
// 记录旧名称,用于地图组件定位 Entity
|
|
|
|
|
const oldName = this.selectedRouteDetails.waypoints[index].name;
|
|
|
|
|
const newName = updatedWaypoint.name;
|
|
|
|
|
// 使用 splice 触发 Vue 响应式更新右侧面板 UI
|
|
|
|
|
this.selectedRouteDetails.waypoints.splice(index, 1, { ...updatedWaypoint });
|
|
|
|
|
// 5. 同步更新地图上的图形
|
|
|
|
|
if (this.$refs.cesiumMap) {
|
|
|
|
|
this.$refs.cesiumMap.updateWaypointGraphic(oldName, newName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.showWaypointDialog = false;
|
|
|
|
|
this.$message.success('航点信息已持久化至数据库');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("更新航点失败:", error);
|
|
|
|
|
this.$message.error('数据库更新失败,请重试');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
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')}`;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 左侧菜单栏操作
|
|
|
|
|
showMenu() {
|
|
|
|
|
this.isMenuHidden = false;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
hideMenu() {
|
|
|
|
|
this.isMenuHidden = true;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
selectTopNav(item) {
|
|
|
|
|
console.log('选中顶部导航:', item);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 右侧面板操作
|
|
|
|
|
showRightPanel() {
|
|
|
|
|
this.isRightPanelHidden = false;
|
|
|
|
|
this.$message.info('显示右侧面板');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 文件下拉菜单方法
|
|
|
|
|
savePlan() {
|
|
|
|
|
this.$message.success('保存计划');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
importPlanFile() {
|
|
|
|
|
this.$message.success('导入计划');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
importACD() {
|
|
|
|
|
this.$message.success('导入ACD');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
importATO() {
|
|
|
|
|
this.$message.success('导入ATO');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
importLayer() {
|
|
|
|
|
this.$message.success('导入图层');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
importRoute() {
|
|
|
|
|
this.$message.success('导入航线');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
exportPlan() {
|
|
|
|
|
this.$message.success('导出计划');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 编辑下拉菜单方法
|
|
|
|
|
routeEdit() {
|
|
|
|
|
this.$message.success('航线编辑');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
militaryMarking() {
|
|
|
|
|
this.$message.success('军事标绘');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
iconEdit() {
|
|
|
|
|
// 这个方法现在由 TopHeader 组件处理状态切换
|
|
|
|
|
// 只需要在这里处理其他逻辑(如果有的话)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleIconEditMode(isEditMode) {
|
|
|
|
|
this.isIconEditMode = isEditMode
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
updateMenuItems(newItems) {
|
|
|
|
|
this.menuItems = newItems
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
handleMenuDragEnd(newItems) {
|
|
|
|
|
this.menuItems = newItems
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
handleAddMenuItems(selectedItems) {
|
|
|
|
|
selectedItems.forEach(item => {
|
|
|
|
|
const newId = Date.now().toString() + Math.random().toString(36).substr(2, 9)
|
|
|
|
|
const newMenuItem = {
|
|
|
|
|
id: newId,
|
|
|
|
|
name: item.name,
|
|
|
|
|
icon: item.icon,
|
|
|
|
|
action: item.id
|
|
|
|
|
}
|
|
|
|
|
this.menuItems.push(newMenuItem)
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
handleMenuAction(actionId) {
|
|
|
|
|
const actionMap = {
|
|
|
|
|
'savePlan': () => this.savePlan(),
|
|
|
|
|
'routeEdit': () => this.routeEdit(),
|
|
|
|
|
'militaryMarking': () => this.militaryMarking(),
|
|
|
|
|
'attributeEdit': () => this.attributeEdit(),
|
|
|
|
|
'timeSettings': () => this.timeSettings(),
|
|
|
|
|
'aircraftSettings': () => this.aircraftSettings(),
|
|
|
|
|
'keyEventEdit': () => this.keyEventEdit(),
|
|
|
|
|
'missileLaunch': () => this.missileLaunch(),
|
|
|
|
|
'toggle2D3D': () => this.toggle2D3D(),
|
|
|
|
|
'toggleRuler': () => this.toggleRuler(),
|
|
|
|
|
'toggleGrid': () => this.toggleGrid(),
|
|
|
|
|
'toggleScale': () => {
|
|
|
|
|
this.showScaleDialog = true
|
|
|
|
|
this.currentScale = {}
|
|
|
|
|
},
|
|
|
|
|
'loadTerrain': () => this.loadTerrain(),
|
|
|
|
|
'changeProjection': () => this.changeProjection(),
|
|
|
|
|
'loadAeroChart': () => this.loadAeroChart(),
|
|
|
|
|
'powerZone': () => {
|
|
|
|
|
this.showPowerZoneDialog = true
|
|
|
|
|
this.currentPowerZone = {}
|
|
|
|
|
},
|
|
|
|
|
'threatZone': () => this.threatZone(),
|
|
|
|
|
'routeCalculation': () => this.routeCalculation(),
|
|
|
|
|
'conflictDisplay': () => this.conflictDisplay(),
|
|
|
|
|
'dataMaterials': () => this.dataMaterials(),
|
|
|
|
|
'coordinateConversion': () => this.coordinateConversion(),
|
|
|
|
|
'pageLayout': () => this.pageLayout(),
|
|
|
|
|
'dataStoragePath': () => this.dataStoragePath(),
|
|
|
|
|
'externalParams': () => {
|
|
|
|
|
this.showExternalParamsDialog = true
|
|
|
|
|
this.currentExternalParams = {}
|
|
|
|
|
},
|
|
|
|
|
'toggleAirport': () => this.toggleAirport(),
|
|
|
|
|
'toggleLandmark': () => this.toggleLandmark(),
|
|
|
|
|
'toggleRoute': () => this.toggleRoute(),
|
|
|
|
|
'layerFavorites': () => this.layerFavorites(),
|
|
|
|
|
'routeFavorites': () => this.routeFavorites()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actionMap[actionId]) {
|
|
|
|
|
actionMap[actionId]()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
handleDeleteMenuItem(deletedItem) {
|
|
|
|
|
const index = this.menuItems.findIndex(item => item.id === deletedItem.id)
|
|
|
|
|
if (index > -1) {
|
|
|
|
|
this.menuItems.splice(index, 1)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
attributeEdit() {
|
|
|
|
|
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切换');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleRuler() {
|
|
|
|
|
this.$message.success('显示/隐藏标尺');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleGrid() {
|
|
|
|
|
this.$message.success('显示/隐藏网格');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
saveScale(scale) {
|
|
|
|
|
console.log('保存比例尺:', scale)
|
|
|
|
|
const scaleText = `${scale.scaleNumerator}:${scale.scaleDenominator}`
|
|
|
|
|
this.$message.success(`比例尺 "${scaleText}" 保存成功`);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 地图下拉菜单方法
|
|
|
|
|
loadTerrain() {
|
|
|
|
|
this.$message.success('加载/切换地形');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
changeProjection() {
|
|
|
|
|
this.$message.success('投影');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
loadAeroChart() {
|
|
|
|
|
this.$message.success('航空图');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 空域下拉菜单方法
|
|
|
|
|
savePowerZone(powerZone) {
|
|
|
|
|
console.log('保存威力区:', powerZone)
|
|
|
|
|
this.$message.success(`威力区 "${powerZone.name}" 保存成功`);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
threatZone() {
|
|
|
|
|
this.$message.success('威胁区');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 工具下拉菜单方法
|
|
|
|
|
routeCalculation() {
|
|
|
|
|
this.$message.success('航线计算');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
conflictDisplay() {
|
|
|
|
|
this.$message.success('冲突显示');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
dataMaterials() {
|
|
|
|
|
this.$message.success('数据资料');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
coordinateConversion() {
|
|
|
|
|
this.$message.success('坐标换算');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 选项下拉菜单方法
|
|
|
|
|
pageLayout() {
|
|
|
|
|
this.showPageLayoutDialog = true;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
dataStoragePath() {
|
|
|
|
|
this.$message.success('数据存储路径');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
saveExternalParams(externalParams) {
|
|
|
|
|
console.log('保存外部参数:', externalParams)
|
|
|
|
|
this.$message.success('外部参数保存成功');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
savePageLayout(position) {
|
|
|
|
|
this.menuPosition = position;
|
|
|
|
|
this.$message.success(`菜单位置已设置为:${this.getPositionLabel(position)}`);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getPositionLabel(position) {
|
|
|
|
|
const labels = {
|
|
|
|
|
'top': '顶部',
|
|
|
|
|
'bottom': '底部',
|
|
|
|
|
'left': '左侧',
|
|
|
|
|
'right': '右侧'
|
|
|
|
|
};
|
|
|
|
|
return labels[position] || position;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
importAirport(path) {
|
|
|
|
|
console.log('导入机场:', path)
|
|
|
|
|
this.$message.success('机场数据导入成功');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
importRouteData(path) {
|
|
|
|
|
console.log('导入航路:', path)
|
|
|
|
|
this.$message.success('航路数据导入成功');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
importLandmark(path) {
|
|
|
|
|
console.log('导入地标:', path)
|
|
|
|
|
this.$message.success('地标数据导入成功');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleAirport() {
|
|
|
|
|
this.showAirport = !this.showAirport;
|
|
|
|
|
this.$message.success(this.showAirport ? '显示机场' : '隐藏机场');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleLandmark() {
|
|
|
|
|
this.showLandmark = !this.showLandmark;
|
|
|
|
|
this.$message.success(this.showLandmark ? '显示地标' : '隐藏地标');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleRoute() {
|
|
|
|
|
this.showRoute = !this.showRoute;
|
|
|
|
|
this.$message.success(this.showRoute ? '显示航线' : '隐藏航线');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
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.action) {
|
|
|
|
|
this.handleMenuAction(item.action)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 点击左侧的方案、冲突、平台时,切换右侧面板内容
|
|
|
|
|
if (item.id === 'file') {
|
|
|
|
|
// 如果当前已经是方案标签页,则关闭右侧面板
|
|
|
|
|
if (this.activeRightTab === 'plan' && !this.isRightPanelHidden) {
|
|
|
|
|
this.isRightPanelHidden = true;
|
|
|
|
|
} else {
|
|
|
|
|
this.activeRightTab = 'plan';
|
|
|
|
|
this.isRightPanelHidden = false;
|
|
|
|
|
}
|
|
|
|
|
} else if (item.id === 'start') {
|
|
|
|
|
// 如果当前已经是冲突标签页,则关闭右侧面板
|
|
|
|
|
if (this.activeRightTab === 'conflict' && !this.isRightPanelHidden) {
|
|
|
|
|
this.isRightPanelHidden = true;
|
|
|
|
|
} else {
|
|
|
|
|
this.activeRightTab = 'conflict';
|
|
|
|
|
this.isRightPanelHidden = false;
|
|
|
|
|
}
|
|
|
|
|
} else if (item.id === 'insert') {
|
|
|
|
|
// 如果当前已经是平台标签页,则关闭右侧面板
|
|
|
|
|
if (this.activeRightTab === 'platform' && !this.isRightPanelHidden) {
|
|
|
|
|
this.isRightPanelHidden = true;
|
|
|
|
|
} else {
|
|
|
|
|
this.activeRightTab = 'platform';
|
|
|
|
|
this.isRightPanelHidden = false;
|
|
|
|
|
}
|
|
|
|
|
} else if(item.id === 'modify'){
|
|
|
|
|
this.drawDom = !this.drawDom
|
|
|
|
|
console.log(this.drawDom,999999)
|
|
|
|
|
}
|
|
|
|
|
if (item.id === 'deduction') {
|
|
|
|
|
// 点击推演按钮,显示/隐藏K时弹出框
|
|
|
|
|
this.showKTimePopup = !this.showKTimePopup;
|
|
|
|
|
}
|
|
|
|
|
if (item.id === 'save') {
|
|
|
|
|
this.savePlan();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// K时弹出框操作
|
|
|
|
|
hideKTimePopup() {
|
|
|
|
|
this.showKTimePopup = false;
|
|
|
|
|
this.$message.info('隐藏推演时钟控制');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 播放控制
|
|
|
|
|
togglePlay() {
|
|
|
|
|
this.isPlaying = !this.isPlaying;
|
|
|
|
|
if (this.isPlaying) {
|
|
|
|
|
this.startPlayback();
|
|
|
|
|
this.$message.success('开始播放');
|
|
|
|
|
} else {
|
|
|
|
|
this.stopPlayback();
|
|
|
|
|
this.$message.info('暂停播放');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
startPlayback() {
|
|
|
|
|
if (this.playbackInterval) {
|
|
|
|
|
clearInterval(this.playbackInterval);
|
|
|
|
|
}
|
|
|
|
|
this.playbackInterval = setInterval(() => {
|
|
|
|
|
this.timeProgress += this.playbackSpeed * 0.1;
|
|
|
|
|
if (this.timeProgress >= 100) {
|
|
|
|
|
this.timeProgress = 0;
|
|
|
|
|
}
|
|
|
|
|
this.updateTimeFromProgress();
|
|
|
|
|
}, 100);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
stopPlayback() {
|
|
|
|
|
if (this.playbackInterval) {
|
|
|
|
|
clearInterval(this.playbackInterval);
|
|
|
|
|
this.playbackInterval = null;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
increaseSpeed() {
|
|
|
|
|
if (this.playbackSpeed < 25) {
|
|
|
|
|
this.playbackSpeed++;
|
|
|
|
|
if (this.isPlaying) {
|
|
|
|
|
this.startPlayback();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
decreaseSpeed() {
|
|
|
|
|
if (this.playbackSpeed > 1) {
|
|
|
|
|
this.playbackSpeed--;
|
|
|
|
|
if (this.isPlaying) {
|
|
|
|
|
this.startPlayback();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
updateTimeFromProgress() {
|
|
|
|
|
const totalSeconds = Math.floor(this.timeProgress * 72);
|
|
|
|
|
const hours = Math.floor(totalSeconds / 3600) - 2;
|
|
|
|
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
|
|
|
const seconds = totalSeconds % 60;
|
|
|
|
|
|
|
|
|
|
const sign = hours >= 0 ? '+' : '-';
|
|
|
|
|
const absHours = Math.abs(hours);
|
|
|
|
|
|
|
|
|
|
this.currentTime = `K${sign}${String(absHours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 时间控制(保留用于底部时间轴)
|
|
|
|
|
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`;
|
|
|
|
|
},
|
|
|
|
|
/** 切换航线:实现多选/开关逻辑 */
|
|
|
|
|
async selectRoute(route) {
|
|
|
|
|
const index = this.activeRouteIds.indexOf(route.id);
|
|
|
|
|
// 航线已在选中列表中
|
|
|
|
|
if (index > -1) {
|
|
|
|
|
this.activeRouteIds.splice(index, 1);
|
|
|
|
|
if (this.$refs.cesiumMap) {
|
|
|
|
|
this.$refs.cesiumMap.removeRouteById(route.id);
|
|
|
|
|
}
|
|
|
|
|
if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) {
|
|
|
|
|
if (this.activeRouteIds.length > 0) {
|
|
|
|
|
const lastId = this.activeRouteIds[this.activeRouteIds.length - 1];
|
|
|
|
|
try {
|
|
|
|
|
const res = await getRoutes(lastId);
|
|
|
|
|
if (res.code === 200 && res.data) {
|
|
|
|
|
this.selectedRouteDetails = {
|
|
|
|
|
id: res.data.id,
|
|
|
|
|
name: res.data.callSign,
|
|
|
|
|
waypoints: res.data.waypoints || []
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error("回显剩余航线失败", e);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.selectedRouteDetails = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.$message.info(`已移除航线: ${route.name}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 航线未被选中
|
|
|
|
|
try {
|
|
|
|
|
const response = await getRoutes(route.id);
|
|
|
|
|
if (response.code === 200 && response.data) {
|
|
|
|
|
const fullRouteData = response.data;
|
|
|
|
|
const waypoints = fullRouteData.waypoints || [];
|
|
|
|
|
this.activeRouteIds.push(route.id);
|
|
|
|
|
this.selectedRouteDetails = {
|
|
|
|
|
id: fullRouteData.id,
|
|
|
|
|
name: fullRouteData.callSign,
|
|
|
|
|
waypoints: waypoints
|
|
|
|
|
};
|
|
|
|
|
if (waypoints.length > 0) {
|
|
|
|
|
// 通知地图渲染
|
|
|
|
|
if (this.$refs.cesiumMap) {
|
|
|
|
|
this.$refs.cesiumMap.renderRouteWaypoints(waypoints, route.id);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.$message.warning('该航线暂无坐标数据,无法在地图展示');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("获取航线详情失败:", error);
|
|
|
|
|
this.$message.error('无法加载该航线的详细航点数据');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// 航线操作
|
|
|
|
|
selectPlan(plan) {
|
|
|
|
|
this.selectedPlanId = plan.id;
|
|
|
|
|
this.selectedPlanDetails = plan;
|
|
|
|
|
this.selectedRouteId = null;
|
|
|
|
|
this.selectedRouteDetails = null;
|
|
|
|
|
},
|
|
|
|
|
createPlan() {
|
|
|
|
|
const newId = Date.now();
|
|
|
|
|
const newPlan = {
|
|
|
|
|
id: newId,
|
|
|
|
|
name: `新方案${this.plans.length + 1}`,
|
|
|
|
|
routes: []
|
|
|
|
|
};
|
|
|
|
|
this.plans.push(newPlan);
|
|
|
|
|
this.selectPlan(newPlan);
|
|
|
|
|
},
|
|
|
|
|
openPlanDialog(plan) {
|
|
|
|
|
this.$prompt('请输入方案名称', '编辑方案', {
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
inputValue: plan.name
|
|
|
|
|
}).then(({ value }) => {
|
|
|
|
|
plan.name = value;
|
|
|
|
|
}).catch(() => {});
|
|
|
|
|
},
|
|
|
|
|
addWaypoint() {
|
|
|
|
|
if (this.selectedRouteDetails) {
|
|
|
|
|
const count = this.selectedRouteDetails.waypoints.length + 1;
|
|
|
|
|
this.selectedRouteDetails.waypoints.push({
|
|
|
|
|
name: `WP${count}`,
|
|
|
|
|
altitude: 1000,
|
|
|
|
|
speed: 250
|
|
|
|
|
});
|
|
|
|
|
if (this.selectedPlanDetails) {
|
|
|
|
|
const route = this.selectedPlanDetails.routes.find(r => r.id === this.selectedRouteId);
|
|
|
|
|
if (route) {
|
|
|
|
|
route.points = this.selectedRouteDetails.waypoints.length;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
cancelRoute() {
|
|
|
|
|
// 清空所有选中的航线
|
|
|
|
|
if (this.$refs.cesiumMap) {
|
|
|
|
|
this.activeRouteIds.forEach(id => {
|
|
|
|
|
this.$refs.cesiumMap.removeRouteById(id);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
this.activeRouteIds = [];
|
|
|
|
|
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%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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: 10px 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: 5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.playback-controls {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.control-btn {
|
|
|
|
|
width: 26px;
|
|
|
|
|
height: 26px;
|
|
|
|
|
border: 1px solid rgba(0, 138, 255, 0.3);
|
|
|
|
|
background: rgba(255, 255, 255, 0.9);
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
color: #008aff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.control-btn:hover:not(:disabled) {
|
|
|
|
|
background: rgba(0, 138, 255, 0.1);
|
|
|
|
|
border-color: rgba(0, 138, 255, 0.5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.control-btn:disabled {
|
|
|
|
|
opacity: 0.4;
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.control-btn i {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.speed-control {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
padding: 2px 8px;
|
|
|
|
|
background: rgba(255, 255, 255, 0.9);
|
|
|
|
|
border: 1px solid rgba(0, 138, 255, 0.3);
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.speed-text {
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #008aff;
|
|
|
|
|
min-width: 24px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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>
|