From 65ed6b13d93c0de34d433682cad7994751bb664f Mon Sep 17 00:00:00 2001 From: ctw <1051735452@qq.com> Date: Fri, 23 Jan 2026 09:38:59 +0800 Subject: [PATCH 01/22] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=89=80=E6=9C=89?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E7=BB=98=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/cesiumMap/index.vue | 327 ++++++++++++++++++++------------- 1 file changed, 199 insertions(+), 128 deletions(-) diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index d0a99ec..0f69877 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -69,6 +69,7 @@ export default { drawingPoints: [], drawingStartPoint: null, isDrawing: false, + activeCursorPosition: null, // 实时鼠标位置 // 实体管理 allEntities: [], // 所有绘制的实体 @@ -349,6 +350,7 @@ export default { this.drawingPoints = []; this.drawingStartPoint = null; this.isDrawing = false; + this.activeCursorPosition = null; this.viewer.scene.canvas.style.cursor = 'default'; }, @@ -368,8 +370,6 @@ export default { // 绘制线 startLineDrawing() { this.drawingPoints = []; - // 记录当前鼠标的实时位置 - let activeCursorPosition = null; // 清除可能存在的旧实体 if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); @@ -381,7 +381,7 @@ export default { this.drawingHandler.setInputAction((movement) => { const newPosition = this.getClickPosition(movement.endPosition); if (newPosition) { - activeCursorPosition = newPosition; + this.activeCursorPosition = newPosition; } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); @@ -393,7 +393,7 @@ export default { // === 第一步:点击第一个点后,立即创建“动态虚线” === if (this.drawingPoints.length === 1) { - activeCursorPosition = position; // 初始化鼠标位置 + this.activeCursorPosition = position; // 初始化鼠标位置 // 创建预览虚线(只创建这一次,之后它会自动随数据更新) this.tempPreviewEntity = this.viewer.entities.add({ @@ -401,11 +401,11 @@ export default { // 关键:使用 CallbackProperty 动态获取位置 positions: new Cesium.CallbackProperty(() => { // 只有当有点且鼠标位置存在时才渲染 - if (this.drawingPoints.length > 0 && activeCursorPosition) { + if (this.drawingPoints.length > 0 && this.activeCursorPosition) { // 获取最后一个已确认的点 const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; // 返回 [最后一个点, 当前鼠标位置] - return [lastPoint, activeCursorPosition]; + return [lastPoint, this.activeCursorPosition]; } return []; }, false), @@ -451,8 +451,8 @@ export default { this.cancelDrawing(); } - // 重置局部变量 - activeCursorPosition = null; + // 重置鼠标位置 + this.activeCursorPosition = null; }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); }, @@ -475,7 +475,9 @@ export default { type: 'line' }; - this.stopDrawing(); + // 重置绘制点数组,保持绘制状态以继续绘制 + this.drawingPoints = []; + this.tempEntity = null; return entity; } else { this.cancelDrawing(); @@ -486,7 +488,6 @@ export default { // 绘制多边形 startPolygonDrawing() { this.drawingPoints = []; - let activeCursorPosition = null; // 存储鼠标实时位置 // 1. 清理旧实体 // 移除之前可能遗留的实体,确保干净的画布 @@ -499,7 +500,7 @@ export default { this.drawingHandler.setInputAction((movement) => { const newPosition = this.getClickPosition(movement.endPosition); if (newPosition) { - activeCursorPosition = newPosition; + this.activeCursorPosition = newPosition; } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); @@ -511,7 +512,7 @@ export default { // === 关键逻辑:点击第一个点时,创建唯一的“动态多边形” === if (this.drawingPoints.length === 1) { - activeCursorPosition = position; // 初始化鼠标位置 + this.activeCursorPosition = position; // 初始化鼠标位置 this.tempEntity = this.viewer.entities.add({ // --- 填充面配置 --- @@ -519,8 +520,8 @@ export default { // hierarchy 使用 CallbackProperty 实现动态填充 hierarchy: new Cesium.CallbackProperty(() => { // 组合:已确定的点 + 当前鼠标位置 - if (activeCursorPosition) { - return new Cesium.PolygonHierarchy([...this.drawingPoints, activeCursorPosition]); + if (this.activeCursorPosition) { + return new Cesium.PolygonHierarchy([...this.drawingPoints, this.activeCursorPosition]); } return new Cesium.PolygonHierarchy(this.drawingPoints); }, false), @@ -533,9 +534,9 @@ export default { polyline: { // positions 使用 CallbackProperty 实现动态闭合线 positions: new Cesium.CallbackProperty(() => { - if (activeCursorPosition) { + if (this.activeCursorPosition) { // 闭合回路:[所有点, 鼠标位置, 回到起点] - return [...this.drawingPoints, activeCursorPosition, this.drawingPoints[0]]; + return [...this.drawingPoints, this.activeCursorPosition, this.drawingPoints[0]]; } return this.drawingPoints; }, false), @@ -561,7 +562,7 @@ export default { } // 重置状态 - activeCursorPosition = null; + this.activeCursorPosition = null; }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); }, @@ -576,15 +577,19 @@ export default { type: 'polygon' } - this.stopDrawing() + // 重置绘制点数组,保持绘制状态以继续绘制 + this.drawingPoints = [] + if (this.tempEntity) { + this.viewer.entities.remove(this.tempEntity) + this.tempEntity = null + } return entity }, // 绘制矩形(优化版:两点定矩形,实时预览) startRectangleDrawing() { + // 重置绘制状态 this.drawingPoints = []; // 存储起点和终点 - let activeCursorPosition = null; // 实时鼠标位置 - let startPoint = null; // 记录起点 // 1. 清理旧实体 if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); @@ -594,7 +599,7 @@ export default { this.drawingHandler.setInputAction((movement) => { const newPosition = this.getClickPosition(movement.endPosition); if (newPosition) { - activeCursorPosition = newPosition; + this.activeCursorPosition = newPosition; } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); @@ -603,19 +608,18 @@ export default { const position = this.getClickPosition(click.position); if (position) { // --- 情况A:第一次点击(确定起点) --- - if (!startPoint) { - startPoint = position; - this.drawingPoints.push(startPoint); - activeCursorPosition = startPoint; // 初始化鼠标位置 + if (this.drawingPoints.length === 0) { + this.drawingPoints.push(position); + this.activeCursorPosition = position; // 初始化鼠标位置 // 创建动态预览矩形 this.tempEntity = this.viewer.entities.add({ rectangle: { // 关键:使用 CallbackProperty 动态计算矩形范围 coordinates: new Cesium.CallbackProperty(() => { - if (startPoint && activeCursorPosition) { + if (this.drawingPoints.length > 0 && this.activeCursorPosition) { // 使用 Cesium 内置工具,根据两个点(对角)自动计算矩形范围 - return Cesium.Rectangle.fromCartesianArray([startPoint, activeCursorPosition]); + return Cesium.Rectangle.fromCartesianArray([this.drawingPoints[0], this.activeCursorPosition]); } return Cesium.Rectangle.fromDegrees(0, 0, 0, 0); }, false), @@ -629,10 +633,10 @@ export default { }); } // --- 情况B:第二次点击(确定终点) --- - else { + else if (this.drawingPoints.length === 1) { this.drawingPoints.push(position); // 停止监听鼠标移动,因为形状已确定 - activeCursorPosition = null; + this.activeCursorPosition = null; // 调用完成逻辑 this.finishRectangleDrawing(); @@ -679,8 +683,8 @@ export default { type: 'rectangle' }; - // 6. 重置状态 - this.stopDrawing(); + // 6. 重置状态,保持绘制状态以继续绘制 + this.drawingPoints = []; }, // 计算矩形面积(辅助方法) @@ -718,73 +722,136 @@ export default { }, // 绘制圆形 startCircleDrawing() { + // 重置绘制状态 this.drawingPoints = []; // 存储圆心 - let activeCursorPosition = null; // 实时鼠标位置 - let centerPoint = null; // 圆心坐标 + this.activeCursorPosition = null; // 1. 清理旧实体 - if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); - this.tempEntity = null; + if (this.tempEntity) { + try { + this.viewer.entities.remove(this.tempEntity); + } catch (e) { + console.warn('Failed to remove temp entity:', e); + } + this.tempEntity = null; + } + if (this.tempPreviewEntity) { + try { + this.viewer.entities.remove(this.tempPreviewEntity); + } catch (e) { + console.warn('Failed to remove temp preview entity:', e); + } + this.tempPreviewEntity = null; + } - // 2. 鼠标移动事件 + // 2. 重置事件处理器 + this.drawingHandler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE); + this.drawingHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK); + this.drawingHandler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK); + + // 3. 鼠标移动事件 this.drawingHandler.setInputAction((movement) => { - const newPosition = this.getClickPosition(movement.endPosition); - if (newPosition) { - activeCursorPosition = newPosition; + try { + const newPosition = this.getClickPosition(movement.endPosition); + if (newPosition) { + this.activeCursorPosition = newPosition; + } + } catch (e) { + console.warn('Mouse move error:', e); } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); - // 3. 鼠标点击事件 + // 4. 鼠标点击事件 this.drawingHandler.setInputAction((click) => { - const position = this.getClickPosition(click.position); - if (position) { - // --- 情况A:第一次点击(确定圆心) --- - if (!centerPoint) { - centerPoint = position; - this.drawingPoints.push(centerPoint); - activeCursorPosition = centerPoint; + try { + const position = this.getClickPosition(click.position); + if (position) { + // --- 情况A:第一次点击(确定圆心) --- + if (this.drawingPoints.length === 0) { + this.drawingPoints.push(position); + this.activeCursorPosition = position; + + // 创建动态预览圆形 + if (this.tempEntity) { + try { + this.viewer.entities.remove(this.tempEntity); + } catch (e) { + console.warn('Failed to remove existing temp entity:', e); + } + this.tempEntity = null; + } - // 创建动态预览圆形 - this.tempEntity = this.viewer.entities.add({ - position: centerPoint, // 圆心固定 - ellipse: { - // 关键:使用 CallbackProperty 动态计算半径(半长轴和半短轴) - semiMajorAxis: new Cesium.CallbackProperty(() => { - if (centerPoint && activeCursorPosition) { - return Cesium.Cartesian3.distance(centerPoint, activeCursorPosition); - } - return 0; - }, false), - semiMinorAxis: new Cesium.CallbackProperty(() => { - if (centerPoint && activeCursorPosition) { - return Cesium.Cartesian3.distance(centerPoint, activeCursorPosition); + // 确保有有效的圆心位置 + if (position) { + // 先创建一个固定半径的临时圆,避免半径为0的情况 + const initialRadius = 100; // 初始半径设为100米 + + this.tempEntity = this.viewer.entities.add({ + position: position, // 直接使用确定的圆心位置 + ellipse: { + // 关键:使用 CallbackProperty 动态计算半径(半长轴和半短轴) + semiMajorAxis: new Cesium.CallbackProperty(() => { + try { + if (this.activeCursorPosition && this.drawingPoints.length > 0 && this.drawingPoints[0]) { + const center = this.drawingPoints[0]; + const edge = this.activeCursorPosition; + if (center && edge && typeof center.x === 'number' && typeof edge.x === 'number') { + const distance = Cesium.Cartesian3.distance(center, edge); + return isFinite(distance) && distance > 0 ? distance : initialRadius; + } + } + return initialRadius; + } catch (e) { + return initialRadius; + } + }, false), + semiMinorAxis: new Cesium.CallbackProperty(() => { + try { + if (this.activeCursorPosition && this.drawingPoints.length > 0 && this.drawingPoints[0]) { + const center = this.drawingPoints[0]; + const edge = this.activeCursorPosition; + if (center && edge && typeof center.x === 'number' && typeof edge.x === 'number') { + const distance = Cesium.Cartesian3.distance(center, edge); + return isFinite(distance) && distance > 0 ? distance : initialRadius; + } + } + return initialRadius; + } catch (e) { + return initialRadius; + } + }, false), + // 样式设置 + material: Cesium.Color.fromCssColorString(this.defaultStyles.polygon.color).withAlpha(0.5), + outline: true, + outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.line.color), + outlineWidth: 2, + // height: 0, // 如果需要贴地可开启或使用 heightReference } - return 0; - }, false), - // 样式设置 - material: Cesium.Color.fromCssColorString(this.defaultStyles.polygon.color).withAlpha(0.5), - outline: true, - outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.line.color), - outlineWidth: 2, - // height: 0, // 如果需要贴地可开启或使用 heightReference + }); } - }); - } - // --- 情况B:第二次点击(确定边缘/半径) --- - else { - // 记录边缘点(虽然圆只需要圆心和半径,但记录下来方便后续处理) - this.drawingPoints.push(position); - activeCursorPosition = null; // 停止动态更新 - - // 传递边缘点位置去结束绘制 - this.finishCircleDrawing(position); + } + // --- 情况B:第二次点击(确定边缘/半径) --- + else if (this.drawingPoints.length === 1) { + // 记录边缘点(虽然圆只需要圆心和半径,但记录下来方便后续处理) + this.drawingPoints.push(position); + this.activeCursorPosition = null; // 停止动态更新 + + // 传递边缘点位置去结束绘制 + this.finishCircleDrawing(position); + } } + } catch (e) { + console.warn('Mouse click error:', e); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); - // 4. 右键取消 + // 5. 右键取消 this.drawingHandler.setInputAction(() => { - this.cancelDrawing(); + try { + this.cancelDrawing(); + } catch (e) { + console.warn('Right click error:', e); + } }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); }, @@ -827,17 +894,15 @@ export default { type: 'circle' }; - // 6. 结束绘制状态 - this.stopDrawing(); + // 6. 重置状态,保持绘制状态以继续绘制 + this.drawingPoints = []; + this.activeCursorPosition = null; }, // 绘制扇形 startSectorDrawing() { + // 重置绘制状态 this.drawingPoints = []; // 存储圆心、半径端点、角度端点 - let activeCursorPosition = null; // 实时鼠标位置 - let centerPoint = null; // 圆心坐标 - let radiusPoint = null; // 半径端点 - let radius = 0; // 半径长度 // 1. 清理旧实体 if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); @@ -849,7 +914,7 @@ export default { this.drawingHandler.setInputAction((movement) => { const newPosition = this.getClickPosition(movement.endPosition); if (newPosition) { - activeCursorPosition = newPosition; + this.activeCursorPosition = newPosition; } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); @@ -858,17 +923,16 @@ export default { const position = this.getClickPosition(click.position); if (position) { // --- 情况A:第一次点击(确定圆心) --- - if (!centerPoint) { - centerPoint = position; - this.drawingPoints.push(centerPoint); - activeCursorPosition = centerPoint; + if (this.drawingPoints.length === 0) { + this.drawingPoints.push(position); + this.activeCursorPosition = position; // 创建动态预览半径线 this.tempPreviewEntity = this.viewer.entities.add({ polyline: { positions: new Cesium.CallbackProperty(() => { - if (centerPoint && activeCursorPosition) { - return [centerPoint, activeCursorPosition]; + if (this.drawingPoints.length > 0 && this.activeCursorPosition) { + return [this.drawingPoints[0], this.activeCursorPosition]; } return []; }, false), @@ -882,10 +946,12 @@ export default { }); } // --- 情况B:第二次点击(确定半径) --- - else if (!radiusPoint) { - radiusPoint = position; - this.drawingPoints.push(radiusPoint); - radius = Cesium.Cartesian3.distance(centerPoint, radiusPoint); + else if (this.drawingPoints.length === 1) { + this.drawingPoints.push(position); + this.activeCursorPosition = position; // 更新 activeCursorPosition 为实际点击位置 + const centerPoint = this.drawingPoints[0]; + const radiusPoint = this.drawingPoints[1]; + const fixedRadius = Cesium.Cartesian3.distance(centerPoint, radiusPoint); // 移除半径预览线 if (this.tempPreviewEntity) { @@ -897,11 +963,15 @@ export default { this.tempEntity = this.viewer.entities.add({ polygon: { hierarchy: new Cesium.CallbackProperty(() => { - if (centerPoint && activeCursorPosition) { - const currentRadius = Cesium.Cartesian3.distance(centerPoint, activeCursorPosition); + if (this.drawingPoints.length > 1 && this.activeCursorPosition) { + const centerPoint = this.drawingPoints[0]; + const radiusPoint = this.drawingPoints[1]; + if (!isFinite(fixedRadius) || fixedRadius === 0) { + return new Cesium.PolygonHierarchy([]); + } const startAngle = this.calculatePointAngle(centerPoint, radiusPoint); - const endAngle = this.calculatePointAngle(centerPoint, activeCursorPosition); - const positions = this.generateSectorPositions(centerPoint, currentRadius, startAngle, endAngle); + const endAngle = this.calculatePointAngle(centerPoint, this.activeCursorPosition); + const positions = this.generateSectorPositions(centerPoint, fixedRadius, startAngle, endAngle); return new Cesium.PolygonHierarchy(positions); } return new Cesium.PolygonHierarchy([]); @@ -914,13 +984,12 @@ export default { }); } // --- 情况C:第三次点击(确定角度) --- - else { - const anglePoint = position; - this.drawingPoints.push(anglePoint); - activeCursorPosition = null; // 停止动态更新 + else if (this.drawingPoints.length === 2) { + this.drawingPoints.push(position); + this.activeCursorPosition = null; // 停止动态更新 // 传递角度点位置去结束绘制 - this.finishSectorDrawing(centerPoint, radiusPoint, anglePoint); + this.finishSectorDrawing(this.drawingPoints[0], this.drawingPoints[1], this.drawingPoints[2]); } } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); @@ -933,7 +1002,7 @@ export default { // 完成扇形绘制 finishSectorDrawing(centerPoint, radiusPoint, anglePoint) { - const radius = Cesium.Cartesian3.distance(centerPoint, anglePoint); + const radius = Cesium.Cartesian3.distance(centerPoint, radiusPoint); const startAngle = this.calculatePointAngle(centerPoint, radiusPoint); const endAngle = this.calculatePointAngle(centerPoint, anglePoint); @@ -978,8 +1047,8 @@ export default { }; this.allEntities.push(entityData); - // 5. 结束绘制状态 - this.stopDrawing(); + // 5. 重置状态,保持绘制状态以继续绘制 + this.drawingPoints = []; }, // 计算角度 @@ -989,8 +1058,8 @@ export default { const centerLL = Cesium.Cartographic.fromCartesian(center); // 计算两点相对于圆心的角度 - const startAngle = Math.atan2(startLL.longitude - centerLL.longitude, startLL.latitude - centerLL.latitude); - const endAngle = Math.atan2(endLL.longitude - centerLL.longitude, endLL.latitude - centerLL.latitude); + const startAngle = Math.atan2(startLL.latitude - centerLL.latitude, startLL.longitude - centerLL.longitude); + const endAngle = Math.atan2(endLL.latitude - centerLL.latitude, endLL.longitude - centerLL.longitude); // 返回角度差 return endAngle - startAngle; @@ -1003,8 +1072,8 @@ export default { const centerLL = Cesium.Cartographic.fromCartesian(center); // 计算两点相对于圆心的角度 - const startAngle = Math.atan2(startLL.longitude - centerLL.longitude, startLL.latitude - centerLL.latitude); - const endAngle = Math.atan2(endLL.longitude - centerLL.longitude, endLL.latitude - centerLL.latitude); + const startAngle = Math.atan2(startLL.latitude - centerLL.latitude, startLL.longitude - centerLL.longitude); + const endAngle = Math.atan2(endLL.latitude - centerLL.latitude, endLL.longitude - centerLL.longitude); // 计算角度差(确保为正值) let angleDiff = endAngle - startAngle; @@ -1022,7 +1091,7 @@ export default { const centerLL = Cesium.Cartographic.fromCartesian(center); // 计算点相对于圆心的角度 - const angle = Math.atan2(pointLL.longitude - centerLL.longitude, pointLL.latitude - centerLL.latitude); + const angle = Math.atan2(pointLL.latitude - centerLL.latitude, pointLL.longitude - centerLL.longitude); return angle; }, @@ -1034,8 +1103,8 @@ export default { // 添加圆心 positions.push(center); - // 计算角度差 - let angleDiff = endAngle - startAngle; + // 计算角度差(顺时针方向) + let angleDiff = startAngle - endAngle; if (angleDiff < 0) { angleDiff += 2 * Math.PI; } @@ -1047,13 +1116,13 @@ export default { const numPoints = Math.max(5, Math.ceil(angleDiff * 180 / Math.PI / 10)); const angleStep = angleDiff / (numPoints - 1); - // 生成扇形的顶点 + // 生成扇形的顶点(顺时针方向) for (let i = 0; i < numPoints; i++) { - const currentAngle = startAngle + i * angleStep; + const currentAngle = startAngle - i * angleStep; const distance = radius / 6378137; // 转换为弧度 - const lat = centerLL.latitude + Math.cos(currentAngle) * distance; - const lng = centerLL.longitude + Math.sin(currentAngle) * distance / Math.cos(centerLL.latitude); + const lat = centerLL.latitude + Math.sin(currentAngle) * distance; + const lng = centerLL.longitude + Math.cos(currentAngle) * distance / Math.cos(centerLL.latitude); const position = Cesium.Cartesian3.fromRadians(lng, lat); positions.push(position); @@ -1065,6 +1134,8 @@ export default { return positions; }, + + // 计算两点之间的距离(米) calculateDistance(point1, point2) { return Cesium.Cartesian3.distance(point1, point2); @@ -1073,7 +1144,6 @@ export default { // 绘制箭头 startArrowDrawing() { this.drawingPoints = []; // 存储起点和终点 - let activeCursorPosition = null; // 实时鼠标位置 // 1. 清理旧实体 if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); @@ -1085,7 +1155,7 @@ export default { this.drawingHandler.setInputAction((movement) => { const newPosition = this.getClickPosition(movement.endPosition); if (newPosition) { - activeCursorPosition = newPosition; + this.activeCursorPosition = newPosition; } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); @@ -1097,7 +1167,7 @@ export default { // --- 情况A:第一次点击(确定起点) --- if (this.drawingPoints.length === 1) { - activeCursorPosition = position; // 初始化鼠标位置 + this.activeCursorPosition = position; // 初始化鼠标位置 // 创建动态预览箭头 this.tempPreviewEntity = this.viewer.entities.add({ @@ -1105,11 +1175,11 @@ export default { // 使用 CallbackProperty 动态获取位置 positions: new Cesium.CallbackProperty(() => { // 只有当有点且鼠标位置存在时才渲染 - if (this.drawingPoints.length > 0 && activeCursorPosition) { + if (this.drawingPoints.length > 0 && this.activeCursorPosition) { // 获取最后一个已确认的点 const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; // 返回 [最后一个点, 当前鼠标位置] - return [lastPoint, activeCursorPosition]; + return [lastPoint, this.activeCursorPosition]; } return []; }, false), @@ -1126,7 +1196,7 @@ export default { // --- 情况B:第二次点击(确定终点) --- else { // 停止监听鼠标移动,因为形状已确定 - activeCursorPosition = null; + this.activeCursorPosition = null; // 调用完成逻辑 this.finishArrowDrawing(); @@ -1153,7 +1223,8 @@ export default { // 创建最终的箭头实体 const entity = this.addArrowEntity([...this.drawingPoints]); - this.stopDrawing(); + // 重置绘制点数组,保持绘制状态以继续绘制 + this.drawingPoints = []; return entity; } else { this.cancelDrawing(); From 84cf5fd996e23fb46255016a27510d3546f03ff3 Mon Sep 17 00:00:00 2001 From: ctw <1051735452@qq.com> Date: Fri, 23 Jan 2026 13:07:55 +0800 Subject: [PATCH 02/22] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8D=95=E4=B8=AA?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=BA=BF=E7=9A=84=E7=BB=98=E5=88=B6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/cesiumMap/index.vue | 338 +++++++++++++++++++++++++++++---- 1 file changed, 301 insertions(+), 37 deletions(-) diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 0f69877..6087fa6 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -67,6 +67,7 @@ export default { tempEntity: null, // 最终实体 tempPreviewEntity: null, // 预览实体(新增) drawingPoints: [], + drawingPointEntities: [], // 存储线绘制时的点实体 drawingStartPoint: null, isDrawing: false, activeCursorPosition: null, // 实时鼠标位置 @@ -162,6 +163,8 @@ export default { }) this.initScaleBar() + this.initPointMovement() + this.initRightClickHandler() console.log('Cesium离线二维地图已加载') } catch (error) { @@ -170,6 +173,52 @@ export default { this.showErrorMessage(); } }, + + initRightClickHandler() { + // 创建屏幕空间事件处理器 + this.rightClickHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas) + + // 右键点击事件:删除单个实体 + this.rightClickHandler.setInputAction((click) => { + // 如果正在绘制,不处理删除操作 + if (this.isDrawing) { + return; + } + + const pickedObject = this.viewer.scene.pick(click.position) + + if (Cesium.defined(pickedObject) && pickedObject.id) { + const pickedEntity = pickedObject.id + + // 查找对应的实体数据 + let entityData = this.allEntities.find(e => e.entity === pickedEntity || e === pickedEntity) + + // 特殊处理:如果点击的是线段上的点,找到对应的线实体 + if (!entityData) { + // 检查是否是线段上的点 + for (const lineEntity of this.allEntities) { + if (lineEntity.type === 'line' && lineEntity.pointEntities) { + if (lineEntity.pointEntities.includes(pickedEntity)) { + entityData = lineEntity + break + } + } + } + } + + if (entityData) { + // 显示确认对话框 + if (confirm('确定要删除这个对象吗?')) { + if (entityData.id) { + this.removeEntity(entityData.id) + } else if (entityData.entity && entityData.entity.id) { + this.removeEntity(entityData.entity.id) + } + } + } + } + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK) + }, showErrorMessage() { const container = document.getElementById('cesiumViewer'); if (container) { @@ -347,13 +396,49 @@ export default { this.tempPreviewEntity = null; } + // 清理点实体 + if (this.drawingPointEntities) { + this.drawingPointEntities.forEach(pointEntity => { + this.viewer.entities.remove(pointEntity); + }); + this.drawingPointEntities = []; + } + this.drawingPoints = []; this.drawingStartPoint = null; this.isDrawing = false; this.activeCursorPosition = null; + // 重新初始化右键点击删除处理器 + if (!this.rightClickHandler) { + this.initRightClickHandler(); + } + this.viewer.scene.canvas.style.cursor = 'default'; }, + + cancelDrawing() { + // 取消绘制,清理临时实体和状态 + if (this.tempEntity) { + this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + } + + if (this.tempPreviewEntity) { + this.viewer.entities.remove(this.tempPreviewEntity); + this.tempPreviewEntity = null; + } + + if (this.drawingPointEntities) { + this.drawingPointEntities.forEach(pointEntity => { + this.viewer.entities.remove(pointEntity); + }); + this.drawingPointEntities = []; + } + + this.drawingPoints = []; + this.activeCursorPosition = null; + }, // ******************************************************************** // 绘制点 startPointDrawing() { @@ -367,9 +452,9 @@ export default { }, // 绘制线 - // 绘制线 startLineDrawing() { this.drawingPoints = []; + this.drawingPointEntities = []; // 存储点实体 // 清除可能存在的旧实体 if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); @@ -377,7 +462,7 @@ export default { this.tempEntity = null; this.tempPreviewEntity = null; - // 1. 鼠标移动事件:仅更新坐标变量,不操作实体 + // 1. 鼠标移动事件:更新坐标变量 this.drawingHandler.setInputAction((movement) => { const newPosition = this.getClickPosition(movement.endPosition); if (newPosition) { @@ -391,37 +476,29 @@ export default { if (position) { this.drawingPoints.push(position); - // === 第一步:点击第一个点后,立即创建“动态虚线” === - if (this.drawingPoints.length === 1) { - this.activeCursorPosition = position; // 初始化鼠标位置 + // 创建点实体并添加到场景中 + this.entityCounter++; + const pointId = `point_${this.entityCounter}`; + const pointEntity = this.viewer.entities.add({ + id: pointId, + position: position, + point: { + pixelSize: this.defaultStyles.point.size, + color: Cesium.Color.fromCssColorString(this.defaultStyles.point.color), + outlineColor: Cesium.Color.WHITE, + outlineWidth: 2 + } + }); + this.drawingPointEntities.push(pointEntity); - // 创建预览虚线(只创建这一次,之后它会自动随数据更新) - this.tempPreviewEntity = this.viewer.entities.add({ - polyline: { - // 关键:使用 CallbackProperty 动态获取位置 - positions: new Cesium.CallbackProperty(() => { - // 只有当有点且鼠标位置存在时才渲染 - if (this.drawingPoints.length > 0 && this.activeCursorPosition) { - // 获取最后一个已确认的点 - const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; - // 返回 [最后一个点, 当前鼠标位置] - return [lastPoint, this.activeCursorPosition]; - } - return []; - }, false), - width: this.defaultStyles.line.width, - // 虚线材质 - material: new Cesium.PolylineDashMaterialProperty({ - color: Cesium.Color.fromCssColorString(this.defaultStyles.line.color), - dashLength: 16 - }), - clampToGround: true // 贴地 - } - }); + // 移除旧的预览虚线 + if (this.tempPreviewEntity) { + this.viewer.entities.remove(this.tempPreviewEntity); + this.tempPreviewEntity = null; } - // === 第二步:点击后续点时,绘制/延长“固定实线” === - else { - // 移除旧的实线,重新画包含新点的实线 + + // 创建或更新实线 + if (this.drawingPoints.length > 1) { if (this.tempEntity) { this.viewer.entities.remove(this.tempEntity); } @@ -434,20 +511,47 @@ export default { } }); } + + // 创建新的预览虚线(使用 CallbackProperty 实现实时更新) + this.tempPreviewEntity = this.viewer.entities.add({ + polyline: { + positions: new Cesium.CallbackProperty(() => { + if (this.activeCursorPosition) { + return [this.drawingPoints[this.drawingPoints.length - 1], this.activeCursorPosition]; + } + return [this.drawingPoints[this.drawingPoints.length - 1]]; + }, false), + width: this.defaultStyles.line.width, + material: new Cesium.PolylineDashMaterialProperty({ + color: Cesium.Color.fromCssColorString(this.defaultStyles.line.color), + dashLength: 16 + }), + clampToGround: true + } + }); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); // 3. 右键完成绘制 this.drawingHandler.setInputAction(() => { - // 完成绘制前,移除那条动态虚线 + // 移除临时实体 if (this.tempPreviewEntity) { this.viewer.entities.remove(this.tempPreviewEntity); this.tempPreviewEntity = null; } + if (this.tempEntity) { + this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + } if (this.drawingPoints.length > 1) { this.finishLineDrawing(); } else { + // 取消绘制时,移除所有点实体 + this.drawingPointEntities.forEach(pointEntity => { + this.viewer.entities.remove(pointEntity); + }); + this.drawingPointEntities = []; this.cancelDrawing(); } @@ -466,7 +570,7 @@ export default { } // 创建最终的实线实体 - const entity = this.addLineEntity([...this.drawingPoints]); + const entity = this.addLineEntity([...this.drawingPoints], [...this.drawingPointEntities]); // 计算长度 const length = this.calculateLineLength([...this.drawingPoints]); @@ -475,11 +579,19 @@ export default { type: 'line' }; + + // 重置绘制点数组,保持绘制状态以继续绘制 this.drawingPoints = []; + this.drawingPointEntities = []; this.tempEntity = null; return entity; } else { + // 取消绘制时,移除所有点实体 + this.drawingPointEntities.forEach(pointEntity => { + this.viewer.entities.remove(pointEntity); + }); + this.drawingPointEntities = []; this.cancelDrawing(); return null; } @@ -1548,7 +1660,7 @@ export default { return entityData }, - addLineEntity(positions) { + addLineEntity(positions, pointEntities = []) { this.entityCounter++ const id = `line_${this.entityCounter}` @@ -1569,6 +1681,7 @@ export default { points: positions.map(p => this.cartesianToLatLng(p)), positions: positions, entity: entity, + pointEntities: pointEntities, // 存储点实体 color: this.defaultStyles.line.color, width: this.defaultStyles.line.width, label: `线 ${this.entityCounter}` @@ -1823,20 +1936,39 @@ export default { }, removeEntity(id) { - const index = this.allEntities.findIndex(e => e.id === id) + // 查找对应的实体数据 + const index = this.allEntities.findIndex(e => + e.id === id || + (e.entity && e.entity.id === id) || + (e.type === 'line' && e.pointEntities && e.pointEntities.some(p => p.id === id)) + ) + if (index > -1) { const entity = this.allEntities[index] // 从地图中移除 - if (entity.entity) { + if (entity instanceof Cesium.Entity) { + // 情况 A: 直接是 Cesium Entity 对象 + this.viewer.entities.remove(entity) + } else if (entity.entity) { + // 情况 B: 包装对象,包含 entity 属性 this.viewer.entities.remove(entity.entity) } + // 移除线实体相关的点实体 + if (entity.type === 'line' && entity.pointEntities) { + entity.pointEntities.forEach(pointEntity => { + this.viewer.entities.remove(pointEntity) + }) + } + + + // 从数组中移除 this.allEntities.splice(index, 1) // 如果删除的是选中的实体,清空选中状态 - if (this.selectedEntity && this.selectedEntity.id === id) { + if (this.selectedEntity && (this.selectedEntity.id === id || (this.selectedEntity.entity && this.selectedEntity.entity.id === id))) { this.selectedEntity = null } } @@ -1861,6 +1993,13 @@ export default { else if (item.id) { this.viewer.entities.removeById(item.id); } + + // 移除线实体相关的点实体 + if (item.type === 'line' && item.pointEntities) { + item.pointEntities.forEach(pointEntity => { + this.viewer.entities.remove(pointEntity); + }); + } } catch (e) { console.warn('删除实体失败:', e); } @@ -2179,6 +2318,121 @@ export default { // ... 原有的比例尺代码保持不变 }, + initPointMovement() { + // 创建屏幕空间事件处理器 + this.pointMovementHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas) + + let selectedPoint = null + let selectedLineEntity = null + let pointIndex = -1 + let originalCameraController = null + let isMoving = false + + // 鼠标按下事件:选择点 + this.pointMovementHandler.setInputAction((click) => { + const pickedObject = this.viewer.scene.pick(click.position) + + if (Cesium.defined(pickedObject) && pickedObject.id) { + const pickedEntity = pickedObject.id + + // 检查是否点击了点实体 + if (pickedEntity.point) { + // 查找包含该点的线实体 + for (const lineEntity of this.allEntities) { + if (lineEntity.type === 'line' && lineEntity.pointEntities) { + const index = lineEntity.pointEntities.indexOf(pickedEntity) + if (index !== -1) { + selectedPoint = pickedEntity + selectedLineEntity = lineEntity + pointIndex = index + isMoving = true + + // 禁用相机控制器,使地图固定 + originalCameraController = this.viewer.scene.screenSpaceCameraController.enableInputs + this.viewer.scene.screenSpaceCameraController.enableInputs = false + break + } + } + } + } + } + }, Cesium.ScreenSpaceEventType.LEFT_DOWN) + + // 鼠标移动事件:移动点 + this.pointMovementHandler.setInputAction((movement) => { + if (isMoving && selectedPoint && selectedLineEntity) { + const newPosition = this.getClickPosition(movement.endPosition) + if (newPosition) { + // 更新点的位置 + selectedPoint.position = newPosition + + // 创建新的位置数组,确保 Cesium 能够检测到变化 + const newPositions = [...selectedLineEntity.positions] + newPositions[pointIndex] = newPosition + + // 移除旧的线段实体 + this.viewer.entities.remove(selectedLineEntity.entity) + + // 清除所有可能存在的重复线段 + const entitiesToRemove = [] + this.viewer.entities.values.forEach(e => { + if (e.id && e.id === selectedLineEntity.id) { + entitiesToRemove.push(e) + } + }) + entitiesToRemove.forEach(e => { + this.viewer.entities.remove(e) + }) + + // 创建新的线段实体 + const newEntity = this.viewer.entities.add({ + id: selectedLineEntity.id, + name: selectedLineEntity.label, + polyline: { + positions: newPositions, + width: selectedLineEntity.width, + material: Cesium.Color.fromCssColorString(selectedLineEntity.color), + clampToGround: true + } + }) + + // 更新线实体的引用和位置数组 + selectedLineEntity.entity = newEntity + selectedLineEntity.positions = newPositions + + // 更新点数据 + selectedLineEntity.points[pointIndex] = this.cartesianToLatLng(newPosition) + + // 重新计算距离 + const length = this.calculateLineLength(selectedLineEntity.positions) + this.measurementResult = { + distance: length, + type: 'line' + } + + // 强制刷新地图渲染 + this.viewer.scene.requestRender() + } + } + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE) + + // 鼠标释放事件:结束移动 + this.pointMovementHandler.setInputAction(() => { + // 恢复相机控制器 + if (originalCameraController !== null) { + this.viewer.scene.screenSpaceCameraController.enableInputs = originalCameraController + originalCameraController = null + } + + + + isMoving = false + selectedPoint = null + selectedLineEntity = null + pointIndex = -1 + }, Cesium.ScreenSpaceEventType.LEFT_UP) + }, + updateScaleBar() { // ... 原有的比例尺更新代码保持不变 }, @@ -2187,6 +2441,16 @@ export default { this.stopDrawing() this.clearAll() + if (this.pointMovementHandler) { + this.pointMovementHandler.destroy() + this.pointMovementHandler = null + } + + if (this.rightClickHandler) { + this.rightClickHandler.destroy() + this.rightClickHandler = null + } + if (this.viewer) { this.viewer.destroy() this.viewer = null From d40be59b6f597b570d55d69b34b5c350fe371270 Mon Sep 17 00:00:00 2001 From: ctw <1051735452@qq.com> Date: Fri, 23 Jan 2026 13:21:16 +0800 Subject: [PATCH 03/22] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BA=BF=E7=9A=84?= =?UTF-8?q?=E6=82=AC=E5=81=9C=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/cesiumMap/index.vue | 81 ++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 6087fa6..887b4d7 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -105,6 +105,11 @@ export default { }, beforeDestroy() { + // 销毁鼠标悬停事件处理器 + if (this.hoverHandler) { + this.hoverHandler.destroy(); + this.hoverHandler = null; + } this.destroyViewer() }, @@ -163,9 +168,10 @@ export default { }) this.initScaleBar() - this.initPointMovement() - this.initRightClickHandler() - console.log('Cesium离线二维地图已加载') + this.initPointMovement() + this.initRightClickHandler() + this.initHoverHandler() + console.log('Cesium离线二维地图已加载') } catch (error) { console.error('地图错误:', error) @@ -219,6 +225,57 @@ export default { } }, Cesium.ScreenSpaceEventType.RIGHT_CLICK) }, + + // 初始化鼠标悬停事件处理器 + initHoverHandler() { + // 创建屏幕空间事件处理器 + this.hoverHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas) + + // 鼠标移动事件:检测是否悬停在线上 + this.hoverHandler.setInputAction((movement) => { + // 如果正在绘制,不处理悬停操作 + if (this.isDrawing) { + return; + } + + const pickedObject = this.viewer.scene.pick(movement.endPosition) + + if (Cesium.defined(pickedObject) && pickedObject.id) { + const pickedEntity = pickedObject.id + + // 查找对应的实体数据 + let entityData = this.allEntities.find(e => e.entity === pickedEntity || e === pickedEntity) + + // 特殊处理:如果悬停的是线段上的点,找到对应的线实体 + if (!entityData) { + // 检查是否是线段上的点 + for (const lineEntity of this.allEntities) { + if (lineEntity.type === 'line' && lineEntity.pointEntities) { + if (lineEntity.pointEntities.includes(pickedEntity)) { + entityData = lineEntity + break + } + } + } + } + + // 如果是线实体,显示长度信息 + if (entityData && entityData.type === 'line') { + const length = this.calculateLineLength(entityData.positions) + this.measurementResult = { + distance: length, + type: 'line' + }; + } else { + // 如果不是线实体,隐藏信息 + this.measurementResult = null; + } + } else { + // 如果没有悬停在任何实体上,隐藏信息 + this.measurementResult = null; + } + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE) + }, showErrorMessage() { const container = document.getElementById('cesiumViewer'); if (container) { @@ -414,6 +471,9 @@ export default { this.initRightClickHandler(); } + // 隐藏测量结果 + this.measurementResult = null; + this.viewer.scene.canvas.style.cursor = 'default'; }, @@ -462,11 +522,24 @@ export default { this.tempEntity = null; this.tempPreviewEntity = null; - // 1. 鼠标移动事件:更新坐标变量 + // 1. 鼠标移动事件:更新坐标变量并实时计算线段长度 this.drawingHandler.setInputAction((movement) => { const newPosition = this.getClickPosition(movement.endPosition); if (newPosition) { this.activeCursorPosition = newPosition; + + // 当已经有至少一个点时,实时计算线段长度 + if (this.drawingPoints.length > 0) { + // 计算从最后一个点到当前鼠标位置的线段长度 + const tempPositions = [...this.drawingPoints, newPosition]; + const length = this.calculateLineLength(tempPositions); + + // 更新测量结果,显示实时长度 + this.measurementResult = { + distance: length, + type: 'line' + }; + } } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); From 1838c436f3e8a7b8309a4800ea4f9c5e4a14710d Mon Sep 17 00:00:00 2001 From: ctw <1051735452@qq.com> Date: Fri, 23 Jan 2026 15:47:09 +0800 Subject: [PATCH 04/22] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=B7=A6=E5=8F=B3?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E6=A0=8F=E6=98=BE=E7=A4=BA=E9=9A=90=E8=97=8F?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= 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/store/getters.js | 1 + ruoyi-ui/src/store/modules/user.js | 7 ++- ruoyi-ui/src/views/childRoom/RightPanel.vue | 2 - ruoyi-ui/src/views/childRoom/index.vue | 26 ++++++++--- ruoyi-ui/src/views/dialogs/PlatformEditDialog.vue | 50 +++++++++++----------- ruoyi-ui/src/views/selectRoom/index.vue | 6 --- ruoyi-ui/vue.config.js | 2 +- 8 files changed, 54 insertions(+), 42 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index c40f2aa..037db5c 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: A20040303ctw! + password: 123456 # 从库数据源 slave: # 从数据源开关/默认关闭 diff --git a/ruoyi-ui/src/store/getters.js b/ruoyi-ui/src/store/getters.js index 3680f95..a3906cf 100644 --- a/ruoyi-ui/src/store/getters.js +++ b/ruoyi-ui/src/store/getters.js @@ -13,6 +13,7 @@ const getters = { introduction: state => state.user.introduction, roles: state => state.user.roles, permissions: state => state.user.permissions, + userLevel: state => state.user.userLevel, permission_routes: state => state.permission.routes, topbarRouters: state => state.permission.topbarRouters, defaultRoutes: state => state.permission.defaultRoutes, diff --git a/ruoyi-ui/src/store/modules/user.js b/ruoyi-ui/src/store/modules/user.js index 6a7b710..5ea90a2 100644 --- a/ruoyi-ui/src/store/modules/user.js +++ b/ruoyi-ui/src/store/modules/user.js @@ -13,7 +13,8 @@ const user = { nickName: '', avatar: '', roles: [], - permissions: [] + permissions: [], + userLevel: '' }, mutations: { @@ -37,6 +38,9 @@ const user = { }, SET_PERMISSIONS: (state, permissions) => { state.permissions = permissions + }, + SET_USER_LEVEL: (state, userLevel) => { + state.userLevel = userLevel } }, @@ -77,6 +81,7 @@ const user = { commit('SET_NAME', user.userName) commit('SET_NICK_NAME', user.nickName) commit('SET_AVATAR', avatar) + commit('SET_USER_LEVEL', user.userLevel || '') /* 初始密码提示 */ if(res.isDefaultModifyPwd) { MessageBox.confirm('您的密码还是初始密码,请修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index c199ccb..01ac0fe 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -27,7 +27,6 @@ 新建 -
-
航点列表
diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index a609569..c271170 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -135,7 +135,6 @@ class="compact-slider blue-slider" />
-
-
-
-
-
删除房间
- Date: Fri, 23 Jan 2026 16:36:00 +0800 Subject: [PATCH 05/22] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=88=AA=E7=BA=BF?= =?UTF-8?q?=E7=9A=84=E5=B1=95=E7=A4=BA=E3=80=81=E5=8F=AF=E5=A4=9A=E9=80=89?= =?UTF-8?q?=E3=80=81=E6=9F=A5=E8=AF=A2=E8=88=AA=E7=BA=BF=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=8F=98=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/system/domain/RouteWaypoints.java | 20 +- .../system/service/impl/RoutesServiceImpl.java | 13 +- .../main/resources/mapper/system/RoutesMapper.xml | 1 + ruoyi-ui/src/views/cesiumMap/index.vue | 230 ++++++++++++++++-- ruoyi-ui/src/views/childRoom/RightPanel.vue | 18 +- ruoyi-ui/src/views/childRoom/TopHeader.vue | 218 ++++++++--------- ruoyi-ui/src/views/childRoom/index.vue | 265 ++++++++++++++++----- ruoyi-ui/vue.config.js | 2 +- 8 files changed, 564 insertions(+), 203 deletions(-) diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/RouteWaypoints.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/RouteWaypoints.java index f8349fc..45bc77e 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/RouteWaypoints.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/RouteWaypoints.java @@ -41,11 +41,11 @@ public class RouteWaypoints extends BaseEntity /** 高度 (米) */ @Excel(name = "高度 (米)") - private Long alt; + private Double alt; /** 速度 (km/h) */ @Excel(name = "速度 (km/h)") - private Long speed; + private Double speed; /** 起始时间 (如: K+00:40:00) */ @Excel(name = "起始时间 (如: K+00:40:00)") @@ -53,7 +53,7 @@ public class RouteWaypoints extends BaseEntity /** 转弯角度 (用于计算转弯半径) */ @Excel(name = "转弯角度 (用于计算转弯半径)") - private Long turnAngle; + private Double turnAngle; public void setId(Long id) { @@ -115,22 +115,22 @@ public class RouteWaypoints extends BaseEntity return lng; } - public void setAlt(Long alt) + public void setAlt(Double alt) { this.alt = alt; } - public Long getAlt() + public Double getAlt() { return alt; } - public void setSpeed(Long speed) + public void setSpeed(Double speed) { this.speed = speed; } - public Long getSpeed() + public Double getSpeed() { return speed; } @@ -145,12 +145,12 @@ public class RouteWaypoints extends BaseEntity return startTime; } - public void setTurnAngle(Long turnAngle) + public void setTurnAngle(Double turnAngle) { this.turnAngle = turnAngle; } - public Long getTurnAngle() + public Double getTurnAngle() { return turnAngle; } @@ -183,7 +183,7 @@ public class RouteWaypoints extends BaseEntity // 单位换算:速度从 km/h 转为 m/s double v_mps = this.speed / 3.6; // 单位换算:角度从 度(Degree) 转为 弧度(Radians) - double radians = Math.toRadians(this.turnAngle.doubleValue()); + double radians = Math.toRadians(this.turnAngle); // 重力加速度 g double g = 9.8; // 计算半径 diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java index 93b1ded..b3ee040 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java @@ -35,7 +35,18 @@ public class RoutesServiceImpl implements IRoutesService @Override public Routes selectRoutesById(Long id) { - return routesMapper.selectRoutesById(id); + // 查出航线基本信息 + Routes routes = routesMapper.selectRoutesById(id); + // 如果查到了航线,就再去查属于它的航点 + if (routes != null) { + RouteWaypoints queryWp = new RouteWaypoints(); + queryWp.setRouteId(id); // 根据航线ID查询 + List wpList = routeWaypointsService.selectRouteWaypointsList(queryWp); + + // 把查出来的航点列表塞进 routes 对象的 waypoints 属性里 + routes.setWaypoints(wpList); + } + return routes; } /** diff --git a/ruoyi-system/src/main/resources/mapper/system/RoutesMapper.xml b/ruoyi-system/src/main/resources/mapper/system/RoutesMapper.xml index e51edf9..2a99e7f 100644 --- a/ruoyi-system/src/main/resources/mapper/system/RoutesMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/RoutesMapper.xml @@ -10,6 +10,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index e17cf32..c674cc9 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -100,6 +100,204 @@ export default { }, methods: { + preventContextMenu(e) { + e.preventDefault(); + }, + clearRoute() { + // 1. 清除记录在 allEntities 中的实体 + this.allEntities.forEach(item => { + this.viewer.entities.remove(item.entity); + }); + this.allEntities = []; + + // 2. 额外保险:清除所有带业务标记的航点 + const entities = this.viewer.entities.values; + for (let i = entities.length - 1; i >= 0; i--) { + const entity = entities[i]; + if (entity.properties && entity.properties.isMissionWaypoint) { + this.viewer.entities.remove(entity); + } + } + }, + updateWaypointGraphic(oldName, newName) { + // 根据旧名字找到对应的实体 + const entities = this.viewer.entities.values; + const target = entities.find(e => e.name === oldName && e.properties.isMissionWaypoint); + + if (target) { + target.name = newName; // 更新实体名 + target.label.text = newName; // 更新地图上显示的文字 + } + }, + // 新建航线绘制 + startMissionRouteDrawing() { + this.stopDrawing(); // 停止其他可能存在的绘制 + this.drawingPoints = []; + let activeCursorPosition = null; + this.isDrawing = true; + this.drawingHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas); + + window.addEventListener('contextmenu', this.preventContextMenu, true); + // 鼠标移动预览逻辑 + this.drawingHandler.setInputAction((movement) => { + const newPosition = this.getClickPosition(movement.endPosition); + if (newPosition) { + activeCursorPosition = newPosition; + } + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + // 左键点击逻辑 + this.drawingHandler.setInputAction((click) => { + const position = this.getClickPosition(click.position); + if (!position) return; + this.drawingPoints.push(position); + const wpIndex = this.drawingPoints.length; + // 绘制业务航点 + this.viewer.entities.add({ + name: `WP${wpIndex}`, + position: position, + properties: { + isMissionWaypoint: true, // 这是一个永久的业务标记 + originalIndex: wpIndex // 存下它是第几个点 + }, + point: { + pixelSize: 10, + color: Cesium.Color.WHITE, + outlineColor: Cesium.Color.fromCssColorString('#0078FF'), + outlineWidth: 3, + disableDepthTestDistance: Number.POSITIVE_INFINITY // 保证不被地形遮挡 + }, + label: { + text: `WP${wpIndex}`, + font: '12px MicroSoft YaHei', + pixelOffset: new Cesium.Cartesian2(0, -20), + fillColor: Cesium.Color.WHITE, + outlineColor: Cesium.Color.BLACK, + outlineWidth: 2, + style: Cesium.LabelStyle.FILL_AND_OUTLINE + } + }); + // 第一次点击后,创建动态黑白斑马线 + if (this.drawingPoints.length === 1) { + this.tempPreviewEntity = this.viewer.entities.add({ + polyline: { + positions: new Cesium.CallbackProperty(() => { + if (this.drawingPoints.length > 0 && activeCursorPosition) { + return [...this.drawingPoints, activeCursorPosition]; + } + return this.drawingPoints; + }, false), + width: 4, + // 黑白斑马材质 + material: new Cesium.PolylineDashMaterialProperty({ + color: Cesium.Color.WHITE, // 主色:白 + gapColor: Cesium.Color.BLACK, // 间隙色:黑 + dashLength: 20.0 // 斑马纹长度 + }), + clampToGround: true + } + }); + } + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + // 右键点击逻辑(结束绘制、抛出数据、恢复右键) + this.drawingHandler.setInputAction(() => { + if (this.drawingPoints.length > 1) { + // 转换坐标并传回给 childRoom/index.vue + const latLngPoints = this.drawingPoints.map((p, index) => { + const coords = this.cartesianToLatLng(p); + return { + id: index + 1, + name: `WP${index + 1}`, + lat: coords.lat, + lng: coords.lng, + alt: 500, // 默认业务属性 + speed: 600 // 默认业务属性 + }; + }); + this.$emit('draw-complete', latLngPoints); + } else { + this.$message.info('点数不足,航线已取消'); + } + // 清理并恢复环境 + this.stopDrawing(); + setTimeout(() => { + window.removeEventListener('contextmenu', this.preventContextMenu, true); + }, 200); + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); + }, + + renderRouteWaypoints(waypoints, routeId = 'default') { + if (!waypoints || waypoints.length < 1) return; + const positions = []; + // 1. 遍历并绘制航点标记 + waypoints.forEach((wp, index) => { + const lon = parseFloat(wp.lng); + const lat = parseFloat(wp.lat); + const pos = Cesium.Cartesian3.fromDegrees(lon, lat, parseFloat(wp.alt || 500)); + positions.push(pos); + + this.viewer.entities.add({ + name: wp.name || `WP${index + 1}`, + position: pos, + properties: { + isMissionWaypoint: true, + routeId: routeId + }, + point: { + pixelSize: 10, + color: Cesium.Color.WHITE, + outlineColor: Cesium.Color.fromCssColorString('#0078FF'), + outlineWidth: 3, + disableDepthTestDistance: Number.POSITIVE_INFINITY + }, + label: { + text: wp.name || `WP${index + 1}`, + font: '12px MicroSoft YaHei', + pixelOffset: new Cesium.Cartesian2(0, -20), + fillColor: Cesium.Color.WHITE, + outlineColor: Cesium.Color.BLACK, + outlineWidth: 2, + style: Cesium.LabelStyle.FILL_AND_OUTLINE + } + }); + }); + + // 2. 绘制连线(仅当点数 > 1 时) + if (positions.length > 1) { + const routeEntity = this.viewer.entities.add({ + id: `route-line-${routeId}`, + polyline: { + positions: positions, + width: 4, + material: new Cesium.PolylineDashMaterialProperty({ + color: Cesium.Color.WHITE, + gapColor: Cesium.Color.BLACK, + dashLength: 20.0 + }), + clampToGround: true + }, + properties: { isMissionRouteLine: true, routeId: routeId } + }); + this.allEntities.push({ id: `route-line-${routeId}`, entity: routeEntity, type: 'line' }); + } + }, + removeRouteById(routeId) { + // 从地图上移除所有属于该 routeId 的实体 + const entityList = this.viewer.entities.values; + for (let i = entityList.length - 1; i >= 0; i--) { + const entity = entityList[i]; + // 获取 entity 身上绑定的 routeId 属性 + if (entity.properties && entity.properties.routeId) { + // Cesium 的属性系统比较特殊,需要 getValue() 拿原始值 + const id = entity.properties.routeId.getValue(); + if (id === routeId) { + this.viewer.entities.remove(entity); + } + } + } + // 同时清理你本地维护的 allEntities 数组 + this.allEntities = this.allEntities.filter(item => item.id !== routeId); + }, checkCesiumLoaded() { if (typeof Cesium === 'undefined') { console.error('Cesium未加载,请检查CDN链接'); @@ -155,6 +353,23 @@ export default { this.initScaleBar() console.log('Cesium离线二维地图已加载') + // 1. 定义全局拾取处理器 + this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas); + + this.handler.setInputAction((click) => { + // 如果正在画线,则屏蔽掉详情弹窗逻辑,互不干扰 + if (this.isDrawing) return; + // 2. 拾取点击位置的对象 + const pickedObject = this.viewer.scene.pick(click.position); + if (Cesium.defined(pickedObject) && pickedObject.id) { + const entity = pickedObject.id; + // 3. 判断是否点中了业务航点 + if (entity.properties && entity.properties.isMissionWaypoint) { + // 这里的 name 依然可以用作查询标识,或者用 ID + this.$emit('open-waypoint-dialog', entity.name); + } + } + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); } catch (error) { console.error('地图错误:', error) @@ -625,6 +840,7 @@ export default { this.drawingHandler.setInputAction(() => { this.cancelDrawing(); }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); + }, finishRectangleDrawing() { @@ -1060,19 +1276,6 @@ export default { return area }, - calculateRectangleArea(coordinates) { - const rect = coordinates.getValue ? coordinates.getValue() : coordinates - const width = Cesium.Cartesian3.distance( - Cesium.Cartesian3.fromRadians(rect.west, rect.north), - Cesium.Cartesian3.fromRadians(rect.east, rect.north) - ) - const height = Cesium.Cartesian3.distance( - Cesium.Cartesian3.fromRadians(rect.west, rect.north), - Cesium.Cartesian3.fromRadians(rect.west, rect.south) - ) - - return width * height - }, // ================== 实体管理 ================== @@ -1167,7 +1370,6 @@ export default { } } }, - clearAll() { // 1. 检查数组是否有内容 if (this.allEntities && this.allEntities.length > 0) { diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index e73c744..5e00650 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -32,8 +32,8 @@ v-for="route in routes" :key="route.id" class="route-item" - :class="{ selected: selectedRouteId === route.id }" - @click="handleSelectRoute(route)" + :class="{ 'active': activeRouteIds.includes(route.id) }" + @click="$emit('select-route', route)" >
@@ -222,9 +222,9 @@ export default { type: Array, default: () => [] }, - selectedRouteId: { - type: [String, Number], - default: null + activeRouteIds: { + type: Array, + default: () => [] }, selectedRouteDetails: { type: Object, @@ -261,11 +261,7 @@ export default { this.$emit('hide') }, - handleSelectRoute(route) { - this.$emit('select-route', route) - }, - - // 新增:新建航线事件 + // 新建航线事件 handleCreateRoute() { this.$emit('create-route') }, @@ -432,7 +428,7 @@ export default { box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15); } -.route-item.selected { +.route-item.active { background: rgba(0, 138, 255, 0.15); border-color: rgba(0, 138, 255, 0.3); box-shadow: 0 2px 10px rgba(0, 138, 255, 0.25); diff --git a/ruoyi-ui/src/views/childRoom/TopHeader.vue b/ruoyi-ui/src/views/childRoom/TopHeader.vue index 6192637..29c5695 100644 --- a/ruoyi-ui/src/views/childRoom/TopHeader.vue +++ b/ruoyi-ui/src/views/childRoom/TopHeader.vue @@ -3,19 +3,19 @@
- 系统logo 网络化任务规划系统
- +
-
{{ item.name }} - + - 新建计划 打开 保存 - + 导入 - @@ -57,15 +57,15 @@ - + 导出 - + - 军事标绘 图标编辑 属性修改 - + 推演编辑 - @@ -96,11 +96,11 @@ - + - 比例尺 - + - 航空图 - + - 威胁区 - + - 坐标换算 - + - 设置 - @@ -194,11 +194,11 @@ 系统说明 - + - {{ roomCode }}
- +
@@ -233,7 +233,7 @@
{{ onlineCount }}人
- +
@@ -242,7 +242,7 @@
{{ combatTime }}
- +
@@ -252,31 +252,31 @@
- +
- + - - + - - + - \ No newline at end of file + diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 555e796..d015614 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -4,7 +4,8 @@
- +

二维GIS地图区域

@@ -21,6 +22,17 @@
+ + + + + + + +
@@ -30,7 +42,6 @@ :combat-time="combatTime" :astro-time="astroTime" :user-avatar="userAvatar" - @select-nav="selectTopNav" @save-plan="savePlan" @import-plan-file="importPlanFile" @import-acd="importACD" @@ -88,7 +99,7 @@ :is-hidden="isRightPanelHidden" :active-tab="activeRightTab" :routes="routes" - :selected-route-id="selectedRouteId" + :active-route-ids="activeRouteIds" :selected-route-details="selectedRouteDetails" :conflicts="conflicts" :conflict-count="conflictCount" @@ -208,6 +219,8 @@ import LeftMenu from './LeftMenu' import RightPanel from './RightPanel' import BottomLeftPanel from './BottomLeftPanel' import TopHeader from './TopHeader' +import { listRoutes, getRoutes, addRoutes } from "@/api/system/routes"; +import { updateWaypoints } from "@/api/system/waypoints"; export default { name: 'MissionPlanningView', components: { @@ -233,6 +246,9 @@ export default { selectedRoute: null, showWaypointDialog: false, selectedWaypoint: null, + showNameDialog: false, + newRouteName: '', + tempMapPoints: [], // 作战信息 roomCode: 'JTF-7-ALPHA', @@ -270,7 +286,7 @@ export default { // 右侧面板 activeRightTab: 'plan', - selectedRouteId: 101, + activeRouteIds: [], // 存储当前所有选中的航线ID selectedRouteDetails: null, // 冲突数据 @@ -314,11 +330,7 @@ export default { ], // 航线数据 - routes: [ - { id: 101, name: 'Alpha进场航线', points: 8, conflict: true }, - { id: 102, name: 'Beta巡逻航线', points: 6, conflict: false }, - { id: 103, name: '侦察覆盖区', points: 4, conflict: false }, - ], + routes: [], // 时间控制 timeProgress: 45, @@ -332,11 +344,11 @@ export default { }; }, mounted() { + this.getList(); // 初始化时左侧菜单隐藏 this.isMenuHidden = true; // 初始化时右侧面板隐藏 this.isRightPanelHidden = true; - // 更新时间 this.updateTime(); setInterval(this.updateTime, 1000); @@ -351,6 +363,25 @@ export default { } }, methods: { + // 处理从地图点击传来的编辑请求 + handleOpenWaypointEdit(wpName) { + // 1. 确保当前有选中的航线详情 + if (!this.selectedRouteDetails || !this.selectedRouteDetails.waypoints) { + this.$message.warning('请先在右侧列表选择一条航线'); + return; + } + // 2. 根据点击的航点名称在数据列表中查找 + const wpData = this.selectedRouteDetails.waypoints.find(item => item.name === wpName); + if (wpData) { + // 3. 深拷贝给编辑弹窗绑定的变量,防止直接修改原始数组 + this.selectedWaypoint = JSON.parse(JSON.stringify(wpData)); + // 4. 打开弹窗 + this.showWaypointDialog = true; + } + else { + this.$message.info('未找到该航点的业务数据'); + } + }, // 显示在线成员弹窗 showOnlineMembersDialog() { this.showOnlineMembers = true; @@ -385,41 +416,114 @@ export default { this.$message.success('航线名称更新成功'); } }, - // 新建航线(占位) + // 新建航线 createRoute() { - this.$message.info('新建航线功能开发中...'); + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.startMissionRouteDrawing(); + this.$message.success('进入航线规划模式'); + } + + }, + /** 从数据库拉取最新的航线列表数据 */ + async getList() { + const query = { scenarioId: 1 }; + try { + const response = await listRoutes(query); + if (response.code === 200) { + this.routes = response.rows.map(item => ({ + id: item.id, + name: item.callSign, + points: item.waypoints ? item.waypoints.length : 0, + conflict: false + })); + } + } catch (error) { + this.$message.error("获取航线列表失败"); + } + }, + handleMapDrawComplete(points) { + if (!points || points.length < 2) { + this.$message.error('航点太少,无法生成航线'); + this.drawDom = false; + return; + } + this.tempMapPoints = points; // 暂存坐标点 + this.showNameDialog = true; // 弹出对话框起名 + }, + /** 弹窗点击“确定”:正式将数据保存到后端数据库 */ + async confirmSaveNewRoute() { + // 严格校验起名逻辑 + if (!this.newRouteName || this.newRouteName.trim() === '') { + this.$message.error('新增航线未命名,请输入名称后保存!'); + return; + } + // 构造符合后端 Routes 实体类的数据结构 + const routeData = { + callSign: this.newRouteName, + scenarioId: 1, // 示例:当前归属方案ID(实际应从全局状态获取) + platformId: 1, + attributes: "{}", + waypoints: this.tempMapPoints.map((p, index) => ({ + name: `WP${index + 1}`, + lat: p.lat, + lng: p.lng, + alt: 5000.0, + speed: 800.0, + startTime: 'K+00:00:00', + turnAngle: 0.0 + })) + }; + + try { + // 调用后端 API 保存到数据库 + const response = await addRoutes(routeData); + if (response.code === 200) { + this.$message.success('航线及其航点已成功保存至数据库'); + // 重置 UI 状态 + this.showNameDialog = false; + this.drawDom = false; + this.newRouteName = ''; + this.tempMapPoints = []; + // 重新拉取右侧航线列表以保持同步 + await this.getList(); + } + } catch (error) { + console.error("保存航线失败:", error); + this.$message.error('保存失败,请检查后端服务'); + } }, // 航点编辑弹窗相关方法 openWaypointDialog(waypoint) { this.selectedWaypoint = waypoint; this.showWaypointDialog = true; }, - updateWaypoint(updatedWaypoint) { - // 1. 检查是否有正在编辑的航线详情 - if (this.selectedRouteDetails && this.selectedRouteDetails.waypoints) { - - // 2. 找到当前被编辑的这个航点在数组中的位置 - const index = this.selectedRouteDetails.waypoints.indexOf(this.selectedWaypoint); - - if (index !== -1) { - // 3. 使用 splice 方法替换数据 - this.selectedRouteDetails.waypoints.splice(index, 1, updatedWaypoint); - - // 4. 更新当前的选中引用,以便连续编辑 - this.selectedWaypoint = updatedWaypoint; - - this.$message.success('航点更新成功'); - } else { - // 如果用 indexOf 没找到,尝试用名字匹配兜底 - const nameIndex = this.selectedRouteDetails.waypoints.findIndex(p => p.name === this.selectedWaypoint.name); - if (nameIndex !== -1) { - this.selectedRouteDetails.waypoints.splice(nameIndex, 1, updatedWaypoint); - this.selectedWaypoint = updatedWaypoint; - this.$message.success('航点更新成功'); - } else { - this.$message.error('更新失败:未找到对应航点'); + /** 航点编辑保存:更新数据库并同步地图显示 */ + async updateWaypoint(updatedWaypoint) { + // 确保有选中的航线详情 + if (!this.selectedRouteDetails || !this.selectedRouteDetails.waypoints) return; + try { + const response = await updateWaypoints(updatedWaypoint); + if (response.code === 200) { + // 找到当前编辑点在本地数组中的位置 + const index = this.selectedRouteDetails.waypoints.findIndex(p => p.id === updatedWaypoint.id); + if (index !== -1) { + // 记录旧名称,用于地图组件定位 Entity + const oldName = this.selectedRouteDetails.waypoints[index].name; + const newName = updatedWaypoint.name; + // 使用 splice 触发 Vue 响应式更新右侧面板 UI + this.selectedRouteDetails.waypoints.splice(index, 1, { ...updatedWaypoint }); + // 5. 同步更新地图上的图形 + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.updateWaypointGraphic(oldName, newName); + } + + this.showWaypointDialog = false; + this.$message.success('航点信息已持久化至数据库'); } } + } catch (error) { + console.error("更新航点失败:", error); + this.$message.error('数据库更新失败,请重试'); } }, updateTime() { @@ -677,9 +781,9 @@ export default { this.isRightPanelHidden = false; } } else if(item.id === 'modify'){ - this.drawDom = !this.drawDom - console.log(this.drawDom,999999) - } + this.drawDom = !this.drawDom + console.log(this.drawDom,999999) + } if (item.id === 'deduction') { // 点击推演按钮,显示/隐藏K时弹出框 this.showKTimePopup = !this.showKTimePopup; @@ -774,23 +878,64 @@ export default { const minutes = (val % 4) * 15; return `K+${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`; }, + /** 切换航线:实现多选/开关逻辑 */ + async selectRoute(route) { + const index = this.activeRouteIds.indexOf(route.id); - // 航线操作 - selectRoute(route) { - this.selectedRouteId = route.id; - // 模拟获取航点数据 - this.selectedRouteDetails = { - name: route.name, - waypoints: [ - { name: 'WP1', altitude: 5000, speed: '800km/h', eta: 'K+00:40:00' }, - { name: 'WP2', altitude: 6000, speed: '850km/h', eta: 'K+00:55:00' }, - { name: 'WP3', altitude: 5500, speed: '820km/h', eta: 'K+01:10:00' }, - { name: 'WP4', altitude: 5800, speed: '830km/h', eta: 'K+01:25:00' }, - ] - }; - // 移除原有的 this.openRouteDialog(route); + // 航线已在选中列表中 + if (index > -1) { + this.activeRouteIds.splice(index, 1); + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.removeRouteById(route.id); + } + if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) { + if (this.activeRouteIds.length > 0) { + const lastId = this.activeRouteIds[this.activeRouteIds.length - 1]; + try { + const res = await getRoutes(lastId); + if (res.code === 200 && res.data) { + this.selectedRouteDetails = { + id: res.data.id, + name: res.data.callSign, + waypoints: res.data.waypoints || [] + }; + } + } catch (e) { + console.error("回显剩余航线失败", e); + } + } else { + this.selectedRouteDetails = null; + } + } + this.$message.info(`已移除航线: ${route.name}`); + return; + } + // 航线未被选中 + try { + const response = await getRoutes(route.id); + if (response.code === 200 && response.data) { + const fullRouteData = response.data; + const waypoints = fullRouteData.waypoints || []; + this.activeRouteIds.push(route.id); + this.selectedRouteDetails = { + id: fullRouteData.id, + name: fullRouteData.callSign, + waypoints: waypoints + }; + if (waypoints.length > 0) { + // 通知地图渲染 + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.renderRouteWaypoints(waypoints, route.id); + } + } else { + this.$message.warning('该航线暂无坐标数据,无法在地图展示'); + } + } + } catch (error) { + console.error("获取航线详情失败:", error); + this.$message.error('无法加载该航线的详细航点数据'); + } }, - addWaypoint() { if (this.selectedRouteDetails) { const count = this.selectedRouteDetails.waypoints.length + 1; @@ -805,9 +950,15 @@ export default { }, cancelRoute() { - this.selectedRouteId = null; + // 清空所有选中的航线 + if (this.$refs.cesiumMap) { + this.activeRouteIds.forEach(id => { + this.$refs.cesiumMap.removeRouteById(id); + }); + } + this.activeRouteIds = []; this.selectedRouteDetails = null; - this.$message.info('已取消选中'); + this.$message.info('已清空所有选中航线'); }, // 冲突操作 @@ -857,7 +1008,7 @@ export default { height: 100%; background: linear-gradient(135deg, #1a2f4b 0%, #2c3e50 100%); /* 正确的写法,直接复制这行替换 */ -background: url('~@/assets/map-background.png'); + background: url('~@/assets/map-background.png'); background-size: cover; background-position: center; z-index: 1; diff --git a/ruoyi-ui/vue.config.js b/ruoyi-ui/vue.config.js index e2698e5..25854e2 100644 --- a/ruoyi-ui/vue.config.js +++ b/ruoyi-ui/vue.config.js @@ -10,7 +10,7 @@ const CompressionPlugin = require('compression-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题 -const baseUrl = 'http://192.168.50.30:8080' // 后端接口 +const baseUrl = 'http://127.0.0.1:8080' // 后端接口 const port = process.env.port || process.env.npm_config_port || 80 // 端口 // 定义 Cesium 源码路径 From 51015fea0c8c3e06c14bbcfc7dd6172291116ccb Mon Sep 17 00:00:00 2001 From: ctw <1051735452@qq.com> Date: Mon, 26 Jan 2026 10:03:52 +0800 Subject: [PATCH 06/22] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=96=87=E6=9C=AC?= =?UTF-8?q?=EF=BC=8C=E5=9B=BE=E7=89=87=E6=8F=92=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/cesiumMap/index.vue | 26 ++++++++++++++++++++------ ruoyi-ui/src/views/childRoom/index.vue | 9 ++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 7bd8947..21e7bc9 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -89,8 +89,8 @@ export default { circle: { color: '#800080', opacity: 0.4, width: 2 }, sector: { color: '#FF6347', opacity: 0.5, width: 2 }, arrow: { color: '#FF0000', width: 6 }, - text: { color: '#000000', font: '32px Microsoft YaHei, PingFang SC, sans-serif', backgroundColor: 'rgba(255, 255, 255, 0.8)' }, - image: { width: 100, height: 100 } + text: { color: '#000000', font: '48px Microsoft YaHei, PingFang SC, sans-serif', backgroundColor: 'rgba(255, 255, 255, 0.8)' }, + image: { width: 150, height: 150 } } } }, @@ -1728,8 +1728,15 @@ export default { scaleByDistance: new Cesium.NearFarScalar( 1000, // 近距离(米) 1.0, // 近距离时的缩放比例 - 1000000, // 远距离(米) - 0.0 // 远距离时的缩放比例 + 500000, // 远距离(米) + 0.1 // 远距离时的缩放比例(不为0,保持可见) + ), + // 随地图缩放调整透明度 + translucencyByDistance: new Cesium.NearFarScalar( + 1000, // 近距离(米) + 1.0, // 近距离时的透明度 + 500000, // 远距离(米) + 0.3 // 远距离时的透明度 ) } }) @@ -1870,8 +1877,15 @@ export default { scaleByDistance: new Cesium.NearFarScalar( 1000, // 近距离(米) 1.0, // 近距离时的缩放比例 - 1000000, // 远距离(米) - 0.0 // 远距离时的缩放比例 + 500000, // 远距离(米) + 0.1 // 远距离时的缩放比例(不为0,保持可见) + ), + // 随地图缩放调整透明度 + translucencyByDistance: new Cesium.NearFarScalar( + 1000, // 近距离(米) + 1.0, // 近距离时的透明度 + 500000, // 远距离(米) + 0.3 // 远距离时的透明度 ) } }) diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 81eb37c..eaf2c72 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -392,7 +392,7 @@ export default { // 右侧面板 activeRightTab: 'plan', activeRouteIds: [], // 存储当前所有选中的航线ID - selectedRouteDetails: null, + // 冲突数据 conflictCount: 2, @@ -682,9 +682,7 @@ export default { this.isMenuHidden = true; }, - selectTopNav(item) { - console.log('选中顶部导航:', item); - }, + // 右侧面板操作 showRightPanel() { @@ -993,9 +991,6 @@ export default { this.$message.success('航线收藏'); }, - showOnlineMembersDialog() { - this.showOnlineMembers = true; - }, hideRightPanel() { this.isRightPanelHidden = true; From 1dad49ca22e1903e8db9c39638e20aba988ad762 Mon Sep 17 00:00:00 2001 From: menghao <1584479611@qq.com> Date: Mon, 26 Jan 2026 13:38:41 +0800 Subject: [PATCH 07/22] =?UTF-8?q?=E7=BA=A7=E8=81=94=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-druid.yml | 2 +- .../ruoyi/system/mapper/RouteWaypointsMapper.java | 8 +++ .../system/service/IRouteWaypointsService.java | 8 +++ .../service/impl/MissionScenarioServiceImpl.java | 26 ++++++++- .../service/impl/RouteWaypointsServiceImpl.java | 5 ++ .../system/service/impl/RoutesServiceImpl.java | 12 +++- .../mapper/system/RouteWaypointsMapper.xml | 4 ++ ruoyi-ui/src/views/childRoom/RightPanel.vue | 9 ++- ruoyi-ui/src/views/childRoom/index.vue | 65 ++++++++++++++++++++-- 9 files changed, 129 insertions(+), 10 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index c40f2aa..037db5c 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: A20040303ctw! + password: 123456 # 从库数据源 slave: # 从数据源开关/默认关闭 diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java index 2e77e10..07ff430 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java @@ -55,6 +55,14 @@ public interface RouteWaypointsMapper public int deleteRouteWaypointsById(Long id); /** + * 删除航线具体航点明细 + * + * @param routeId 航线主键 + * @return 结果 + */ + public int deleteRouteWaypointsByRouteId(Long routeId); + + /** * 批量删除航线具体航点明细 * * @param ids 需要删除的数据主键集合 diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IRouteWaypointsService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IRouteWaypointsService.java index fc046e8..3e74e8d 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/IRouteWaypointsService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IRouteWaypointsService.java @@ -58,4 +58,12 @@ public interface IRouteWaypointsService * @return 结果 */ public int deleteRouteWaypointsById(Long id); + + /** + * 删除航线具体航点明细信息 + * + * @param routeId 航线具体航点明细主键 + * @return 结果 + */ + public int deleteRouteWaypointsByRouteId(Long routeId); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/MissionScenarioServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/MissionScenarioServiceImpl.java index 600effa..ed72252 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/MissionScenarioServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/MissionScenarioServiceImpl.java @@ -1,11 +1,15 @@ package com.ruoyi.system.service.impl; import java.util.List; + +import com.ruoyi.system.domain.Routes; +import com.ruoyi.system.service.IRoutesService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.system.mapper.MissionScenarioMapper; import com.ruoyi.system.domain.MissionScenario; import com.ruoyi.system.service.IMissionScenarioService; +import org.springframework.transaction.annotation.Transactional; /** * 任务方案/沙箱Service业务层处理 @@ -19,6 +23,9 @@ public class MissionScenarioServiceImpl implements IMissionScenarioService @Autowired private MissionScenarioMapper missionScenarioMapper; + @Autowired + private IRoutesService routesService; + /** * 查询任务方案/沙箱 * @@ -74,9 +81,15 @@ public class MissionScenarioServiceImpl implements IMissionScenarioService * @return 结果 */ @Override + @Transactional public int deleteMissionScenarioByIds(Long[] ids) { - return missionScenarioMapper.deleteMissionScenarioByIds(ids); + int rows = 0; + for (Long id : ids) { + // 循环调用单条删除逻辑,确保每套方案下的从属数据都被清理干净 + rows += this.deleteMissionScenarioById(id); + } + return rows; } /** @@ -86,8 +99,19 @@ public class MissionScenarioServiceImpl implements IMissionScenarioService * @return 结果 */ @Override + @Transactional public int deleteMissionScenarioById(Long id) { + // 查找属于该方案的所有航线 + Routes queryRoute = new Routes(); + queryRoute.setScenarioId(id); + List relatedRoutes = routesService.selectRoutesList(queryRoute); + // 循环删除每一条航线 + if (relatedRoutes != null) { + for (Routes route : relatedRoutes) { + routesService.deleteRoutesById(route.getId()); + } + } return missionScenarioMapper.deleteMissionScenarioById(id); } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RouteWaypointsServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RouteWaypointsServiceImpl.java index fbc0848..63be94c 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RouteWaypointsServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RouteWaypointsServiceImpl.java @@ -101,4 +101,9 @@ public class RouteWaypointsServiceImpl implements IRouteWaypointsService { return routeWaypointsMapper.deleteRouteWaypointsById(id); } + + @Override + public int deleteRouteWaypointsByRouteId(Long routeId) { + return routeWaypointsMapper.deleteRouteWaypointsByRouteId(routeId); + } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java index b3ee040..e106817 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java @@ -109,9 +109,17 @@ public class RoutesServiceImpl implements IRoutesService * @return 结果 */ @Override + @Transactional public int deleteRoutesByIds(Long[] ids) { - return routesMapper.deleteRoutesByIds(ids); + int rows = 0; + for (Long id : ids) { + // 1. 清理航点 + routeWaypointsService.deleteRouteWaypointsByRouteId(id); + // 2. 累加删除航线的行数 + rows += routesMapper.deleteRoutesById(id); + } + return rows; } /** @@ -121,8 +129,10 @@ public class RoutesServiceImpl implements IRoutesService * @return 结果 */ @Override + @Transactional public int deleteRoutesById(Long id) { + routeWaypointsService.deleteRouteWaypointsByRouteId(id); return routesMapper.deleteRoutesById(id); } } diff --git a/ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml b/ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml index f6209fb..7ccc118 100644 --- a/ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml @@ -91,6 +91,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" delete from route_waypoints where id = #{id} + + delete from route_waypoints where route_id = #{routeId} + + delete from route_waypoints where id in diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index a377ed9..8bf4f5e 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -83,6 +83,7 @@
+
@@ -103,7 +104,7 @@
- +
@@ -251,6 +252,10 @@ export default { type: String, default: 'plan' }, + plans: { + type: Array, + default: () => [] + }, routes: { type: Array, default: () => [] @@ -258,7 +263,7 @@ export default { activeRouteIds: { type: Array, 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 63e6ab0..b522d75 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -125,6 +125,7 @@ @select-route="selectRoute" @create-plan="createPlan" @create-route="createRoute" + @delete-route="handleDeleteRoute" @open-plan-dialog="openPlanDialog" @open-route-dialog="openRouteDialog" @open-waypoint-dialog="openWaypointDialog" @@ -270,7 +271,7 @@ import LeftMenu from './LeftMenu' import RightPanel from './RightPanel' import BottomLeftPanel from './BottomLeftPanel' import TopHeader from './TopHeader' -import { listRoutes, getRoutes, addRoutes } from "@/api/system/routes"; +import { listRoutes, getRoutes, addRoutes,delRoutes } from "@/api/system/routes"; import { updateWaypoints } from "@/api/system/waypoints"; export default { name: 'MissionPlanningView', @@ -546,7 +547,36 @@ export default { this.$refs.cesiumMap.startMissionRouteDrawing(); this.$message.success('进入航线规划模式'); } - + }, + async handleDeleteRoute(route) { + try { + // 二次确认,防止误删 + await this.$confirm(`确定要彻底删除航线 "${route.name}" 吗?`, '提示', { + type: 'warning' + }); + if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) { + this.selectedRouteDetails = null; + } + const res = await delRoutes(route.id); + if (res.code === 200) { + this.$message.success('删除成功'); + // 同步地图:如果该航线正在显示,立即清除 + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.removeRouteById(route.id); + } + // 同步状态:从选中列表中移除该 ID + const idx = this.activeRouteIds.indexOf(route.id); + if (idx > -1) { + this.activeRouteIds.splice(idx, 1); + } + await this.getList(); + } + } catch (e) { + if (e !== 'cancel') { + console.error("删除航线失败:", e); + this.$message.error('删除操作失败'); + } + } }, /** 从数据库拉取最新的航线列表数据 */ async getList() { @@ -581,10 +611,16 @@ export default { this.$message.error('新增航线未命名,请输入名称后保存!'); return; } + // 获取当前选中的方案 ID + const currentScenarioId = this.selectedPlanId; + if (!currentScenarioId) { + this.$message.warning('请先在左侧选择一个方案,再保存航线!'); + return; + } // 构造符合后端 Routes 实体类的数据结构 const routeData = { callSign: this.newRouteName, - scenarioId: 1, // 示例:当前归属方案ID(实际应从全局状态获取) + scenarioId: currentScenarioId, platformId: 1, attributes: "{}", waypoints: this.tempMapPoints.map((p, index) => ({ @@ -602,7 +638,27 @@ export default { // 调用后端 API 保存到数据库 const response = await addRoutes(routeData); if (response.code === 200) { - this.$message.success('航线及其航点已成功保存至数据库'); + this.$message.success('航线及其航点已成功保存至当前方案'); + // 获取后端生成的正式 ID + const savedRoute = response.data; + const newRouteId = savedRoute ? savedRoute.id : null; + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.clearRoute(); + // 如果拿到了正式 ID,则按照“正式航线”的规则渲染一次 + if (newRouteId) { + if (!this.activeRouteIds.includes(savedRoute.id)) { + this.activeRouteIds.push(savedRoute.id); + } + // 更新当前详情,确保右侧下方航点列表立刻显示 + this.selectedRouteDetails = { + id: newRouteId, + name: this.newRouteName, + waypoints: routeData.waypoints + }; + //使用正式 ID 渲染点和线 + this.$refs.cesiumMap.renderRouteWaypoints(routeData.waypoints, newRouteId); + } + } // 重置 UI 状态 this.showNameDialog = false; this.drawDom = false; @@ -1108,7 +1164,6 @@ export default { /** 切换航线:实现多选/开关逻辑 */ async selectRoute(route) { const index = this.activeRouteIds.indexOf(route.id); - // 航线已在选中列表中 if (index > -1) { this.activeRouteIds.splice(index, 1); From 1a4434d9f64700564f3bb07552b56ddddc40b66a Mon Sep 17 00:00:00 2001 From: ctw <1051735452@qq.com> Date: Mon, 26 Jan 2026 13:39:42 +0800 Subject: [PATCH 08/22] =?UTF-8?q?=E5=8F=B3=E4=BE=A7=E6=A0=91=E5=BD=A2?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/childRoom/RightPanel.vue | 365 ++++++++++++++++------------ ruoyi-ui/src/views/childRoom/index.vue | 156 +++++++++--- 2 files changed, 332 insertions(+), 189 deletions(-) diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index fe63cf0..d035038 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -26,98 +26,79 @@ 新建方案 -
+
+
- -
-
{{ plan.name }}
-
{{ plan.routes.length }}个航线
-
-
- -
-
-
-
- -
-
-
航线列表
- - 新建航线 - -
- -
-
- -
-
{{ route.name }}
-
{{ route.points }}个航点
-
- - 冲突 - -
- -
-
-
-
- -
-
航点列表
-
-
- -
-
{{ point.name }}
-
高度: {{ point.altitude }}m | 速度: {{ point.speed }}
+
+ +
+
{{ plan.name }}
+
{{ routes.filter(r => r.scenarioId === plan.id).length }}个航线
+
+
+ + + +
-
- - + +
+
+
+ +
+
{{ route.name }}
+
{{ route.points }}个航点
+
+ + 冲突 + +
+ + + +
+
+ +
+
+
+ +
+
{{ point.name }}
+
高度: {{ point.altitude }}m | 速度: {{ point.speed }}
+
+
+ +
+
+
+
+
- -
- - 添加航点 - - - 取消 - -
-
-
@@ -187,7 +167,6 @@
-
-
-1) { + // 折叠方案时,取消选中该方案的所有航线 + this.expandedPlans.splice(index, 1) + const planRoutes = this.routes.filter(r => r.scenarioId === planId) + planRoutes.forEach(route => { + if (this.activeRouteIds.includes(route.id)) { + // 触发航线隐藏 + this.handleToggleRouteVisibility(route) + } + }) + // 折叠方案时,取消选中方案 + this.$emit('select-plan', { id: null }) + } else { + // 展开方案时,选中该方案 + this.expandedPlans.push(planId) + const plan = this.plans.find(p => p.id === planId) + if (plan) { + this.$emit('select-plan', plan) + } + // 选中该方案的所有航线 + const planRoutes = this.routes.filter(r => r.scenarioId === planId) + planRoutes.forEach(route => { + if (!this.activeRouteIds.includes(route.id)) { + this.handleSelectRoute(route) + } + }) + } + }, + + // 切换航线展开/折叠 + toggleRoute(routeId) { + const route = this.routes.find(r => r.id === routeId) + if (!route) return + + const isRouteSelected = this.activeRouteIds.includes(routeId) + const isRouteExpanded = this.expandedRoutes.includes(routeId) + + if (isRouteSelected) { + // 航线已选中 + if (isRouteExpanded) { + // 航线已展开,点击则收回航点 + const index = this.expandedRoutes.indexOf(routeId) + this.expandedRoutes.splice(index, 1) + } else { + // 航线未展开,点击则展开航点 + this.expandedRoutes.push(routeId) + } + } else { + // 航线未选中,点击则选中并显示航线和航点 + this.handleSelectRoute(route) + // 选中后自动展开航点 + this.$nextTick(() => { + if (!this.expandedRoutes.includes(routeId)) { + this.expandedRoutes.push(routeId) + } + }) + } + }, + handleHide() { this.$emit('hide') }, @@ -314,6 +364,7 @@ export default { }, handleSelectRoute(route) { + // 确保航线有航点数据 this.$emit('select-route', route) }, @@ -325,6 +376,14 @@ export default { this.$emit('create-route') }, + handleCreateRouteForPlan(plan) { + this.$emit('create-route', plan) + }, + + handleDeletePlan(plan) { + // 暂时留空,后续实现 + }, + handleOpenPlanDialog(plan) { this.$emit('open-plan-dialog', plan) }, @@ -333,16 +392,27 @@ export default { this.$emit('open-route-dialog', route) }, - handleOpenWaypointDialog(point) { - this.$emit('open-waypoint-dialog', point) + handleToggleRouteVisibility(route) { + this.$emit('toggle-route-visibility', route) + // 当隐藏航线时,自动收回航点列表 + const routeIndex = this.expandedRoutes.indexOf(route.id) + if (routeIndex > -1) { + this.expandedRoutes.splice(routeIndex, 1) + } }, - handleAddWaypoint() { - this.$emit('add-waypoint') + getRouteClasses(routeId) { + return { + active: this.activeRouteIds.includes(routeId) + } }, - handleCancelRoute() { - this.$emit('cancel-route') + handleDeleteRoute(route) { + // 暂时留空,后续实现 + }, + + handleOpenWaypointDialog(point) { + this.$emit('open-waypoint-dialog', point) }, handleViewConflict(conflict) { @@ -466,115 +536,90 @@ export default { opacity: 0.9; } -.route-list { +.tree-list { display: flex; flex-direction: column; gap: 8px; } -.route-item { +.tree-item { + border-radius: 6px; + transition: all 0.3s; + border: 1px solid rgba(0, 138, 255, 0.1); +} + +.tree-item-header { display: flex; align-items: center; gap: 10px; padding: 10px; background: rgba(255, 255, 255, 0.8); - border-radius: 6px; cursor: pointer; transition: all 0.3s; - border: 1px solid rgba(0, 138, 255, 0.1); - position: relative; + border-radius: 6px; } -.route-item:hover { +.tree-item-header:hover { background: rgba(0, 138, 255, 0.1); transform: translateX(-2px); box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15); } -.route-item.active { - background: rgba(0, 138, 255, 0.15); - border-color: rgba(0, 138, 255, 0.3); - box-shadow: 0 2px 10px rgba(0, 138, 255, 0.25); -} - -.route-info { - flex: 1; -} - -.route-name { - font-size: 14px; - font-weight: 500; - color: #333; -} - -.route-meta { - font-size: 12px; - color: #999; +.tree-item.plan-item .tree-item-header { + background: rgba(255, 255, 255, 0.9) !important; } -.route-actions { - display: flex; - gap: 8px; +.tree-item.route-item .tree-item-header { + background: rgba(255, 255, 255, 0.8) !important; } -.route-actions i { - cursor: pointer; - color: #008aff; - font-size: 14px; - padding: 4px; - border-radius: 4px; - transition: all 0.2s; +.tree-item.route-item:not(.active) .tree-item-header { + background: rgba(255, 255, 255, 0.8) !important; } -.route-actions i:hover { - background: rgba(0, 138, 255, 0.1); - transform: scale(1.2); +.tree-item.waypoint-item .tree-item-header { + background: rgba(224, 238, 255, 0.8); } -.waypoint-list { - display: flex; - flex-direction: column; - gap: 8px; +.tree-item.active .tree-item-header { + background: rgba(0, 138, 255, 0.15) !important; + border-color: rgba(0, 138, 255, 0.3); + box-shadow: 0 2px 10px rgba(0, 138, 255, 0.25); } -.waypoint-item { - display: flex; - align-items: center; - gap: 10px; - padding: 10px; - background: rgba(255, 255, 255, 0.8); - border-radius: 6px; - transition: all 0.3s; - border: 1px solid rgba(0, 138, 255, 0.1); +.tree-item.selected .tree-item-header { + background: rgba(0, 138, 255, 0.1) !important; + border-color: rgba(0, 138, 255, 0.2); } -.waypoint-item:hover { - background: rgba(0, 138, 255, 0.1); - transform: translateX(-2px); - box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15); +.tree-icon { + font-size: 16px; + color: #008aff; + flex-shrink: 0; } -.waypoint-info { +.tree-item-info { flex: 1; } -.waypoint-name { +.tree-item-name { font-size: 14px; font-weight: 500; color: #333; } -.waypoint-meta { +.tree-item-meta { font-size: 12px; color: #999; } -.waypoint-actions { +.tree-item-actions { display: flex; gap: 8px; + flex-shrink: 0; } -.waypoint-actions i { +.tree-item-actions i { cursor: pointer; color: #008aff; font-size: 14px; @@ -583,11 +628,27 @@ export default { transition: all 0.2s; } -.waypoint-actions i:hover { +.tree-item-actions i:hover { background: rgba(0, 138, 255, 0.1); transform: scale(1.2); } +.tree-children { + margin-left: 20px; + margin-top: 4px; + display: flex; + flex-direction: column; + gap: 4px; +} + +.route-children { + margin-left: 25px; +} + +.waypoint-children { + margin-left: 50px; +} + .action-buttons { display: flex; gap: 10px; diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index eaf2c72..72a0ef6 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -106,6 +106,7 @@ ({ @@ -1139,56 +1147,92 @@ export default { const minutes = (val % 4) * 15; return `K+${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`; }, + + createPlan() { + this.$message.success('创建方案'); + }, + + openPlanDialog(plan) { + this.$message.success('打开方案编辑对话框'); + }, + selectPlan(plan) { - this.selectedPlanId = plan.id; - this.selectedPlanDetails = plan; + if (plan && plan.id) { + this.selectedPlanId = plan.id; + this.selectedPlanDetails = plan; + } else { + this.selectedPlanId = null; + this.selectedPlanDetails = null; + } this.selectedRouteId = null; this.selectedRouteDetails = null; }, - /** 切换航线:实现多选/开关逻辑 */ + /** 切换航线:实现复杂的交互逻辑 */ async selectRoute(route) { const index = this.activeRouteIds.indexOf(route.id); + const isRouteExpanded = this.$refs.rightPanel ? this.$refs.rightPanel.expandedRoutes.includes(route.id) : false; // 航线已在选中列表中 if (index > -1) { - this.activeRouteIds.splice(index, 1); - if (this.$refs.cesiumMap) { - this.$refs.cesiumMap.removeRouteById(route.id); - } - if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) { - if (this.activeRouteIds.length > 0) { - const lastId = this.activeRouteIds[this.activeRouteIds.length - 1]; - try { - const res = await getRoutes(lastId); - if (res.code === 200 && res.data) { - this.selectedRouteDetails = { - id: res.data.id, - name: res.data.callSign, - waypoints: res.data.waypoints || [] - }; + if (isRouteExpanded) { + // 航线已展开,点击则收回航点(不取消选中) + // 这个逻辑在 RightPanel 的 toggleRoute 中处理 + return; + } else { + // 航线未展开,点击则取消选中(从地图移除) + this.activeRouteIds.splice(index, 1); + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.removeRouteById(route.id); + } + if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) { + if (this.activeRouteIds.length > 0) { + const lastId = this.activeRouteIds[this.activeRouteIds.length - 1]; + try { + const res = await getRoutes(lastId); + if (res.code === 200 && res.data) { + this.selectedRouteId = res.data.id; + this.selectedRouteDetails = { + id: res.data.id, + name: res.data.callSign, + waypoints: res.data.waypoints || [] + }; + } + } catch (e) { + console.error("回显剩余航线失败", e); } - } catch (e) { - console.error("回显剩余航线失败", e); + } else { + this.selectedRouteId = null; + this.selectedRouteDetails = null; } - } else { - this.selectedRouteDetails = null; } + this.$message.info(`已取消航线: ${route.name}`); + return; } - this.$message.info(`已移除航线: ${route.name}`); - return; } - // 航线未被选中 + + // 航线未被选中,点击则选中并显示航线和航点 try { const response = await getRoutes(route.id); if (response.code === 200 && response.data) { const fullRouteData = response.data; const waypoints = fullRouteData.waypoints || []; this.activeRouteIds.push(route.id); - this.selectedRouteDetails = { - id: fullRouteData.id, - name: fullRouteData.callSign, - waypoints: waypoints - }; + this.selectedRouteId = fullRouteData.id; + this.selectedRouteDetails = { + id: fullRouteData.id, + name: fullRouteData.callSign, + waypoints: waypoints + }; + + // 更新 routes 数组中对应航线的 waypoints 字段 + const routeIndex = this.routes.findIndex(r => r.id === route.id); + if (routeIndex > -1) { + this.$set(this.routes, routeIndex, { + ...this.routes[routeIndex], + waypoints: waypoints + }); + } + if (waypoints.length > 0) { // 通知地图渲染 if (this.$refs.cesiumMap) { @@ -1229,6 +1273,44 @@ export default { this.$message.info('已清空所有选中航线'); }, + // 切换航线显示/隐藏 + toggleRouteVisibility(route) { + const index = this.activeRouteIds.indexOf(route.id); + + if (index > -1) { + // 航线已显示,隐藏它 + // 使用过滤创建新数组,确保 Vue 能够检测到变化 + this.activeRouteIds = this.activeRouteIds.filter(id => id !== route.id); + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.removeRouteById(route.id); + } + if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) { + if (this.activeRouteIds.length > 0) { + const lastId = this.activeRouteIds[this.activeRouteIds.length - 1]; + getRoutes(lastId).then(res => { + if (res.code === 200 && res.data) { + this.selectedRouteId = res.data.id; + this.selectedRouteDetails = { + id: res.data.id, + name: res.data.callSign, + waypoints: res.data.waypoints || [] + }; + } + }).catch(e => { + console.error("获取航线详情失败", e); + }); + } else { + this.selectedRouteId = null; + this.selectedRouteDetails = null; + } + } + this.$message.info(`已隐藏航线: ${route.name}`); + } else { + // 航线已隐藏,显示它 + this.selectRoute(route); + } + }, + // 冲突操作 runConflictCheck() { this.conflictCount = 2; From c59b06d910301d24dcc9a1c2f942d6261be5cd4b Mon Sep 17 00:00:00 2001 From: ctw <1051735452@qq.com> Date: Mon, 26 Jan 2026 15:31:48 +0800 Subject: [PATCH 09/22] 1 --- ruoyi-ui/src/views/childRoom/RightPanel.vue | 5 +- ruoyi-ui/src/views/childRoom/index.vue | 213 +++++++++++++++++++--------- 2 files changed, 148 insertions(+), 70 deletions(-) diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index d035038..3633faa 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -324,7 +324,7 @@ export default { }) } }, - + // 切换航线展开/折叠 toggleRoute(routeId) { const route = this.routes.find(r => r.id === routeId) @@ -354,11 +354,12 @@ export default { }) } }, - + handleHide() { this.$emit('hide') }, + // 新建航线事件 handleSelectPlan(plan) { this.$emit('select-plan', plan) }, diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 72a0ef6..2574340 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -114,7 +114,7 @@ :selected-plan-details="selectedPlanDetails" :selected-route-id="selectedRouteId" :routes="routes" - :activeRouteIds="activeRouteIds" + :active-route-ids="activeRouteIds" :selected-route-details="selectedRouteDetails" :conflicts="conflicts" :conflict-count="conflictCount" @@ -272,7 +272,8 @@ import LeftMenu from './LeftMenu' import RightPanel from './RightPanel' import BottomLeftPanel from './BottomLeftPanel' import TopHeader from './TopHeader' -import { listRoutes, getRoutes, addRoutes } from "@/api/system/routes"; +import { listScenario} from "@/api/system/scenario"; +import { listRoutes, getRoutes, addRoutes,delRoutes } from "@/api/system/routes"; import { updateWaypoints } from "@/api/system/waypoints"; export default { name: 'MissionPlanningView', @@ -303,9 +304,6 @@ export default { selectedRoute: null, showWaypointDialog: false, selectedWaypoint: null, - showNameDialog: false, - newRouteName: '', - tempMapPoints: [], // 威力区、比例尺、外部参数弹窗 showPowerZoneDialog: false, currentPowerZone: {}, @@ -315,6 +313,9 @@ export default { currentExternalParams: {}, showPageLayoutDialog: false, menuPosition: 'left', + showNameDialog: false, + newRouteName: '', + tempMapPoints: [], // 作战信息 roomCode: 'JTF-7-ALPHA', @@ -395,7 +396,6 @@ export default { activeRightTab: 'plan', activeRouteIds: [], // 存储当前所有选中的航线ID - // 冲突数据 conflictCount: 2, conflicts: [ @@ -437,20 +437,7 @@ export default { ], // 航线数据 - 改为方案-航线-航点三级结构 - plans: [ - { - id: 1, - name: '方案A', - routes: [ - ] - }, - { - id: 2, - name: '方案B', - routes: [ - ] - } - ], + plans: [], // 时间控制 timeProgress: 45, @@ -544,20 +531,60 @@ export default { this.selectedPlanId = plan.id; this.selectedPlanDetails = plan; } - + if (this.$refs.cesiumMap) { this.$refs.cesiumMap.startMissionRouteDrawing(); this.$message.success('进入航线规划模式'); } - + }, + async handleDeleteRoute(route) { + try { + // 二次确认,防止误删 + await this.$confirm(`确定要彻底删除航线 "${route.name}" 吗?`, '提示', { + type: 'warning' + }); + if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) { + this.selectedRouteDetails = null; + } + const res = await delRoutes(route.id); + if (res.code === 200) { + this.$message.success('删除成功'); + // 同步地图:如果该航线正在显示,立即清除 + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.removeRouteById(route.id); + } + // 同步状态:从选中列表中移除该 ID + const idx = this.activeRouteIds.indexOf(route.id); + if (idx > -1) { + this.activeRouteIds.splice(idx, 1); + } + await this.getList(); + } + } catch (e) { + if (e !== 'cancel') { + console.error("删除航线失败:", e); + this.$message.error('删除操作失败'); + } + } }, /** 从数据库拉取最新的航线列表数据 */ + /** 从数据库拉取最新的数据 */ async getList() { - const query = {}; // 移除固定的 scenarioId,获取所有航线 try { - const response = await listRoutes(query); - if (response.code === 200) { - this.routes = response.rows.map(item => ({ + // 1. 获取所有方案 + const scenarioRes = await listScenario({}); + if (scenarioRes.code === 200) { + this.plans = scenarioRes.rows.map(s => ({ + id: s.id, + name: s.name, + routes: [] + })); + } + + // 2. 获取所有航线 + const routeRes = await listRoutes({}); + if (routeRes.code === 200) { + const allRoutes = routeRes.rows.map(item => ({ id: item.id, name: item.callSign, points: item.waypoints ? item.waypoints.length : 0, @@ -565,9 +592,18 @@ export default { conflict: false, scenarioId: item.scenarioId })); + + // 3. 将航线分配到对应的方案中 + this.plans.forEach(plan => { + plan.routes = allRoutes.filter(r => r.scenarioId === plan.id); + }); + + // 保存一份扁平的 routes 供搜索或其他组件使用 + this.routes = allRoutes; } } catch (error) { - this.$message.error("获取航线列表失败"); + console.error("数据加载失败:", error); + this.$message.error("获取方案列表失败"); } }, handleMapDrawComplete(points) { @@ -586,10 +622,16 @@ export default { this.$message.error('新增航线未命名,请输入名称后保存!'); return; } + // 获取当前选中的方案 ID + const currentScenarioId = this.selectedPlanId; + if (!currentScenarioId) { + this.$message.warning('请先在左侧选择一个方案,再保存航线!'); + return; + } // 构造符合后端 Routes 实体类的数据结构 const routeData = { callSign: this.newRouteName, - scenarioId: this.selectedPlanId || 1, // 使用当前选中的方案ID,默认1 + scenarioId: currentScenarioId || 1, // 使用当前选中的方案ID,默认1 platformId: 1, attributes: "{}", waypoints: this.tempMapPoints.map((p, index) => ({ @@ -607,7 +649,27 @@ export default { // 调用后端 API 保存到数据库 const response = await addRoutes(routeData); if (response.code === 200) { - this.$message.success('航线及其航点已成功保存至数据库'); + this.$message.success('航线及其航点已成功保存至当前方案'); + // 获取后端生成的正式 ID + const savedRoute = response.data; + const newRouteId = savedRoute ? savedRoute.id : null; + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.clearRoute(); + // 如果拿到了正式 ID,则按照“正式航线”的规则渲染一次 + if (newRouteId) { + if (!this.activeRouteIds.includes(savedRoute.id)) { + this.activeRouteIds.push(savedRoute.id); + } + // 更新当前详情,确保右侧下方航点列表立刻显示 + this.selectedRouteDetails = { + id: newRouteId, + name: this.newRouteName, + waypoints: routeData.waypoints + }; + //使用正式 ID 渲染点和线 + this.$refs.cesiumMap.renderRouteWaypoints(routeData.waypoints, newRouteId); + } + } // 重置 UI 状态 this.showNameDialog = false; this.drawDom = false; @@ -1147,15 +1209,7 @@ export default { const minutes = (val % 4) * 15; return `K+${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`; }, - - createPlan() { - this.$message.success('创建方案'); - }, - - openPlanDialog(plan) { - this.$message.success('打开方案编辑对话框'); - }, - + // 航线操作 selectPlan(plan) { if (plan && plan.id) { this.selectedPlanId = plan.id; @@ -1167,11 +1221,10 @@ export default { this.selectedRouteId = null; this.selectedRouteDetails = null; }, - /** 切换航线:实现复杂的交互逻辑 */ + /** 切换航线:实现多选/开关逻辑 */ async selectRoute(route) { const index = this.activeRouteIds.indexOf(route.id); const isRouteExpanded = this.$refs.rightPanel ? this.$refs.rightPanel.expandedRoutes.includes(route.id) : false; - // 航线已在选中列表中 if (index > -1) { if (isRouteExpanded) { @@ -1209,7 +1262,7 @@ export default { return; } } - + // 航线未被选中,点击则选中并显示航线和航点 try { const response = await getRoutes(route.id); @@ -1217,13 +1270,13 @@ export default { const fullRouteData = response.data; const waypoints = fullRouteData.waypoints || []; this.activeRouteIds.push(route.id); - this.selectedRouteId = fullRouteData.id; - this.selectedRouteDetails = { - id: fullRouteData.id, - name: fullRouteData.callSign, - waypoints: waypoints - }; - + this.selectedRouteId = fullRouteData.id; + this.selectedRouteDetails = { + id: fullRouteData.id, + name: fullRouteData.callSign, + waypoints: waypoints + }; + // 更新 routes 数组中对应航线的 waypoints 字段 const routeIndex = this.routes.findIndex(r => r.id === route.id); if (routeIndex > -1) { @@ -1232,7 +1285,7 @@ export default { waypoints: waypoints }); } - + if (waypoints.length > 0) { // 通知地图渲染 if (this.$refs.cesiumMap) { @@ -1248,6 +1301,25 @@ export default { } }, + createPlan() { + const newId = Date.now(); + const newPlan = { + id: newId, + name: `新方案${this.plans.length + 1}`, + routes: [] + }; + this.plans.push(newPlan); + this.selectPlan(newPlan); + }, + openPlanDialog(plan) { + this.$prompt('请输入方案名称', '编辑方案', { + confirmButtonText: '确定', + cancelButtonText: '取消', + inputValue: plan.name + }).then(({ value }) => { + plan.name = value; + }).catch(() => {}); + }, addWaypoint() { if (this.selectedRouteDetails) { const count = this.selectedRouteDetails.waypoints.length + 1; @@ -1257,7 +1329,12 @@ export default { speed: '800km/h', eta: `K+01:${(count * 15).toString().padStart(2, '0')}:00` }); - this.$message.success('添加航点成功'); + if (this.selectedPlanDetails) { + const route = this.selectedPlanDetails.routes.find(r => r.id === this.selectedRouteId); + if (route) { + route.points = this.selectedRouteDetails.waypoints.length; + } + } } }, @@ -1276,7 +1353,7 @@ export default { // 切换航线显示/隐藏 toggleRouteVisibility(route) { const index = this.activeRouteIds.indexOf(route.id); - + if (index > -1) { // 航线已显示,隐藏它 // 使用过滤创建新数组,确保 Vue 能够检测到变化 @@ -1286,23 +1363,23 @@ export default { } if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) { if (this.activeRouteIds.length > 0) { - const lastId = this.activeRouteIds[this.activeRouteIds.length - 1]; - getRoutes(lastId).then(res => { - if (res.code === 200 && res.data) { - this.selectedRouteId = res.data.id; - this.selectedRouteDetails = { - id: res.data.id, - name: res.data.callSign, - waypoints: res.data.waypoints || [] - }; - } - }).catch(e => { - console.error("获取航线详情失败", e); - }); - } else { - this.selectedRouteId = null; - this.selectedRouteDetails = null; + const lastId = this.activeRouteIds[this.activeRouteIds.length - 1]; + getRoutes(lastId).then(res => { + if (res.code === 200 && res.data) { + this.selectedRouteId = res.data.id; + this.selectedRouteDetails = { + id: res.data.id, + name: res.data.callSign, + waypoints: res.data.waypoints || [] + }; } + }).catch(e => { + console.error("获取航线详情失败", e); + }); + } else { + this.selectedRouteId = null; + this.selectedRouteDetails = null; + } } this.$message.info(`已隐藏航线: ${route.name}`); } else { @@ -1363,7 +1440,7 @@ export default { background-position: center; z-index: 1; } - +/* ...其余样式省略,保持不变... */ .map-overlay-text { position: absolute; top: 50%; From 6071fa04025c0edebdd7d58cb3f3e87581ec6c5d Mon Sep 17 00:00:00 2001 From: menghao <1584479611@qq.com> Date: Mon, 26 Jan 2026 15:50:22 +0800 Subject: [PATCH 10/22] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/childRoom/RightPanel.vue | 8 +---- ruoyi-ui/src/views/childRoom/index.vue | 47 +++++++++++++++++------------ 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index 3633faa..2b32a52 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -71,7 +71,7 @@
- +
@@ -359,7 +359,6 @@ export default { this.$emit('hide') }, - // 新建航线事件 handleSelectPlan(plan) { this.$emit('select-plan', plan) }, @@ -407,11 +406,6 @@ export default { active: this.activeRouteIds.includes(routeId) } }, - - handleDeleteRoute(route) { - // 暂时留空,后续实现 - }, - handleOpenWaypointDialog(point) { this.$emit('open-waypoint-dialog', point) }, diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 84a2431..eae1084 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -125,6 +125,7 @@ @select-plan="selectPlan" @select-route="selectRoute" @create-route="createRoute" + @delete-route="handleDeleteRoute" @create-plan="createPlan" @open-plan-dialog="openPlanDialog" @open-route-dialog="openRouteDialog" @@ -272,6 +273,7 @@ import LeftMenu from './LeftMenu' import RightPanel from './RightPanel' import BottomLeftPanel from './BottomLeftPanel' import TopHeader from './TopHeader' +import { listScenario} from "@/api/system/scenario"; import { listRoutes, getRoutes, addRoutes,delRoutes } from "@/api/system/routes"; import { updateWaypoints } from "@/api/system/waypoints"; export default { @@ -436,20 +438,7 @@ export default { ], // 航线数据 - 改为方案-航线-航点三级结构 - plans: [ - { - id: 1, - name: '方案A', - routes: [ - ] - }, - { - id: 2, - name: '方案B', - routes: [ - ] - } - ], + plans: [], // 时间控制 timeProgress: 45, @@ -580,12 +569,23 @@ export default { } }, /** 从数据库拉取最新的航线列表数据 */ + /** 从数据库拉取最新的数据 */ async getList() { - const query = {}; // 移除固定的 scenarioId,获取所有航线 try { - const response = await listRoutes(query); - if (response.code === 200) { - this.routes = response.rows.map(item => ({ + // 1. 获取所有方案 + const scenarioRes = await listScenario({}); + if (scenarioRes.code === 200) { + this.plans = scenarioRes.rows.map(s => ({ + id: s.id, + name: s.name, + routes: [] + })); + } + + // 2. 获取所有航线 + const routeRes = await listRoutes({}); + if (routeRes.code === 200) { + const allRoutes = routeRes.rows.map(item => ({ id: item.id, name: item.callSign, points: item.waypoints ? item.waypoints.length : 0, @@ -593,9 +593,18 @@ export default { conflict: false, scenarioId: item.scenarioId })); + + // 3. 将航线分配到对应的方案中 + this.plans.forEach(plan => { + plan.routes = allRoutes.filter(r => r.scenarioId === plan.id); + }); + + // 保存一份扁平的 routes 供搜索或其他组件使用 + this.routes = allRoutes; } } catch (error) { - this.$message.error("获取航线列表失败"); + console.error("数据加载失败:", error); + this.$message.error("获取方案列表失败"); } }, handleMapDrawComplete(points) { From 0dbc8ccb227a5082f19d689444cc3c57809200f5 Mon Sep 17 00:00:00 2001 From: menghao <1584479611@qq.com> Date: Mon, 26 Jan 2026 19:52:33 +0800 Subject: [PATCH 11/22] =?UTF-8?q?1=E5=88=AB=E6=8B=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/src/views/cesiumMap/index.vue | 7 ++- ruoyi-ui/src/views/childRoom/index.vue | 88 +++++++++++++++++++--------------- ruoyi-ui/vue.config.js | 2 +- 3 files changed, 56 insertions(+), 41 deletions(-) diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 21e7bc9..735b177 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -241,13 +241,18 @@ export default { }, renderRouteWaypoints(waypoints, routeId = 'default') { + console.log('renderRouteWaypoints被调用,航点数据:', waypoints); + console.log('航点数量:', waypoints ? waypoints.length : 0); if (!waypoints || waypoints.length < 1) return; const positions = []; // 1. 遍历并绘制航点标记 waypoints.forEach((wp, index) => { + console.log(`处理航点${index}:`, wp); + console.log(`航点${index}的字段:`, Object.keys(wp)); const lon = parseFloat(wp.lng); const lat = parseFloat(wp.lat); - const pos = Cesium.Cartesian3.fromDegrees(lon, lat, parseFloat(wp.alt || 500)); + console.log(`航点${index}坐标:`, {lon, lat, alt: wp.altitude || wp.alt}); + const pos = Cesium.Cartesian3.fromDegrees(lon, lat, parseFloat(wp.altitude || wp.alt || 500)); positions.push(pos); this.viewer.entities.add({ diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index eae1084..333a82b 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -618,21 +618,21 @@ export default { }, /** 弹窗点击“确定”:正式将数据保存到后端数据库 */ async confirmSaveNewRoute() { - // 严格校验起名逻辑 + // 1. 严格校验 if (!this.newRouteName || this.newRouteName.trim() === '') { this.$message.error('新增航线未命名,请输入名称后保存!'); return; } - // 获取当前选中的方案 ID const currentScenarioId = this.selectedPlanId; if (!currentScenarioId) { this.$message.warning('请先在左侧选择一个方案,再保存航线!'); return; } - // 构造符合后端 Routes 实体类的数据结构 + + // 2. 构造数据 const routeData = { callSign: this.newRouteName, - scenarioId: currentScenarioId || 1, // 使用当前选中的方案ID,默认1 + scenarioId: currentScenarioId, platformId: 1, attributes: "{}", waypoints: this.tempMapPoints.map((p, index) => ({ @@ -645,45 +645,55 @@ export default { turnAngle: 0.0 })) }; + try { + const response = await addRoutes(routeData); + if (response.code === 200) { + this.$message.success('航线及其航点已成功保存至当前方案'); - try { - // 调用后端 API 保存到数据库 - const response = await addRoutes(routeData); - if (response.code === 200) { - this.$message.success('航线及其航点已成功保存至当前方案'); - // 获取后端生成的正式 ID - const savedRoute = response.data; - const newRouteId = savedRoute ? savedRoute.id : null; - if (this.$refs.cesiumMap) { - this.$refs.cesiumMap.clearRoute(); - // 如果拿到了正式 ID,则按照“正式航线”的规则渲染一次 - if (newRouteId) { - if (!this.activeRouteIds.includes(savedRoute.id)) { - this.activeRouteIds.push(savedRoute.id); - } - // 更新当前详情,确保右侧下方航点列表立刻显示 - this.selectedRouteDetails = { - id: newRouteId, - name: this.newRouteName, - waypoints: routeData.waypoints - }; - //使用正式 ID 渲染点和线 - this.$refs.cesiumMap.renderRouteWaypoints(routeData.waypoints, newRouteId); + const savedRoute = response.data; + const newRouteId = savedRoute ? savedRoute.id : null; + + // 1. 立即停止绘制并清除临时线(不要用 clearRoute) + if (this.$refs.cesiumMap) { + this.drawDom = false; + if (this.$refs.cesiumMap.clearTempRoute) this.$refs.cesiumMap.clearTempRoute(); } + + // 2. 先执行刷新列表,同步后端数据库状态 + await this.getList(); + + // 3. 在列表刷新后的“下一帧”执行选中和渲染逻辑 + this.$nextTick(async () => { + if (newRouteId) { + // 确保新 ID 在激活列表中(触发右侧勾选 UI) + if (!this.activeRouteIds.includes(newRouteId)) { + this.activeRouteIds.push(newRouteId); + } + + // 重新锁定选中详情,防止被 getList 重置为空 + this.selectedRouteId = newRouteId; + this.selectedRouteDetails = { + id: newRouteId, + name: this.newRouteName, + waypoints: routeData.waypoints + }; + + // 显式执行连线渲染 + if (this.$refs.cesiumMap) { + this.$refs.cesiumMap.renderRouteWaypoints(routeData.waypoints, newRouteId); + } + } + }); + + // 4. 重置弹窗 UI + this.showNameDialog = false; + this.newRouteName = ''; + this.tempMapPoints = []; } - // 重置 UI 状态 - this.showNameDialog = false; - this.drawDom = false; - this.newRouteName = ''; - this.tempMapPoints = []; - // 重新拉取右侧航线列表以保持同步 - await this.getList(); + } catch (error) { + console.error("保存失败:", error); } - } catch (error) { - console.error("保存航线失败:", error); - this.$message.error('保存失败,请检查后端服务'); - } - }, + }, // 航点编辑弹窗相关方法 openWaypointDialog(waypoint) { this.selectedWaypoint = waypoint; diff --git a/ruoyi-ui/vue.config.js b/ruoyi-ui/vue.config.js index 0488a18..25854e2 100644 --- a/ruoyi-ui/vue.config.js +++ b/ruoyi-ui/vue.config.js @@ -10,7 +10,7 @@ const CompressionPlugin = require('compression-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题 -const baseUrl = 'http://192.168.50.145:8080' // 后端接口 +const baseUrl = 'http://127.0.0.1:8080' // 后端接口 const port = process.env.port || process.env.npm_config_port || 80 // 端口 // 定义 Cesium 源码路径 From 7b003f8ec09528c8c1953c463700a1d2dd8ceab2 Mon Sep 17 00:00:00 2001 From: menghao <1584479611@qq.com> Date: Thu, 29 Jan 2026 11:21:11 +0800 Subject: [PATCH 12/22] =?UTF-8?q?=E6=96=B9=E6=A1=88-=E8=88=AA=E7=BA=BF-?= =?UTF-8?q?=E8=88=AA=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/web/controller/RoutesController.java | 7 +- .../system/service/impl/RoutesServiceImpl.java | 11 +- ruoyi-ui/src/views/cesiumMap/index.vue | 610 ++++++--------------- ruoyi-ui/src/views/childRoom/RightPanel.vue | 59 +- ruoyi-ui/src/views/childRoom/index.vue | 334 +++++++---- ruoyi-ui/src/views/selectRoom/index.vue | 5 +- 6 files changed, 457 insertions(+), 569 deletions(-) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoutesController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoutesController.java index 15ee0d2..18a57c5 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoutesController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoutesController.java @@ -77,7 +77,12 @@ public class RoutesController extends BaseController @PostMapping public AjaxResult add(@RequestBody Routes routes) { - return toAjax(routesService.insertRoutes(routes)); + // 1. 执行插入,MyBatis 会通过 useGeneratedKeys="true" 自动将新 ID 注入 routes 对象 + int rows = routesService.insertRoutes(routes); + + // 2. 不要用 toAjax,直接返回 success 并带上 routes 对象 + // 这样前端 response.data 就会包含这个带有 ID 的完整对象 + return rows > 0 ? AjaxResult.success(routes) : AjaxResult.error("新增航线失败"); } /** diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java index e106817..cf781a4 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java @@ -58,7 +58,16 @@ public class RoutesServiceImpl implements IRoutesService @Override public List selectRoutesList(Routes routes) { - return routesMapper.selectRoutesList(routes); + // 获取基础列表 + List list = routesMapper.selectRoutesList(routes); + // 遍历列表,为每一条航线补全它的航点信息 + for (Routes r : list) { + RouteWaypoints queryWp = new RouteWaypoints(); + queryWp.setRouteId(r.getId()); + List wpList = routeWaypointsService.selectRouteWaypointsList(queryWp); + r.setWaypoints(wpList); + } + return list; } /** diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 735b177..0e746b8 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -12,7 +12,6 @@ @import-data="importData" @locate="handleLocate" /> -
- + + diff --git a/ruoyi-ui/src/views/dialogs/WaypointEditDialog.vue b/ruoyi-ui/src/views/dialogs/WaypointEditDialog.vue index 57e05d4..6bb0006 100644 --- a/ruoyi-ui/src/views/dialogs/WaypointEditDialog.vue +++ b/ruoyi-ui/src/views/dialogs/WaypointEditDialog.vue @@ -14,9 +14,9 @@ - + - + +
+ ⚠️ 首尾航点坡度已锁定为 0,不可编辑 +
@@ -78,7 +82,6 @@ export default { } }, data() { - // 定义一个通用的数字校验函数,允许 0 const validateNumber = (rule, value, callback) => { // 检查 value 是否为 undefined, null 或 空字符串 // 注意:这里不能简单用 !value,因为 !0 是 true @@ -92,16 +95,19 @@ export default { return { formData: { name: '', - altitude: 0, - speed: 0, - turnBank: 0, - startTime: '' + alt: 5000, + speed: 800, + turnAngle: 0, + startTime: '', + currentIndex: -1, + totalPoints: 0, + isBankDisabled: false }, rules: { name: [ { required: true, message: '请输入航点名称', trigger: 'blur' } ], - altitude: [ + alt: [ // 使用自定义 validator 替代 type: 'number' 组合,确保 0 被接受 { required: true, validator: validateNumber, message: '请输入有效高度', trigger: ['blur', 'change'] } ], @@ -109,7 +115,7 @@ export default { // 重点修改:使用自定义 validator,允许速度为 0 { required: true, validator: validateNumber, message: '请输入有效速度', trigger: ['blur', 'change'] } ], - turnBank: [ + turnAngle: [ // 同样应用到转弯坡度 { required: true, validator: validateNumber, message: '请输入有效转弯坡度', trigger: ['blur', 'change'] } ], @@ -133,17 +139,21 @@ export default { }, methods: { initFormData() { - // 保持数据类型正确,确保是 Number 类型 + const index = this.waypoint.currentIndex !== undefined ? this.waypoint.currentIndex : -1; + const total = this.waypoint.totalPoints || 0; + const locked = (index === 0) || (total > 0 && index === total - 1); + this.formData = { name: this.waypoint.name || '', - // 确保初始化时也是数字类型 - altitude: this.waypoint.altitude !== undefined && this.waypoint.altitude !== null ? Number(this.waypoint.altitude) : 0, + alt: this.waypoint.alt !== undefined && this.waypoint.alt !== null ? Number(this.waypoint.alt) : 0, speed: this.waypoint.speed !== undefined && this.waypoint.speed !== null ? Number(this.waypoint.speed) : 0, - turnBank: this.waypoint.turnBank !== undefined && this.waypoint.turnBank !== null ? Number(this.waypoint.turnBank) : 0, - startTime: this.waypoint.startTime || '' + startTime: this.waypoint.startTime || '', + currentIndex: index, + totalPoints: total, + isBankDisabled: locked, + turnAngle: locked ? 0 : (Number(this.waypoint.turnAngle) || 0) }; - // 弹窗打开时,尝试清除之前的校验痕迹 this.$nextTick(() => { if (this.$refs.formRef) { this.$refs.formRef.clearValidate(); From d014acd87e15377113a9c0336b9efad70cebedbc Mon Sep 17 00:00:00 2001 From: ctw <1051735452@qq.com> Date: Mon, 2 Feb 2026 15:10:02 +0800 Subject: [PATCH 22/22] =?UTF-8?q?=E6=9C=80=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/.env.development | 2 +- ruoyi-ui/package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development index 4d098ba..ebae234 100644 --- a/ruoyi-ui/.env.development +++ b/ruoyi-ui/.env.development @@ -8,7 +8,7 @@ ENV = 'development' VUE_APP_BASE_API = '/dev-api' # 访问地址(绕过 /dev-api 代理,用于解决静态资源/图片访问 401 认证问题) -VUE_APP_BACKEND_URL = 'http://localhost:8080' +VUE_APP_BACKEND_URL = 'http://192.168.50.145:8080' # 路由懒加载 VUE_CLI_BABEL_TRANSPILE_MODULES = true diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json index f0e65c5..4aa7fcd 100644 --- a/ruoyi-ui/package.json +++ b/ruoyi-ui/package.json @@ -38,6 +38,7 @@ "js-beautify": "1.13.0", "js-cookie": "3.0.1", "jsencrypt": "3.0.0-rc.1", + "mammoth": "^1.11.0", "nprogress": "0.2.0", "quill": "2.0.2", "screenfull": "5.0.2", @@ -52,7 +53,6 @@ "vuex": "3.6.0" }, "devDependencies": { - "express": "^4.18.2", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@open-wc/webpack-import-meta-loader": "^0.4.7", @@ -62,6 +62,7 @@ "chalk": "4.1.0", "compression-webpack-plugin": "6.1.2", "connect": "3.6.6", + "express": "^4.18.2", "sass": "1.32.13", "sass-loader": "10.1.1", "script-ext-html-webpack-plugin": "2.1.5",