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);