|
|
@@ -0,0 +1,2123 @@
|
|
|
+<template>
|
|
|
+ <div class="page">
|
|
|
+ <!-- 选择靶区弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ :close-on-press-escape="false"
|
|
|
+ :visible.sync="rangeDialogVisible"
|
|
|
+ center
|
|
|
+ title="选择靶区场景"
|
|
|
+ width="560px"
|
|
|
+ >
|
|
|
+ <el-form :model="rangeForm" label-width="100px">
|
|
|
+ <el-row :gutter="16">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="靶区场景" prop="rangeId">
|
|
|
+ <el-select v-model="rangeForm.rangeId" placeholder="请选择靶区">
|
|
|
+ <el-option
|
|
|
+ v-for="r in rangeForm.ranges"
|
|
|
+ :key="r.id"
|
|
|
+ :label="r.name"
|
|
|
+ :value="r.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="编辑方式" prop="rangeId">
|
|
|
+ <el-select v-model="rangeForm.editType" placeholder="请选择编辑方式">
|
|
|
+ <el-option label="手动编辑" value="手动编辑"/>
|
|
|
+ <el-option label="导入XML文件" value="导入XML文件"/>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 预览所选靶区 -->
|
|
|
+ <!-- <el-card-->
|
|
|
+ <!-- v-if="currentRangeInfo && rangeForm.editType !== '导入XML文件'"-->
|
|
|
+ <!-- body-style="padding:12px;"-->
|
|
|
+ <!-- class="ui-card panel-card"-->
|
|
|
+ <!-- shadow="never"-->
|
|
|
+ <!-- >-->
|
|
|
+ <!-- <div class="sub-title">-->
|
|
|
+ <!-- <i class="el-icon-map-location"></i> 场景详情(预览)-->
|
|
|
+ <!-- </div>-->
|
|
|
+ <!-- <el-form-->
|
|
|
+ <!-- :model="currentRangeInfo"-->
|
|
|
+ <!-- class="range-preview-form"-->
|
|
|
+ <!-- label-width="90px"-->
|
|
|
+ <!-- size="small"-->
|
|
|
+ <!-- >-->
|
|
|
+ <!-- <el-row :gutter="12">-->
|
|
|
+ <!-- <el-col :span="12">-->
|
|
|
+ <!-- <el-form-item label="名称">-->
|
|
|
+ <!-- <el-input v-model="currentRangeInfo.name" disabled/>-->
|
|
|
+ <!-- </el-form-item>-->
|
|
|
+ <!-- </el-col>-->
|
|
|
+ <!-- <el-col v-if="currentRangeInfo.area" :span="12">-->
|
|
|
+ <!-- <el-form-item label="面积">-->
|
|
|
+ <!-- <el-input v-model="currentRangeInfo.area" disabled/>-->
|
|
|
+ <!-- </el-form-item>-->
|
|
|
+ <!-- </el-col>-->
|
|
|
+ <!-- <el-col v-if="currentRangeInfo.description" :span="24">-->
|
|
|
+ <!-- <el-form-item label="描述">-->
|
|
|
+ <!-- <el-input-->
|
|
|
+ <!-- v-model="currentRangeInfo.description"-->
|
|
|
+ <!-- :rows="3"-->
|
|
|
+ <!-- disabled-->
|
|
|
+ <!-- type="textarea"-->
|
|
|
+ <!-- />-->
|
|
|
+ <!-- </el-form-item>-->
|
|
|
+ <!-- </el-col>-->
|
|
|
+ <!-- </el-row>-->
|
|
|
+ <!-- <div class="form-hint">-->
|
|
|
+ <!-- 以上为所选场景其他配置信息,点击「进入配置」应用到该想定。-->
|
|
|
+ <!-- </div>-->
|
|
|
+ <!-- </el-form>-->
|
|
|
+ <!-- </el-card>-->
|
|
|
+
|
|
|
+ <!-- 导入 XML -->
|
|
|
+ <el-form-item
|
|
|
+ v-if="rangeForm.editType === '导入XML文件'"
|
|
|
+ label="导入XML文件"
|
|
|
+ >
|
|
|
+ <el-upload
|
|
|
+ :auto-upload="false"
|
|
|
+ :before-upload="beforeUpload"
|
|
|
+ :file-list="fileList"
|
|
|
+ :limit="1"
|
|
|
+ :on-change="handleFileChange"
|
|
|
+ :on-remove="handleRemove"
|
|
|
+ accept=".xml"
|
|
|
+ action=""
|
|
|
+ class="upload-demo"
|
|
|
+ >
|
|
|
+ <el-button icon="el-icon-upload" type="primary">选择 XML 文件</el-button>
|
|
|
+ <div slot="tip" class="el-upload__tip">
|
|
|
+ 仅支持 .xml,点击「进入配置」后将解析导入。
|
|
|
+ </div>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <span slot="footer">
|
|
|
+ <el-button plain @click="goBack">取消</el-button>
|
|
|
+ <el-button :disabled="!rangeForm.rangeId" type="primary" @click="confirmRange">进入配置</el-button>
|
|
|
+ </span>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 主体:选择场景后再渲染 -->
|
|
|
+ <template v-if="showMain">
|
|
|
+ <el-container class="root" direction="vertical">
|
|
|
+ <!-- 顶部 -->
|
|
|
+ <el-header class="header">
|
|
|
+ <div class="header-inner">
|
|
|
+ <el-steps
|
|
|
+ :active="step"
|
|
|
+ :space="260"
|
|
|
+ align-center
|
|
|
+ class="steps-light"
|
|
|
+ finish-status="success"
|
|
|
+ simple
|
|
|
+ >
|
|
|
+ <el-step v-for="(s,i) in stepMeta" :key="i" :icon="s.icon" :title="s.title"/>
|
|
|
+ </el-steps>
|
|
|
+ <div class="header-actions">
|
|
|
+ <el-button icon="el-icon-back" type="danger" @click="goBack">返回</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="step-index">
|
|
|
+ 步骤 {{ step }} / {{ stepMeta.length }}
|
|
|
+ <span class="version-badge">想定版本:{{ scenarioVersion }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="header-actions">
|
|
|
+ <el-button icon="el-icon-download" plain @click="exportPlan">导出</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-header>
|
|
|
+
|
|
|
+ <!-- 工作区 -->
|
|
|
+ <el-container class="workspace">
|
|
|
+ <!-- Step 1 装备配置 -->
|
|
|
+ <template v-if="step===1">
|
|
|
+ <!-- 左侧 -->
|
|
|
+ <el-aside :width="leftWidth" class="aside aside-left">
|
|
|
+ <el-card class="ui-card fill-card" shadow="hover">
|
|
|
+ <div slot="header" class="card-header">
|
|
|
+ <div class="title-left">
|
|
|
+ <i class="fa fa-cogs icon-primary"></i><span>装备配置</span>
|
|
|
+ </div>
|
|
|
+ <div class="title-right">
|
|
|
+ <el-select
|
|
|
+ v-model="activeTab"
|
|
|
+ :popper-append-to-body="false"
|
|
|
+ class="switcher"
|
|
|
+ size="mini"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="t in equipmentTabs"
|
|
|
+ :key="t.key"
|
|
|
+ :label="t.label"
|
|
|
+ :value="t.key"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+
|
|
|
+ <el-button
|
|
|
+ v-if="activeTab==='target'"
|
|
|
+ icon="el-icon-plus"
|
|
|
+ size="mini"
|
|
|
+ type="primary"
|
|
|
+ @click="openAddTargetDialog"
|
|
|
+ >添加靶标
|
|
|
+ </el-button>
|
|
|
+
|
|
|
+ <el-button
|
|
|
+ v-if="activeTab==='jammer'"
|
|
|
+ icon="el-icon-plus"
|
|
|
+ size="mini"
|
|
|
+ type="primary"
|
|
|
+ @click="openAddJammerDialog"
|
|
|
+ >添加干扰
|
|
|
+ </el-button>
|
|
|
+
|
|
|
+ <el-button
|
|
|
+ v-if="activeTab==='measurement'"
|
|
|
+ icon="el-icon-plus"
|
|
|
+ size="mini"
|
|
|
+ type="primary"
|
|
|
+ @click="openAddMeasurementDialog"
|
|
|
+ >添加测量
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 列表 + 时序 合体(交由子组件渲染) -->
|
|
|
+ <EquipmentListWithTimeline
|
|
|
+ :type-key="activeTab"
|
|
|
+ :items="activeTab==='target' ? targets : (activeTab==='jammer' ? jammers : measurements)"
|
|
|
+ @edit="(i) => activeTab==='target' ? editTarget(i) : activeTab==='jammer' ? editJammer(i) : editMeasurement(i)"
|
|
|
+ @remove="(i) => activeTab==='target' ? removeTarget(i) : activeTab==='jammer' ? removeJammer(i) : removeMeasurement(i)"
|
|
|
+ @refresh="rebuildPreview"
|
|
|
+ />
|
|
|
+ </el-card>
|
|
|
+ </el-aside>
|
|
|
+
|
|
|
+ <!-- 右侧 -->
|
|
|
+ <el-aside :width="rightWidth" class="aside aside-right">
|
|
|
+ <el-card class="ui-card fill-card" shadow="hover">
|
|
|
+ <div slot="header" class="card-header">
|
|
|
+ <div class="title-left">
|
|
|
+ <i class="fa fa-file-text-o icon-primary"></i>
|
|
|
+ <span>{{ getPreviewTitle() }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="preview-scroll fill-scroll">
|
|
|
+ <!-- KPI -->
|
|
|
+ <div class="kpi-grid">
|
|
|
+ <div class="kpi-card">
|
|
|
+ <div class="kpi-value">{{ stat.total }}</div>
|
|
|
+ <div class="kpi-label">装备总数</div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-card">
|
|
|
+ <div class="kpi-value">{{ stat.target }}</div>
|
|
|
+ <div class="kpi-label">靶标装备</div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-card">
|
|
|
+ <div class="kpi-value">{{ stat.jammer }}</div>
|
|
|
+ <div class="kpi-label">干扰装备</div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-card">
|
|
|
+ <div class="kpi-value">{{ stat.measure }}</div>
|
|
|
+ <div class="kpi-label">测量装备</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template v-if="activeTab==='target'">
|
|
|
+ <TargetSchemeInfo :scheme="targetSchemeView" class="mb-16"/>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-if="activeTab==='jammer'">
|
|
|
+ <JammerSchemeInfo :scheme="jammerSchemeView" class="mb-16"/>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-if="activeTab==='measurement'">
|
|
|
+ <MeasurementSchemeInfo :scheme="measureSchemeView" class="mb-16"/>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-aside>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- Step 2 任务规划(整块交由面板组件) -->
|
|
|
+ <template v-if="step===2">
|
|
|
+ <el-main class="main-full">
|
|
|
+ <MissileMissionPanel
|
|
|
+ :planId="plan.id"
|
|
|
+ :taskId="plan.taskId"
|
|
|
+ :subTaskId="plan.subTaskId"
|
|
|
+ :missiles.sync="missiles"
|
|
|
+ :open-missile-panels.sync="openMissilePanels"
|
|
|
+ :scenario-name="scenarioName"
|
|
|
+ :scenario-version.sync="scenarioVersion"
|
|
|
+ :missile-count="missionMissileCount"
|
|
|
+ @import-trajectory="handleTrajectory"
|
|
|
+ @preview-trajectory="handleTrajectory"
|
|
|
+ />
|
|
|
+ </el-main>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- Step 3 环境设置(保留) -->
|
|
|
+ <template v-if="step===3">
|
|
|
+ <el-main class="main-full">
|
|
|
+ <el-card class="ui-card fill-card" shadow="hover">
|
|
|
+ <div slot="header" class="card-header">
|
|
|
+ <div class="title-left">
|
|
|
+ <i class="fa fa-cloud icon-primary"></i><span>战场环境规划</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="fill-scroll">
|
|
|
+ <el-collapse v-model="activeEnvTab">
|
|
|
+ <el-collapse-item name="meteorology" title="气象条件">
|
|
|
+ <el-form :model="weatherForm" class="dense-form" label-width="120px" size="small" style="margin-top: 20px">
|
|
|
+ <el-row :gutter="16">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="风速(m/s)">
|
|
|
+ <el-input v-model.number="weatherForm.wind_speed" :min="0" step="0.1" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="风向(°)">
|
|
|
+ <el-input v-model.number="weatherForm.wind_direction" :max="360" :min="0" step="1" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="温度(°C)">
|
|
|
+ <el-input v-model.number="weatherForm.temperature" step="0.5" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="能见度(km)">
|
|
|
+ <el-input v-model.number="weatherForm.visibility" :min="0.1" step="0.5" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form>
|
|
|
+ </el-collapse-item>
|
|
|
+ </el-collapse>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-main>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- Step 4 想定预览 -->
|
|
|
+ <template v-if="step===4">
|
|
|
+ <el-main class="main-full">
|
|
|
+ <el-card class="ui-card fill-card" shadow="hover">
|
|
|
+ <div slot="header" class="card-header">
|
|
|
+ <div class="title-left">
|
|
|
+ <i class="fa fa-file-text icon-primary"></i><span>想定预览(版本:{{ scenarioVersion }})</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="fill-scroll">
|
|
|
+ <!-- 装备数量 -->
|
|
|
+ <el-card body-style="padding:16px;" class="ui-card panel-card mb-16" shadow="never">
|
|
|
+ <div class="sub-title"><i class="el-icon-odometer"></i> 装备数量预览</div>
|
|
|
+ <div class="kpi-grid">
|
|
|
+ <div class="kpi-card">
|
|
|
+ <div class="kpi-value">{{ stat.total }}</div>
|
|
|
+ <div class="kpi-label">装备总数</div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-card">
|
|
|
+ <div class="kpi-value">{{ stat.target }}</div>
|
|
|
+ <div class="kpi-label">靶标装备</div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-card">
|
|
|
+ <div class="kpi-value">{{ stat.jammer }}</div>
|
|
|
+ <div class="kpi-label">干扰装备</div>
|
|
|
+ </div>
|
|
|
+ <div class="kpi-card">
|
|
|
+ <div class="kpi-value">{{ stat.measure }}</div>
|
|
|
+ <div class="kpi-label">测量装备</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 装备信息总览(三类) -->
|
|
|
+ <el-card body-style="padding:16px;" class="ui-card panel-card mb-16" shadow="never">
|
|
|
+ <div class="sub-title"><i class="el-icon-tickets"></i> 装备信息总览</div>
|
|
|
+ <el-row :gutter="12">
|
|
|
+ <el-col :span="8" v-if="equTree.find(item=>item.name==='靶标方案')">
|
|
|
+ <el-card body-style="padding:10px;" class="ui-card soft" shadow="never">
|
|
|
+ <div class="module-title"><i class="el-icon-location-information"></i>
|
|
|
+ 靶标装备({{ equTree.find(item=>item.name==='靶标方案').equList.length }})
|
|
|
+ </div>
|
|
|
+ <el-table :data="equTree.find(item=>item.name==='靶标方案').equList" border height="220" size="mini">
|
|
|
+ <el-table-column label="名称" min-width="120" prop="name"/>
|
|
|
+ <el-table-column label="类型" prop="type" width="70"/>
|
|
|
+ <el-table-column label="状态" width="90">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-tag
|
|
|
+ :type="scope.row.statusClass==='badge-success'?'success':(scope.row.statusClass==='badge-warn'?'warning':'info')"
|
|
|
+ size="mini">
|
|
|
+ {{ scope.row.statusText }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="激活" prop="time" width="90"/>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="8" v-if="equTree.find(item=>item.name==='干扰方案')">
|
|
|
+ <el-card body-style="padding:10px;" class="ui-card soft" shadow="never">
|
|
|
+ <div class="module-title"><i class="el-icon-microphone"></i> 干扰装备({{ equTree.find(item=>item.name==='干扰方案').equList.length }})
|
|
|
+ </div>
|
|
|
+ <el-table :data="equTree.find(item=>item.name==='干扰方案').equList" border height="220" size="mini">
|
|
|
+ <el-table-column label="名称" min-width="120" prop="name"/>
|
|
|
+ <el-table-column label="类型" prop="type" width="70"/>
|
|
|
+ <el-table-column label="频率(MHz)" width="100">
|
|
|
+ <template slot-scope="scope">{{ scope.row.params && scope.row.params.freq }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="8" v-if="equTree.find(item=>item.name==='测量方案')">
|
|
|
+ <el-card body-style="padding:10px;" class="ui-card soft" shadow="never">
|
|
|
+ <div class="module-title"><i class="el-icon-view"></i> 测量装备({{ equTree.find(item=>item.name==='测量方案').equList.length }})
|
|
|
+ </div>
|
|
|
+ <el-table :data="equTree.find(item=>item.name==='测量方案').equList" border height="220" size="mini">
|
|
|
+ <el-table-column label="名称" min-width="120" prop="name"/>
|
|
|
+ <el-table-column label="类型" prop="type" width="70"/>
|
|
|
+ <el-table-column label="启动" prop="time" width="90"/>
|
|
|
+ <el-table-column label="范围(km)" width="90">
|
|
|
+ <template slot-scope="scope">{{ scope.row.params && scope.row.params.range }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 完整事件时序(仍使用父级聚合) -->
|
|
|
+ <el-card body-style="padding:16px;" class="ui-card panel-card" shadow="never">
|
|
|
+ <div class="module-title module-title-row">
|
|
|
+ <div><i class="el-icon-time"></i> 完整事件时序</div>
|
|
|
+ </div>
|
|
|
+ <div class="timeline-wrap fill-scroll">
|
|
|
+ <GanttChart :timelineData="filteredAllEvents" :default-duration="600"></GanttChart>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-main>
|
|
|
+ </template>
|
|
|
+ </el-container>
|
|
|
+
|
|
|
+ <!-- 底部操作 -->
|
|
|
+ <div class="footer-actions">
|
|
|
+ <div class="actions-inner">
|
|
|
+ <el-button :disabled="step<=1" @click="goPrev"><i class="el-icon-arrow-left el-icon--left"></i> 上一步
|
|
|
+ </el-button>
|
|
|
+ <div style="flex:1;"></div>
|
|
|
+ <el-button v-if="step<4" type="primary" @click="goNext">下一步 <i
|
|
|
+ class="el-icon-arrow-right el-icon--right"></i></el-button>
|
|
|
+ <el-button v-else icon="el-icon-check" type="success" @click="saveFinal">保存想定</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-container>
|
|
|
+ </template>
|
|
|
+ <!-- 新增/编辑:靶标 -->
|
|
|
+ <el-dialog :close-on-click-modal="false" :title="editModeTarget ? '编辑靶标' : '添加靶标'"
|
|
|
+ :visible.sync="addTargetDialog" width="900px">
|
|
|
+ <el-form ref="targetFormRef" :model="newTargetForm" :rules="targetRules" class="dense-form" label-width="110px"
|
|
|
+ size="small">
|
|
|
+ <el-row :gutter="16">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="靶标名称" prop="name">
|
|
|
+ <el-input v-model="newTargetForm.name" placeholder="请输入"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="靶标类型" prop="type">
|
|
|
+ <el-select v-model="newTargetForm.type" style="width:100%" @change="onTargetTypeChange">
|
|
|
+ <el-option label="静态" value="静态"/>
|
|
|
+ <el-option label="动态" value="动态"/>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <template v-if="newTargetForm.type==='静态'">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="固定点位来源">
|
|
|
+ <el-switch v-model="newTargetForm.fixedFromPlan" active-text="使用方案固定点位"
|
|
|
+ inactive-text="手动/地图选点"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="状态" prop="statusText">
|
|
|
+ <el-select v-model="newTargetForm.statusText" style="width:100%">
|
|
|
+ <el-option label="可用" value="可用"/>
|
|
|
+ <el-option label="维护中" value="维护中"/>
|
|
|
+ <el-option label="停用" value="停用"/>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item :prop="newTargetForm.fixedFromPlan ? '' : 'lon'" label="经度">
|
|
|
+ <el-input v-model="newTargetForm.lon" placeholder="116.320000"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item :prop="newTargetForm.fixedFromPlan ? '' : 'lat'" label="纬度">
|
|
|
+ <el-input v-model="newTargetForm.lat" placeholder="39.950000"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="激活时间" prop="time">
|
|
|
+ <el-time-picker v-model="newTargetForm.time" format="HH:mm:ss" placeholder="选择时间"
|
|
|
+ style="width:100%;" value-format="HH:mm:ss"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12"></el-col>
|
|
|
+
|
|
|
+ <template v-if="newTargetForm.type==='动态'">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="运动模式">
|
|
|
+ <el-select v-model="targetForm.motion">
|
|
|
+ <el-option label="直线" value="linear"/>
|
|
|
+ <el-option label="圆周" value="circular"/>
|
|
|
+ <el-option label="随机" value="random"/>
|
|
|
+ <el-option label="自定义" value="custom"/>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="速度(km/h)">
|
|
|
+ <el-input v-model.number="targetForm.speed" :min="0" step="0.1" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="持续时间(s)">
|
|
|
+ <el-input v-model.number="targetForm.duration" :min="1" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="工作模式">
|
|
|
+ <el-select v-model="targetForm.mode">
|
|
|
+ <el-option label="正常" value="normal"/>
|
|
|
+ <el-option label="模拟" value="simulated"/>
|
|
|
+ <el-option label="增强" value="enhanced"/>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-col v-if="!newTargetForm.fixedFromPlan" :span="24">
|
|
|
+ <!-- 地图选点 -->
|
|
|
+ <el-form-item label="地图选点">
|
|
|
+ <div class="map-container">
|
|
|
+ <div class="map-placeholder" @click.stop="selectMapPosition">
|
|
|
+ <img alt="map" class="map-image" src="/img/banner.jpg"/>
|
|
|
+ <div
|
|
|
+ v-if="mapMarkerPosition"
|
|
|
+ class="map-marker"
|
|
|
+ :style="{left: mapMarkerPosition.x + 'px', top: mapMarkerPosition.y + 'px'}"
|
|
|
+ >
|
|
|
+ <i class="el-icon-location icon-marker"></i>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <p class="map-hint">点击地图可快速带出经纬度</p>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="addTargetDialog=false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="confirmAddTarget">{{ editModeTarget ? '保存' : '确认添加' }}</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 新增/编辑:干扰 -->
|
|
|
+ <el-dialog :close-on-click-modal="false" :title="editModeJammer ? '编辑干扰装备' : '添加干扰装备'"
|
|
|
+ :visible.sync="addJammerDialog" size="40%">
|
|
|
+ <el-form ref="jammerFormRef" :model="newJammerForm" :rules="jammerRules" class="dense-form" label-width="110px"
|
|
|
+ size="small">
|
|
|
+ <el-row :gutter="16">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="装备名称" prop="name">
|
|
|
+ <el-input v-model="newJammerForm.name"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="装备类型" prop="type">
|
|
|
+ <el-select v-model="newJammerForm.type" style="width:100%">
|
|
|
+ <el-option label="电磁" value="电磁"/>
|
|
|
+ <el-option label="雷达" value="雷达"/>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="经度" prop="lon">
|
|
|
+ <el-input v-model="newJammerForm.lon"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="纬度" prop="lat">
|
|
|
+ <el-input v-model="newJammerForm.lat"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="干扰频率(MHz)">
|
|
|
+ <el-input v-model.number="jammerForm.freq" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="干扰功率(W)">
|
|
|
+ <el-input v-model.number="jammerForm.power" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="持续时间(s)">
|
|
|
+ <el-input v-model.number="jammerForm.duration" :min="1" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="24">
|
|
|
+ <div class="subsection-header">
|
|
|
+ <span>激活计划</span>
|
|
|
+ <el-button icon="el-icon-plus" size="mini" type="primary" @click="addJammerSchedule">新增计划</el-button>
|
|
|
+ </div>
|
|
|
+ <el-table :data="newJammerForm.schedules" border size="small">
|
|
|
+ <el-table-column label="激活时间" width="140">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-time-picker v-model="scope.row.time" format="HH:mm:ss" placeholder="HH:mm:ss" size="mini"
|
|
|
+ value-format="HH:mm:ss"/>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="触发类型" width="150">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-select v-model="scope.row.triggerType" size="mini" style="width:140px;">
|
|
|
+ <el-option label="导弹接近" value="导弹接近"/>
|
|
|
+ <el-option label="雷达信号异常" value="雷达信号异常"/>
|
|
|
+ <el-option label="目标激活" value="目标激活"/>
|
|
|
+ <el-option label="手动触发" value="手动触发"/>
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="阈值参数" width="140">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-input v-model="scope.row.threshold" placeholder="如:距离<5km" size="mini"/>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="触发动作" width="160">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-select v-model="scope.row.action" size="mini" style="width:150px;">
|
|
|
+ <el-option label="立即启动干扰" value="立即启动干扰"/>
|
|
|
+ <el-option label="进入准备状态" value="进入准备状态"/>
|
|
|
+ <el-option label="发出警报" value="发出警报"/>
|
|
|
+ <el-option label="执行预设序列" value="执行预设序列"/>
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="说明">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-input v-model="scope.row.triggerDesc" placeholder="补充说明(可选)" size="mini"/>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="延迟(s)" width="100">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-input v-model.number="scope.row.delaySec" :min="0" size="mini" type="number"/>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="90">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-button class="danger-btn" size="mini" type="text" @click="removeJammerSchedule(scope.$index)">
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="addJammerDialog=false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="confirmAddJammer">{{ editModeJammer ? '保存' : '确认添加' }}</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 新增/编辑:测量 -->
|
|
|
+ <el-dialog :close-on-click-modal="false" :title="editModeMeasurement ? '编辑测量装备' : '添加测量装备'"
|
|
|
+ :visible.sync="addMeasurementDialog" size="40%">
|
|
|
+ <el-form ref="measureFormRef" :model="newMeasurementForm" :rules="measureRules" class="dense-form"
|
|
|
+ label-width="110px"
|
|
|
+ size="small">
|
|
|
+ <el-row :gutter="16">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="装备名称" prop="name">
|
|
|
+ <el-input v-model="newMeasurementForm.name"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="装备类型" prop="type">
|
|
|
+ <el-select v-model="newMeasurementForm.type" style="width:100%">
|
|
|
+ <el-option label="雷达" value="雷达"/>
|
|
|
+ <el-option label="光学" value="光学"/>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="经度" prop="lon">
|
|
|
+ <el-input v-model="newMeasurementForm.lon"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="纬度" prop="lat">
|
|
|
+ <el-input v-model="newMeasurementForm.lat"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="启动时间" prop="time">
|
|
|
+ <el-time-picker v-model="newMeasurementForm.time" format="HH:mm:ss" placeholder="选择时间"
|
|
|
+ style="width:100%;" value-format="HH:mm:ss"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12"></el-col>
|
|
|
+
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="测量范围(km)">
|
|
|
+ <el-input v-model.number="measureForm.range" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="测量精度(m)">
|
|
|
+ <el-input v-model.number="measureForm.precision" :min="0.1" step="0.1" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="俯仰角(°)">
|
|
|
+ <el-input v-model.number="measureForm.deg" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :spn="12">
|
|
|
+ <el-form-item label="离地高度(m)">
|
|
|
+ <el-input v-model.number="measureForm.height" type="number"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-form-item label="相对位置备注">
|
|
|
+ <el-input v-model="newMeasurementForm.posNote" placeholder="如:指挥所东南角 200m 高度20m"/>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :span="24">
|
|
|
+ <div class="subsection-header">
|
|
|
+ <span>激活计划</span>
|
|
|
+ <el-button icon="el-icon-plus" size="mini" type="primary" @click="addMeasurementSchedule">新增计划
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <el-table :data="newMeasurementForm.schedules" border size="small">
|
|
|
+ <el-table-column label="激活时间" width="140">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-time-picker v-model="scope.row.time" format="HH:mm:ss" placeholder="HH:mm:ss" size="mini"
|
|
|
+ value-format="HH:mm:ss"/>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="触发类型" width="150">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-select v-model="scope.row.triggerType" size="mini" style="width:140px;">
|
|
|
+ <el-option label="导弹接近" value="导弹接近"/>
|
|
|
+ <el-option label="雷达信号异常" value="雷达信号异常"/>
|
|
|
+ <el-option label="目标激活" value="目标激活"/>
|
|
|
+ <el-option label="手动触发" value="手动触发"/>
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="阈值参数" width="140">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-input v-model="scope.row.threshold" placeholder="如:距离<5km" size="mini"/>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="触发动作" width="160">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-select v-model="scope.row.action" size="mini" style="width:150px;">
|
|
|
+ <el-option label="立即启动干扰" value="立即启动干扰"/>
|
|
|
+ <el-option label="进入准备状态" value="进入准备状态"/>
|
|
|
+ <el-option label="发出警报" value="发出警报"/>
|
|
|
+ <el-option label="执行预设序列" value="执行预设序列"/>
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="说明">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-input v-model="scope.row.triggerDesc" placeholder="补充说明(可选)" size="mini"/>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="延迟(s)" width="100">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-input v-model.number="scope.row.delaySec" :min="0" size="mini" type="number"/>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="90">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-button class="danger-btn" size="mini" type="text"
|
|
|
+ @click="removeMeasurementSchedule(scope.$index)">删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="addMeasurementDialog=false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="confirmAddMeasurement">{{
|
|
|
+ editModeMeasurement ? '保存' : '确认添加'
|
|
|
+ }}
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <dd-upload ref="uploadDd" />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import TargetSchemeInfo from './scenario/scheme/TargetSchemeInfo.vue';
|
|
|
+import JammerSchemeInfo from './scenario/scheme/JammerSchemeInfo.vue';
|
|
|
+import MeasurementSchemeInfo from './scenario/scheme/MeasurementSchemeInfo.vue';
|
|
|
+import EquipmentListWithTimeline from './scenario/equipment/EquipmentListWithTimeline.vue';
|
|
|
+import MissileMissionPanel from './scenario/mission/MissileMissionPanel.vue';
|
|
|
+import GanttChart from './scenario/GanttChart.vue';
|
|
|
+import MissileMissionCard from "@/views/decision/testBuild/scenario/mission/MissileMissionCard.vue";
|
|
|
+import {battlefieldEnvironmentInsert, getBattlefieldEnvironment} from "@/api/battlefieldEnvironment";
|
|
|
+import {getEquTree} from "@/api/faultSimulation";
|
|
|
+import DdUpload from "@/views/decision/testBuild/scenario/dd-upload.vue";
|
|
|
+
|
|
|
+export default {
|
|
|
+ components: {
|
|
|
+ DdUpload,
|
|
|
+ MissileMissionCard,
|
|
|
+ TargetSchemeInfo,
|
|
|
+ JammerSchemeInfo,
|
|
|
+ MeasurementSchemeInfo,
|
|
|
+ EquipmentListWithTimeline,
|
|
|
+ MissileMissionPanel,
|
|
|
+ GanttChart
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ //路由参数
|
|
|
+ plan: JSON.parse(this.$route.query.plan),
|
|
|
+ saveTimer: null,
|
|
|
+ saveLoad: false,
|
|
|
+ fileList: [],
|
|
|
+ xmlImport: {rawFile: null},
|
|
|
+ rangeDialogVisible: true,
|
|
|
+ showMain: false,
|
|
|
+ rangeForm: {
|
|
|
+ rangeId: 'A',
|
|
|
+ editType: '手动编辑',
|
|
|
+ ranges: [
|
|
|
+ {
|
|
|
+ id: 'A',
|
|
|
+ name: 'A靶区',
|
|
|
+ description: '主要用于基础训练',
|
|
|
+ area: '15.5 km²',
|
|
|
+ status: 'active',
|
|
|
+ capacity: '50 units'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'B',
|
|
|
+ name: 'B靶区',
|
|
|
+ description: '主要用于综合训练',
|
|
|
+ area: '30.5 km²',
|
|
|
+ status: 'maintenance',
|
|
|
+ capacity: '50 units'
|
|
|
+ },
|
|
|
+ {id: 'C', name: 'C靶区'},
|
|
|
+ {id: 'all', name: '综合靶区'}
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ selectedRange: '',
|
|
|
+ scenarioName: 'XXXXxx',
|
|
|
+ scenarioVersion: 'v1.0.0',
|
|
|
+ missionType: '对陆目标打击',
|
|
|
+ createTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
|
|
+ missionMissileCount: 3,
|
|
|
+
|
|
|
+ // 方案基座(用于右侧方案视图)
|
|
|
+ targetPlanBase: {
|
|
|
+ name: '靶标布设方案-01', creator: '张三', status: '已编制',
|
|
|
+ algorithm: '随机布设', density: '中等', coveragePct: 72,
|
|
|
+ createdAt: new Date().toISOString().slice(0, 16).replace('T', ' '),
|
|
|
+ points: [
|
|
|
+ {name: '静态靶标-1', type: '固定式靶标', lon: '116.320000', lat: '39.950000', time: '00:10:00'},
|
|
|
+ {name: '动态靶标-1', type: '机动式靶标', lon: '116.450000', lat: '40.120000', time: '00:12:30'}
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ jammerPlanBase: {
|
|
|
+ name: '干扰布设方案-01', creator: '李四', status: '已编制',
|
|
|
+ algorithm: '组合干扰', range: '5 km 半径',
|
|
|
+ includesPreset: ['箔条', '发烟罐', '电磁干扰-A'],
|
|
|
+ createdAt: new Date().toISOString().slice(0, 16).replace('T', ' '),
|
|
|
+ points: [
|
|
|
+ {
|
|
|
+ name: '电磁干扰器-A',
|
|
|
+ type: '组合式干扰装备',
|
|
|
+ lon: '116.350000',
|
|
|
+ lat: '39.980000',
|
|
|
+ freq: 350,
|
|
|
+ power: 500,
|
|
|
+ duration: 180,
|
|
|
+ scheduleTime: '00:10:55'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '雷达干扰器-B',
|
|
|
+ type: '组合式干扰装备',
|
|
|
+ lon: '116.420000',
|
|
|
+ lat: '40.050000',
|
|
|
+ freq: 420,
|
|
|
+ power: 600,
|
|
|
+ duration: 160,
|
|
|
+ scheduleTime: '00:13:20'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ measurePlanBase: {
|
|
|
+ name: '测量布设方案-01', creator: '王五', status: '已编制',
|
|
|
+ algorithm: '多传感器融合布设算法示',
|
|
|
+ includesPreset: ['高速相机-Alpha', '红外相机-Beta', '常速相机-Gamma'],
|
|
|
+ createdAt: new Date().toISOString().slice(0, 16).replace('T', ' '),
|
|
|
+ points: [
|
|
|
+ {
|
|
|
+ name: '雷达测量站-1',
|
|
|
+ type: '雷达',
|
|
|
+ posNote: '指挥所东南角 200m 高度20m',
|
|
|
+ height: 20,
|
|
|
+ lon: '116.300000',
|
|
|
+ lat: '39.900000',
|
|
|
+ time: '00:09:30',
|
|
|
+ range: 50,
|
|
|
+ precision: 0.5,
|
|
|
+ freq: 100
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '光学测量仪-2',
|
|
|
+ type: '光学',
|
|
|
+ posNote: '指挥所东南角 300m 高度15m',
|
|
|
+ height: 15,
|
|
|
+ lon: '116.500000',
|
|
|
+ lat: '40.180000',
|
|
|
+ time: '00:11:15',
|
|
|
+ range: 50,
|
|
|
+ precision: 0.5,
|
|
|
+ freq: 100
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+
|
|
|
+ step: 1,
|
|
|
+ stepMeta: [
|
|
|
+ {title: '装备配置', icon: 'el-icon-s-tools'},
|
|
|
+ {title: '试验任务规划', icon: 'el-icon-s-flag'},
|
|
|
+ {title: '战场环境规划', icon: 'el-icon-cloudy'},
|
|
|
+ {title: '想定预览', icon: 'el-icon-document'}
|
|
|
+ ],
|
|
|
+ equipmentTabs: [
|
|
|
+ {key: 'target', label: '靶标装备'},
|
|
|
+ {key: 'jammer', label: '干扰装备'},
|
|
|
+ {key: 'measurement', label: '测量装备'}
|
|
|
+ ],
|
|
|
+ activeTab: 'target',
|
|
|
+
|
|
|
+ targets: [
|
|
|
+ {
|
|
|
+ id: 'T-1',
|
|
|
+ name: '静态靶标-1',
|
|
|
+ type: '静态',
|
|
|
+ fixedFromPlan: true,
|
|
|
+ lon: '116.320000',
|
|
|
+ lat: '39.950000',
|
|
|
+ time: '00:10:00',
|
|
|
+ statusText: '可用',
|
|
|
+ statusClass: 'badge-success',
|
|
|
+ params: {mode: 'normal', duration: 300, motion: 'fixed', speed: 0}
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'T-2',
|
|
|
+ name: '动态靶标-1',
|
|
|
+ type: '动态',
|
|
|
+ fixedFromPlan: false,
|
|
|
+ lon: '116.450000',
|
|
|
+ lat: '40.120000',
|
|
|
+ time: '00:12:30',
|
|
|
+ statusText: '可用',
|
|
|
+ statusClass: 'badge-success',
|
|
|
+ params: {mode: 'normal', duration: 240, motion: 'linear', speed: 30}
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ jammers: [
|
|
|
+ {
|
|
|
+ id: 'J-1',
|
|
|
+ name: '电磁干扰器-A',
|
|
|
+ type: '电磁',
|
|
|
+ lon: '116.350000',
|
|
|
+ lat: '39.980000',
|
|
|
+ params: {freq: 350, power: 500, duration: 180},
|
|
|
+ schedules: [{time: '00:10:55', triggerType: 'time', triggerDesc: '原计划', delaySec: 0}]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'J-2',
|
|
|
+ name: '雷达干扰器-B',
|
|
|
+ type: '雷达',
|
|
|
+ lon: '116.420000',
|
|
|
+ lat: '40.050000',
|
|
|
+ params: {freq: 420, power: 600, duration: 160},
|
|
|
+ schedules: [{time: '00:13:20', triggerType: 'time', triggerDesc: '', delaySec: 0}]
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ measurements: [
|
|
|
+ {
|
|
|
+ id: 'M-1',
|
|
|
+ name: '雷达测量站-1',
|
|
|
+ type: '雷达',
|
|
|
+ lon: '116.300000',
|
|
|
+ lat: '39.900000',
|
|
|
+ time: '00:09:30',
|
|
|
+ params: {range: 50, freq: 100, precision: 0.5},
|
|
|
+ schedules: []
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'M-2',
|
|
|
+ name: '光学测量仪-2',
|
|
|
+ type: '光学',
|
|
|
+ lon: '116.500000',
|
|
|
+ lat: '40.180000',
|
|
|
+ time: '00:11:15',
|
|
|
+ params: {range: 50, freq: 100, precision: 0.5},
|
|
|
+ schedules: []
|
|
|
+ }
|
|
|
+ ],
|
|
|
+
|
|
|
+ missiles: [],
|
|
|
+ openMissilePanels: [],
|
|
|
+
|
|
|
+ activeEnvTab:['meteorology'],
|
|
|
+ weatherForm: {wind_speed: 3.5, wind_direction: 90, temperature: 25, visibility: 10},
|
|
|
+ geoForm: {terrain: 'hilly', alt: 350, desc: ''},
|
|
|
+
|
|
|
+ legend: {showMeasurement: true, showTarget: true, showJammer: true},
|
|
|
+
|
|
|
+ addTargetDialog: false,
|
|
|
+ addJammerDialog: false,
|
|
|
+ addMeasurementDialog: false,
|
|
|
+ editModeTarget: false,
|
|
|
+ editIndexTarget: -1,
|
|
|
+ editModeJammer: false,
|
|
|
+ editIndexJammer: -1,
|
|
|
+ editModeMeasurement: false,
|
|
|
+ editIndexMeasurement: -1,
|
|
|
+
|
|
|
+ newTargetForm: {
|
|
|
+ name: '',
|
|
|
+ type: '静态',
|
|
|
+ fixedFromPlan: false,
|
|
|
+ lon: '',
|
|
|
+ lat: '',
|
|
|
+ time: '',
|
|
|
+ statusText: '可用',
|
|
|
+ schemePointName: ''
|
|
|
+ },
|
|
|
+ newJammerForm: {name: '', type: '电磁', lon: '', lat: '', schedules: []},
|
|
|
+ newMeasurementForm: {name: '', type: '雷达', lon: '', lat: '', time: '', schedules: [], posNote: ''},
|
|
|
+
|
|
|
+ targetForm: {mode: 'normal', duration: 300, motion: 'fixed', speed: 0},
|
|
|
+ jammerForm: {freq: 350, power: 500, duration: 180},
|
|
|
+ measureForm: {range: 50, freq: 100, precision: 0.5, deg: 10, height: 20},
|
|
|
+
|
|
|
+ mapMarkerPosition: null,
|
|
|
+
|
|
|
+ targetRules: {
|
|
|
+ name: [{required: true, message: '填写名称', trigger: 'blur'}],
|
|
|
+ type: [{required: true, message: '选择类型', trigger: 'change'}],
|
|
|
+ lon: [{required: true, message: '填写经度', trigger: 'blur'}],
|
|
|
+ lat: [{required: true, message: '填写纬度', trigger: 'blur'}],
|
|
|
+ time: [{required: true, message: '选择时间', trigger: 'change'}]
|
|
|
+ },
|
|
|
+ jammerRules: {
|
|
|
+ name: [{required: true, message: '填写名称', trigger: 'blur'}],
|
|
|
+ type: [{required: true, message: '选择类型', trigger: 'change'}],
|
|
|
+ lon: [{required: true, message: '填写经度', trigger: 'blur'}],
|
|
|
+ lat: [{required: true, message: '填写纬度', trigger: 'blur'}]
|
|
|
+ },
|
|
|
+ measureRules: {
|
|
|
+ name: [{required: true, message: '填写名称', trigger: 'blur'}],
|
|
|
+ type: [{required: true, message: '选择类型', trigger: 'change'}],
|
|
|
+ lon: [{required: true, message: '填写经度', trigger: 'blur'}],
|
|
|
+ lat: [{required: true, message: '填写纬度', trigger: 'blur'}],
|
|
|
+ time: [{required: true, message: '选择时间', trigger: 'change'}]
|
|
|
+ },
|
|
|
+
|
|
|
+ leftWidth: '58%',
|
|
|
+ rightWidth: '42%',
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ currentRangeInfo() {
|
|
|
+ if (!this.rangeForm.rangeId) return null;
|
|
|
+ return this.rangeForm.ranges.find(r => r.id === this.rangeForm.rangeId) || null;
|
|
|
+ },
|
|
|
+ targetSchemeView() {
|
|
|
+ const types = Array.from(new Set(this.targets.map(t => t.type))).filter(Boolean);
|
|
|
+ return {
|
|
|
+ ...this.targetPlanBase,
|
|
|
+ count: this.targets.length,
|
|
|
+ types,
|
|
|
+ points: this.targets,
|
|
|
+ coveragePct: this.targetPlanBase.coveragePct
|
|
|
+ };
|
|
|
+ },
|
|
|
+ jammerSchemeView() {
|
|
|
+ const types = Array.from(new Set(this.jammers.map(j => j.type))).filter(Boolean);
|
|
|
+ const includes = Array.from(new Set([...(this.jammerPlanBase.includesPreset || []), ...this.jammers.map(j => j.name)]));
|
|
|
+ return {...this.jammerPlanBase, count: this.jammers.length, types, points: this.jammers, includes};
|
|
|
+ },
|
|
|
+ measureSchemeView() {
|
|
|
+ const types = Array.from(new Set(this.measurements.map(m => m.type))).filter(Boolean);
|
|
|
+ const includes = Array.from(new Set([...(this.measurePlanBase.includesPreset || []), ...this.measurements.map(m => m.name)]));
|
|
|
+ return {...this.measurePlanBase, count: this.measurements.length, types, includes, points: this.measurements};
|
|
|
+ },
|
|
|
+ stat() {
|
|
|
+ return {
|
|
|
+ total: this.targets.length + this.jammers.length + this.measurements.length,
|
|
|
+ target: this.targets.length,
|
|
|
+ jammer: this.jammers.length,
|
|
|
+ measure: this.measurements.length
|
|
|
+ };
|
|
|
+ },
|
|
|
+ // 父级仍负责汇总 Step4 的完整时序
|
|
|
+ allEvents() {
|
|
|
+ const events = [];
|
|
|
+ this.measurements.forEach((m) => {
|
|
|
+ if (m.time) events.push({
|
|
|
+ time: `T0+${m.time}`, rawTime: m.time, name: m.name,
|
|
|
+ title: '测量装备启动', desc: `${m.name} 开始工作`,
|
|
|
+ kindText: '测量装备', typeClass: 'tl-secondary', badgeClass: 'badge-secondary', triggerTypeText: '定时触发'
|
|
|
+ });
|
|
|
+ });
|
|
|
+ this.targets.forEach((t) => {
|
|
|
+ if (t.time) events.push({
|
|
|
+ time: `T0+${t.time}`, rawTime: t.time, name: t.name,
|
|
|
+ title: t.type === '动态' ? '动态靶标运行' : '靶标激活',
|
|
|
+ desc: `${t.name} ${t.type === '动态' ? '按设定运动' : '激活'}`,
|
|
|
+ kindText: '靶标装备',
|
|
|
+ typeClass: t.type === '动态' ? 'tl-danger' : 'tl-warn',
|
|
|
+ badgeClass: t.statusText === '可用' ? 'badge-success' : (t.statusText === '维护中' ? 'badge-warn' : 'badge-accent'),
|
|
|
+ triggerTypeText: '定时触发'
|
|
|
+ });
|
|
|
+ });
|
|
|
+ this.jammers.forEach((j) => {
|
|
|
+ (j.schedules || []).forEach((s) => {
|
|
|
+ if (!s.time) return;
|
|
|
+ events.push({
|
|
|
+ time: `T0+${s.time}`, rawTime: s.time, name: j.name,
|
|
|
+ title: '干扰装备启动',
|
|
|
+ desc: `${j.name} ${s.triggerType === 'condition' ? '(条件触发)' : '(激活)'}`,
|
|
|
+ kindText: s.triggerType === 'condition' ? '干扰装备(触发)' : '干扰装备',
|
|
|
+ typeClass: s.triggerType === 'condition' ? 'tl-danger' : 'tl-muted',
|
|
|
+ badgeClass: s.triggerType === 'condition' ? 'badge-danger' : 'badge-accent',
|
|
|
+ triggerTypeText: s.triggerType === 'time' ? '定时触发' : (s.triggerType === 'manual' ? '手动触发' : '条件触发')
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ console.log('events', events.sort((a, b) => this.hhmmssToSec(a.rawTime) - this.hhmmssToSec(b.rawTime)));
|
|
|
+ return events.sort((a, b) => this.hhmmssToSec(a.rawTime) - this.hhmmssToSec(b.rawTime));
|
|
|
+ },
|
|
|
+ filteredAllEvents() {
|
|
|
+ return this.allEvents.filter(e => {
|
|
|
+ if (e.kindText === '测量装备' && !this.legend.showMeasurement) return false;
|
|
|
+ if (e.kindText.indexOf('靶标') > -1 && !this.legend.showTarget) return false;
|
|
|
+ if (e.kindText.indexOf('干扰') > -1 && !this.legend.showJammer) return false;
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ missionMissileCount: {
|
|
|
+ immediate: true,
|
|
|
+ handler(n) {
|
|
|
+ const count = Number(n) || 1;
|
|
|
+ const arr = this.missiles.slice();
|
|
|
+ if (arr.length < count) {
|
|
|
+ for (let i = arr.length; i < count; i++) {
|
|
|
+ arr.push({
|
|
|
+ id: `MSL-${i + 1}`,
|
|
|
+ targetType: 'land',
|
|
|
+ targetName: '',
|
|
|
+ targetCoord: '',
|
|
|
+ targetSpeed: 0,
|
|
|
+ targetDesc: '',
|
|
|
+ traj: {start: '116.3,39.9,0', end: '116.5,40.1,0.5', timeSec: 90}
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } else if (arr.length > count) {
|
|
|
+ arr.length = count;
|
|
|
+ }
|
|
|
+ this.missiles = arr;
|
|
|
+ this.openMissilePanels = this.missiles.map(m => m.id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ // 获取环境信息
|
|
|
+ getBattlefieldEnvironment({simulationId:this.plan.id}).then((res)=>{
|
|
|
+ this.weatherForm = JSON.parse(res.data.environmentJson)
|
|
|
+ })
|
|
|
+ getEquTree({subTaskId:this.plan.subTaskId}).then((res)=>{
|
|
|
+ this.equTree = res.data
|
|
|
+ })
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ beforeUpload(file) {
|
|
|
+ const okExt = /\.xml$/i.test(file.name);
|
|
|
+ const okSize = file.size <= 2 * 1024 * 1024;
|
|
|
+ if (!okExt) this.$message.error('仅支持 .xml 文件');
|
|
|
+ if (!okSize) this.$message.error('文件不能超过 2MB');
|
|
|
+ return okExt && okSize;
|
|
|
+ },
|
|
|
+ handleFileChange(file, fileList) {
|
|
|
+ this.fileList = fileList.slice(-1);
|
|
|
+ this.xmlImport.rawFile = file.raw;
|
|
|
+ },
|
|
|
+ handleRemove() {
|
|
|
+ this.fileList = [];
|
|
|
+ this.xmlImport = {rawFile: null};
|
|
|
+ },
|
|
|
+ confirmRange() {
|
|
|
+ const selected = this.currentRangeInfo;
|
|
|
+ if (!selected) {
|
|
|
+ this.$message.warning('请先选择靶区');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.selectedRange = selected.name || '';
|
|
|
+ this.rangeDialogVisible = false;
|
|
|
+ this.showMain = true;
|
|
|
+ this.$message.success(`已选择靶区:${this.selectedRange}`);
|
|
|
+ },
|
|
|
+ exportPlan() {
|
|
|
+ },
|
|
|
+ goBack() {
|
|
|
+ this.$router && this.$router.go(-1);
|
|
|
+ },
|
|
|
+ hhmmssToSec(hms) {
|
|
|
+ const [h = 0, m = 0, s = 0] = (hms || '00:00:00').split(':').map(n => parseInt(n, 10) || 0);
|
|
|
+ return h * 3600 + m * 60 + s;
|
|
|
+ },
|
|
|
+ getPreviewTitle() {
|
|
|
+ return this.activeTab === 'target'
|
|
|
+ ? '靶标布设方案信息'
|
|
|
+ : this.activeTab === 'jammer'
|
|
|
+ ? '干扰装备布设方案信息'
|
|
|
+ : '测量装备布设方案信息';
|
|
|
+ },
|
|
|
+ getTimelineType(c) {
|
|
|
+ return c === 'tl-secondary' ? 'success'
|
|
|
+ : c === 'tl-warn' ? 'warning'
|
|
|
+ : c === 'tl-danger' ? 'danger'
|
|
|
+ : c === 'tl-muted' ? 'info'
|
|
|
+ : 'primary';
|
|
|
+ },
|
|
|
+ getTagType(c) {
|
|
|
+ return c === 'badge-success' ? 'success'
|
|
|
+ : c === 'badge-warn' ? 'warning'
|
|
|
+ : c === 'badge-danger' ? 'danger'
|
|
|
+ : c === 'badge-accent' ? 'info'
|
|
|
+ : c === 'badge-secondary' ? 'primary'
|
|
|
+ : 'default';
|
|
|
+ },
|
|
|
+ goPrev() {
|
|
|
+ this.step = Math.max(1, this.step - 1);
|
|
|
+ },
|
|
|
+ goNext() {
|
|
|
+ this.step = Math.min(4, this.step + 1);
|
|
|
+ },
|
|
|
+
|
|
|
+ // —— 新增/编辑弹窗相关(保留) ——
|
|
|
+ openAddTargetDialog() {
|
|
|
+ this.editModeTarget = false;
|
|
|
+ this.editIndexTarget = -1;
|
|
|
+ this.newTargetForm = {
|
|
|
+ name: '',
|
|
|
+ type: '静态',
|
|
|
+ fixedFromPlan: false,
|
|
|
+ lon: '',
|
|
|
+ lat: '',
|
|
|
+ time: '',
|
|
|
+ statusText: '可用',
|
|
|
+ schemePointName: ''
|
|
|
+ };
|
|
|
+ this.targetForm = {mode: 'normal', duration: 300, motion: 'fixed', speed: 0};
|
|
|
+ this.mapMarkerPosition = null;
|
|
|
+ this.addTargetDialog = true;
|
|
|
+ },
|
|
|
+ openAddJammerDialog() {
|
|
|
+ this.editModeJammer = false;
|
|
|
+ this.editIndexJammer = -1;
|
|
|
+ this.newJammerForm = {name: '', type: '电磁', lon: '', lat: '', schedules: []};
|
|
|
+ this.jammerForm = {freq: 350, power: 500, duration: 180};
|
|
|
+ this.addJammerDialog = true;
|
|
|
+ },
|
|
|
+ openAddMeasurementDialog() {
|
|
|
+ this.editModeMeasurement = false;
|
|
|
+ this.editIndexMeasurement = -1;
|
|
|
+ this.newMeasurementForm = {name: '', type: '雷达', lon: '', lat: '', time: '', schedules: [], posNote: ''};
|
|
|
+ this.measureForm = {range: 50, freq: 100, precision: 0.5, deg: 10, height: 20};
|
|
|
+ this.addMeasurementDialog = true;
|
|
|
+ },
|
|
|
+ selectMapPosition(e) {
|
|
|
+ const x = Math.max(0, Math.min(e.offsetX, e.currentTarget.clientWidth));
|
|
|
+ const y = Math.max(0, Math.min(e.offsetY, e.currentTarget.clientHeight));
|
|
|
+ this.mapMarkerPosition = {x, y};
|
|
|
+ const baseLon = 116.3, baseLat = 39.9;
|
|
|
+ this.newTargetForm.lon = (baseLon + x / 200).toFixed(6);
|
|
|
+ this.newTargetForm.lat = (baseLat + y / 200).toFixed(6);
|
|
|
+ },
|
|
|
+ onTargetTypeChange() {
|
|
|
+ if (this.newTargetForm.type === '动态') this.newTargetForm.fixedFromPlan = false;
|
|
|
+ },
|
|
|
+ confirmAddTarget() {
|
|
|
+ this.$refs.targetFormRef.validate(valid => {
|
|
|
+ if (!valid) return;
|
|
|
+ const statusMap = {'可用': 'badge-success', '维护中': 'badge-warn', '停用': 'badge-accent'};
|
|
|
+ const params = {
|
|
|
+ mode: this.targetForm.mode,
|
|
|
+ duration: this.targetForm.duration,
|
|
|
+ motion: this.newTargetForm.type === '动态' ? this.targetForm.motion : 'fixed',
|
|
|
+ speed: this.newTargetForm.type === '动态' ? this.targetForm.speed : 0
|
|
|
+ };
|
|
|
+ if (this.editModeTarget && this.editIndexTarget > -1) {
|
|
|
+ const t = this.targets[this.editIndexTarget];
|
|
|
+ Object.assign(t, {
|
|
|
+ name: this.newTargetForm.name, type: this.newTargetForm.type,
|
|
|
+ fixedFromPlan: !!this.newTargetForm.fixedFromPlan, lon: this.newTargetForm.lon, lat: this.newTargetForm.lat,
|
|
|
+ time: this.newTargetForm.time, statusText: this.newTargetForm.statusText,
|
|
|
+ statusClass: statusMap[this.newTargetForm.statusText] || 'badge-success',
|
|
|
+ params: {...params}
|
|
|
+ });
|
|
|
+ this.$message.success('已保存靶标修改');
|
|
|
+ } else {
|
|
|
+ const id = `T-${Date.now()}`;
|
|
|
+ this.targets.push({
|
|
|
+ id, name: this.newTargetForm.name, type: this.newTargetForm.type,
|
|
|
+ fixedFromPlan: !!this.newTargetForm.fixedFromPlan, lon: this.newTargetForm.lon, lat: this.newTargetForm.lat,
|
|
|
+ time: this.newTargetForm.time, statusText: this.newTargetForm.statusText,
|
|
|
+ statusClass: statusMap[this.newTargetForm.statusText] || 'badge-success',
|
|
|
+ params
|
|
|
+ });
|
|
|
+ this.$message.success('已添加靶标');
|
|
|
+ }
|
|
|
+ this.addTargetDialog = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ editTarget(i) {
|
|
|
+ const t = this.targets[i];
|
|
|
+ this.editModeTarget = true;
|
|
|
+ this.editIndexTarget = i;
|
|
|
+ this.newTargetForm = {
|
|
|
+ name: t.name,
|
|
|
+ type: t.type,
|
|
|
+ fixedFromPlan: !!t.fixedFromPlan,
|
|
|
+ lon: t.lon,
|
|
|
+ lat: t.lat,
|
|
|
+ time: t.time,
|
|
|
+ statusText: t.statusText,
|
|
|
+ schemePointName: t.name
|
|
|
+ };
|
|
|
+ this.targetForm = {
|
|
|
+ ...(t.params || {}),
|
|
|
+ motion: (t.params && t.params.motion) || (t.type === '动态' ? 'linear' : 'fixed')
|
|
|
+ };
|
|
|
+ this.mapMarkerPosition = null;
|
|
|
+ this.addTargetDialog = true;
|
|
|
+ },
|
|
|
+ removeTarget(i) {
|
|
|
+ this.$confirm(`确定删除 "${this.targets[i].name}"?`, '提示', {type: 'warning'})
|
|
|
+ .then(() => {
|
|
|
+ this.targets.splice(i, 1);
|
|
|
+ this.$message.success('已删除');
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ addJammerSchedule() {
|
|
|
+ this.newJammerForm.schedules.push({
|
|
|
+ time: '',
|
|
|
+ triggerType: '导弹接近',
|
|
|
+ threshold: '距离<5km',
|
|
|
+ action: '立即启动干扰',
|
|
|
+ triggerDesc: '',
|
|
|
+ delaySec: 0
|
|
|
+ });
|
|
|
+ },
|
|
|
+ removeJammerSchedule(i) {
|
|
|
+ this.newJammerForm.schedules.splice(i, 1);
|
|
|
+ },
|
|
|
+ confirmAddJammer() {
|
|
|
+ if ((this.newJammerForm.schedules || []).length === 0) return this.$message.warning('至少添加一条激活计划');
|
|
|
+ if (this.newJammerForm.schedules.some(s => !s.time || !s.triggerType)) return this.$message.warning('存在未填写完整的计划');
|
|
|
+ this.$refs.jammerFormRef.validate(valid => {
|
|
|
+ if (!valid) return;
|
|
|
+ if (this.editModeJammer && this.editIndexJammer > -1) {
|
|
|
+ const j = this.jammers[this.editIndexJammer];
|
|
|
+ j.name = this.newJammerForm.name;
|
|
|
+ j.type = this.newJammerForm.type;
|
|
|
+ j.lon = this.newJammerForm.lon;
|
|
|
+ j.lat = this.newJammerForm.lat;
|
|
|
+ j.params = {...this.jammerForm};
|
|
|
+ j.schedules = JSON.parse(JSON.stringify(this.newJammerForm.schedules));
|
|
|
+ this.$message.success('已保存干扰装备修改');
|
|
|
+ } else {
|
|
|
+ const id = `J-${Date.now()}`;
|
|
|
+ this.jammers.push({
|
|
|
+ id,
|
|
|
+ name: this.newJammerForm.name,
|
|
|
+ type: this.newJammerForm.type,
|
|
|
+ lon: this.newJammerForm.lon,
|
|
|
+ lat: this.newJammerForm.lat,
|
|
|
+ params: {...this.jammerForm},
|
|
|
+ schedules: JSON.parse(JSON.stringify(this.newJammerForm.schedules))
|
|
|
+ });
|
|
|
+ this.$message.success('已添加干扰装备');
|
|
|
+ }
|
|
|
+ this.addJammerDialog = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ editJammer(i) {
|
|
|
+ const j = this.jammers[i];
|
|
|
+ this.editModeJammer = true;
|
|
|
+ this.editIndexJammer = i;
|
|
|
+ this.newJammerForm = {
|
|
|
+ name: j.name,
|
|
|
+ type: j.type,
|
|
|
+ lon: j.lon,
|
|
|
+ lat: j.lat,
|
|
|
+ schedules: JSON.parse(JSON.stringify(j.schedules || []))
|
|
|
+ };
|
|
|
+ this.jammerForm = {...(j.params || {freq: 350, power: 500, duration: 180})};
|
|
|
+ this.addJammerDialog = true;
|
|
|
+ },
|
|
|
+ removeJammer(i) {
|
|
|
+ this.$confirm(`确定删除 "${this.jammers[i].name}"?`, '提示', {type: 'warning'})
|
|
|
+ .then(() => {
|
|
|
+ this.jammers.splice(i, 1);
|
|
|
+ this.$message.success('已删除');
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ addMeasurementSchedule() {
|
|
|
+ this.newMeasurementForm.schedules.push({
|
|
|
+ time: '',
|
|
|
+ triggerType: '导弹接近',
|
|
|
+ threshold: '距离<5km',
|
|
|
+ action: '发出警报',
|
|
|
+ triggerDesc: '',
|
|
|
+ delaySec: 0
|
|
|
+ });
|
|
|
+ },
|
|
|
+ removeMeasurementSchedule(i) {
|
|
|
+ this.newMeasurementForm.schedules.splice(i, 1);
|
|
|
+ },
|
|
|
+ confirmAddMeasurement() {
|
|
|
+ this.$refs.measureFormRef.validate(valid => {
|
|
|
+ if (!valid) return;
|
|
|
+ if ((this.newMeasurementForm.schedules || []).some(s => !s.time || !s.triggerType || !s.action)) {
|
|
|
+ return this.$message.warning('测量装备存在未填写完整的激活计划');
|
|
|
+ }
|
|
|
+ if (this.editModeMeasurement && this.editIndexMeasurement > -1) {
|
|
|
+ const m = this.measurements[this.editIndexMeasurement];
|
|
|
+ m.name = this.newMeasurementForm.name;
|
|
|
+ m.type = this.newMeasurementForm.type;
|
|
|
+ m.lon = this.newMeasurementForm.lon;
|
|
|
+ m.lat = this.newMeasurementForm.lat;
|
|
|
+ m.time = this.newMeasurementForm.time;
|
|
|
+ m.posNote = this.newMeasurementForm.posNote || '';
|
|
|
+ m.params = {...this.measureForm};
|
|
|
+ m.schedules = JSON.parse(JSON.stringify(this.newMeasurementForm.schedules || []));
|
|
|
+ this.$message.success('已保存测量装备修改');
|
|
|
+ } else {
|
|
|
+ const id = `M-${Date.now()}`;
|
|
|
+ this.measurements.push({
|
|
|
+ id, name: this.newMeasurementForm.name, type: this.newMeasurementForm.type,
|
|
|
+ lon: this.newMeasurementForm.lon, lat: this.newMeasurementForm.lat, time: this.newMeasurementForm.time,
|
|
|
+ posNote: this.newMeasurementForm.posNote || '',
|
|
|
+ params: {...this.measureForm},
|
|
|
+ schedules: JSON.parse(JSON.stringify(this.newMeasurementForm.schedules || []))
|
|
|
+ });
|
|
|
+ this.$message.success('已添加测量装备');
|
|
|
+ }
|
|
|
+ this.addMeasurementDialog = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ editMeasurement(i) {
|
|
|
+ const m = this.measurements[i];
|
|
|
+ this.editModeMeasurement = true;
|
|
|
+ this.editIndexMeasurement = i;
|
|
|
+ this.newMeasurementForm = {
|
|
|
+ name: m.name,
|
|
|
+ type: m.type,
|
|
|
+ lon: m.lon,
|
|
|
+ lat: m.lat,
|
|
|
+ time: m.time,
|
|
|
+ schedules: JSON.parse(JSON.stringify(m.schedules || [])),
|
|
|
+ posNote: m.posNote || ''
|
|
|
+ };
|
|
|
+ this.measureForm = {...(m.params || {range: 50, freq: 100, precision: 0.5, deg: 10, height: 20})};
|
|
|
+ this.addMeasurementDialog = true;
|
|
|
+ },
|
|
|
+ removeMeasurement(i) {
|
|
|
+ this.$confirm(`确定删除 "${this.measurements[i].name}"?`, '提示', {type: 'warning'})
|
|
|
+ .then(() => {
|
|
|
+ this.measurements.splice(i, 1);
|
|
|
+ this.$message.success('已删除');
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ rebuildPreview() {
|
|
|
+ this.$message.success('时序已刷新');
|
|
|
+ },
|
|
|
+ handleTrajectory(plan) {
|
|
|
+ this.$refs.uploadDd.show({
|
|
|
+ missileId:plan.id,
|
|
|
+ simulationId:this.plan.id,
|
|
|
+ })
|
|
|
+ },
|
|
|
+ async saveFinal() {
|
|
|
+ this.saveLoad = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 保存环境配置
|
|
|
+ await battlefieldEnvironmentInsert({simulationId:this.plan.id,environmentJson:JSON.stringify(this.weatherForm)})
|
|
|
+
|
|
|
+ } catch (e) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$message.success('方案已保存(示例)');
|
|
|
+ const m = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(this.scenarioVersion || '');
|
|
|
+ if (m) {
|
|
|
+ const [maj, min, pat] = [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10) + 1];
|
|
|
+ this.scenarioVersion = `v${maj}.${min}.${pat}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.saveLoad = false;
|
|
|
+ this.$confirm("是否开始推演?", "提示", {type: "warning"}).then(() => {
|
|
|
+ this.$router.push("/Deduction/stratDeduction")
|
|
|
+ }).catch(() => {
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* ===== 入口页同色系主题(深蓝大屏) ===== */
|
|
|
+:root {
|
|
|
+ --brand: #0B2A59; /* 顶部导航深蓝 */
|
|
|
+ --bg: #071A35; /* 页面底色 */
|
|
|
+ --card: #0D2346; /* 卡片主体 */
|
|
|
+ --card-soft: #0F2B57; /* 次级卡片/表格底 */
|
|
|
+ --panel: #0B2143; /* 面板型卡片 */
|
|
|
+ --border: #1C3C72;
|
|
|
+ --text: #F4F8FF;
|
|
|
+ --text-2: #C9D7F0;
|
|
|
+ --muted: #9AB0D1;
|
|
|
+ --accent: #66B1FF; /* 入口页主高光蓝 */
|
|
|
+ --accent-2: #2BD99F; /* 入口页“选择方案/成功”绿 */
|
|
|
+ --warn: #FFC15A;
|
|
|
+ --danger: #FF7070;
|
|
|
+ --radius: 14px;
|
|
|
+ --radius-lg: 16px;
|
|
|
+ --header-h: 84px;
|
|
|
+ --footer-h: 60px;
|
|
|
+ --shadow: 0 10px 30px rgba(0, 0, 0, .35);
|
|
|
+ --glow: 0 0 0 1px rgba(255, 255, 255, .06), 0 12px 24px rgba(5, 24, 54, .45);
|
|
|
+}
|
|
|
+
|
|
|
+html, body, #app, .page {
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.page {
|
|
|
+ min-height: 100%;
|
|
|
+ background: radial-gradient(900px 500px at 85% -10%, rgba(102, 177, 255, .12), transparent 60%),
|
|
|
+ radial-gradient(800px 480px at -10% 25%, rgba(43, 217, 159, .10), transparent 60%),
|
|
|
+ var(--bg);
|
|
|
+ color: var(--text);
|
|
|
+ overflow: hidden;
|
|
|
+ font-family: "PingFang SC", "Microsoft YaHei", Segoe UI, system-ui, -apple-system, sans-serif;
|
|
|
+ letter-spacing: .2px;
|
|
|
+}
|
|
|
+
|
|
|
+.version-badge {
|
|
|
+ margin-left: 10px;
|
|
|
+ padding: 2px 8px;
|
|
|
+ border: 1px solid rgba(255, 255, 255, .28);
|
|
|
+ border-radius: 999px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #EAF2FF;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 卡片与头部 ===== */
|
|
|
+.ui-card {
|
|
|
+ background: var(--card) !important;
|
|
|
+ border: 1px solid rgba(255, 255, 255, .08) !important;
|
|
|
+ border-radius: var(--radius-lg) !important;
|
|
|
+ box-shadow: var(--glow) !important;
|
|
|
+}
|
|
|
+
|
|
|
+.ui-card.soft {
|
|
|
+ background: var(--card-soft) !important;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-card {
|
|
|
+ background: var(--panel) !important;
|
|
|
+}
|
|
|
+
|
|
|
+.header {
|
|
|
+ height: var(--header-h) !important;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: linear-gradient(180deg, var(--brand), #0A244B);
|
|
|
+ border-bottom: 1px solid rgba(255, 255, 255, .06);
|
|
|
+ padding: 0 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.header-inner {
|
|
|
+ width: 100%;
|
|
|
+ max-width: 1760px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.header-actions {
|
|
|
+ margin-left: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 12px 14px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #EAF2FF;
|
|
|
+ background: linear-gradient(180deg, rgba(255, 255, 255, .07), rgba(255, 255, 255, .02));
|
|
|
+ border-bottom: 1px solid rgba(255, 255, 255, .08);
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== Steps 对齐入口页 ===== */
|
|
|
+.steps-light {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.steps-light ::v-deep .el-steps--simple {
|
|
|
+ background: transparent;
|
|
|
+ padding: 6px 10px;
|
|
|
+ border-radius: var(--radius);
|
|
|
+}
|
|
|
+
|
|
|
+.steps-light ::v-deep .el-step__title {
|
|
|
+ color: #EAF2FF;
|
|
|
+ font-weight: 700;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.steps-light ::v-deep .is-process .el-step__title {
|
|
|
+ color: #fff;
|
|
|
+ text-shadow: 0 1px 0 rgba(0, 0, 0, .2);
|
|
|
+}
|
|
|
+
|
|
|
+.steps-light ::v-deep .el-step__icon-inner {
|
|
|
+ color: #EAF2FF;
|
|
|
+}
|
|
|
+
|
|
|
+.step-index {
|
|
|
+ color: #EAF2FF;
|
|
|
+ font-weight: 800;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 布局与滚动 ===== */
|
|
|
+.workspace {
|
|
|
+ height: calc(100% - var(--header-h) - var(--footer-h));
|
|
|
+}
|
|
|
+
|
|
|
+.aside {
|
|
|
+ padding: 12px 12px 8px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.aside-left {
|
|
|
+ border-right: 1px solid rgba(255, 255, 255, .08);
|
|
|
+}
|
|
|
+
|
|
|
+.main-full {
|
|
|
+ padding: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.fill-card {
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.fill-card ::v-deep .el-card__body {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.fill-scroll {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: auto;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 列表区 ===== */
|
|
|
+.switcher {
|
|
|
+ min-width: 160px;
|
|
|
+}
|
|
|
+
|
|
|
+.left-body-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-rows:1fr 1fr;
|
|
|
+ gap: 12px;
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .el-table {
|
|
|
+ background: var(--card-soft);
|
|
|
+ color: var(--text-2);
|
|
|
+ border-color: rgba(255, 255, 255, .08);
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .el-table th {
|
|
|
+ background: linear-gradient(180deg, rgba(255, 255, 255, .06), rgba(255, 255, 255, .03));
|
|
|
+ color: var(--text);
|
|
|
+ font-weight: 700;
|
|
|
+ border-color: rgba(255, 255, 255, .08);
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .el-table td {
|
|
|
+ border-color: rgba(255, 255, 255, .06);
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td {
|
|
|
+ background: rgba(255, 255, 255, .02);
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .el-table__row:hover td {
|
|
|
+ background: rgba(102, 177, 255, .06) !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 时间轴(与入口页卡片层次一致) ===== */
|
|
|
+.timeline-section {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin: 0 2px 6px;
|
|
|
+ color: #EAF2FF;
|
|
|
+ font-weight: 700;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-wrap {
|
|
|
+ padding-right: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.tl-card {
|
|
|
+ border-left: 3px solid var(--accent);
|
|
|
+ padding: 10px 12px;
|
|
|
+ background: rgba(255, 255, 255, .03) !important;
|
|
|
+}
|
|
|
+
|
|
|
+.tl-title {
|
|
|
+ font-weight: 800;
|
|
|
+ color: #EAF2FF;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.tl-desc {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--text-2);
|
|
|
+}
|
|
|
+
|
|
|
+.m-l-6 {
|
|
|
+ margin-left: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.el-timeline-item__tail {
|
|
|
+ border-left-color: rgba(255, 255, 255, .08) !important;
|
|
|
+}
|
|
|
+
|
|
|
+.el-timeline-item.is-success .el-timeline-item__node {
|
|
|
+ background: #2BD99F !important;
|
|
|
+}
|
|
|
+
|
|
|
+.el-timeline-item.is-warning .el-timeline-item__node {
|
|
|
+ background: #FFC15A !important;
|
|
|
+}
|
|
|
+
|
|
|
+.el-timeline-item.is-danger .el-timeline-item__node {
|
|
|
+ background: #FF7070 !important;
|
|
|
+}
|
|
|
+
|
|
|
+.el-timeline-item.is-info .el-timeline-item__node {
|
|
|
+ background: #69B1FF !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== KPI 卡片 ===== */
|
|
|
+.kpi-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns:repeat(4, 1fr);
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.kpi-card {
|
|
|
+ border: 1px solid rgba(102, 177, 255, .28);
|
|
|
+ border-radius: 10px;
|
|
|
+ text-align: center;
|
|
|
+ padding: 12px 8px;
|
|
|
+ background: radial-gradient(240px 140px at 85% -30%, rgba(102, 177, 255, .16), transparent 60%),
|
|
|
+ radial-gradient(220px 140px at 0% 140%, rgba(43, 217, 159, .12), transparent 60%),
|
|
|
+ linear-gradient(180deg, rgba(255, 255, 255, .06), rgba(255, 255, 255, .02));
|
|
|
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
|
|
|
+}
|
|
|
+
|
|
|
+.kpi-value {
|
|
|
+ font-size: 26px;
|
|
|
+ font-weight: 900;
|
|
|
+ color: #E2EEFF;
|
|
|
+ letter-spacing: .5px;
|
|
|
+}
|
|
|
+
|
|
|
+.kpi-label {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--muted);
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 子标题左色条(入口页常用) ===== */
|
|
|
+.sub-title {
|
|
|
+ position: relative;
|
|
|
+ padding-left: 10px;
|
|
|
+ font-weight: 800;
|
|
|
+ color: #EAF2FF;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.sub-title:before {
|
|
|
+ content: "";
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 2px;
|
|
|
+ bottom: 2px;
|
|
|
+ width: 3px;
|
|
|
+ background: linear-gradient(180deg, var(--accent), rgba(102, 177, 255, .3));
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+/* Descriptions 表格底色 */
|
|
|
+.desc-table ::v-deep .el-descriptions__body {
|
|
|
+ background: rgba(255, 255, 255, .02);
|
|
|
+}
|
|
|
+
|
|
|
+.desc-table ::v-deep .el-descriptions__label {
|
|
|
+ color: #CFE2FF;
|
|
|
+}
|
|
|
+
|
|
|
+.desc-table ::v-deep .el-descriptions__content {
|
|
|
+ color: #EAF2FF;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 弹窗(入口页玻璃感) ===== */
|
|
|
+::v-deep .el-dialog {
|
|
|
+ background: linear-gradient(180deg, rgba(13, 35, 70, .96), rgba(11, 33, 67, .96));
|
|
|
+ border: 1px solid rgba(255, 255, 255, .08);
|
|
|
+ box-shadow: var(--shadow);
|
|
|
+ border-radius: 16px;
|
|
|
+ backdrop-filter: blur(6px);
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .el-dialog__header {
|
|
|
+ background: linear-gradient(180deg, rgba(255, 255, 255, .06), rgba(255, 255, 255, .02));
|
|
|
+ border-bottom: 1px solid rgba(255, 255, 255, .08);
|
|
|
+ color: var(--text);
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 底部操作条 ===== */
|
|
|
+.footer-actions {
|
|
|
+ position: fixed;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ height: var(--footer-h);
|
|
|
+ background: rgba(8, 26, 52, .96);
|
|
|
+ border-top: 1px solid rgba(255, 255, 255, .06);
|
|
|
+ backdrop-filter: blur(6px);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ z-index: 20;
|
|
|
+}
|
|
|
+
|
|
|
+.actions-inner {
|
|
|
+ width: 100%;
|
|
|
+ max-width: 1760px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 10px 16px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 自适应 ===== */
|
|
|
+@media (max-width: 1480px) {
|
|
|
+ .kpi-grid {
|
|
|
+ grid-template-columns:repeat(2, 1fr);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 1280px) {
|
|
|
+ .workspace {
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .aside-left, .aside-right {
|
|
|
+ width: 100% !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 试验任务规划 · 折叠面板内的卡片优化 ===== */
|
|
|
+
|
|
|
+/* 折叠项标题行(“导弹 #1 …”那一行) */
|
|
|
+::v-deep .el-collapse-item__header {
|
|
|
+ background: linear-gradient(180deg, rgba(255, 255, 255, .06), rgba(255, 255, 255, .02));
|
|
|
+ border: 1px solid rgba(255, 255, 255, .10);
|
|
|
+ border-bottom: none;
|
|
|
+ border-radius: 12px 12px 0 0;
|
|
|
+ color: var(--text);
|
|
|
+ font-weight: 700;
|
|
|
+ padding: 0 14px;
|
|
|
+ min-height: 44px;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .el-collapse-item__arrow {
|
|
|
+ color: #9ED0FF;
|
|
|
+}
|
|
|
+
|
|
|
+/* 折叠项内容面板包裹层 */
|
|
|
+::v-deep .el-collapse-item__wrap {
|
|
|
+ background: transparent;
|
|
|
+ border: 1px solid rgba(255, 255, 255, .10);
|
|
|
+ border-top: none;
|
|
|
+ border-radius: 0px 0px 12px 12px;
|
|
|
+ margin-top: -1px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 目标卡片:就在折叠项里那张 .ui-card.panel-card */
|
|
|
+.main-full .panel-card {
|
|
|
+ background: linear-gradient(180deg, rgba(255, 255, 255, .04), rgba(255, 255, 255, .02)) !important;
|
|
|
+ border: 1px solid rgba(255, 255, 255, .10) !important;
|
|
|
+ border-radius: 0 0 14px 14px !important; /* 与header圆角匹配 */
|
|
|
+ box-shadow: 0 8px 24px rgba(0, 0, 0, .28) !important;
|
|
|
+ transition: box-shadow .25s ease, transform .25s ease, border-color .25s ease;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+/* 轻微悬浮反馈 */
|
|
|
+.main-full .panel-card:hover {
|
|
|
+ transform: translateY(-1px);
|
|
|
+ box-shadow: 0 14px 36px rgba(0, 0, 0, .34) !important;
|
|
|
+ border-color: rgba(102, 177, 255, .28) !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 卡片内部:标题栏与分组(你这张卡片用的是 el-form + el-divider) */
|
|
|
+.main-full .panel-card .sub-title,
|
|
|
+.main-full .panel-card .card-header {
|
|
|
+ background: linear-gradient(180deg, rgba(255, 255, 255, .06), rgba(255, 255, 255, .02));
|
|
|
+ border: 1px solid rgba(255, 255, 255, .08);
|
|
|
+ border-left: 3px solid var(--accent);
|
|
|
+ border-radius: 10px;
|
|
|
+ padding: 8px 10px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 分隔线更细、更亮一点 */
|
|
|
+.main-full .panel-card ::v-deep .el-divider {
|
|
|
+ background-color: transparent;
|
|
|
+ margin: 10px 0 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.main-full .panel-card ::v-deep .el-divider__text {
|
|
|
+ color: #CFE2FF;
|
|
|
+ font-weight: 700;
|
|
|
+ letter-spacing: .2px;
|
|
|
+ background: rgba(255, 255, 255, .02);
|
|
|
+ padding: 2px 10px;
|
|
|
+ border: 1px solid rgba(255, 255, 255, .08);
|
|
|
+ border-radius: 999px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 表单致密化 + 对齐 */
|
|
|
+.main-full .panel-card .dense-form ::v-deep .el-form-item {
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.main-full .panel-card .dense-form ::v-deep .el-form-item__label {
|
|
|
+ color: var(--text-2);
|
|
|
+ padding-right: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.main-full .panel-card .dense-form ::v-deep .el-input__inner,
|
|
|
+.main-full .panel-card .dense-form ::v-deep .el-textarea__inner,
|
|
|
+.main-full .panel-card .dense-form ::v-deep .el-select .el-input__inner {
|
|
|
+ background: rgba(255, 255, 255, .04);
|
|
|
+ border: 1px solid rgba(255, 255, 255, .14);
|
|
|
+ color: var(--text);
|
|
|
+}
|
|
|
+
|
|
|
+.main-full .panel-card .dense-form ::v-deep .el-input__inner:focus,
|
|
|
+.main-full .panel-card .dense-form ::v-deep .el-textarea__inner:focus {
|
|
|
+ border-color: var(--accent);
|
|
|
+ box-shadow: 0 0 0 3px rgba(102, 177, 255, .18);
|
|
|
+}
|
|
|
+
|
|
|
+/* 右上角小工具区(你在“弹道轨迹”分隔线下方有一个右侧按钮容器) */
|
|
|
+.main-full .panel-card .dense-form .el-row + div[style*="text-align:right"] {
|
|
|
+ margin-top: -6px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.main-full .panel-card .dense-form .el-button--info {
|
|
|
+ border-radius: 999px;
|
|
|
+ border-color: rgba(255, 255, 255, .22);
|
|
|
+ background: rgba(255, 255, 255, .04);
|
|
|
+ color: var(--text);
|
|
|
+}
|
|
|
+
|
|
|
+/* 轨迹预览占位卡片再提亮一点边框与背景 */
|
|
|
+.main-full .panel-card .trajectory-placeholder {
|
|
|
+ background: linear-gradient(180deg, rgba(255, 255, 255, .03), rgba(255, 255, 255, .01));
|
|
|
+ border: 1px dashed rgba(102, 177, 255, .28);
|
|
|
+}
|
|
|
+
|
|
|
+.main-full .panel-card .placeholder-icon {
|
|
|
+ opacity: .9;
|
|
|
+}
|
|
|
+
|
|
|
+.main-full .panel-card .placeholder-text {
|
|
|
+ color: var(--text-2);
|
|
|
+}
|
|
|
+
|
|
|
+/* 折叠项间距 & 组与组之间留白 */
|
|
|
+.main-full ::v-deep .el-collapse {
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+.main-full ::v-deep .el-collapse-item {
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 小号徽章:给“导弹 #1/#2”做编号(可选)——标题里已有文本时也会加底纹 */
|
|
|
+::v-deep .el-collapse-item__header:before {
|
|
|
+ content: "";
|
|
|
+ position: absolute;
|
|
|
+ left: 10px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: var(--accent);
|
|
|
+ box-shadow: 0 0 0 4px rgba(102, 177, 255, .18);
|
|
|
+}
|
|
|
+
|
|
|
+/* 自适应:窄屏时让表单行距更松一些 */
|
|
|
+@media (max-width: 1280px) {
|
|
|
+ .main-full .panel-card .dense-form ::v-deep .el-form-item {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.map-container { /* 可选的外层包装,主要用于文字等 */
|
|
|
+}
|
|
|
+
|
|
|
+.map-placeholder {
|
|
|
+ position: relative; /* 作为定位上下文 */
|
|
|
+ width: 100%;
|
|
|
+ height: 240px; /* 你也可换成想要的高度 */
|
|
|
+ overflow: hidden;
|
|
|
+ border: 1px dashed rgba(102, 177, 255, .28);
|
|
|
+ border-radius: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.map-image {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover; /* 铺满容器 */
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.map-marker {
|
|
|
+ position: absolute; /* 让 left/top 生效 —— 核心修复 */
|
|
|
+ transform: translate(-50%, -100%); /* 锚点在图标尖端,看起来更准 */
|
|
|
+ pointer-events: none; /* 不挡住后续点击 */
|
|
|
+}
|
|
|
+
|
|
|
+.icon-marker {
|
|
|
+ font-size: 24px; /* 提高“高度很小”的可见感受 */
|
|
|
+ color: #ff5252;
|
|
|
+ text-shadow: 0 2px 6px rgba(0, 0, 0, .35);
|
|
|
+}
|
|
|
+
|
|
|
+.map-hint {
|
|
|
+ margin-top: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--text-2);
|
|
|
+}
|
|
|
+
|
|
|
+</style>
|
|
|
+
|