From 5dd0eea865f733ee3f7aede2da3dcf52456c588a Mon Sep 17 00:00:00 2001 From: ctw <1051735452@qq.com> Date: Wed, 25 Feb 2026 16:05:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-druid.yml | 2 +- ruoyi-ui/src/views/cesiumMap/index.vue | 61 ++++--- ruoyi-ui/src/views/childRoom/RightPanel.vue | 20 ++- ruoyi-ui/src/views/childRoom/index.vue | 190 ++++++++++++--------- 4 files changed, 159 insertions(+), 114 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index 0e75200..c8a125e 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -8,7 +8,7 @@ spring: master: url: jdbc:mysql://localhost:3306/ry?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root - password: 123456 + password: A20040303ctw! # 从库数据源 slave: # 从数据源开关/默认关闭 diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index bddfa1a..1cc4bd5 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -100,7 +100,7 @@ @@ -123,11 +123,12 @@ width="300px" > - + @@ -283,7 +284,7 @@ export default { powerZoneDialogVisible: false, powerZoneForm: { routeId: null, - radius: 1000, + radius: 10, color: 'rgba(255, 0, 0, 0.3)' }, // 航线飞机标牌显示状态:routeId -> true 显示 / false 隐藏,不设则默认显示 @@ -621,7 +622,7 @@ export default { polyline: { positions: solidPositions, width: 3, - material: Cesium.Color.fromCssColorString('#800080'), + material: Cesium.Color.fromCssColorString('#2E5C3E'), arcType: Cesium.ArcType.NONE } }); @@ -643,7 +644,7 @@ export default { }, false), width: 3, material: new Cesium.PolylineDashMaterialProperty({ - color: Cesium.Color.fromCssColorString('#800080'), + color: Cesium.Color.fromCssColorString('#2E5C3E'), dashLength: 16 }), arcType: Cesium.ArcType.NONE @@ -785,7 +786,7 @@ export default { }, false), width: 3, material: new Cesium.PolylineDashMaterialProperty({ - color: Cesium.Color.fromCssColorString('#800080'), + color: Cesium.Color.fromCssColorString('#2E5C3E'), dashLength: 16 }), arcType: Cesium.ArcType.NONE @@ -845,7 +846,7 @@ export default { polyline: { positions: solidPositions, width: 3, - material: Cesium.Color.fromCssColorString('#800080'), + material: Cesium.Color.fromCssColorString('#2E5C3E'), arcType: Cesium.ArcType.NONE } }); @@ -859,7 +860,7 @@ export default { }, false), width: 3, material: new Cesium.PolylineDashMaterialProperty({ - color: Cesium.Color.fromCssColorString('#800080'), + color: Cesium.Color.fromCssColorString('#2E5C3E'), dashLength: 16 }), arcType: Cesium.ArcType.NONE @@ -975,7 +976,7 @@ export default { polyline: { positions: solidPositions, width: 3, - material: Cesium.Color.fromCssColorString('#800080'), + material: Cesium.Color.fromCssColorString('#2E5C3E'), arcType: Cesium.ArcType.NONE } }); @@ -1532,9 +1533,9 @@ export default { const wpColor = wpStyle.color || '#ffffff'; const wpOutline = wpStyle.outlineColor || '#0078FF'; const wpOutlineW = wpStyle.outlineWidth != null ? wpStyle.outlineWidth : 2; - // 航线默认紫色、线宽 3;与主航线一致的线型用于主线和转弯半径弧 + // 航线默认墨绿色、线宽 3;与主航线一致的线型用于主线和转弯半径弧 const lineWidth = lineStyle.width != null ? lineStyle.width : 3; - const lineColor = lineStyle.color || '#800080'; + const lineColor = lineStyle.color || '#2E5C3E'; const gapColor = lineStyle.gapColor != null ? lineStyle.gapColor : '#000000'; const dashLen = lineStyle.dashLength != null ? lineStyle.dashLength : 20; const useDash = (lineStyle.style || 'solid') === 'dash'; @@ -1570,9 +1571,10 @@ export default { } return !!nextLogical; }; - // 遍历并绘制航点标记:转弯半径处不画中心点,改在下面画弧线时画两端两个点 + // 遍历并绘制航点标记:转弯半径处不画中心点;盘旋处也不画圆心点,只保留圆弧,避免主折线出现穿过圆心的直线 waypoints.forEach((wp, index) => { if (isTurnWaypointWithArc(index)) return; + if (this.isHoldWaypoint(wp)) return; const pos = originalPositions[index]; this.viewer.entities.add({ id: `wp_${routeId}_${wp.id}`, @@ -1740,9 +1742,11 @@ export default { } for (let k = 1; k < arcPoints.length; k++) finalPathPositions.push(arcPoints[k]); finalPathPositions.push(exit); + // 盘旋不单独着色:仅作为主航线取点数据源,show:false 由主航线折线用 lineWidth/lineMaterial 统一绘制 this.viewer.entities.add({ id: `hold-line-${routeId}-${i}`, - polyline: { positions: [entry, ...arcPoints.slice(1), exit], width: 8, material: Cesium.Color.ORANGE, arcType: Cesium.ArcType.NONE, zIndex: 20 }, + show: false, + polyline: { positions: [entry, ...arcPoints.slice(1), exit], width: lineWidth, material: lineMaterial, arcType: Cesium.ArcType.NONE, zIndex: 20 }, properties: { routeId: routeId } }); lastPos = exit; @@ -1830,6 +1834,15 @@ export default { const now = Cesium.JulianDate.now(); const positions = []; for (let i = 0; i < ids.length; i++) { + // 盘旋段优先用整圆弧线,不取圆心点,避免主折线出现穿过圆心的直线 + const holdEnt = this.viewer.entities.getById(`hold-line-${routeId}-${i}`); + if (holdEnt && holdEnt.polyline && holdEnt.polyline.positions) { + const arr = holdEnt.polyline.positions.getValue(now); + if (arr && arr.length) { + for (let k = 0; k < arr.length; k++) positions.push(Cesium.Cartesian3.clone(arr[k])); + continue; + } + } const ent = this.viewer.entities.getById(`wp_${routeId}_${ids[i]}`); if (ent && ent.position) { const pos = ent.position.getValue(now); @@ -1846,14 +1859,6 @@ export default { continue; } } - const holdEnt = this.viewer.entities.getById(`hold-line-${routeId}-${i}`); - if (holdEnt && holdEnt.polyline && holdEnt.polyline.positions) { - const arr = holdEnt.polyline.positions.getValue(now); - if (arr && arr.length) { - for (let k = 0; k < arr.length; k++) positions.push(Cesium.Cartesian3.clone(arr[k])); - continue; - } - } } return positions.length > 0 ? positions : null; }, @@ -5369,10 +5374,10 @@ export default { this.contextMenu.visible = false return } - // 重置表单,保留上次的 routeId + // 重置表单,保留上次的 routeId(半径单位为千米) this.powerZoneForm = { routeId: ed.routeId, - radius: 1000, + radius: 10, color: 'rgba(255, 0, 0, 0.3)' } this.contextMenu.visible = false @@ -5406,8 +5411,8 @@ export default { return platformEntity.position.getValue(now) }, false), ellipse: { - semiMinorAxis: this.powerZoneForm.radius, - semiMajorAxis: this.powerZoneForm.radius, + semiMinorAxis: this.powerZoneForm.radius * 1000, + semiMajorAxis: this.powerZoneForm.radius * 1000, material: Cesium.Color.fromCssColorString(this.powerZoneForm.color), outline: true, outlineColor: Cesium.Color.fromCssColorString(this.powerZoneForm.color).withAlpha(1.0), @@ -5419,7 +5424,7 @@ export default { this.viewer.scene.requestRender() } this.powerZoneDialogVisible = false - this.$message.success(`已添加半径 ${this.powerZoneForm.radius} 米的威力区`) + this.$message.success(`已添加半径 ${this.powerZoneForm.radius} 千米的威力区`) }, // 从右键菜单删除实体 deleteEntityFromContextMenu() { diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index 0854d2e..cb80ba7 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -119,16 +119,24 @@
{{ $t('rightPanel.involvedRoutes') }}: - {{ conflict.routes.join('、') }} + {{ conflict.routeName || (conflict.routes && conflict.routes.join('、')) }}
-
+
+ 问题航段: + {{ conflict.fromWaypoint }} → {{ conflict.toWaypoint }} +
+
{{ $t('rightPanel.conflictTime') }}: {{ conflict.time }}
-
+
{{ $t('rightPanel.conflictPosition') }}: {{ conflict.position }}
+
+ 建议: + {{ conflict.suggestion }} +
@@ -831,6 +839,12 @@ export default { font-weight: 500; } +.detail-item.suggestion .value { + white-space: normal; + word-break: break-word; + color: #008aff; +} + .conflict-actions { display: flex; gap: 10px; diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 78d2d52..30b5aa3 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -218,7 +218,6 @@ -

仅推演当前展示的航线;K 时可随时由房主/管理员在右上角「作战时间」处修改。

@@ -261,14 +260,6 @@
-
- - {{ deductionWarnings[0] || '存在航段将提前到达下一航点。' }} - - 等 {{ deductionWarnings.length }} 条 - - 在此添加盘旋 -
{{ addHoldDialogTip }}
@@ -598,26 +589,9 @@ export default { activeRouteIds: [], // 存储当前所有选中的航线ID /** 航线上锁状态:routeId -> true 上锁,与地图右键及右侧列表锁图标同步 */ routeLocked: {}, - // 冲突数据 - conflictCount: 2, - conflicts: [ - { - id: 1, - title: '航线空间冲突', - routes: ['Alpha进场航线', 'Beta巡逻航线'], - time: 'K+01:20:00', - position: 'E116° N39°', - severity: 'high' - }, - { - id: 2, - title: '时间窗口重叠', - routes: ['侦察覆盖区', '无人机巡逻'], - time: 'K+02:15:00', - position: 'E117° N38°', - severity: 'medium' - } - ], + // 冲突数据(由 runConflictCheck 根据当前航线与时间轴计算真实问题) + conflictCount: 0, + conflicts: [], // 平台数据 activePlatformTab: 'air', @@ -743,12 +717,16 @@ export default { const response = await getRoutes(routeId); if (response.code === 200 && response.data) { const fullRouteData = response.data; - // 同步更新父组件状态,保持和 selectRoute 方法一致的结构 + const fromList = this.routes.find(r => r.id === routeId); + // 同步更新父组件状态,合并 list 中的 platform 以便拖拽后重绘平台不丢失 this.selectedRouteId = fullRouteData.id; this.selectedRouteDetails = { id: fullRouteData.id, name: fullRouteData.callSign, - waypoints: fullRouteData.waypoints || [] + waypoints: fullRouteData.waypoints || [], + platformId: fromList?.platformId, + platform: fromList?.platform, + attributes: fromList?.attributes }; } } catch (error) { @@ -908,11 +886,13 @@ export default { if (i !== -1) this.selectedRouteDetails.waypoints.splice(i, 1, merged); } if (this.$refs.cesiumMap) { + // 平台信息来自 list 的 route(selectedRouteDetails 来自 getRoutes 可能不含 platformId/platform) + const routeForPlatform = this.routes.find(r => r.id === routeId) || route; this.$refs.cesiumMap.renderRouteWaypoints( waypoints, routeId, - route.platformId, - route.platform, + routeForPlatform.platformId, + routeForPlatform.platform, this.parseRouteStyle(route.attributes) ); } @@ -1000,11 +980,11 @@ export default { // 关闭弹窗 this.showPlatformDialog = false; }, - /** 新建航线时写入数据库的默认样式(与地图默认显示一致:紫色实线线宽3) */ + /** 新建航线时写入数据库的默认样式(与地图默认显示一致:墨绿色实线线宽3) */ getDefaultRouteAttributes() { const defaultAttrs = { waypointStyle: { pixelSize: 7, color: '#ffffff', outlineColor: '#0078FF', outlineWidth: 2 }, - lineStyle: { style: 'solid', width: 3, color: '#800080', gapColor: '#000000', dashLength: 20 } + lineStyle: { style: 'solid', width: 3, color: '#2E5C3E', gapColor: '#000000', dashLength: 20 } }; return JSON.stringify(defaultAttrs); }, @@ -1238,9 +1218,24 @@ export default { } // 2. 构造数据(含盘旋航点的 pointType、holdParams;地图标签默认字号 14、颜色 #333333) - // 新建航线时:首尾航点转弯坡度固定为 0,中间可编辑航点默认 45° + // 默认相对 K 时:按“路程÷默认速度”累加;用向上取整避免因整数分钟舍去导致冲突检测误报“无法按时到达” const wpCount = this.tempMapPoints.length; - const finalWaypoints = this.tempMapPoints.map((p, index) => { + let cumulativeMinutes = 0; + const pointsWithStartTime = this.tempMapPoints.map((p, index) => { + const startTime = this.minutesToStartTime(index === 0 ? 0 : Math.ceil(cumulativeMinutes)); + if (index < wpCount - 1) { + const next = this.tempMapPoints[index + 1]; + const dist = this.segmentDistance( + { lat: p.lat, lng: p.lng, alt: p.alt != null ? p.alt : 5000 }, + { lat: next.lat, lng: next.lng, alt: next.alt != null ? next.alt : 5000 } + ); + const speedKmh = Number(p.speed) || 800; + cumulativeMinutes += (dist / 1000) * (60 / speedKmh); + } + return { ...p, startTime }; + }); + // 新建航线时:首尾航点转弯坡度固定为 0,中间可编辑航点默认 45° + const finalWaypoints = pointsWithStartTime.map((p, index) => { const isFirstOrLast = index === 0 || index === wpCount - 1; const defaultTurnAngle = isFirstOrLast ? 0.0 : 45.0; return { @@ -1249,7 +1244,7 @@ export default { lng: p.lng, alt: p.alt != null ? p.alt : 5000.0, speed: p.speed != null ? p.speed : 800.0, - startTime: p.startTime || 'K+00:00:00', + startTime: p.startTime, turnAngle: p.turnAngle != null ? p.turnAngle : defaultTurnAngle, labelFontSize: p.labelFontSize != null ? p.labelFontSize : 14, labelColor: p.labelColor || '#333333', @@ -2354,11 +2349,14 @@ export default { const pos = { lng: p.lng, lat: p.lat, alt: p.alt }; return { segments: [{ startTime: globalMin, endTime: globalMax, startPos: pos, endPos: pos, type: 'wait' }], - warnings + warnings, + earlyArrivalLegs: [], + lateArrivalLegs: [] }; } const effectiveTime = [points[0].minutes]; const segments = []; + const lateArrivalLegs = []; // 无法按时到达的航段,供冲突检测用 const path = pathData && pathData.path; const segmentEndIndices = pathData && pathData.segmentEndIndices; const holdArcRanges = pathData && pathData.holdArcRanges || {}; @@ -2425,6 +2423,13 @@ export default { warnings.push( `某航段:距离约 ${(dist / 1000).toFixed(1)}km,计划 ${(scheduled - points[i].minutes).toFixed(0)} 分钟,当前速度 ${speedKmh}km/h 无法按时到达,约需 ≥${Math.ceil(requiredSpeedKmh)}km/h,请调整相对K时或速度。` ); + lateArrivalLegs.push({ + legIndex: i, + fromName: waypoints[i].name, + toName: waypoints[i + 1].name, + requiredSpeedKmh: Math.ceil(requiredSpeedKmh), + speedKmh + }); } else if (actualArrival < scheduled - 0.5) { warnings.push('存在航段将提前到达下一航点,平台将在该点等待至计划时间再飞往下一段。'); } @@ -2449,7 +2454,7 @@ export default { earlyArrivalLegs.push({ legIndex: i, scheduled, actualArrival, fromName: waypoints[i].name, toName: waypoints[i + 1].name }); } } - return { segments, warnings, earlyArrivalLegs }; + return { segments, warnings, earlyArrivalLegs, lateArrivalLegs }; }, /** 从路径片段中按比例 t(0~1)取点:按弧长比例插值 */ @@ -2800,10 +2805,14 @@ export default { const waypoints = fullRouteData.waypoints || []; this.activeRouteIds.push(route.id); this.selectedRouteId = fullRouteData.id; + // 合并 list 中的 platformId/platform,以便拖拽航点后重绘时平台图标不丢失 this.selectedRouteDetails = { id: fullRouteData.id, name: fullRouteData.callSign, - waypoints: waypoints + waypoints: waypoints, + platformId: route.platformId, + platform: route.platform, + attributes: route.attributes }; // 更新 routes 数组中对应航线的 waypoints 字段 @@ -2952,11 +2961,15 @@ export default { const lastId = this.activeRouteIds[this.activeRouteIds.length - 1]; getRoutes(lastId).then(res => { if (res.code === 200 && res.data) { + const fromList = this.routes.find(r => r.id === lastId); this.selectedRouteId = res.data.id; this.selectedRouteDetails = { id: res.data.id, name: res.data.callSign, - waypoints: res.data.waypoints || [] + waypoints: res.data.waypoints || [], + platformId: fromList?.platformId, + platform: fromList?.platform, + attributes: fromList?.attributes }; } }).catch(e => { @@ -2973,10 +2986,58 @@ export default { } }, - // 冲突操作 + // 冲突操作:根据当前展示的航线与时间轴计算真实问题(提前到达、无法按时到达) runConflictCheck() { - this.conflictCount = 2; - this.$message.warning('检测到2处航线冲突'); + 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(); + + routeIds.forEach(routeId => { + const route = this.routes.find(r => r.id === routeId); + if (!route || !route.waypoints || route.waypoints.length < 2) return; + let pathData = null; + if (this.$refs.cesiumMap && this.$refs.cesiumMap.getRoutePathWithSegmentIndices) { + const ret = this.$refs.cesiumMap.getRoutePathWithSegmentIndices(route.waypoints); + if (ret.path && ret.path.length > 0 && ret.segmentEndIndices) { + pathData = { path: ret.path, segmentEndIndices: ret.segmentEndIndices, holdArcRanges: ret.holdArcRanges || {} }; + } + } + const { earlyArrivalLegs, lateArrivalLegs } = this.buildRouteTimeline(route.waypoints, minMinutes, maxMinutes, pathData); + + const routeName = route.name || `航线${route.id}`; + (earlyArrivalLegs || []).forEach(leg => { + list.push({ + id: id++, + title: '提前到达', + routeName, + fromWaypoint: leg.fromName, + toWaypoint: leg.toName, + time: this.minutesToStartTime(leg.actualArrival), + suggestion: '该航段将提前到达下一航点,建议在此段加入盘旋或延后下一航点计划时间。', + severity: 'high' + }); + }); + (lateArrivalLegs || []).forEach(leg => { + list.push({ + id: id++, + title: '无法按时到达', + routeName, + fromWaypoint: leg.fromName, + toWaypoint: leg.toName, + suggestion: `当前速度不足,建议将本段速度提升至 ≥${leg.requiredSpeedKmh} km/h,或延后下一航点计划时间。`, + severity: 'high' + }); + }); + }); + + this.conflicts = list; + this.conflictCount = list.length; + if (list.length > 0) { + this.$message.warning(`检测到 ${list.length} 处航线时间问题`); + } else { + this.$message.success('未发现航线时间冲突'); + } }, viewConflict(conflict) { @@ -3011,7 +3072,7 @@ export default { overflow: hidden; } -/* 地图背景 - 保持不变 */ +/* 地图背景:使用相对路径便于 IDE 与构建解析;若需图片请将 map-background.png 放到 src/assets/ */ .map-background { position: absolute; top: 0; @@ -3019,8 +3080,7 @@ export default { width: 100%; height: 100%; background: linear-gradient(135deg, #1a2f4b 0%, #2c3e50 100%); - /* 正确的写法,直接复制这行替换 */ - background: url('~@/assets/map-background.png'); + /* 若已存在 src/assets/map-background.png,可改为:background: url('../../assets/map-background.png'); 并注释掉上一行 */ background-size: cover; background-position: center; z-index: 1; @@ -3244,40 +3304,6 @@ export default { line-height: 1.5; } -.deduction-hint { - margin: 0 0 8px 0; - font-size: 12px; - color: #909399; - line-height: 1.4; -} - -.deduction-warnings { - display: flex; - align-items: center; - gap: 6px; - margin-top: 8px; - padding: 6px 10px; - background: rgba(230, 162, 60, 0.15); - border: 1px solid rgba(230, 162, 60, 0.5); - border-radius: 6px; - font-size: 12px; - color: #b88230; -} -.deduction-warnings i { - flex-shrink: 0; -} -.deduction-warnings span { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.deduction-warnings .warnings-more { - flex-shrink: 0; - color: #008aff; - cursor: help; -} - .popup-hide-btn { position: absolute; top: -28px;