Compare commits

...

4 Commits

  1. 4
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java
  2. 9
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RouteWaypointsServiceImpl.java
  3. 4
      ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml
  4. 7
      ruoyi-ui/src/lang/en.js
  5. 7
      ruoyi-ui/src/lang/zh.js
  6. 893
      ruoyi-ui/src/views/cesiumMap/index.vue
  7. 159
      ruoyi-ui/src/views/childRoom/RightPanel.vue
  8. 464
      ruoyi-ui/src/views/childRoom/index.vue
  9. 8
      ruoyi-ui/src/views/dialogs/RouteEditDialog.vue

4
ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java

@ -1,6 +1,7 @@
package com.ruoyi.system.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.system.domain.RouteWaypoints;
/**
@ -30,6 +31,9 @@ public interface RouteWaypointsMapper
/** 查询指定航线下最大的序号 */
public Integer selectMaxSeqByRouteId(Long routeId);
/** 将指定航线中 seq >= targetSeq 的航点序号均加 1,用于在指定位置插入新航点 */
int incrementSeqFrom(@Param("routeId") Long routeId, @Param("seq") Long targetSeq);
/**
* 新增航线具体航点明细
*

9
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RouteWaypointsServiceImpl.java

@ -52,11 +52,14 @@ public class RouteWaypointsServiceImpl implements IRouteWaypointsService
@Override
public int insertRouteWaypoints(RouteWaypoints routeWaypoints)
{
// 1. 获取该航线当前的最高序号
Long requestedSeq = routeWaypoints.getSeq();
Integer maxSeq = routeWaypointsMapper.selectMaxSeqByRouteId(routeWaypoints.getRouteId());
// 2. 如果是第一条,序号为1;否则在最大值基础上 +1
if (maxSeq == null) {
// 若前端传入有效 seq(在指定位置插入),则先将该位置及之后的航点 seq 均加 1,再插入
if (requestedSeq != null && requestedSeq > 0 && maxSeq != null && requestedSeq <= maxSeq) {
routeWaypointsMapper.incrementSeqFrom(routeWaypoints.getRouteId(), requestedSeq);
// 使用前端传入的 seq
} else if (maxSeq == null) {
routeWaypoints.setSeq(1L);
} else {
routeWaypoints.setSeq((long) (maxSeq + 1));

4
ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml

@ -51,6 +51,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select max(seq) from ry.route_waypoints where route_id = #{routeId}
</select>
<update id="incrementSeqFrom">
update route_waypoints set seq = seq + 1 where route_id = #{routeId} and seq &gt;= #{seq}
</update>
<insert id="insertRouteWaypoints" parameterType="RouteWaypoints" useGeneratedKeys="true" keyProperty="id">
insert into route_waypoints
<trim prefix="(" suffix=")" suffixOverrides=",">

7
ruoyi-ui/src/lang/en.js

@ -118,9 +118,16 @@ export default {
conflictTime: 'Conflict Time',
conflictPosition: 'Conflict Position',
viewDetails: 'View Details',
locate: 'Locate',
resolveConflict: 'Resolve Conflict',
noConflict: 'No Conflict',
noMatchFilter: 'No matches for current filter',
recheck: 'Recheck',
conflictFilter: 'Type',
conflictTypeAll: 'All',
conflictTypeTime: 'Time',
conflictTypeSpace: 'Space',
conflictTypeSpectrum: 'Spectrum',
air: 'Air',
sea: 'Sea',
ground: 'Ground'

7
ruoyi-ui/src/lang/zh.js

@ -118,9 +118,16 @@ export default {
conflictTime: '冲突时间',
conflictPosition: '冲突位置',
viewDetails: '查看详情',
locate: '定位',
resolveConflict: '解决冲突',
noConflict: '暂无冲突',
noMatchFilter: '当前筛选无匹配项',
recheck: '重新检测',
conflictFilter: '类型筛选',
conflictTypeAll: '全部',
conflictTypeTime: '时间',
conflictTypeSpace: '空间',
conflictTypeSpectrum: '频谱',
air: '空中',
sea: '海上',
ground: '地面'

893
ruoyi-ui/src/views/cesiumMap/index.vue

File diff suppressed because it is too large

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

@ -123,58 +123,6 @@
</div>
</div>
</div>
<div v-if="activeTab === 'conflict'" class="tab-content conflict-content">
<div v-if="conflicts.length > 0" class="conflict-list">
<div
v-for="conflict in conflicts"
:key="conflict.id"
class="conflict-item"
>
<div class="conflict-header">
<i class="el-icon-warning" style="color: #f56c6c;"></i>
<span class="conflict-title">{{ conflict.title }}</span>
<el-tag size="mini" type="danger">{{ $t('rightPanel.serious') }}</el-tag>
</div>
<div class="conflict-details">
<div class="detail-item">
<span class="label">{{ $t('rightPanel.involvedRoutes') }}</span>
<span class="value">{{ conflict.routeName || (conflict.routes && conflict.routes.join('、')) }}</span>
</div>
<div v-if="conflict.fromWaypoint && conflict.toWaypoint" class="detail-item">
<span class="label">问题航段</span>
<span class="value">{{ conflict.fromWaypoint }} {{ conflict.toWaypoint }}</span>
</div>
<div v-if="conflict.time" class="detail-item">
<span class="label">{{ $t('rightPanel.conflictTime') }}</span>
<span class="value">{{ conflict.time }}</span>
</div>
<div v-if="conflict.position" class="detail-item">
<span class="label">{{ $t('rightPanel.conflictPosition') }}</span>
<span class="value">{{ conflict.position }}</span>
</div>
<div v-if="conflict.suggestion" class="detail-item suggestion">
<span class="label">建议</span>
<span class="value">{{ conflict.suggestion }}</span>
</div>
</div>
<div class="conflict-actions">
<el-button type="text" size="mini" class="blue-text-btn" @click="handleViewConflict(conflict)">
{{ $t('rightPanel.viewDetails') }}
</el-button>
<el-button type="text" size="mini" class="blue-text-btn" @click="handleResolveConflict(conflict)">
{{ $t('rightPanel.resolveConflict') }}
</el-button>
</div>
</div>
</div>
<div v-else class="no-conflict">
<i class="el-icon-success" style="color: #67c23a; font-size: 24px;"></i>
<p>{{ $t('rightPanel.noConflict') }}</p>
<el-button size="mini" class="blue-btn" @click="handleRunConflictCheck">
{{ $t('rightPanel.recheck') }}
</el-button>
</div>
</div>
<div v-if="activeTab === 'platform'" class="tab-content platform-content">
<div class="section-header" style="padding: 10px 0; display: flex; justify-content: space-between; align-items: center;">
<div class="section-title">平台列表</div>
@ -341,14 +289,11 @@ export default {
type: Object,
default: null
},
conflicts: {
/** 父组件要求展开的航线 ID 列表(如冲突定位时),会展开对应方案与航线 */
expandRouteIds: {
type: Array,
default: () => []
},
conflictCount: {
type: Number,
default: 0
},
airPlatforms: {
type: Array,
default: () => []
@ -371,6 +316,19 @@ export default {
}
},
watch: {
expandRouteIds(newVal) {
if (newVal && newVal.length) {
newVal.forEach(routeId => {
const r = this.routes.find(route => route.id === routeId);
if (r && r.scenarioId != null && !this.expandedPlans.includes(r.scenarioId)) {
this.expandedPlans.push(r.scenarioId);
}
if (routeId != null && !this.expandedRoutes.includes(routeId)) {
this.expandedRoutes.push(routeId);
}
});
}
},
selectedPlanId(newId) {
if (newId) {
console.log('>>> [子组件同步] 检测到方案切换,自动展开 ID:', newId);
@ -548,18 +506,6 @@ export default {
});
},
handleViewConflict(conflict) {
this.$emit('view-conflict', conflict)
},
handleResolveConflict(conflict) {
this.$emit('resolve-conflict', conflict)
},
handleRunConflictCheck() {
this.$emit('run-conflict-check')
},
handleOpenPlatformDialog(platform) {
this.$emit('open-platform-dialog', platform)
},
@ -849,81 +795,6 @@ export default {
padding: 10px 0;
}
.conflict-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.conflict-item {
background: rgba(255, 255, 255, 0.8);
border-radius: 6px;
padding: 12px;
border: 1px solid rgba(245, 108, 108, 0.2);
transition: all 0.3s;
}
.conflict-item:hover {
background: rgba(245, 108, 108, 0.1);
box-shadow: 0 2px 8px rgba(245, 108, 108, 0.15);
}
.conflict-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.conflict-title {
flex: 1;
font-weight: 600;
color: #f56c6c;
}
.conflict-details {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 10px;
}
.detail-item {
display: flex;
gap: 8px;
font-size: 13px;
}
.detail-item .label {
color: #999;
min-width: 70px;
}
.detail-item .value {
color: #333;
font-weight: 500;
}
.detail-item.suggestion .value {
white-space: normal;
word-break: break-word;
color: #008aff;
}
.conflict-actions {
display: flex;
gap: 10px;
}
.no-conflict {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
padding: 40px 20px;
color: #999;
}
.platform-categories {
height: 100%;
}

464
ruoyi-ui/src/views/childRoom/index.vue

@ -194,8 +194,7 @@
:route-locked-by="routeLockedBy"
:current-user-id="currentUserId"
:selected-route-details="selectedRouteDetails"
:conflicts="conflicts"
:conflict-count="conflictCount"
:expand-route-ids="expandRouteIdsForPanel"
:air-platforms="airPlatforms"
:sea-platforms="seaPlatforms"
:ground-platforms="groundPlatforms"
@ -213,9 +212,6 @@
@cancel-route="cancelRoute"
@toggle-route-visibility="(route, opts) => toggleRouteVisibility(route, opts)"
@toggle-route-lock="handleToggleRouteLockFromPanel"
@view-conflict="viewConflict"
@resolve-conflict="resolveConflict"
@run-conflict-check="runConflictCheck"
@open-platform-dialog="openPlatformDialog"
@delete-platform="handleDeletePlatform"
@open-import-dialog="showImportDialog = true"
@ -338,6 +334,7 @@
v-model="showRouteDialog"
:route="selectedRoute"
:room-id="currentRoomId"
:initial-tab="routeEditInitialTab"
@save="updateRoute"
/>
@ -415,6 +412,15 @@
:room-id="currentRoomId"
/>
<!-- 冲突列表弹窗可拖动可调整大小分页点击左侧冲突按钮即打开并自动检测 -->
<conflict-drawer
v-if="!screenshotMode"
:visible.sync="showConflictDrawer"
:conflicts="conflicts"
@view-conflict="viewConflict"
@resolve-conflict="resolveConflict"
/>
<!-- 白板面板底部 -->
<whiteboard-panel
v-show="showWhiteboardPanel && !screenshotMode"
@ -496,6 +502,7 @@ import RightPanel from './RightPanel'
import BottomLeftPanel from './BottomLeftPanel'
import TopHeader from './TopHeader'
import FourTPanel from './FourTPanel'
import ConflictDrawer from './ConflictDrawer'
import WhiteboardPanel from './WhiteboardPanel'
import { createRoomWebSocket } from '@/utils/websocket';
import { listScenario, addScenario, delScenario } from "@/api/system/scenario";
@ -510,6 +517,18 @@ import PlatformImportDialog from "@/views/dialogs/PlatformImportDialog.vue";
import ExportRoutesDialog from "@/views/dialogs/ExportRoutesDialog.vue";
import ImportRoutesDialog from "@/views/dialogs/ImportRoutesDialog.vue";
import GanttDrawer from './GanttDrawer.vue';
import {
CONFLICT_TYPE,
defaultConflictConfig,
detectTimeWindowOverlap,
detectTrackSeparation,
detectPlatformPlacementTooClose,
detectRestrictedZoneIntrusion,
parseRestrictedZonesFromDrawings,
detectSpectrumConflicts,
createSpectrumLedgerEntry,
normalizeConflictList
} from '@/utils/conflictDetection';
export default {
name: 'MissionPlanningView',
components: {
@ -531,6 +550,7 @@ export default {
BottomLeftPanel,
TopHeader,
FourTPanel,
ConflictDrawer,
WhiteboardPanel
},
data() {
@ -685,6 +705,12 @@ export default {
showKTimePopup: false,
// 4T4T/
show4TPanel: false,
/** 冲突列表弹窗(点击左侧冲突按钮即打开并自动执行检测) */
showConflictDrawer: false,
/** 定位冲突时让右侧面板展开的航线 ID 列表 */
expandRouteIdsForPanel: [],
/** 打开航线编辑弹窗时默认选中的 tab(解决冲突时传 'waypoints' 直接打开航点列表) */
routeEditInitialTab: null,
//
showWhiteboardPanel: false,
@ -715,6 +741,10 @@ export default {
// runConflictCheck 线
conflictCount: 0,
conflicts: [],
/** 冲突检测配置(时间缓冲、航迹最小间隔、平台摆放最小距离、禁限区关键词、频谱邻频保护等) */
conflictConfig: { ...defaultConflictConfig },
/** 频谱资源台账(用于频谱冲突检测),可后续从接口或界面维护 */
spectrumLedger: [],
//
activePlatformTab: 'air',
@ -2075,6 +2105,7 @@ export default {
// 线
openRouteDialog(route) {
this.selectedRoute = route;
this.routeEditInitialTab = null;
this.showRouteDialog = true;
// 线广
if (this.wsConnection && this.wsConnection.sendObjectEditLock && route && route.id != null) {
@ -3777,13 +3808,9 @@ export default {
// /退
this.toggleWhiteboardMode();
} else if (item.id === 'start') {
//
if (this.activeRightTab === 'conflict' && !this.isRightPanelHidden) {
this.isRightPanelHidden = true;
} else {
this.activeRightTab = 'conflict';
this.isRightPanelHidden = false;
}
//
this.showConflictDrawer = true;
this.$nextTick(() => { this.runConflictCheck(); });
} else if (item.id === 'insert') {
//
if (this.activeRightTab === 'platform' && !this.isRightPanelHidden) {
@ -4113,9 +4140,9 @@ export default {
/**
* 按速度与计划时间构建航线时间轴含飞行段盘旋段与提前到达则等待的等待段
* pathData 可选{ path, segmentEndIndices, holdArcRanges } getRoutePathWithSegmentIndices 提供用于输出 hold
* holdRadiusByLegIndex 可选{ [legIndex]: number }为盘旋段指定半径用于推演时落点精准在切点
* 圆形盘旋半径由速度+坡度公式固定计算盘旋时间靠多转圈数解决不反算半径
*/
buildRouteTimeline(waypoints, globalMin, globalMax, pathData, holdRadiusByLegIndex) {
buildRouteTimeline(waypoints, globalMin, globalMax, pathData) {
const warnings = [];
if (!waypoints || waypoints.length === 0) return { segments: [], warnings };
const points = waypoints.map((wp, idx) => ({
@ -4153,7 +4180,12 @@ export default {
const path = pathData && pathData.path;
const segmentEndIndices = pathData && pathData.segmentEndIndices;
const holdArcRanges = pathData && pathData.holdArcRanges || {};
let skipNextLeg = false;
for (let i = 0; i < points.length - 1; i++) {
if (skipNextLeg) {
skipNextLeg = false;
continue;
}
if (this.isHoldWaypoint(waypoints[i + 1]) && path && segmentEndIndices && holdArcRanges[i]) {
const range = holdArcRanges[i];
const startIdx = i === 0 ? 0 : segmentEndIndices[i - 1] + 1;
@ -4183,25 +4215,35 @@ export default {
}
const holdEndTime = points[i + 1].minutes; // K+10
const exitPos = holdPathSlice.length ? holdPathSlice[holdPathSlice.length - 1] : (toEntrySlice.length ? toEntrySlice[toEntrySlice.length - 1] : { lng: points[i].lng, lat: points[i].lat, alt: points[i].alt });
let loopEndIdx = 1;
for (let k = 1; k < Math.min(holdPathSlice.length, 200); k++) {
if (this.segmentDistance(holdPathSlice[0], holdPathSlice[k]) < 80) { loopEndIdx = k; break; }
let loopEndIdx;
if (range.loopEndIndex != null) {
loopEndIdx = range.loopEndIndex - range.start;
} else {
const minSearchIdx = Math.max(2, Math.floor(holdPathSlice.length * 0.33));
loopEndIdx = holdPathSlice.length - 1;
for (let k = minSearchIdx; k < holdPathSlice.length; k++) {
if (this.segmentDistance(holdPathSlice[0], holdPathSlice[k]) < 80) { loopEndIdx = k; break; }
}
}
const holdClosedLoopPath = holdPathSlice.slice(0, loopEndIdx + 1);
if (loopEndIdx < 1) loopEndIdx = 1;
if (loopEndIdx >= holdPathSlice.length) loopEndIdx = holdPathSlice.length - 1;
const holdClosedLoopRaw = holdPathSlice.slice(0, loopEndIdx + 1);
const holdClosedLoopPath = holdClosedLoopRaw.length >= 2
? [...holdClosedLoopRaw.slice(0, -1), { ...holdClosedLoopRaw[0] }]
: holdClosedLoopRaw;
const holdLoopLength = this.pathSliceDistance(holdClosedLoopPath) || 1;
//
let exitIdxOnLoop = holdPathSlice.length - 1;
let minD = 1e9;
for (let k = 0; k < holdPathSlice.length; k++) {
const d = this.segmentDistance(holdPathSlice[k], exitPos);
if (d < minD) { minD = d; exitIdxOnLoop = k; }
}
const holdExitDistanceOnLoop = this.pathSliceDistance(holdPathSlice.slice(0, exitIdxOnLoop + 1));
const holdEntryToExitRaw = holdPathSlice.slice(loopEndIdx);
const holdEntryToExitSlice = holdEntryToExitRaw.length >= 2
? [{ ...holdClosedLoopPath[0] }, ...holdEntryToExitRaw.slice(1)]
: holdEntryToExitRaw;
const holdExitDistanceOnLoop = this.pathSliceDistance(holdEntryToExitSlice);
const holdSpeedKmh = points[i + 1].speed || 800;
const HOLD_SPEED_KMH = 800;
const speedMpMin = (HOLD_SPEED_KMH * 1000) / 60;
const requiredDistAtK10 = (holdEndTime - arrivalEntry) * speedMpMin;
let n = Math.ceil((requiredDistAtK10 - holdExitDistanceOnLoop) / holdLoopLength);
const rawLoops = (requiredDistAtK10 - holdExitDistanceOnLoop) / holdLoopLength;
let n = Math.ceil(rawLoops - 1e-9);
if (n < 0 || !Number.isFinite(n)) n = 0;
const segmentEndTime = arrivalEntry + (holdExitDistanceOnLoop + n * holdLoopLength) / speedMpMin;
if (segmentEndTime > holdEndTime) {
@ -4222,22 +4264,22 @@ export default {
const distExitToNext = this.pathSliceDistance(toNextSlice);
const travelExitMin = (distExitToNext / 1000) * (60 / holdSpeedKmh);
const arrivalNext = segmentEndTime + travelExitMin;
effectiveTime[i + 1] = holdEndTime;
effectiveTime[i + 1] = segmentEndTime;
if (i + 2 < points.length) effectiveTime[i + 2] = arrivalNext;
const posCur = { lng: points[i].lng, lat: points[i].lat, alt: points[i].alt };
const entryPos = toEntrySlice.length ? toEntrySlice[toEntrySlice.length - 1] : posCur;
const holdWp = waypoints[i + 1];
const holdParams = this.parseHoldParams(holdWp);
const holdCenter = holdWp ? { lng: parseFloat(holdWp.lng), lat: parseFloat(holdWp.lat), alt: Number(holdWp.alt) || 0 } : null;
const overrideR = holdRadiusByLegIndex && holdRadiusByLegIndex[i] != null ? holdRadiusByLegIndex[i] : null;
const holdRadius = (overrideR != null && Number.isFinite(overrideR)) ? overrideR : (holdParams && holdParams.radius != null ? holdParams.radius : null);
const computedR = this.$refs.cesiumMap ? this.$refs.cesiumMap.getWaypointRadius(holdWp) : null;
const holdRadius = (computedR != null && computedR > 0) ? computedR : 500;
const holdClockwise = holdParams && holdParams.clockwise !== false;
const holdCircumference = holdRadius != null ? 2 * Math.PI * holdRadius : null;
const holdEntryAngle = holdCenter && entryPos && holdRadius != null
? this.angleFromCenterToPoint(holdCenter.lng, holdCenter.lat, entryPos.lng, entryPos.lat)
: null;
segments.push({ startTime: effectiveTime[i], endTime: arrivalEntry, startPos: posCur, endPos: entryPos, type: 'fly', legIndex: i, pathSlice: toEntrySlice, speedKmh: speedKmhForLeg });
const holdEntryToExitPath = holdClosedLoopPath.slice(0, exitIdxOnLoop + 1);
const holdEntryToExitPath = holdEntryToExitSlice;
segments.push({
startTime: arrivalEntry,
endTime: segmentEndTime,
@ -4259,8 +4301,18 @@ export default {
holdClockwise,
holdEntryAngle
});
segments.push({ startTime: segmentEndTime, endTime: arrivalNext, startPos: exitPos, endPos: toNextSlice.length ? toNextSlice[toNextSlice.length - 1] : exitPos, type: 'fly', legIndex: i, pathSlice: toNextSlice, speedKmh: holdSpeedKmh });
continue; // i++ WP2WP3
// fly
const exitEndPos = toNextSlice.length ? toNextSlice[toNextSlice.length - 1] : exitPos;
// WP_{i+2} fly
// effectiveTime 使
if (i + 2 < points.length && this.isHoldWaypoint(waypoints[i + 2])) {
segments.push({ startTime: segmentEndTime, endTime: arrivalNext, startPos: exitPos, endPos: exitEndPos, type: 'fly', legIndex: i + 1, pathSlice: toNextSlice, speedKmh: holdSpeedKmh });
} else {
segments.push({ startTime: segmentEndTime, endTime: arrivalNext, startPos: exitPos, endPos: exitEndPos, type: 'fly', legIndex: i + 1, pathSlice: toNextSlice, speedKmh: holdSpeedKmh });
// fly leg i+1
skipNextLeg = true;
}
continue;
}
const dist = this.segmentDistance(points[i], points[i + 1]);
const speedKmh = points[i].speed || 800;
@ -4287,7 +4339,15 @@ export default {
effectiveTime[i + 1] = Math.max(actualArrival, scheduled);
const posCur = { lng: points[i].lng, lat: points[i].lat, alt: points[i].alt };
const posNext = { lng: points[i + 1].lng, lat: points[i + 1].lat, alt: points[i + 1].alt };
segments.push({ startTime: effectiveTime[i], endTime: actualArrival, startPos: posCur, endPos: posNext, type: 'fly', legIndex: i, speedKmh: speedKmh });
let flyPathSlice = null;
if (path && segmentEndIndices) {
const startIdx = i === 0 ? 0 : (segmentEndIndices[i - 1] != null ? segmentEndIndices[i - 1] : 0);
const endIdx = segmentEndIndices[i];
if (endIdx != null && endIdx >= startIdx) {
flyPathSlice = path.slice(startIdx, endIdx + 1);
}
}
segments.push({ startTime: effectiveTime[i], endTime: actualArrival, startPos: posCur, endPos: posNext, type: 'fly', legIndex: i, speedKmh: speedKmh, pathSlice: flyPathSlice });
if (actualArrival < effectiveTime[i + 1]) {
segments.push({ startTime: actualArrival, endTime: effectiveTime[i + 1], startPos: posNext, endPos: posNext, type: 'wait', legIndex: i });
}
@ -4402,24 +4462,27 @@ export default {
const cesiumMap = this.$refs.cesiumMap;
let pathData = null;
if (cesiumMap && cesiumMap.getRoutePathWithSegmentIndices) {
const cachedRadii = (routeId != null && cesiumMap._routeHoldRadiiByRoute && cesiumMap._routeHoldRadiiByRoute[routeId]) ? cesiumMap._routeHoldRadiiByRoute[routeId] : {};
const cachedEllipse = (routeId != null && cesiumMap._routeHoldEllipseParamsByRoute && cesiumMap._routeHoldEllipseParamsByRoute[routeId]) ? cesiumMap._routeHoldEllipseParamsByRoute[routeId] : {};
const opts = (Object.keys(cachedRadii).length > 0 || Object.keys(cachedEllipse).length > 0) ? { holdRadiusByLegIndex: cachedRadii, holdEllipseParamsByLegIndex: cachedEllipse } : {};
const opts = Object.keys(cachedEllipse).length > 0 ? { holdEllipseParamsByLegIndex: cachedEllipse } : {};
const ret = cesiumMap.getRoutePathWithSegmentIndices(waypoints, opts);
if (ret.path && ret.path.length > 0 && ret.segmentEndIndices && ret.segmentEndIndices.length > 0) {
pathData = { path: ret.path, segmentEndIndices: ret.segmentEndIndices, holdArcRanges: ret.holdArcRanges || {} };
}
}
let { segments, warnings, earlyArrivalLegs } = this.buildRouteTimeline(waypoints, globalMin, globalMax, pathData);
const holdRadiusByLegIndex = {};
// +
// /semiMajor/semiMinor
const holdEllipseParamsByLegIndex = {};
if (cesiumMap && segments && pathData) {
for (let idx = 0; idx < segments.length; idx++) {
const s = segments[idx];
if (s.type !== 'hold' || s.holdCenter == null) continue;
const i = s.legIndex;
const holdEndTime = s.holdEndTime != null ? s.holdEndTime : this.waypointStartTimeToMinutesDecimal(waypoints[i + 1]?.startTime);
const holdWp = waypoints[i + 1];
if (!holdWp) continue;
const isHoldEllipse = (holdWp.pointType || holdWp.point_type) === 'hold_ellipse';
if (!isHoldEllipse || !cesiumMap.computeEllipseParamsForDuration) continue;
const holdEndTime = s.holdEndTime != null ? s.holdEndTime : this.waypointStartTimeToMinutesDecimal(holdWp?.startTime);
const segTarget = holdWp && (holdWp.segmentTargetMinutes ?? holdWp.displayStyle?.segmentTargetMinutes);
const arrivalAtHold = (holdWp && holdWp.segmentMode === 'fixed_time' && segTarget != null && segTarget !== '')
? Number(segTarget) : s.startTime;
@ -4428,123 +4491,36 @@ export default {
const totalHoldDistM = speedKmh * (holdDurationMin / 60) * 1000;
const prevWp = waypoints[i];
const nextWp = (i + 2) < waypoints.length ? waypoints[i + 2] : holdWp;
if (!prevWp || !holdWp) continue;
if (!prevWp) continue;
const centerCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(holdWp.lng), parseFloat(holdWp.lat), Number(holdWp.alt) || 0);
const prevCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(prevWp.lng), parseFloat(prevWp.lat), Number(prevWp.alt) || 0);
const nextCartesian = nextWp ? Cesium.Cartesian3.fromDegrees(parseFloat(nextWp.lng), parseFloat(nextWp.lat), Number(nextWp.alt) || 0) : centerCartesian;
const clockwise = s.holdClockwise !== false;
const isHoldEllipse = waypoints[i + 1] && (waypoints[i + 1].pointType || waypoints[i + 1].point_type) === 'hold_ellipse';
const isEllipse = isHoldEllipse || s.holdRadius == null;
if (isEllipse && !isHoldEllipse && cesiumMap.computeEllipseParamsForDuration) {
const holdParams = this.parseHoldParams(holdWp);
const headingDeg = holdParams && holdParams.headingDeg != null ? holdParams.headingDeg : 0;
const a0 = holdParams && (holdParams.semiMajor != null || holdParams.semiMajorAxis != null) ? (holdParams.semiMajor ?? holdParams.semiMajorAxis) : 500;
const b0 = holdParams && (holdParams.semiMinor != null || holdParams.semiMinorAxis != null) ? (holdParams.semiMinor ?? holdParams.semiMinorAxis) : 300;
const out = cesiumMap.computeEllipseParamsForDuration(centerCartesian, prevCartesian, nextCartesian, clockwise, totalHoldDistM, headingDeg, a0, b0);
if (out && out.semiMajor != null && out.semiMinor != null) {
holdEllipseParamsByLegIndex[i] = {
semiMajor: out.semiMajor,
semiMinor: out.semiMinor,
headingDeg
};
}
} else if (!isEllipse && cesiumMap.computeHoldRadiusForDuration) {
let R = cesiumMap.computeHoldRadiusForDuration(centerCartesian, prevCartesian, nextCartesian, clockwise, totalHoldDistM);
if (R == null || !Number.isFinite(R)) {
R = totalHoldDistM / (2 * Math.PI);
}
if (R != null && Number.isFinite(R) && R > 0) {
holdRadiusByLegIndex[i] = R;
}
const holdParams = this.parseHoldParams(holdWp);
const headingDeg = holdParams && holdParams.headingDeg != null ? holdParams.headingDeg : 0;
const a0 = holdParams && (holdParams.semiMajor != null || holdParams.semiMajorAxis != null) ? (holdParams.semiMajor ?? holdParams.semiMajorAxis) : 500;
const b0 = holdParams && (holdParams.semiMinor != null || holdParams.semiMinorAxis != null) ? (holdParams.semiMinor ?? holdParams.semiMinorAxis) : 300;
const out = cesiumMap.computeEllipseParamsForDuration(centerCartesian, prevCartesian, nextCartesian, s.holdClockwise !== false, totalHoldDistM, headingDeg, a0, b0);
if (out && out.semiMajor != null && out.semiMinor != null) {
holdEllipseParamsByLegIndex[i] = { semiMajor: out.semiMajor, semiMinor: out.semiMinor, headingDeg };
}
}
const hasCircle = Object.keys(holdRadiusByLegIndex).length > 0;
const hasEllipse = Object.keys(holdEllipseParamsByLegIndex).length > 0;
if (hasCircle || hasEllipse) {
let pathData2 = null;
let segments2 = null;
for (let iter = 0; iter < 4; iter++) {
const ret2 = cesiumMap.getRoutePathWithSegmentIndices(waypoints, { holdRadiusByLegIndex, holdEllipseParamsByLegIndex });
if (!ret2.path || ret2.path.length === 0 || !ret2.segmentEndIndices || ret2.segmentEndIndices.length === 0) break;
pathData2 = { path: ret2.path, segmentEndIndices: ret2.segmentEndIndices, holdArcRanges: ret2.holdArcRanges || {} };
const out = this.buildRouteTimeline(waypoints, globalMin, globalMax, pathData2, holdRadiusByLegIndex);
segments2 = out.segments;
let changed = false;
if (hasCircle) {
const nextRadii = {};
for (let idx = 0; idx < segments2.length; idx++) {
const s = segments2[idx];
if (s.type !== 'hold' || s.holdRadius == null || s.holdCenter == null) continue;
const i = s.legIndex;
const holdWpCircle = waypoints[i + 1];
const holdEndTimeCircle = s.holdEndTime != null ? s.holdEndTime : this.waypointStartTimeToMinutesDecimal(holdWpCircle?.startTime);
const segTargetCircle = holdWpCircle && (holdWpCircle.segmentTargetMinutes ?? holdWpCircle.displayStyle?.segmentTargetMinutes);
const arrivalAtHoldCircle = (holdWpCircle && holdWpCircle.segmentMode === 'fixed_time' && segTargetCircle != null && segTargetCircle !== '')
? Number(segTargetCircle) : s.startTime;
const holdDurationMin = Math.max(0, holdEndTimeCircle - arrivalAtHoldCircle);
const speedKmh = s.speedKmh != null ? s.speedKmh : (Number(holdWpCircle?.speed) || 800);
const totalHoldDistM = speedKmh * (holdDurationMin / 60) * 1000;
const prevWp = waypoints[i];
const holdWp = holdWpCircle;
const nextWp = (i + 2) < waypoints.length ? waypoints[i + 2] : holdWp;
if (!prevWp || !holdWp) continue;
const centerCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(holdWp.lng), parseFloat(holdWp.lat), Number(holdWp.alt) || 0);
const prevCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(prevWp.lng), parseFloat(prevWp.lat), Number(prevWp.alt) || 0);
const nextCartesian = nextWp ? Cesium.Cartesian3.fromDegrees(parseFloat(nextWp.lng), parseFloat(nextWp.lat), Number(nextWp.alt) || 0) : centerCartesian;
let Rnew = cesiumMap.computeHoldRadiusForDuration(centerCartesian, prevCartesian, nextCartesian, s.holdClockwise !== false, totalHoldDistM);
if (Rnew == null || !Number.isFinite(Rnew)) Rnew = totalHoldDistM / (2 * Math.PI);
if (Rnew != null && Number.isFinite(Rnew) && Rnew > 0) {
nextRadii[i] = Rnew;
if (holdRadiusByLegIndex[i] == null || Math.abs(nextRadii[i] - holdRadiusByLegIndex[i]) > 1) changed = true;
}
}
Object.assign(holdRadiusByLegIndex, nextRadii);
}
if (hasEllipse) {
for (let idx = 0; idx < segments2.length; idx++) {
const s = segments2[idx];
if (s.type !== 'hold' || s.holdRadius != null || s.holdCenter == null) continue;
const i = s.legIndex;
const holdWp = waypoints[i + 1];
if ((holdWp && (holdWp.pointType || holdWp.point_type) === 'hold_ellipse')) continue;
const holdParams = this.parseHoldParams(holdWp);
const holdEndTime = s.holdEndTime != null ? s.holdEndTime : this.waypointStartTimeToMinutesDecimal(holdWp?.startTime);
const segTargetEllipse = holdWp && (holdWp.segmentTargetMinutes ?? holdWp.displayStyle?.segmentTargetMinutes);
const arrivalAtHold = (holdWp && holdWp.segmentMode === 'fixed_time' && segTargetEllipse != null && segTargetEllipse !== '')
? Number(segTargetEllipse) : s.startTime;
const holdDurationMin = Math.max(0, holdEndTime - arrivalAtHold);
const speedKmh = s.speedKmh != null ? s.speedKmh : (Number(holdWp?.speed) || 800);
const totalHoldDistM = speedKmh * (holdDurationMin / 60) * 1000;
const prevWp = waypoints[i];
const nextWp = (i + 2) < waypoints.length ? waypoints[i + 2] : holdWp;
if (!prevWp || !holdWp || !cesiumMap.computeEllipseParamsForDuration) continue;
const centerCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(holdWp.lng), parseFloat(holdWp.lat), Number(holdWp.alt) || 0);
const prevCartesian = Cesium.Cartesian3.fromDegrees(parseFloat(prevWp.lng), parseFloat(prevWp.lat), Number(prevWp.alt) || 0);
const nextCartesian = nextWp ? Cesium.Cartesian3.fromDegrees(parseFloat(nextWp.lng), parseFloat(nextWp.lat), Number(nextWp.alt) || 0) : centerCartesian;
const headingDeg = holdParams && holdParams.headingDeg != null ? holdParams.headingDeg : 0;
const a0 = holdParams && (holdParams.semiMajor != null || holdParams.semiMajorAxis != null) ? (holdParams.semiMajor ?? holdParams.semiMajorAxis) : 500;
const b0 = holdParams && (holdParams.semiMinor != null || holdParams.semiMinorAxis != null) ? (holdParams.semiMinor ?? holdParams.semiMinorAxis) : 300;
const out = cesiumMap.computeEllipseParamsForDuration(centerCartesian, prevCartesian, nextCartesian, s.holdClockwise !== false, totalHoldDistM, headingDeg, a0, b0);
if (out && out.semiMajor != null) {
const smj = out.semiMajor;
const smn = out.semiMinor;
const old = holdEllipseParamsByLegIndex[i];
if (!old || Math.abs(smj - old.semiMajor) > 1) changed = true;
holdEllipseParamsByLegIndex[i] = { semiMajor: smj, semiMinor: smn, headingDeg };
}
}
}
if (!changed || iter === 3) break;
if (hasEllipse) {
const ret2 = cesiumMap.getRoutePathWithSegmentIndices(waypoints, { holdEllipseParamsByLegIndex });
if (ret2.path && ret2.path.length > 0 && ret2.segmentEndIndices && ret2.segmentEndIndices.length > 0) {
pathData = { path: ret2.path, segmentEndIndices: ret2.segmentEndIndices, holdArcRanges: ret2.holdArcRanges || {} };
const out2 = this.buildRouteTimeline(waypoints, globalMin, globalMax, pathData);
segments = out2.segments;
}
if (pathData2) pathData = pathData2;
if (segments2) segments = segments2;
if (routeId != null) {
if (cesiumMap.setRouteHoldRadii) cesiumMap.setRouteHoldRadii(routeId, holdRadiusByLegIndex);
if (cesiumMap.setRouteHoldEllipseParams) cesiumMap.setRouteHoldEllipseParams(routeId, holdEllipseParamsByLegIndex);
if (routeId != null && cesiumMap.setRouteHoldEllipseParams) {
cesiumMap.setRouteHoldEllipseParams(routeId, holdEllipseParamsByLegIndex);
}
} else if (routeId != null) {
if (cesiumMap.setRouteHoldRadii) cesiumMap.setRouteHoldRadii(routeId, {});
if (cesiumMap.setRouteHoldEllipseParams) cesiumMap.setRouteHoldEllipseParams(routeId, {});
} else if (routeId != null && cesiumMap.setRouteHoldEllipseParams) {
cesiumMap.setRouteHoldEllipseParams(routeId, {});
}
// 使
if (routeId != null && cesiumMap.setRouteHoldRadii) {
cesiumMap.setRouteHoldRadii(routeId, {});
}
}
const path = pathData ? pathData.path : null;
@ -5076,13 +5052,16 @@ export default {
}
},
// 线
// 线
runConflictCheck() {
const list = [];
let id = 1;
const routeIds = this.activeRouteIds && this.activeRouteIds.length > 0 ? this.activeRouteIds : this.routes.map(r => r.id);
const { minMinutes, maxMinutes } = this.getDeductionTimeRange();
const config = this.conflictConfig || defaultConflictConfig;
const waypointStartTimeToMinutes = (s) => this.waypointStartTimeToMinutes(s);
const allRaw = [];
// ---------- 线----------
routeIds.forEach(routeId => {
const route = this.routes.find(r => r.id === routeId);
if (!route || !route.waypoints || route.waypoints.length < 2) return;
@ -5094,65 +5073,208 @@ export default {
}
}
const { earlyArrivalLegs, lateArrivalLegs, holdDelayConflicts } = this.buildRouteTimeline(route.waypoints, minMinutes, maxMinutes, pathData);
const routeName = route.name || `航线${route.id}`;
(earlyArrivalLegs || []).forEach(leg => {
list.push({
id: id++,
// /
allRaw.push({
type: CONFLICT_TYPE.TIME,
subType: 'early_arrival',
title: '提前到达',
routeName,
routeIds: [routeId],
fromWaypoint: leg.fromName,
toWaypoint: leg.toName,
time: this.minutesToStartTime(leg.actualArrival),
suggestion: '该航段将提前到达下一航点,建议在此段加入盘旋或延后下一航点计划时间。',
suggestion: '该航段将提前到达下一航点。可选措施:① 适当降低本段巡航速度;② 在本段或下一航点前增加盘旋等待;③ 视任务需要调整下一航点相对K时/计划时间。若存在定速点或定时点,请优先调整未受约束的速度或时间。',
severity: 'high'
});
});
(lateArrivalLegs || []).forEach(leg => {
list.push({
id: id++,
// /
allRaw.push({
type: CONFLICT_TYPE.TIME,
subType: 'late_arrival',
title: '无法按时到达',
routeName,
routeIds: [routeId],
fromWaypoint: leg.fromName,
toWaypoint: leg.toName,
suggestion: `当前速度不足,建议将本段速度提升至 ≥${leg.requiredSpeedKmh} km/h,或延后下一航点计划时间`,
suggestion: `当前速度不足,理论上需将本段速度提升至 ≥${leg.requiredSpeedKmh} km/h 才能按时到达。可选措施:① 在安全范围内提高本段或前一段速度;② 适当提前下一航点相对K时/计划时间;③ 结合任务需要调整上游航段或加入盘旋缓冲。若存在定速点或定时点,请优先从未锁定的速度或时间入手`,
severity: 'high'
});
});
(holdDelayConflicts || []).forEach(conf => {
list.push({
id: id++,
// /
allRaw.push({
type: CONFLICT_TYPE.TIME,
subType: 'hold_delay',
title: '盘旋时间不足',
routeName,
routeIds: [routeId],
fromWaypoint: conf.fromName,
toWaypoint: conf.toName,
time: this.minutesToStartTime(conf.setExitTime),
position: conf.holdCenter ? `经度 ${conf.holdCenter.lng.toFixed(5)}°, 纬度 ${conf.holdCenter.lat.toFixed(5)}°` : undefined,
suggestion: `警告:设定的盘旋时间不足以支撑战斗机完成最后一圈,实际切出将延迟 ${conf.delaySeconds} 秒。`,
suggestion: `警告:设定的盘旋时间不足以支撑战斗机完成最后一圈,实际切出将延迟 ${conf.delaySeconds} 秒。可选措施:① 增加盘旋圈数或调整盘旋结束时间;② 在允许范围内调整盘旋段速度;③ 结合上下游航段,微调相关航点的相对K时/计划时间。若存在定速点或定时点,请优先调整未受约束的参数。`,
severity: 'high',
holdCenter: conf.holdCenter
holdCenter: conf.holdCenter,
positionLng: conf.holdCenter && conf.holdCenter.lng,
positionLat: conf.holdCenter && conf.holdCenter.lat,
positionAlt: conf.holdCenter && conf.holdCenter.alt
});
});
});
this.conflicts = list;
this.conflictCount = list.length;
if (list.length > 0) {
this.$message.warning(`检测到 ${list.length} 处航线时间问题`);
// /
// detectTimeWindowOverlap
// ---------- ----------
const getPositionAtMinutesForConflict = (routeId, minutesFromK) => {
const route = this.routes.find(r => r.id === routeId);
if (!route || !route.waypoints || route.waypoints.length === 0) return null;
const { position } = this.getPositionAtMinutesFromK(route.waypoints, minutesFromK, minMinutes, maxMinutes, routeId);
return position;
};
const trackConflicts = detectTrackSeparation(routeIds, minMinutes, maxMinutes, getPositionAtMinutesForConflict, config);
trackConflicts.forEach(c => allRaw.push(c));
// ---------- ----------
const platformIcons = this.$refs.cesiumMap && this.$refs.cesiumMap.getPlatformIconPositions ? this.$refs.cesiumMap.getPlatformIconPositions() : [];
const placementConflicts = detectPlatformPlacementTooClose(platformIcons, config);
placementConflicts.forEach(c => allRaw.push(c));
// ---------- ----------
let restrictedZones = [];
if (this.$refs.cesiumMap && this.$refs.cesiumMap.getFrontendDrawingsData) {
const drawings = this.$refs.cesiumMap.getFrontendDrawingsData();
const entities = (drawings && drawings.entities) || [];
restrictedZones = parseRestrictedZonesFromDrawings(entities, config.restrictedZoneNameKeywords || defaultConflictConfig.restrictedZoneNameKeywords);
restrictedZones = restrictedZones.map(z => ({ ...z, points: z.points || (z.data && z.data.points) || [] })).filter(z => z.points && z.points.length >= 3);
}
const restrictedConflicts = detectRestrictedZoneIntrusion(routeIds, minMinutes, maxMinutes, getPositionAtMinutesForConflict, restrictedZones, config);
restrictedConflicts.forEach(c => allRaw.push(c));
// ---------- ----------
if (this.spectrumLedger && this.spectrumLedger.length >= 2) {
const spectrumConflicts = detectSpectrumConflicts(this.spectrumLedger, config);
spectrumConflicts.forEach(c => allRaw.push(c));
}
this.conflicts = normalizeConflictList(allRaw, 1);
this.conflictCount = this.conflicts.length;
if (this.conflicts.length > 0) {
this.$message.warning(`检测到 ${this.conflicts.length} 处冲突`);
} else {
this.$message.success('未发现航线时间冲突');
this.$message.success('未发现冲突');
}
},
/** 查看冲突:展开问题航线、显示右侧方案树、定位到冲突位置并跳转时间轴 */
viewConflict(conflict) {
this.$message.info(`查看冲突:${conflict.title}`);
const routeIds = conflict.routeIds || [];
if (routeIds.length > 0) {
// 线线
const prevActive = this.activeRouteIds || [];
this.activeRouteIds = [...new Set([...prevActive, ...routeIds])];
this.isRightPanelHidden = false;
this.activeRightTab = 'plan';
this.expandRouteIdsForPanel = [...routeIds];
// 线
if (this.$refs.cesiumMap) {
routeIds.forEach(async (routeId) => {
const route = this.routes.find(r => r.id === routeId);
if (!route) return;
//
let waypoints = Array.isArray(route.waypoints) ? route.waypoints : [];
if (!waypoints.length) {
try {
const res = await getRoutes(route.id);
if (res && res.code === 200 && res.data && Array.isArray(res.data.waypoints)) {
waypoints = res.data.waypoints;
// routes
const idx = this.routes.findIndex(r => r.id === route.id);
if (idx > -1) {
this.$set(this.routes, idx, {
...this.routes[idx],
waypoints
});
}
}
} catch (e) {
console.warn('viewConflict: 获取航线航点失败', e);
}
}
if (waypoints.length > 0) {
// 线
const roomId = this.currentRoomId;
if (roomId && route.platformId) {
try {
const styleRes = await getPlatformStyle({ roomId, routeId, platformId: route.platformId });
if (styleRes && styleRes.data) {
this.$refs.cesiumMap.setPlatformStyle(routeId, styleRes.data);
}
} catch (_) {}
}
this.$refs.cesiumMap.renderRouteWaypoints(
waypoints,
routeId,
route.platformId,
route.platform,
this.parseRouteStyle(route.attributes)
);
}
});
}
}
if (conflict.positionLng != null && conflict.positionLat != null && this.$refs.cesiumMap && this.$refs.cesiumMap.flyToPosition) {
this.$refs.cesiumMap.flyToPosition(conflict.positionLng, conflict.positionLat, conflict.positionAlt, 1.5);
}
if (conflict.minutesFromK != null && Number.isFinite(conflict.minutesFromK)) {
const { minMinutes, maxMinutes } = this.getDeductionTimeRange();
const span = Math.max(0, maxMinutes - minMinutes) || 120;
const progress = Math.max(0, Math.min(100, ((conflict.minutesFromK - minMinutes) / span) * 100));
this.timeProgress = progress;
}
this.$message({ message: `已定位:${conflict.title}`, type: 'info', duration: 2000 });
},
resolveConflict(conflict) {
this.$message.success(`解决冲突:${conflict.title}`);
//
this.conflicts = this.conflicts.filter(c => c.id !== conflict.id);
this.conflictCount = this.conflicts.length;
/** 解决冲突:根据建议打开该航线的航点列表(编辑航线弹窗-航点 tab),由用户修改盘旋/速度/相对K时等,不直接删除冲突 */
async resolveConflict(conflict) {
const routeId = conflict.routeIds && conflict.routeIds[0];
if (!routeId) {
this.$message.warning('无法确定关联航线');
return;
}
let route = this.routes.find(r => r.id === routeId);
if (!route) {
this.$message.warning('未找到该航线');
return;
}
if (!route.waypoints || route.waypoints.length === 0) {
try {
const res = await getRoutes(routeId);
if (res.data && res.data.waypoints) {
route = { ...route, waypoints: res.data.waypoints };
const idx = this.routes.findIndex(r => r.id === routeId);
if (idx !== -1) this.routes.splice(idx, 1, route);
}
} catch (e) {
console.warn('获取航线航点失败', e);
}
}
this.selectedRouteId = routeId;
this.selectedRouteDetails = route.waypoints ? { ...route } : null;
this.selectedRoute = route.waypoints ? { ...route } : route;
this.routeEditInitialTab = 'waypoints';
this.showRouteDialog = true;
if (this.wsConnection && this.wsConnection.sendObjectEditLock && route && route.id != null) {
this.wsConnection.sendObjectEditLock('route', route.id);
this.routeEditLockedId = route.id;
}
this.$message.info('请根据建议在航点列表中修改(如加入盘旋或调整相对K时/速度)');
},
//

8
ruoyi-ui/src/views/dialogs/RouteEditDialog.vue

@ -331,7 +331,9 @@ export default {
props: {
value: { type: Boolean, default: false },
route: { type: Object, default: () => ({}) },
roomId: { type: [String, Number], default: null }
roomId: { type: [String, Number], default: null },
/** 打开时默认选中的 tab:'basic' | 'platform' | 'waypoints',解决冲突时传 'waypoints' 直接打开航点列表 */
initialTab: { type: String, default: '' }
},
data() {
return {
@ -416,7 +418,11 @@ export default {
visible(val) {
if (val) {
this.loadPosition()
if (this.initialTab === 'waypoints' || this.initialTab === 'platform') {
this.activeTab = this.initialTab
}
if (this.activeTab === 'platform') this.loadPlatforms()
if (this.activeTab === 'waypoints' && this.panelWidth < 920) this.panelWidth = 920
this.skipBasicStyleSyncOnce = true
}
},

Loading…
Cancel
Save