diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoomPlatformIconController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoomPlatformIconController.java index 0869b0d..f28ac5e 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoomPlatformIconController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoomPlatformIconController.java @@ -108,7 +108,7 @@ public class RoomPlatformIconController extends BaseController { @PutMapping public AjaxResult edit(@RequestBody RoomPlatformIcon roomPlatformIcon) { RoomPlatformIcon before = roomPlatformIcon.getId() != null - ? roomPlatformIconService.selectById(roomPlatformIcon.getId()) : null; + ? roomPlatformIconService.selectById(roomPlatformIcon.getId()) : null; int rows = roomPlatformIconService.update(roomPlatformIcon); if (rows > 0 && before != null) { RoomPlatformIcon after = roomPlatformIconService.selectById(roomPlatformIcon.getId()); @@ -171,5 +171,5 @@ public class RoomPlatformIconController extends BaseController { } } return toAjax(rows); -} + } } diff --git a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue index 73f324f..e7502e4 100644 --- a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue +++ b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue @@ -256,21 +256,22 @@ 磁方位 - + + @@ -746,7 +747,7 @@ export default { /** 根据视口边界修正菜单位置,避免菜单在屏幕底部或右侧被截断 */ adjustedPosition() { const padding = 12 - const menuMaxWidth = 264 + const menuMaxWidth = 228 const menuMaxHeight = 640 const winW = typeof window !== 'undefined' ? window.innerWidth : 1920 const winH = typeof window !== 'undefined' ? window.innerHeight : 1080 @@ -786,6 +787,12 @@ export default { const pt = ed.pointType || ed.point_type || '' return pt === 'hold_circle' || pt === 'hold_ellipse' }, + rangingDistanceUnitLabel() { + const u = this.rangingDistanceUnit + if (u === 'nm') return '海里 NM' + if (u === 'km') return '公里 km' + return '米 m' + }, addWaypointDialogTitle() { if (!this.addWaypointDialogMode) return '设置参数' const dir = this.addWaypointDialogMode === 'before' ? '向前' : '向后' @@ -1221,7 +1228,7 @@ export default { }, selectRangingUnit(unit) { - if (unit === 'km' || unit === 'nm') { + if (unit === 'm' || unit === 'km' || unit === 'nm') { this.$emit('ranging-distance-unit', unit) } this.showRangingUnitMenu = false @@ -1239,8 +1246,10 @@ export default { --ctx-surface: rgba(255, 255, 255, 0.72); position: fixed; z-index: 9999; - min-width: 188px; - max-width: 264px; + box-sizing: border-box; + width: 228px; + min-width: 228px; + max-width: 228px; padding: 12px 0; color: var(--ctx-text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', 'Microsoft YaHei', Arial, sans-serif; @@ -1559,14 +1568,19 @@ export default { backdrop-filter: blur(10px); border: 1px solid rgba(22, 93, 255, 0.1); border-radius: 10px; + max-width: 100%; + box-sizing: border-box; } .sub-menu-item { - padding: 8px 16px 8px 28px; + padding: 8px 12px 8px 20px; cursor: pointer; transition: background-color 0.2s ease, color 0.2s ease; font-size: 12px; color: var(--ctx-text); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .sub-menu-item:hover { diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 18fa556..9968453 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -620,7 +620,7 @@ export default { }, /** 辅助线:水平/竖直约束,'none' | 'horizontal' | 'vertical' */ auxiliaryLineConstraint: 'none', - /** 测距模式距离显示:'km' 公里 | 'nm' 海里(1 海里 = 1852 m) */ + /** 线段累计距离显示:'m' 米 | 'km' 公里 | 'nm' 海里(1 海里 = 1852 m) */ rangingDistanceUnit: 'km', // 鼠标经纬度 coordinatesText: '经度: --, 纬度: --', @@ -834,10 +834,21 @@ export default { } }, rangingDistanceUnit() { - if (this.toolMode !== 'ranging' || !this.allEntities) return - this.allEntities.forEach((ed) => { - if (ed && ed.type === 'line') this.updateLineSegmentLabels(ed) - }) + const seen = new Set() + const refresh = (ed) => { + if (!ed || ed.type !== 'line' || ed.routeId != null) return + const id = ed.id + if (id != null && seen.has(String(id))) return + if (id != null) seen.add(String(id)) + this.updateLineSegmentLabels(ed) + } + if (this.allEntities) this.allEntities.forEach(refresh) + if (this.whiteboardEntityDataMap) Object.values(this.whiteboardEntityDataMap).forEach(refresh) + // requestRenderMode 下仅改单位不会触发 postRender,需立即同步 DOM 标签并请求一帧 + this.syncMapScreenDomLabels() + if (this.viewer && this.viewer.scene && this.viewer.scene.requestRenderMode) { + this.viewer.scene.requestRender() + } } }, computed: { @@ -2620,7 +2631,7 @@ export default { this._routeWaypointsByRoute[routeId] = waypoints; if (!this._routeHoldRadiiByRoute) this._routeHoldRadiiByRoute = {}; if (!this._routeHoldEllipseParamsByRoute) this._routeHoldEllipseParamsByRoute = {}; - // 判断航点 i 是否为“转弯半径”航点(将用弧线两端两个点替代中心点);非首尾默认 45° 坡度 + // 判断航点 i 是否为“转弯半径”航点(将用弧线两端两个点替代中心点);缺省坡度 45°(仅中间航点参与弧线几何) const isTurnWaypointWithArc = (i) => { if (i < 1 || i >= waypoints.length - 1) return false; const wp = waypoints[i]; @@ -2868,8 +2879,114 @@ export default { } // 绘制连线(含盘旋弧) if (waypoints.length > 1) { - const finalPathPositions = [originalPositions[0]]; - let lastPos = originalPositions[0]; + let finalPathPositions; + let lastPos; + if (this.isHoldWaypoint(waypoints[0])) { + finalPathPositions = []; + const wpFirstHold = waypoints[0]; + const currPosFh = originalPositions[0]; + const nextPosFh = originalPositions[1]; + lastPos = this.getSyntheticInboundBeforeFirstHold(currPosFh, nextPosFh); + const paramsFh = this.parseHoldParams(wpFirstHold); + const legIndexHoldFh = 0; + const ptFh = wpFirstHold.pointType || wpFirstHold.point_type; + const useCircleFh = ptFh === 'hold_circle'; + const effectiveTurnAngleFh = this.getEffectiveTurnAngle(wpFirstHold, 0, waypoints.length); + const prevWpForHoldFh = wpFirstHold; + const turnRadiusFh = this.getHoldRadiusFromPrevSpeed(wpFirstHold, prevWpForHoldFh, effectiveTurnAngleFh) || 500; + const radiusFh = turnRadiusFh; + const turnRadiusForHoldFh = turnRadiusFh; + const defaultSemiMajorFh = paramsFh && (paramsFh.semiMajor != null || paramsFh.semiMajorAxis != null) ? (paramsFh.semiMajor ?? paramsFh.semiMajorAxis) : 500; + const defaultSemiMinorFh = paramsFh && (paramsFh.semiMinor != null || paramsFh.semiMinorAxis != null) ? (paramsFh.semiMinor ?? paramsFh.semiMinorAxis) : 300; + const defaultHeadingRadFh = ((paramsFh && paramsFh.headingDeg != null ? paramsFh.headingDeg : 0) * Math.PI) / 180; + const edgeLengthMFh = Math.max(1000, paramsFh && paramsFh.edgeLength != null ? paramsFh.edgeLength : this.DEFAULT_RACE_TRACK_EDGE_LENGTH_M); + const arcRadiusMFh = turnRadiusFh; + const clockwiseFh = paramsFh && paramsFh.clockwise !== false; + const currPosClonedFh = Cesium.Cartesian3.clone(currPosFh); + const lastPosClonedFh = Cesium.Cartesian3.clone(lastPos); + const nextPosClonedFh = Cesium.Cartesian3.clone(nextPosFh); + const routeIdHoldFh = routeId; + const thatFh = this; + const buildHoldPositionsFh = (radiusOrEllipse, centerOverride) => { + const isCircleArg = typeof radiusOrEllipse === 'number'; + const R = isCircleArg ? radiusOrEllipse : 0; + const smj = isCircleArg ? defaultSemiMajorFh : (radiusOrEllipse.semiMajor ?? defaultSemiMajorFh); + const smn = isCircleArg ? defaultSemiMinorFh : (radiusOrEllipse.semiMinor ?? defaultSemiMinorFh); + const hd = isCircleArg ? defaultHeadingRadFh : ((radiusOrEllipse.headingDeg != null ? radiusOrEllipse.headingDeg * Math.PI / 180 : defaultHeadingRadFh)); + const centerPos = centerOverride || currPosClonedFh; + let entry; let exit; let centerForCircle; + if (useCircleFh) { + centerForCircle = thatFh.getHoldCenterFromPrevNext(lastPosClonedFh, centerPos, R, clockwiseFh); + entry = thatFh.getCircleTangentEntryPoint(centerForCircle, lastPosClonedFh, R, clockwiseFh); + exit = thatFh.getCircleTangentExitPoint(centerForCircle, nextPosClonedFh || centerPos, R, clockwiseFh); + } else { + entry = thatFh.getEllipseTangentEntryPoint(centerPos, lastPosClonedFh, smj, smn, hd, clockwiseFh); + exit = thatFh.getEllipseTangentExitPoint(centerPos, nextPosClonedFh || centerPos, smj, smn, hd, clockwiseFh); + } + let arcPoints; + if (useCircleFh) { + const center = centerForCircle; + const enu = Cesium.Transforms.eastNorthUpToFixedFrame(center); + const eastVec = Cesium.Matrix4.getColumn(enu, 0, new Cesium.Cartesian3()); + const northVec = Cesium.Matrix4.getColumn(enu, 1, new Cesium.Cartesian3()); + const toEntry = Cesium.Cartesian3.subtract(entry, center, new Cesium.Cartesian3()); + const startAngle = Math.atan2(Cesium.Cartesian3.dot(toEntry, eastVec), Cesium.Cartesian3.dot(toEntry, northVec)); + const fullCirclePoints = thatFh.getCircleFullCircle(center, R, startAngle, clockwiseFh, 64); + const arcToExit = thatFh.getCircleArcEntryToExit(center, R, entry, exit, clockwiseFh, 32); + arcPoints = [entry, ...(fullCirclePoints || []).slice(1), ...(arcToExit || []).slice(1)]; + if (!arcPoints || arcPoints.length < 2) arcPoints = [Cesium.Cartesian3.clone(entry), Cesium.Cartesian3.clone(exit)]; + return arcPoints; + } + const tEntry = thatFh.cartesianToEllipseParam(centerPos, smj, smn, hd, entry); + const entryLocalAngle = Math.atan2(smn * Math.sin(tEntry), smj * Math.cos(tEntry)); + const fullCirclePoints = thatFh.getEllipseFullCircle(centerPos, smj, smn, hd, entryLocalAngle, clockwiseFh, 128); + arcPoints = thatFh.buildEllipseHoldArc(centerPos, smj, smn, hd, entry, exit, clockwiseFh, 120); + return [entry, ...(fullCirclePoints || []).slice(1), ...(arcPoints || []).slice(1)]; + }; + const raceTrackDirectionRadFh = this.getRaceTrackDirectionRad( + currPosClonedFh, + lastPosClonedFh, + nextPosClonedFh, + paramsFh && paramsFh.headingDeg != null ? paramsFh.headingDeg : 0 + ); + const buildRaceTrackPositionsFh = (centerOverride) => thatFh.buildRaceTrackWithEntryExit(centerOverride || currPosClonedFh, lastPosClonedFh, nextPosClonedFh, raceTrackDirectionRadFh, edgeLengthMFh, arcRadiusMFh, clockwiseFh, 24); + const holdPositionsFh = useCircleFh ? buildHoldPositionsFh(radiusFh) : buildRaceTrackPositionsFh(); + for (let k = 0; k < holdPositionsFh.length; k++) finalPathPositions.push(holdPositionsFh[k]); + const wpIdHoldFh = wpFirstHold.id; + const getHoldPositionsFh = () => { + let centerOverride = null; + if (thatFh.waypointDragging && thatFh.waypointDragging.routeId === routeIdHoldFh && thatFh.waypointDragging.dbId === wpIdHoldFh) { + const wpEnt = thatFh.viewer.entities.getById(`wp_${routeIdHoldFh}_${wpIdHoldFh}`); + if (wpEnt && wpEnt.position) { + const p = wpEnt.position.getValue(Cesium.JulianDate.now()); + if (p) centerOverride = p; + } + } + if (useCircleFh) { + const R = (thatFh._routeHoldRadiiByRoute && thatFh._routeHoldRadiiByRoute[routeIdHoldFh] && thatFh._routeHoldRadiiByRoute[routeIdHoldFh][legIndexHoldFh] != null) + ? thatFh._routeHoldRadiiByRoute[routeIdHoldFh][legIndexHoldFh] + : turnRadiusForHoldFh; + return buildHoldPositionsFh(R, centerOverride); + } + return buildRaceTrackPositionsFh(centerOverride); + }; + this.viewer.entities.add({ + id: `hold-line-${routeId}-0`, + show: false, + polyline: { + positions: new Cesium.CallbackProperty(getHoldPositionsFh, false), + width: lineWidth, + material: lineMaterial, + arcType: Cesium.ArcType.NONE, + zIndex: 20 + }, + properties: { routeId: routeId } + }); + lastPos = holdPositionsFh[holdPositionsFh.length - 1]; + } else { + finalPathPositions = [originalPositions[0]]; + lastPos = originalPositions[0]; + } for (let i = 1; i < waypoints.length; i++) { const currPos = originalPositions[i]; const wp = waypoints[i]; @@ -3197,9 +3314,8 @@ export default { return null; }, - /** 渲染/路径用:非首尾航点默认转弯坡度 45°,首尾为 0 */ + /** 渲染/路径用:未设置转弯坡度时默认 45°(首尾可存盘并在盘旋等场景参与计算) */ getEffectiveTurnAngle(wp, index, waypointsLength) { - if (index === 0 || index === waypointsLength - 1) return wp.turnAngle != null ? wp.turnAngle : 0; return wp.turnAngle != null ? wp.turnAngle : 45; }, /** 转弯半径 R = v²/(g·tanθ),v 为速度(m/s),g=9.8,θ 为转弯坡度/坡度角(弧度) */ @@ -3229,6 +3345,31 @@ export default { return this.getHoldRadiusFromPrevSpeed(null, prevWp, turnAngle); }, + /** + * 首点为盘旋时没有「上一航点」,在 hold→下一航点 的反方向取一参考点, + * 供 getHoldCenterFromPrevNext / 切点 几何与中间航点盘旋一致。 + */ + getSyntheticInboundBeforeFirstHold(holdCartesian, nextCartesian) { + const toNext = Cesium.Cartesian3.subtract(nextCartesian, holdCartesian, new Cesium.Cartesian3()); + let dist = Cesium.Cartesian3.magnitude(toNext); + if (dist < 1e-3) { + const enu = Cesium.Transforms.eastNorthUpToFixedFrame(holdCartesian); + const east = Cesium.Matrix4.getColumn(enu, 0, new Cesium.Cartesian3()); + return Cesium.Cartesian3.add( + holdCartesian, + Cesium.Cartesian3.multiplyByScalar(east, -50000, new Cesium.Cartesian3()), + new Cesium.Cartesian3() + ); + } + Cesium.Cartesian3.normalize(toNext, toNext); + const back = Math.max(dist, 5000); + return Cesium.Cartesian3.add( + holdCartesian, + Cesium.Cartesian3.multiplyByScalar(toNext, -back, new Cesium.Cartesian3()), + new Cesium.Cartesian3() + ); + }, + isHoldWaypoint(wp) { const t = (wp && wp.pointType) || (wp && wp.point_type) || 'normal'; return t === 'hold_circle' || t === 'hold_ellipse'; @@ -4584,11 +4725,69 @@ export default { const originalPositions = waypoints.map(wp => Cesium.Cartesian3.fromDegrees(parseFloat(wp.lng), parseFloat(wp.lat), Number(wp.alt) || 0) ); - const path = [toLngLatAlt(originalPositions[0])]; + const path = []; const segmentEndIndices = []; const holdArcRanges = {}; - let lastPos = originalPositions[0]; + let lastPos; let prevWasHold = false; + if (waypoints.length > 1 && this.isHoldWaypoint(waypoints[0])) { + const wp = waypoints[0]; + const currPos = originalPositions[0]; + const nextPos = originalPositions[1]; + lastPos = this.getSyntheticInboundBeforeFirstHold(currPos, nextPos); + const params = this.parseHoldParams(wp); + const legIndexFirst = 0; + const pt = wp.pointType || wp.point_type; + const useCircle = pt === 'hold_circle'; + const clockwise = params && params.clockwise !== false; + const arcStartIdx = path.length; + let holdPositions; + if (useCircle) { + const prevWpForSpeed = wp; + const holdTurnAngle = this.getEffectiveTurnAngle(wp, 0, waypoints.length); + const turnRadius = this.getHoldRadiusFromPrevSpeed(wp, prevWpForSpeed, holdTurnAngle) || 500; + const radius = (holdRadiusByLegIndex[legIndexFirst] != null && Number.isFinite(holdRadiusByLegIndex[legIndexFirst])) + ? holdRadiusByLegIndex[legIndexFirst] + : turnRadius; + const center = this.getHoldCenterFromPrevNext(lastPos, currPos, radius, clockwise); + const entry = this.getCircleTangentEntryPoint(center, lastPos, radius, clockwise); + const exit = this.getCircleTangentExitPoint(center, nextPos || currPos, radius, clockwise); + const enuPath = Cesium.Transforms.eastNorthUpToFixedFrame(center); + const eastPath = Cesium.Matrix4.getColumn(enuPath, 0, new Cesium.Cartesian3()); + const northPath = Cesium.Matrix4.getColumn(enuPath, 1, new Cesium.Cartesian3()); + const toEntryPath = Cesium.Cartesian3.subtract(entry, center, new Cesium.Cartesian3()); + const startAnglePath = Math.atan2(Cesium.Cartesian3.dot(toEntryPath, eastPath), Cesium.Cartesian3.dot(toEntryPath, northPath)); + const fullCirclePath = this.getCircleFullCircle(center, radius, startAnglePath, clockwise, 64); + const arcToExitPath = this.getCircleArcEntryToExit(center, radius, entry, exit, clockwise, 32); + holdPositions = [entry, ...(fullCirclePath || []).slice(1), ...(arcToExitPath || []).slice(1)]; + if (!holdPositions || holdPositions.length < 2) holdPositions = [Cesium.Cartesian3.clone(entry), Cesium.Cartesian3.clone(exit)]; + lastPos = exit; + } else { + const edgeLengthM = Math.max(1000, params && params.edgeLength != null ? params.edgeLength : this.DEFAULT_RACE_TRACK_EDGE_LENGTH_M); + const prevWpForSpeed = wp; + const holdTurnAngle = this.getEffectiveTurnAngle(wp, 0, waypoints.length); + const arcRadiusM = this.getHoldRadiusFromPrevSpeed(wp, prevWpForSpeed, holdTurnAngle) || 500; + const directionRad = this.getRaceTrackDirectionRad( + currPos, + lastPos, + nextPos, + params && params.headingDeg != null ? params.headingDeg : 0 + ); + holdPositions = this.buildRaceTrackWithEntryExit(currPos, lastPos, nextPos, directionRad, edgeLengthM, arcRadiusM, clockwise, 24); + lastPos = holdPositions.length ? holdPositions[holdPositions.length - 1] : currPos; + } + const holdLoopEndOffset = holdPositions._loopEndIndex != null ? holdPositions._loopEndIndex : null; + for (let k = 0; k < holdPositions.length; k++) path.push(toLngLatAlt(holdPositions[k])); + holdArcRanges[-1] = { + start: arcStartIdx, + end: path.length - 1, + loopEndIndex: holdLoopEndOffset != null ? arcStartIdx + holdLoopEndOffset : null + }; + prevWasHold = true; + } else { + path.push(toLngLatAlt(originalPositions[0])); + lastPos = originalPositions[0]; + } for (let i = 1; i < waypoints.length; i++) { const currPos = originalPositions[i]; const wp = waypoints[i]; @@ -5390,7 +5589,7 @@ export default { alt: wp.alt != null ? Number(wp.alt) : 5000, speed: wp.speed != null ? wp.speed : 800, startTime: wp.startTime || 'K+00:00:00', - turnAngle: wp.turnAngle != null ? wp.turnAngle : (idx === 0 || idx === this.copyPreviewWaypoints.length - 1 ? 0 : 45), + turnAngle: wp.turnAngle != null ? wp.turnAngle : 45, labelFontSize: wp.labelFontSize != null ? wp.labelFontSize : 14, labelColor: wp.labelColor || '#333333', ...(wp.pointType && { pointType: wp.pointType }), @@ -6981,26 +7180,13 @@ export default { const length = this.calculateLineLength(tempPositions); // 默认为真方位,因为绘制过程中还没有bearingType属性 const bearing = this.calculateTrueBearing(tempPositions); - // 根据工具模式决定显示格式 - if (this.toolMode === 'ranging') { - this.hoverTooltip = { - visible: true, - content: `${this.formatRangingLengthText(length)} ,${bearing.toFixed(1)}°`, - position: { - x: movement.endPosition.x + 10, - y: movement.endPosition.y - 10 - } - }; - } else { - // 空域模式:使用米,完整格式 - this.hoverTooltip = { - visible: true, - content: `长度:${length.toFixed(2)} 米\n真方位:${bearing.toFixed(2)}°`, - position: { - x: movement.endPosition.x + 10, - y: movement.endPosition.y - 10 - } - }; + this.hoverTooltip = { + visible: true, + content: `${this.formatRangingLengthText(length)},${bearing.toFixed(2)}°`, + position: { + x: movement.endPosition.x + 10, + y: movement.endPosition.y - 10 + } } } else { // 如果没有点,隐藏提示 @@ -7041,7 +7227,7 @@ export default { kind: 'drawPoint', entityId: pointId, pixelOffset: { x: 15, y: 0 }, - text: isStartPoint ? '起点' : `${(cumulativeDistance / 1000).toFixed(2)}km` + text: isStartPoint ? '起点' : this.formatRangingLengthText(cumulativeDistance) } } this.drawingPointEntities.push(pointEntity); @@ -7124,7 +7310,20 @@ export default { type: 'line', label: '测距', color: this.defaultStyles.line ? this.defaultStyles.line.color : '#165dff', - data: { points, width: (this.defaultStyles.line && this.defaultStyles.line.width) || 2 } + data: { + points, + width: (this.defaultStyles.line && this.defaultStyles.line.width) || 2, + bearingType: 'true' + } + } + // 与主地图 addLineEntity 不同:白板落库为折线后不再保留绘制时的顶点 entity(否则删除线后仍会留下红点) + this.unregisterDrawingPointDomLabelsForEntities(this.drawingPointEntities) + if (this.drawingPointEntities && this.drawingPointEntities.length) { + this.drawingPointEntities.forEach((pe) => { + try { + this.viewer.entities.remove(pe) + } catch (_) {} + }) } this.$emit('whiteboard-draw-complete', entityData) this.drawingPoints = [] @@ -8711,17 +8910,21 @@ export default { } return totalLength }, - /** 测距:米 → 当前单位文案(方位角另拼);isTotal 时最后一段显示「共…」 */ - formatRangingLengthText(meters, opts = {}) { + /** 线段距离:米 → 当前单位后缀 m / km / NM(与角度拼成「距离,角度°」) */ + formatRangingLengthText(meters) { const m = Number(meters) - if (!Number.isFinite(m)) return this.rangingDistanceUnit === 'nm' ? '0.00海里' : '0.0km' - const isTotal = opts.isTotal === true + if (!Number.isFinite(m)) { + if (this.rangingDistanceUnit === 'nm') return '0.00NM' + if (this.rangingDistanceUnit === 'km') return '0.00km' + return '0.00m' + } if (this.rangingDistanceUnit === 'nm') { - const v = (m / 1852).toFixed(2) - return isTotal ? `共${v}海里` : `${v}海里` + return `${(m / 1852).toFixed(2)}NM` + } + if (this.rangingDistanceUnit === 'km') { + return `${(m / 1000).toFixed(2)}km` } - const v = (m / 1000).toFixed(1) - return isTotal ? `共${v}km` : `${v}km` + return `${m.toFixed(2)}m` }, /** 更新测距/空域线段每段终点的标签:屏幕 DOM,与 HoverTooltip 同类渲染 */ updateLineSegmentLabels(entityData) { @@ -8763,17 +8966,7 @@ export default { const segLen = Cesium.Cartesian3.distance(positions[i], positions[i + 1]) cumulativeLength += segLen const bearing = bearingFn([positions[i], positions[i + 1]]) - const text = - this.toolMode === 'ranging' - ? `${this.formatRangingLengthText(cumulativeLength)} ,${bearing.toFixed(1)}°` - : `累计长度:${cumulativeLength.toFixed(2)} 米\n${ - bearingType === 'magnetic' ? '磁方位' : '真方位' - }:${bearing.toFixed(2)}°` - const isLast = i === positions.length - 2 - const displayText = - isLast && this.toolMode === 'ranging' - ? `${this.formatRangingLengthText(cumulativeLength, { isTotal: true })} ,${bearing.toFixed(1)}°` - : text + const displayText = `${this.formatRangingLengthText(cumulativeLength)},${bearing.toFixed(2)}°` const id = `${pref}${i}` this._mapScreenDomLabelRegistry[id] = { kind: 'segment', @@ -8781,7 +8974,7 @@ export default { segmentVertexIndex: i + 1, pixelOffset: { x: 15, y: 0 }, text: displayText, - multiline: displayText.indexOf('\n') >= 0 + multiline: false } } }, @@ -11806,8 +11999,25 @@ export default { } if (entityData.isWhiteboard) { this.$emit('whiteboard-entity-deleted', entityData) + if (entityData.type === 'line') { + this.unregisterLineSegmentDomLabels(entityData.id) + if (entityData.pointEntities && entityData.pointEntities.length) { + entityData.pointEntities.forEach((pe) => { + try { + this.viewer.entities.remove(pe) + } catch (_) {} + }) + } + } + if (entityData.type === 'text' && entityData.entity && entityData.entity.id) { + this.unregisterMapTextDomLabel(`map-dom-maptext-text-${entityData.entity.id}`) + } if (entityData.entity) this.viewer.entities.remove(entityData.entity) if (this.whiteboardEntityDataMap && entityData.id) delete this.whiteboardEntityDataMap[entityData.id] + this.syncMapScreenDomLabels() + if (this.viewer.scene && this.viewer.scene.requestRenderMode) { + this.viewer.scene.requestRender() + } } else if (entityData.type === 'detectionZone' || entityData.type === 'powerZone') { const type = entityData.type const zoneId = entityData.zoneId @@ -12193,6 +12403,13 @@ export default { } // 更新实体样式 this.updateEntityStyle(entityData) + if (property === 'bearingType' && entityData.type === 'line' && entityData.routeId == null) { + this.updateLineSegmentLabels(entityData) + this.syncMapScreenDomLabels() + if (this.viewer && this.viewer.scene && this.viewer.scene.requestRenderMode) { + this.viewer.scene.requestRender() + } + } // 白板实体:通知父组件更新 contentByTime if (entityData.isWhiteboard && this.getDrawingEntityTypes().includes(entityData.type)) { this.$emit('whiteboard-drawing-updated', this.serializeWhiteboardDrawingEntity(entityData)) @@ -12699,6 +12916,9 @@ export default { data.points = entityData.points data.width = entityData.width != null ? entityData.width : 2 data.color = entityData.color || entityData.data?.color + if (entityData.type === 'line') { + data.bearingType = entityData.bearingType || entityData.data?.bearingType || 'true' + } } break case 'arrow': @@ -12932,7 +13152,30 @@ export default { this.whiteboardHiddenEntityShows[id] = origShow entity.show = false }) + // 主地图测距/画线段的屏幕 DOM 标签不随 Cesium entity.show 隐藏,进入白板时需从注册表移除 + if (this.allEntities && this.allEntities.length) { + this.allEntities.forEach((ed) => { + if (!ed || ed.type !== 'line' || ed.routeId != null || ed.isWhiteboard) return + const lid = ed.id + if (lid != null && !String(lid).startsWith('wb_')) { + this.unregisterLineSegmentDomLabels(lid) + } + }) + } + this.ensureMapScreenDomLabelRegistry() + if (this._mapScreenDomLabelRegistry) { + Object.keys(this._mapScreenDomLabelRegistry).forEach((k) => { + if (k.startsWith('map-dom-drawlpt-')) delete this._mapScreenDomLabelRegistry[k] + }) + } + this.hoverTooltip = this.hoverTooltip || {} + this.hoverTooltip.visible = false + this.measurementResult = null this.renderWhiteboardEntities(this.whiteboardEntities || []) + this.syncMapScreenDomLabels() + if (this.viewer.scene && this.viewer.scene.requestRenderMode) { + this.viewer.scene.requestRender() + } } else { Object.keys(this.whiteboardHiddenEntityShows || {}).forEach(id => { const entity = this.viewer.entities.getById(id) @@ -12940,6 +13183,20 @@ export default { }) this.whiteboardHiddenEntityShows = {} this.clearWhiteboardEntities() + // 还原主地图测距线段旁的累计距离标签 + if (this.allEntities && this.allEntities.length) { + this.allEntities.forEach((ed) => { + if (!ed || ed.type !== 'line' || ed.routeId != null || ed.isWhiteboard) return + const lid = ed.id + if (lid != null && !String(lid).startsWith('wb_')) { + this.updateLineSegmentLabels(ed) + } + }) + } + this.syncMapScreenDomLabels() + if (this.viewer.scene && this.viewer.scene.requestRenderMode) { + this.viewer.scene.requestRender() + } } }, /** 清除白板实体(id 以 wb_ 开头) */ @@ -12970,7 +13227,15 @@ export default { if (id) wantIds.add(id) } else { const id = (ed.id || '').startsWith('wb_') ? ed.id : ('wb_' + (ed.id || '')) - if (id) wantIds.add(id) + if (id) { + wantIds.add(id) + // 测距线顶点 entity id 为 `${lineId}_vtx_${i}`,须加入 wantIds,否则首轮清理会误删端点 + if (ed.type === 'line' && ed.data && Array.isArray(ed.data.points)) { + for (let vi = 0; vi < ed.data.points.length; vi++) { + wantIds.add(`${id}_vtx_${vi}`) + } + } + } } }) @@ -12986,6 +13251,16 @@ export default { if (ed && ed.type === 'text' && ed.entity && ed.entity.id) { this.unregisterMapTextDomLabel(`map-dom-maptext-text-${ed.entity.id}`) } + if (ed && ed.type === 'line') { + this.unregisterLineSegmentDomLabels(ed.id) + if (ed.pointEntities && ed.pointEntities.length) { + ed.pointEntities.forEach((pe) => { + try { + this.viewer.entities.remove(pe) + } catch (_) {} + }) + } + } if (ed && ed.entity) this.viewer.entities.remove(ed.entity) delete this.whiteboardEntityDataMap[id] } @@ -13105,6 +13380,16 @@ export default { if (existing.type === 'text' && existing.entity.id) { this.unregisterMapTextDomLabel(`map-dom-maptext-text-${existing.entity.id}`) } + if (existing.type === 'line') { + this.unregisterLineSegmentDomLabels(existing.id) + if (existing.pointEntities && existing.pointEntities.length) { + existing.pointEntities.forEach((pe) => { + try { + this.viewer.entities.remove(pe) + } catch (_) {} + }) + } + } this.viewer.entities.remove(existing.entity) delete this.whiteboardEntityDataMap[id] } @@ -13276,7 +13561,51 @@ export default { entityData.borderColor = sectorBorderColor break } - case 'line': + case 'line': { + const pts = (data.points || []).map(p => Cesium.Cartesian3.fromDegrees(p.lng, p.lat)) + if (pts.length < 2) return + const lineWidth = data.width != null ? data.width : 2 + const lineColor = data.color || color + const lineId = entityData.id + const pointEntities = [] + for (let i = 0; i < pts.length; i++) { + const ptId = `${lineId}_vtx_${i}` + const pe = this.viewer.entities.add({ + id: ptId, + position: pts[i], + point: { + pixelSize: this.defaultStyles.point.size, + color: Cesium.Color.fromCssColorString(this.defaultStyles.point.color), + outlineColor: Cesium.Color.WHITE, + outlineWidth: 2, + disableDepthTestDistance: Number.POSITIVE_INFINITY + } + }) + pointEntities.push(pe) + } + const positionsProp = new Cesium.CallbackProperty(() => { + return pointEntities.map((pEnt) => { + const pos = pEnt.position.getValue(Cesium.JulianDate.now()) + return pos || Cesium.Cartesian3.ZERO + }) + }, false) + entity = this.viewer.entities.add({ + id: lineId, + polyline: { + positions: positionsProp, + width: lineWidth, + material: Cesium.Color.fromCssColorString(lineColor), + arcType: Cesium.ArcType.NONE + } + }) + entityData.positions = pts + entityData.points = data.points || pts.map(p => this.cartesianToLatLng(p)) + entityData.width = lineWidth + entityData.color = lineColor + entityData.pointEntities = pointEntities + entityData.bearingType = data.bearingType || 'true' + break + } case 'auxiliaryLine': { const pts = (data.points || []).map(p => Cesium.Cartesian3.fromDegrees(p.lng, p.lat)) if (pts.length < 2) return @@ -13347,6 +13676,9 @@ export default { if (entityData.type === 'text') { this.registerMapTextForInsertedText(entityData) } + if (entityData.type === 'line') { + this.updateLineSegmentLabels(entityData) + } } }, /** 从房间/方案加载的 frontend_drawings JSON 恢复空域图形(先清空当前图形再导入;加载期间不触发自动保存) */ @@ -14597,18 +14929,23 @@ export default { '0 0 2px #fff, 0 0 2px #fff, 0 0 2px #fff, 0 1px 2px rgba(0,0,0,0.35)' } } else if (r.kind === 'segment') { - const positions = this.getLineEntityPositionsForDomLabels( - this.allEntities.find((e) => e.id === r.lineId && e.type === 'line'), - time - ) - world = - positions && positions.length > r.segmentVertexIndex - ? positions[r.segmentVertexIndex] - : undefined - text = r.text || '' - multiline = !!r.multiline - transform = 'translate(0, -50%)' - themeClass = 'map-screen-dom-label--tooltip' + // 白板下主地图测距线的 DOM 标签应在进入白板时已注销;此处兜底避免残留仍显示 + if (this.whiteboardMode && r.lineId != null && !String(r.lineId).startsWith('wb_')) { + visible = false + } else { + const lineEd = + (this.allEntities && this.allEntities.find((e) => e.id === r.lineId && e.type === 'line')) || + (this.whiteboardEntityDataMap && this.whiteboardEntityDataMap[r.lineId]) + const positions = this.getLineEntityPositionsForDomLabels(lineEd, time) + world = + positions && positions.length > r.segmentVertexIndex + ? positions[r.segmentVertexIndex] + : undefined + text = r.text || '' + multiline = !!r.multiline + transform = 'translate(0, -50%)' + themeClass = 'map-screen-dom-label--tooltip' + } } else if (r.kind === 'drawPoint') { const e = this.viewer.entities.getById(r.entityId) world = e && e.position && e.position.getValue(time) diff --git a/ruoyi-ui/src/views/childRoom/SixStepsOverlay.vue b/ruoyi-ui/src/views/childRoom/SixStepsOverlay.vue index 6569c12..d0ecc58 100644 --- a/ruoyi-ui/src/views/childRoom/SixStepsOverlay.vue +++ b/ruoyi-ui/src/views/childRoom/SixStepsOverlay.vue @@ -113,8 +113,18 @@ + +
+ +
-
@@ -775,7 +775,7 @@ export default { alt: row.alt, speed: speedVal, startTime: this.minutesToStartTime(row.minutesFromK), - turnAngle: row.turnAngle != null ? row.turnAngle : 0, + turnAngle: row.turnAngle != null ? row.turnAngle : 45, turnRadius, pointType: row.pointType || null, holdParams: row.holdParams || null, diff --git a/ruoyi-ui/src/views/dialogs/WaypointEditDialog.vue b/ruoyi-ui/src/views/dialogs/WaypointEditDialog.vue index 2179248..85162e2 100644 --- a/ruoyi-ui/src/views/dialogs/WaypointEditDialog.vue +++ b/ruoyi-ui/src/views/dialogs/WaypointEditDialog.vue @@ -116,13 +116,9 @@ -
- ⚠️ 首尾航点坡度已锁定为 0,不可编辑 -