From 878e98d4f8583b3dc3d96c9a46a8b2ca5d2d9636 Mon Sep 17 00:00:00 2001 From: menghao <1584479611@qq.com> Date: Wed, 18 Mar 2026 09:34:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=86=B2=E7=AA=81=E5=8D=A1=E6=AD=BBbug?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/childRoom/ConflictDrawer.vue | 20 +++++++++-- ruoyi-ui/src/views/childRoom/index.vue | 45 +++++++++++++++++-------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/ruoyi-ui/src/views/childRoom/ConflictDrawer.vue b/ruoyi-ui/src/views/childRoom/ConflictDrawer.vue index ce1000d..8a4e6d9 100644 --- a/ruoyi-ui/src/views/childRoom/ConflictDrawer.vue +++ b/ruoyi-ui/src/views/childRoom/ConflictDrawer.vue @@ -32,7 +32,10 @@
-
+
+ 正在检测冲突… +
+
-
+

{{ filteredConflicts.length > 0 ? $t('rightPanel.noMatchFilter') : $t('rightPanel.noConflict') }}

@@ -111,6 +114,10 @@ export default { conflicts: { type: Array, default: () => [] + }, + loading: { + type: Boolean, + default: false } }, data() { @@ -322,6 +329,15 @@ export default { padding: 12px; } +.conflict-loading { + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + color: #909399; + font-size: 14px; +} + .conflict-list { display: flex; flex-direction: column; diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 27d2a4c..04185c8 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -416,6 +416,7 @@ v-if="!screenshotMode" :visible.sync="showConflictDrawer" :conflicts="conflicts" + :loading="conflictCheckRunning" @view-conflict="viewConflict" @resolve-conflict="resolveConflict" /> @@ -706,6 +707,8 @@ export default { show4TPanel: false, /** 冲突列表弹窗(点击左侧冲突按钮即打开并自动执行检测) */ showConflictDrawer: false, + /** 冲突检测进行中(避免主线程长时间阻塞导致页面卡死) */ + conflictCheckRunning: false, /** 定位冲突时让右侧面板展开的航线 ID 列表 */ expandRouteIdsForPanel: [], /** 打开航线编辑弹窗时默认选中的 tab(解决冲突时传 'waypoints' 直接打开航点列表) */ @@ -3966,9 +3969,15 @@ export default { // 白板:进入/退出白板模式 this.toggleWhiteboardMode(); } else if (item.id === 'start') { - // 冲突:打开可拖动冲突列表弹窗,并立即执行检测(无需再点“重新检测”) + // 冲突:打开弹窗并异步执行检测,避免航线多时主线程阻塞导致页面卡死 this.showConflictDrawer = true; - this.$nextTick(() => { this.runConflictCheck(); }); + this.conflictCheckRunning = true; + this.$nextTick(() => { + setTimeout(() => { + this.runConflictCheck(); + this.conflictCheckRunning = false; + }, 0); + }); } else if (item.id === 'insert') { // 如果当前已经是平台标签页,则关闭右侧面板 if (this.activeRightTab === 'platform' && !this.isRightPanelHidden) { @@ -5219,6 +5228,8 @@ export default { const waypointStartTimeToMinutes = (s) => this.waypointStartTimeToMinutes(s); const allRaw = []; + /** 按航线缓存 timeline(segments+path),供航迹间隔检测复用,避免每 (routeId,t) 重复 buildRouteTimeline 导致航线多时卡死 */ + const routeIdToTimeline = {}; // ---------- 时间冲突:单航线内(提前到达、无法按时到达、盘旋时间不足)---------- routeIds.forEach(routeId => { @@ -5231,10 +5242,16 @@ export default { pathData = { path: ret.path, segmentEndIndices: ret.segmentEndIndices, holdArcRanges: ret.holdArcRanges || {} }; } } - const { earlyArrivalLegs, lateArrivalLegs, holdDelayConflicts } = this.buildRouteTimeline(route.waypoints, minMinutes, maxMinutes, pathData); + const timeline = this.buildRouteTimeline(route.waypoints, minMinutes, maxMinutes, pathData); + const { earlyArrivalLegs, lateArrivalLegs, holdDelayConflicts } = timeline; + routeIdToTimeline[routeId] = { + segments: timeline.segments, + path: pathData && pathData.path ? pathData.path : null, + segmentEndIndices: pathData && pathData.segmentEndIndices ? pathData.segmentEndIndices : null + }; const routeName = route.name || `航线${route.id}`; (earlyArrivalLegs || []).forEach(leg => { - // 提前到达:给出多种可选思路,统一在提示里说明“视定速/定时约束优先调整未受约束量” + // 提前到达:相对K时=离开该点时间;到达早于该时间时可降速/盘旋等待/调晚下一航点相对K时;受定速/定时约束时只调未锁定参数 allRaw.push({ type: CONFLICT_TYPE.TIME, subType: 'early_arrival', @@ -5244,12 +5261,12 @@ export default { fromWaypoint: leg.fromName, toWaypoint: leg.toName, time: this.minutesToStartTime(leg.actualArrival), - suggestion: '该航段将提前到达下一航点。可选措施:① 适当降低本段巡航速度;② 在本段或下一航点前增加盘旋等待;③ 视任务需要调整下一航点相对K时/计划时间。若存在定速点或定时点,请优先调整未受约束的速度或时间。', + suggestion: '① 降低本段速度 ② 下一航点为盘旋点时依靠盘旋等待 ③ 将下一航点相对K时调晚', severity: 'high' }); }); (lateArrivalLegs || []).forEach(leg => { - // 无法按时到达:同时提示“提速”和“调整时间/路径”等多种方案 + // 无法按时到达:需在下一航点相对K时前到达;可提速或调早下一航点相对K时;受定速/定时约束时只调未锁定参数 allRaw.push({ type: CONFLICT_TYPE.TIME, subType: 'late_arrival', @@ -5258,12 +5275,12 @@ export default { routeIds: [routeId], fromWaypoint: leg.fromName, toWaypoint: leg.toName, - suggestion: `当前速度不足,理论上需将本段速度提升至 ≥${leg.requiredSpeedKmh} km/h 才能按时到达。可选措施:① 在安全范围内提高本段或前一段速度;② 适当提前下一航点相对K时/计划时间;③ 结合任务需要调整上游航段或加入盘旋缓冲。若存在定速点或定时点,请优先从未锁定的速度或时间入手。`, + suggestion: `① 将本段速度提升至 ≥${leg.requiredSpeedKmh} km/h ② 将下一航点相对K时调早 ③ 调整上游航段速度或时间`, severity: 'high' }); }); (holdDelayConflicts || []).forEach(conf => { - // 盘旋时间不足:提示可修改盘旋圈数/时间、速度或切出时刻 + // 盘旋时间不足:项目不控制盘旋圈数。定时盘旋时盘旋时间=该点相对K时-到达时间、速度由半径与时间反算;非定时时速度继承上一航点。只能通过延长相对K时/调半径或上一航点速度等改善 allRaw.push({ type: CONFLICT_TYPE.TIME, subType: 'hold_delay', @@ -5274,7 +5291,7 @@ export default { 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} 秒。可选措施:① 增加盘旋圈数或调整盘旋结束时间;② 在允许范围内调整盘旋段速度;③ 结合上下游航段,微调相关航点的相对K时/计划时间。若存在定速点或定时点,请优先调整未受约束的参数。`, + suggestion: `实际切出将延迟 ${conf.delaySeconds} 秒。① 延长该盘旋点相对K时 ② 定时盘旋调转弯半径,非定时调上一航点速度或本点相对K时 ③ 微调上下游航点相对K时`, severity: 'high', holdCenter: conf.holdCenter, positionLng: conf.holdCenter && conf.holdCenter.lng, @@ -5287,12 +5304,12 @@ export default { // 时间窗重叠:仅“时间段有交集”不算冲突(不同地点飞机可同时起飞)。后续若有跑道/频段等资源绑定,再按“同一资源占用时间重叠”报冲突。 // 故此处不再调用 detectTimeWindowOverlap。 - // ---------- 空间冲突:航迹最小间隔 ---------- + // ---------- 空间冲突:航迹最小间隔(使用缓存的 timeline,避免每 (routeId,t) 重复 buildRouteTimeline)---------- 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 c = routeIdToTimeline[routeId]; + if (!c || !c.segments || c.segments.length === 0) return null; + const pos = this.getPositionFromTimeline(c.segments, minutesFromK, c.path, c.segmentEndIndices); + return pos || null; }; const trackConflicts = detectTrackSeparation(routeIds, minMinutes, maxMinutes, getPositionAtMinutesForConflict, config); trackConflicts.forEach(c => allRaw.push(c));