diff --git a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue index 0aed7ea..9ff3958 100644 --- a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue +++ b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue @@ -7,13 +7,17 @@ - + @@ -417,6 +421,10 @@ export default { this.$emit('toggle-route-lock') }, + handleCopyRoute() { + this.$emit('copy-route') + }, + toggleColorPicker(property) { if (this.showColorPickerFor === property) { this.showColorPickerFor = null diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index e614619..37c14bd 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -42,6 +42,7 @@ @toggle-route-lock="toggleRouteLock" @start-route-before-platform="handleStartRouteBeforePlatform" @start-route-after-platform="handleStartRouteAfterPlatform" + @copy-route="handleCopyRouteFromMenu" /> @@ -58,6 +59,11 @@ {{ platformIconRotateTip }} + +
+ 点击地图放置复制航线,右键取消 +
+ ({}) } }, watch: { @@ -186,8 +197,7 @@ export default { }, // 航线飞机标牌显示状态:routeId -> true 显示 / false 隐藏,不设则默认显示 routeLabelVisible: {}, - // 航线上锁状态:routeId -> true 上锁(不可编辑)/ false 或未设 可编辑 - routeLocked: {}, + // 航线上锁状态由父组件通过 prop routeLocked 传入,与右侧列表锁图标同步 // 从平台右键进入的航线绘制:{ platformInfo: { platformId, platform }, mode: 'before'|'after' } platformRouteDrawing: null, // 默认样式 @@ -240,7 +250,18 @@ export default { // 实体点击防抖,避免双击误触导致相机高度剧烈变化 entityClickDebounceTimer: null, lastEntityClickTime: 0, - cameraStateBeforeEntityClick: null + cameraStateBeforeEntityClick: null, + // 航点拖拽编辑:{ entity, routeId, dbId, originalAlt };未超过阈值前为 pending + waypointDragging: null, + waypointDragPending: null, + WAYPOINT_DRAG_THRESHOLD_PX: 8, + /** 拖拽航点前相机 enableInputs 状态,松开时恢复 */ + waypointDragCameraInputsEnabled: undefined, + lastClickWasDrag: false, + // 航线复制预览:整条航线跟随鼠标,左键放置 + copyPreviewWaypoints: null, + copyPreviewEntity: null, + copyPreviewMouseCartesian: null } }, components: { @@ -1246,7 +1267,7 @@ export default { dashLength: dashLen }) : Cesium.Color.fromCssColorString(lineColor); - // 先收集所有航点的世界坐标 + // 先收集所有航点的世界坐标,并缓存航点 id 列表供拖拽时动态连线 const originalPositions = []; waypoints.forEach((wp) => { const lon = parseFloat(wp.lng); @@ -1254,6 +1275,8 @@ export default { const altValue = Number(wp.alt || 5000); originalPositions.push(Cesium.Cartesian3.fromDegrees(lon, lat, altValue)); }); + if (!this._routeWaypointIdsByRoute) this._routeWaypointIdsByRoute = {}; + this._routeWaypointIdsByRoute[routeId] = waypoints.map((wp) => wp.id); // 判断航点 i 是否为“转弯半径”航点(将用弧线两端两个点替代中心点) const isTurnWaypointWithArc = (i) => { if (i < 1 || i >= waypoints.length - 1) return false; @@ -1291,11 +1314,11 @@ export default { }, label: { text: wp.name || `WP${index + 1}`, - font: `${wp.labelFontSize != null ? Math.min(28, Math.max(10, Number(wp.labelFontSize))) : 14}px Microsoft YaHei`, + font: `${wp.labelFontSize != null ? Math.min(28, Math.max(10, Number(wp.labelFontSize))) : 14}px PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif`, pixelOffset: new Cesium.Cartesian2(0, -Math.max(14, pixelSize + 8)), - fillColor: Cesium.Color.fromCssColorString(wp.labelColor || '#333333'), - outlineColor: Cesium.Color.BLACK, - outlineWidth: 1, + fillColor: Cesium.Color.fromCssColorString(wp.labelColor || '#2c2c2c'), + outlineColor: Cesium.Color.fromCssColorString('#e8e8e8'), + outlineWidth: 0.5, style: Cesium.LabelStyle.FILL_AND_OUTLINE } }); @@ -1349,14 +1372,11 @@ export default { properties: { routeId: routeId }, label: { text: labelText, - font: '16px Microsoft YaHei', - fillColor: Cesium.Color.fromCssColorString('#333333'), - outlineColor: Cesium.Color.fromCssColorString('#e0e0e0'), - outlineWidth: 1, + font: '15px PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif', + fillColor: Cesium.Color.fromCssColorString('#2c2c2c'), + outlineColor: Cesium.Color.fromCssColorString('#e8e8e8'), + outlineWidth: 0.5, style: Cesium.LabelStyle.FILL_AND_OUTLINE, - showBackground: true, - backgroundColor: Cesium.Color.fromCssColorString('rgba(255, 255, 255, 0.95)'), - backgroundPadding: new Cesium.Cartesian2(10, 6), verticalOrigin: Cesium.VerticalOrigin.BOTTOM, horizontalOrigin: Cesium.HorizontalOrigin.CENTER, pixelOffset: new Cesium.Cartesian2(0, -42), @@ -1452,11 +1472,11 @@ export default { }, label: { text: wpName, - font: `${wp.labelFontSize != null ? Math.min(28, Math.max(10, Number(wp.labelFontSize))) : 14}px Microsoft YaHei`, + font: `${wp.labelFontSize != null ? Math.min(28, Math.max(10, Number(wp.labelFontSize))) : 14}px PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif`, pixelOffset: new Cesium.Cartesian2(0, -Math.max(14, pixelSize + 8)), - fillColor: Cesium.Color.fromCssColorString(wp.labelColor || '#333333'), - outlineColor: Cesium.Color.BLACK, - outlineWidth: 1, + fillColor: Cesium.Color.fromCssColorString(wp.labelColor || '#2c2c2c'), + outlineColor: Cesium.Color.fromCssColorString('#e8e8e8'), + outlineWidth: 0.5, style: Cesium.LabelStyle.FILL_AND_OUTLINE } }); @@ -1469,11 +1489,14 @@ export default { } } } - // 绘制包含弧线的 Polyline(lineWidth/lineMaterial 已在上方与航点样式一起计算,默认紫色线宽3) + // 绘制主航线:用 CallbackProperty 从各航点实体取当前位置,拖拽时实时连线 + const that = this; const routeEntity = this.viewer.entities.add({ id: lineId, polyline: { - positions: finalPathPositions, + positions: new Cesium.CallbackProperty(function () { + return that.getRouteLinePositionsFromWaypointEntities(routeId) || finalPathPositions; + }, false), width: lineWidth, material: lineMaterial, arcType: Cesium.ArcType.NONE, @@ -1486,6 +1509,41 @@ export default { } } }, + /** 从各航点/弧线/盘旋实体取当前位置,供主航线折线实时连线(拖拽时动态跟随) */ + getRouteLinePositionsFromWaypointEntities(routeId) { + const ids = this._routeWaypointIdsByRoute && this._routeWaypointIdsByRoute[routeId]; + if (!ids || !ids.length || !this.viewer) return null; + const now = Cesium.JulianDate.now(); + const positions = []; + for (let i = 0; i < ids.length; i++) { + const ent = this.viewer.entities.getById(`wp_${routeId}_${ids[i]}`); + if (ent && ent.position) { + const pos = ent.position.getValue(now); + if (pos) { + positions.push(Cesium.Cartesian3.clone(pos)); + continue; + } + } + const arcEnt = this.viewer.entities.getById(`arc-line-${routeId}-${i}`); + if (arcEnt && arcEnt.polyline && arcEnt.polyline.positions) { + const arr = arcEnt.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 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; + }, + // 计算单个点的转弯半径 getWaypointRadius(wp) { const speed = wp.speed || 800; @@ -1883,6 +1941,7 @@ export default { } if (shouldRemove) this.viewer.entities.remove(entity); } + if (this._routeWaypointIdsByRoute) delete this._routeWaypointIdsByRoute[routeId]; this.allEntities = this.allEntities.filter(item => item.id !== routeId && item.id !== `route-platform-${routeId}` && item.id !== `route-platform-label-${routeId}`); }, /** @@ -2042,6 +2101,43 @@ export default { if (this.isDrawing) return; + // 若上一次为拖拽结束,本次左键不打开弹窗 + if (this.lastClickWasDrag) { + this.lastClickWasDrag = false; + return; + } + // 航线复制预览模式:左键放置 + if (this.copyPreviewWaypoints && this.copyPreviewWaypoints.length > 0) { + const placePosition = this.getClickPosition(click.position); + if (placePosition) { + const carto = Cesium.Cartographic.fromCartesian(placePosition); + const placeLat = Cesium.Math.toDegrees(carto.latitude); + const placeLng = Cesium.Math.toDegrees(carto.longitude); + const first = this.copyPreviewWaypoints[0]; + const firstLat = parseFloat(first.lat); + const firstLng = parseFloat(first.lng); + const dLat = placeLat - firstLat; + const dLng = placeLng - firstLng; + const isHold = (pt) => pt === 'hold_circle' || pt === 'hold_ellipse'; + const points = this.copyPreviewWaypoints.map((wp, idx) => ({ + name: isHold(wp.pointType || wp.point_type) ? 'HOLD' : `WP${idx + 1}`, + lat: parseFloat(wp.lat) + dLat, + lng: parseFloat(wp.lng) + dLng, + 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), + labelFontSize: wp.labelFontSize != null ? wp.labelFontSize : 14, + labelColor: wp.labelColor || '#333333', + ...(wp.pointType && { pointType: wp.pointType }), + ...(wp.holdParams != null && { holdParams: typeof wp.holdParams === 'string' ? wp.holdParams : JSON.stringify(wp.holdParams) }) + })); + this.clearCopyPreview(); + this.$emit('route-copy-placed', points); + } + return; + } + const pickedObject = this.viewer.scene.pick(click.position); if (Cesium.defined(pickedObject) && pickedObject.id) { const entity = pickedObject.id; @@ -2116,6 +2212,100 @@ export default { this.lastEntityClickTime = 0; } }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); + + // 航点拖拽:左键按下仅记录 pending,移动超过阈值后才真正开始拖拽(避免小范围移动带跑地图) + this.handler.setInputAction((click) => { + if (this.isDrawing || this.copyPreviewWaypoints) return; + const pickedObject = this.viewer.scene.pick(click.position); + if (!Cesium.defined(pickedObject) || !pickedObject.id) return; + const now = Cesium.JulianDate.now(); + const props = pickedObject.id.properties ? pickedObject.id.properties.getValue(now) : null; + if (!props || !props.isMissionWaypoint) return; + const routeId = props.routeId && (props.routeId.getValue ? props.routeId.getValue() : props.routeId); + const dbId = props.dbId && (props.dbId.getValue ? props.dbId.getValue() : props.dbId); + if (routeId == null || dbId == null) return; + if (this.routeLocked[routeId]) return; + const entity = pickedObject.id; + const carto = Cesium.Cartographic.fromCartesian(entity.position.getValue(Cesium.JulianDate.now())); + this.waypointDragPending = { + entity, + routeId, + dbId, + originalAlt: carto.height, + startScreenX: click.position.x, + startScreenY: click.position.y + }; + }, Cesium.ScreenSpaceEventType.LEFT_DOWN); + + // 拖拽 / 复制预览:共用一个 MOUSE_MOVE;航点拖拽需超过阈值才生效 + this.handler.setInputAction((movement) => { + if (this.waypointDragPending) { + const dx = movement.endPosition.x - this.waypointDragPending.startScreenX; + const dy = movement.endPosition.y - this.waypointDragPending.startScreenY; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist >= this.WAYPOINT_DRAG_THRESHOLD_PX) { + if (this.entityClickDebounceTimer) { + clearTimeout(this.entityClickDebounceTimer); + this.entityClickDebounceTimer = null; + } + this.waypointDragging = { + entity: this.waypointDragPending.entity, + routeId: this.waypointDragPending.routeId, + dbId: this.waypointDragPending.dbId, + originalAlt: this.waypointDragPending.originalAlt + }; + this.waypointDragPending = null; + // 拖拽航点时锁定地图,方便操作 + if (this.viewer && this.viewer.scene && this.viewer.scene.screenSpaceCameraController) { + this.waypointDragCameraInputsEnabled = this.viewer.scene.screenSpaceCameraController.enableInputs; + this.viewer.scene.screenSpaceCameraController.enableInputs = false; + } + const pos = this.getClickPositionWithHeight(movement.endPosition, this.waypointDragging.originalAlt); + if (pos) { + this.waypointDragging.entity.position = pos; + if (this.viewer.scene.requestRender) this.viewer.scene.requestRender(); + } + } + if (this.waypointDragPending) return; + } + if (this.waypointDragging) { + const pos = this.getClickPositionWithHeight(movement.endPosition, this.waypointDragging.originalAlt); + if (pos) { + this.waypointDragging.entity.position = pos; + if (this.viewer.scene.requestRender) this.viewer.scene.requestRender(); + } + return; + } + if (this.copyPreviewWaypoints && this.copyPreviewWaypoints.length >= 2) { + const cartesian = this.getClickPosition(movement.endPosition); + if (cartesian) { + this.copyPreviewMouseCartesian = cartesian; + this.updateCopyPreviewPolyline(); + if (this.viewer.scene.requestRender) this.viewer.scene.requestRender(); + } + } + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + + // 拖拽结束:恢复地图操作、持久化新位置并清除状态;未超过阈值则只清 pending(视为点击) + this.handler.setInputAction((click) => { + if (this.waypointDragging) { + if (this.viewer && this.viewer.scene && this.viewer.scene.screenSpaceCameraController && this.waypointDragCameraInputsEnabled !== undefined) { + this.viewer.scene.screenSpaceCameraController.enableInputs = this.waypointDragCameraInputsEnabled; + this.waypointDragCameraInputsEnabled = undefined; + } + const entity = this.waypointDragging.entity; + const routeId = this.waypointDragging.routeId; + const dbId = this.waypointDragging.dbId; + const pos = entity.position.getValue(Cesium.JulianDate.now()); + const ll = this.cartesianToLatLngAlt(pos); + if (ll) { + this.$emit('waypoint-position-changed', { dbId, routeId, lat: ll.lat, lng: ll.lng, alt: ll.alt }); + } + this.lastClickWasDrag = true; + this.waypointDragging = null; + } + this.waypointDragPending = null; + }, Cesium.ScreenSpaceEventType.LEFT_UP); } catch (error) { console.error('地图错误:', error) // 如果Cesium加载失败,显示错误信息 @@ -2131,6 +2321,12 @@ export default { if (this.isDrawing) { return; } + // 若处于航线复制预览,右键取消 + if (this.copyPreviewWaypoints) { + this.clearCopyPreview(); + this.contextMenu.visible = false; + return; + } // 隐藏之前可能显示的菜单 this.contextMenu.visible = false; @@ -4149,6 +4345,23 @@ export default { lng: Cesium.Math.toDegrees(cartographic.longitude) } }, + /** 笛卡尔坐标转经纬高(含高度,用于航点拖拽持久化) */ + cartesianToLatLngAlt(cartesian) { + if (!Cesium.defined(cartesian)) return null + const cartographic = Cesium.Cartographic.fromCartesian(cartesian) + return { + lat: Cesium.Math.toDegrees(cartographic.latitude), + lng: Cesium.Math.toDegrees(cartographic.longitude), + alt: cartographic.height + } + }, + /** 屏幕坐标转笛卡尔,指定高度(用于拖拽时保持航点高度) */ + getClickPositionWithHeight(pixelPosition, height) { + const cartesian = this.viewer.camera.pickEllipsoid(pixelPosition, this.viewer.scene.globe.ellipsoid) + if (!Cesium.defined(cartesian)) return null + const cartographic = Cesium.Cartographic.fromCartesian(cartesian) + return Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, height != null ? height : cartographic.height) + }, degreesToDMS(decimalDegrees) { const degrees = Math.floor(decimalDegrees) const minutesDecimal = (decimalDegrees - degrees) * 60 @@ -4442,6 +4655,65 @@ export default { } }, /** 右键航线:上锁/解锁,上锁后该航线不可编辑 */ + /** 右键菜单「复制航线」:通知父组件,由父组件拉取航点后调用 startRouteCopyPreview */ + handleCopyRouteFromMenu() { + const ed = this.contextMenu.entityData; + if (!ed || ed.type !== 'route' || ed.routeId == null) { + this.contextMenu.visible = false; + return; + } + this.contextMenu.visible = false; + this.$emit('copy-route', ed.routeId); + }, + /** 开始航线复制预览:整条航线跟随鼠标,左键放置后父组件弹窗保存 */ + startRouteCopyPreview(waypoints) { + if (!waypoints || waypoints.length < 2) return; + this.clearCopyPreview(); + this.copyPreviewWaypoints = waypoints; + this.copyPreviewMouseCartesian = this.viewer.camera.pickEllipsoid( + new Cesium.Cartesian2(this.viewer.scene.canvas.clientWidth / 2, this.viewer.scene.canvas.clientHeight / 2), + this.viewer.scene.globe.ellipsoid + ) || Cesium.Cartesian3.fromDegrees(parseFloat(waypoints[0].lng), parseFloat(waypoints[0].lat), Number(waypoints[0].alt) || 5000); + this.updateCopyPreviewPolyline(); + }, + /** 更新复制预览折线:以当前鼠标位置为起点偏移整条航线 */ + updateCopyPreviewPolyline() { + if (!this.copyPreviewWaypoints || this.copyPreviewWaypoints.length < 2 || !this.copyPreviewMouseCartesian) return; + const first = this.copyPreviewWaypoints[0]; + const firstLon = parseFloat(first.lng) * (Math.PI / 180); + const firstLat = parseFloat(first.lat) * (Math.PI / 180); + const carto = Cesium.Cartographic.fromCartesian(this.copyPreviewMouseCartesian); + const dLon = carto.longitude - firstLon; + const dLat = carto.latitude - firstLat; + const positions = this.copyPreviewWaypoints.map(wp => + Cesium.Cartesian3.fromRadians( + parseFloat(wp.lng) * (Math.PI / 180) + dLon, + parseFloat(wp.lat) * (Math.PI / 180) + dLat, + Number(wp.alt) || 5000 + ) + ); + if (this.copyPreviewEntity) { + this.viewer.entities.remove(this.copyPreviewEntity); + this.copyPreviewEntity = null; + } + this.copyPreviewEntity = this.viewer.entities.add({ + polyline: { + positions: positions, + width: 4, + material: Cesium.Color.fromCssColorString('rgba(0, 120, 255, 0.75)'), + arcType: Cesium.ArcType.NONE + } + }); + }, + /** 清除航线复制预览 */ + clearCopyPreview() { + if (this.copyPreviewEntity) { + this.viewer.entities.remove(this.copyPreviewEntity); + this.copyPreviewEntity = null; + } + this.copyPreviewWaypoints = null; + this.copyPreviewMouseCartesian = null; + }, toggleRouteLock() { const ed = this.contextMenu.entityData; if (!ed || ed.type !== 'route' || ed.routeId == null) { @@ -4450,7 +4722,6 @@ export default { } const routeId = ed.routeId; const nextLocked = !this.routeLocked[routeId]; - this.$set(this.routeLocked, routeId, nextLocked); this.contextMenu.visible = false; this.$message && this.$message.success(nextLocked ? '航线已上锁,无法修改' : '航线已解锁,可以编辑'); this.$emit('route-lock-changed', { routeId, locked: nextLocked }); @@ -5933,6 +6204,23 @@ export default { white-space: nowrap; } +/* 航线复制预览提示 */ +.copy-route-tip { + position: absolute; + bottom: 60px; + left: 50%; + transform: translateX(-50%); + z-index: 99; + background: rgba(0, 120, 255, 0.9); + color: #fff; + padding: 10px 20px; + border-radius: 6px; + font-size: 14px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2); + pointer-events: none; + white-space: nowrap; +} + /* 地图右下角信息面板:比例尺在上、经纬度在下,整体略下移减少遮挡 */ .map-info-panel { position: absolute; diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index 27c6bbb..0854d2e 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -70,6 +70,11 @@
+
@@ -278,6 +283,11 @@ export default { type: Array, default: () => [] }, + /** 航线上锁状态:routeId -> true 上锁,与地图右键上锁/解锁同步 */ + routeLocked: { + type: Object, + default: () => ({}) + }, selectedPlanId: { type: [String, Number], default: null diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 6fd0762..59a0b65 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -13,12 +13,17 @@ :tool-mode="drawDom ? 'ranging' : (airspaceDrawDom ? 'airspace' : 'airspace')" :scaleConfig="scaleConfig" :coordinateFormat="coordinateFormat" + :route-locked="routeLocked" @draw-complete="handleMapDrawComplete" + @route-lock-changed="handleRouteLockChanged" @drawing-points-update="missionDrawingPointsCount = $event" @platform-route-drawing-started="missionDrawingActive = true" @drawing-cancelled="missionDrawingActive = false" @open-waypoint-dialog="handleOpenWaypointEdit" @open-route-dialog="handleOpenRouteEdit" + @copy-route="handleCopyRoute" + @route-copy-placed="handleRouteCopyPlaced" + @waypoint-position-changed="handleWaypointPositionChanged" @scale-click="handleScaleClick" @platform-icon-updated="onPlatformIconUpdated" @platform-icon-removed="onPlatformIconRemoved" @@ -173,6 +178,7 @@ :selected-route-id="selectedRouteId" :routes="routes" :active-route-ids="activeRouteIds" + :route-locked="routeLocked" :selected-route-details="selectedRouteDetails" :conflicts="conflicts" :conflict-count="conflictCount" @@ -192,6 +198,7 @@ @add-waypoint="addWaypoint" @cancel-route="cancelRoute" @toggle-route-visibility="toggleRouteVisibility" + @toggle-route-lock="handleToggleRouteLockFromPanel" @view-conflict="viewConflict" @resolve-conflict="resolveConflict" @run-conflict-check="runConflictCheck" @@ -589,6 +596,8 @@ export default { plans: [], activeRightTab: 'plan', activeRouteIds: [], // 存储当前所有选中的航线ID + /** 航线上锁状态:routeId -> true 上锁,与地图右键及右侧列表锁图标同步 */ + routeLocked: {}, // 冲突数据 conflictCount: 2, conflicts: [ @@ -818,6 +827,106 @@ export default { } }, + /** 右键「复制航线」:拉取航点后进入复制预览,左键放置后弹窗保存 */ + async handleCopyRoute(routeId) { + try { + const res = await getRoutes(routeId); + if (res.code !== 200 || !res.data) { + this.$message.error('获取航线数据失败'); + return; + } + const waypoints = res.data.waypoints || []; + if (waypoints.length < 2) { + this.$message.warning('航线航点不足,无法复制'); + return; + } + if (this.$refs.cesiumMap && typeof this.$refs.cesiumMap.startRouteCopyPreview === 'function') { + this.$refs.cesiumMap.startRouteCopyPreview(waypoints); + this.$message.info('移动鼠标到目标位置,左键放置复制航线;右键取消'); + } + } catch (e) { + this.$message.error('获取航线数据失败'); + console.error(e); + } + }, + + /** 复制航线已放置:用当前偏移后的航点打开「保存新航线」弹窗 */ + handleRouteCopyPlaced(points) { + this.tempMapPoints = points || []; + this.tempMapPlatform = null; + this.showNameDialog = true; + }, + + /** 地图上拖拽航点结束:将新位置写回数据库并刷新显示 */ + async handleWaypointPositionChanged({ dbId, routeId, lat, lng, alt }) { + let waypoints = null; + let route = null; + if (this.selectedRouteDetails && this.selectedRouteDetails.id === routeId) { + waypoints = this.selectedRouteDetails.waypoints; + route = this.selectedRouteDetails; + } + if (!waypoints) { + const r = this.routes.find(r => r.id === routeId); + if (r && r.waypoints) { + waypoints = r.waypoints; + route = r; + } + } + if (!waypoints || !route) { + this.$message.error('未找到对应航线数据'); + return; + } + const wp = waypoints.find(p => p.id === dbId); + if (!wp) { + this.$message.error('未找到对应航点'); + return; + } + const payload = { + id: wp.id, + routeId: wp.routeId != null ? wp.routeId : routeId, + name: wp.name, + seq: wp.seq, + lat: Number(lat), + lng: Number(lng), + alt: Number(alt), + speed: wp.speed, + startTime: (wp.startTime != null && wp.startTime !== '') ? wp.startTime : 'K+00:00:00', + turnAngle: wp.turnAngle + }; + if (wp.pointType != null) payload.pointType = wp.pointType; + if (wp.holdParams != null) payload.holdParams = wp.holdParams; + if (wp.labelFontSize != null) payload.labelFontSize = wp.labelFontSize; + if (wp.labelColor != null) payload.labelColor = wp.labelColor; + try { + const response = await updateWaypoints(payload); + if (response.code === 200) { + const merged = { ...wp, ...payload }; + const idx = waypoints.findIndex(p => p.id === dbId); + if (idx !== -1) waypoints.splice(idx, 1, merged); + if (this.selectedRouteDetails && this.selectedRouteDetails.id === routeId) { + const i = this.selectedRouteDetails.waypoints.findIndex(p => p.id === dbId); + if (i !== -1) this.selectedRouteDetails.waypoints.splice(i, 1, merged); + } + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.renderRouteWaypoints( + waypoints, + routeId, + route.platformId, + route.platform, + this.parseRouteStyle(route.attributes) + ); + } + this.$message.success('航点位置已更新'); + this.$nextTick(() => this.updateDeductionPositions()); + } else { + throw new Error(response.msg || '更新失败'); + } + } catch (error) { + console.error('更新航点位置失败:', error); + this.$message.error(error.message || '更新失败,请重试'); + } + }, + // 显示在线成员弹窗 showOnlineMembersDialog() { this.showOnlineMembers = true; @@ -2812,6 +2921,18 @@ export default { }, // 切换航线显示/隐藏 + /** 地图右键上锁/解锁后同步到列表 */ + handleRouteLockChanged({ routeId, locked }) { + this.$set(this.routeLocked, routeId, locked); + }, + /** 右侧列表锁图标点击:切换该航线上锁状态,与地图右键状态同步 */ + handleToggleRouteLockFromPanel(route) { + if (!route || route.id == null) return; + const nextLocked = !this.routeLocked[route.id]; + this.$set(this.routeLocked, route.id, nextLocked); + this.$message.success(nextLocked ? '航线已上锁,无法修改' : '航线已解锁,可以编辑'); + }, + toggleRouteVisibility(route) { const index = this.activeRouteIds.indexOf(route.id);