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/cesiumMap/DrawingToolbar.vue b/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue index 9acf607..c23a904 100644 --- a/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue +++ b/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue @@ -40,6 +40,10 @@ export default { { id: 'polygon', name: '面', icon: 'el-icon-s-grid' }, { id: 'rectangle', name: '矩形', icon: 'el-icon-s-data' }, { id: 'circle', name: '圆形', icon: 'el-icon-circle-plus-outline' }, + { id: 'sector', name: '扇形', icon: 'el-icon-s-operation' }, + { id: 'arrow', name: '箭头', icon: 'el-icon-right' }, + { id: 'text', name: '文本', icon: 'el-icon-document' }, + { id: 'image', name: '图片', icon: 'el-icon-picture-outline' }, { id: 'locate', name: '定位', icon: 'el-icon-aim' }, { id: 'clear', name: '清除', icon: 'el-icon-delete' }, { id: 'import', name: '导入', icon: 'el-icon-upload' }, diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 7b6e554..21e7bc9 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -26,6 +26,9 @@ import * as Cesium from 'cesium' import 'cesium/Build/Cesium/Widgets/widgets.css' import DrawingToolbar from './DrawingToolbar.vue' import MeasurementPanel from './MeasurementPanel.vue' +import axios from 'axios' +import request from '@/utils/request' +import { getToken } from '@/utils/auth' export default { name: 'CesiumMap', props: { @@ -64,8 +67,10 @@ export default { tempEntity: null, // 最终实体 tempPreviewEntity: null, // 预览实体(新增) drawingPoints: [], + drawingPointEntities: [], // 存储线绘制时的点实体 drawingStartPoint: null, isDrawing: false, + activeCursorPosition: null, // 实时鼠标位置 // 实体管理 allEntities: [], // 所有绘制的实体 @@ -81,7 +86,11 @@ export default { line: { color: '#00FF00', width: 3 }, polygon: { color: '#0000FF', opacity: 0.5, width: 2 }, rectangle: { color: '#FFA500', opacity: 0.3, width: 2 }, - circle: { color: '#800080', opacity: 0.4, width: 2 } + 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: '48px Microsoft YaHei, PingFang SC, sans-serif', backgroundColor: 'rgba(255, 255, 255, 0.8)' }, + image: { width: 150, height: 150 } } } }, @@ -96,6 +105,11 @@ export default { }, beforeDestroy() { + // 销毁鼠标悬停事件处理器 + if (this.hoverHandler) { + this.hoverHandler.destroy(); + this.hoverHandler = null; + } this.destroyViewer() }, @@ -352,6 +366,10 @@ export default { }) this.initScaleBar() + this.initPointMovement() + this.initRightClickHandler() + this.initHoverHandler() + console.log('Cesium离线二维地图已加载') console.log('Cesium离线二维地图已加载') // 1. 定义全局拾取处理器 this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas); @@ -377,6 +395,103 @@ 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) + }, + + // 初始化鼠标悬停事件处理器 + 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) { @@ -519,6 +634,18 @@ export default { case 'circle': this.startCircleDrawing() break + case 'sector': + this.startSectorDrawing() + break + case 'arrow': + this.startArrowDrawing() + break + case 'text': + this.startTextDrawing() + break + case 'image': + this.startImageDrawing() + break } this.viewer.scene.canvas.style.cursor = 'crosshair' @@ -542,12 +669,52 @@ 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.measurementResult = null; 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() { @@ -561,11 +728,9 @@ export default { }, // 绘制线 - // 绘制线 startLineDrawing() { this.drawingPoints = []; - // 记录当前鼠标的实时位置 - let activeCursorPosition = null; + this.drawingPointEntities = []; // 存储点实体 // 清除可能存在的旧实体 if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); @@ -573,11 +738,24 @@ export default { this.tempEntity = null; this.tempPreviewEntity = null; - // 1. 鼠标移动事件:仅更新坐标变量,不操作实体 + // 1. 鼠标移动事件:更新坐标变量并实时计算线段长度 this.drawingHandler.setInputAction((movement) => { const newPosition = this.getClickPosition(movement.endPosition); if (newPosition) { - activeCursorPosition = 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); @@ -587,37 +765,29 @@ export default { if (position) { this.drawingPoints.push(position); - // === 第一步:点击第一个点后,立即创建“动态虚线” === - if (this.drawingPoints.length === 1) { - 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 && activeCursorPosition) { - // 获取最后一个已确认的点 - const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; - // 返回 [最后一个点, 当前鼠标位置] - return [lastPoint, 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); } @@ -630,25 +800,52 @@ 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(); } - // 重置局部变量 - activeCursorPosition = null; + // 重置鼠标位置 + this.activeCursorPosition = null; }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); }, @@ -662,7 +859,7 @@ export default { } // 创建最终的实线实体 - const entity = this.addLineEntity([...this.drawingPoints]); + const entity = this.addLineEntity([...this.drawingPoints], [...this.drawingPointEntities]); // 计算长度 const length = this.calculateLineLength([...this.drawingPoints]); @@ -671,9 +868,19 @@ export default { type: 'line' }; - this.stopDrawing(); + + + // 重置绘制点数组,保持绘制状态以继续绘制 + 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; } @@ -682,7 +889,6 @@ export default { // 绘制多边形 startPolygonDrawing() { this.drawingPoints = []; - let activeCursorPosition = null; // 存储鼠标实时位置 // 1. 清理旧实体 // 移除之前可能遗留的实体,确保干净的画布 @@ -695,7 +901,7 @@ export default { this.drawingHandler.setInputAction((movement) => { const newPosition = this.getClickPosition(movement.endPosition); if (newPosition) { - activeCursorPosition = newPosition; + this.activeCursorPosition = newPosition; } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); @@ -707,7 +913,7 @@ export default { // === 关键逻辑:点击第一个点时,创建唯一的“动态多边形” === if (this.drawingPoints.length === 1) { - activeCursorPosition = position; // 初始化鼠标位置 + this.activeCursorPosition = position; // 初始化鼠标位置 this.tempEntity = this.viewer.entities.add({ // --- 填充面配置 --- @@ -715,8 +921,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), @@ -729,9 +935,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), @@ -757,7 +963,7 @@ export default { } // 重置状态 - activeCursorPosition = null; + this.activeCursorPosition = null; }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); }, @@ -772,15 +978,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); @@ -790,7 +1000,7 @@ export default { this.drawingHandler.setInputAction((movement) => { const newPosition = this.getClickPosition(movement.endPosition); if (newPosition) { - activeCursorPosition = newPosition; + this.activeCursorPosition = newPosition; } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); @@ -799,19 +1009,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), @@ -825,10 +1034,10 @@ export default { }); } // --- 情况B:第二次点击(确定终点) --- - else { + else if (this.drawingPoints.length === 1) { this.drawingPoints.push(position); // 停止监听鼠标移动,因为形状已确定 - activeCursorPosition = null; + this.activeCursorPosition = null; // 调用完成逻辑 this.finishRectangleDrawing(); @@ -840,7 +1049,6 @@ export default { this.drawingHandler.setInputAction(() => { this.cancelDrawing(); }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); - }, finishRectangleDrawing() { @@ -876,8 +1084,8 @@ export default { type: 'rectangle' }; - // 6. 重置状态 - this.stopDrawing(); + // 6. 重置状态,保持绘制状态以继续绘制 + this.drawingPoints = []; }, // 计算矩形面积(辅助方法) @@ -915,75 +1123,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) { - const distance = Cesium.Cartesian3.distance(centerPoint, activeCursorPosition); - return Math.max(distance, 1); // 最小半径为1米,避免0值错误 - } - return 1; - }, false), - semiMinorAxis: new Cesium.CallbackProperty(() => { - if (centerPoint && activeCursorPosition) { - const distance = Cesium.Cartesian3.distance(centerPoint, activeCursorPosition); - return Math.max(distance, 1); // 最小半径为1米,避免0值错误 + // 确保有有效的圆心位置 + 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 1; - }, 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); }, @@ -991,8 +1260,7 @@ export default { const centerPoint = this.drawingPoints[0]; // 1. 计算最终半径 - let radius = Cesium.Cartesian3.distance(centerPoint, edgePosition); - radius = Math.max(radius, 1); // 最小半径为1米,避免0值错误 + const radius = Cesium.Cartesian3.distance(centerPoint, edgePosition); // 2. 移除动态预览实体 if (this.tempEntity) { @@ -1027,63 +1295,677 @@ export default { type: 'circle' }; - // 6. 结束绘制状态 - this.stopDrawing(); + // 6. 重置状态,保持绘制状态以继续绘制 + this.drawingPoints = []; + this.activeCursorPosition = null; }, - // ================== 实体创建方法 ================== + // 绘制扇形 + startSectorDrawing() { + // 重置绘制状态 + this.drawingPoints = []; // 存储圆心、半径端点、角度端点 - addPointEntity(lat, lng) { - this.entityCounter++ - const id = `point_${this.entityCounter}` + // 1. 清理旧实体 + if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); + if (this.tempPreviewEntity) this.viewer.entities.remove(this.tempPreviewEntity); + this.tempEntity = null; + this.tempPreviewEntity = null; - const entity = this.viewer.entities.add({ - id: id, - name: `点 ${this.entityCounter}`, - position: Cesium.Cartesian3.fromDegrees(lng, lat), - point: { - pixelSize: this.defaultStyles.point.size, - color: Cesium.Color.fromCssColorString(this.defaultStyles.point.color), - outlineColor: Cesium.Color.WHITE, - outlineWidth: 2 - }, - label: { - text: `${this.entityCounter}`, - font: '14px Arial', - fillColor: Cesium.Color.WHITE, - outlineColor: Cesium.Color.BLACK, - outlineWidth: 2, - style: Cesium.LabelStyle.FILL_AND_OUTLINE, - verticalOrigin: Cesium.VerticalOrigin.CENTER, - horizontalOrigin: Cesium.HorizontalOrigin.CENTER + // 2. 鼠标移动事件 + this.drawingHandler.setInputAction((movement) => { + const newPosition = this.getClickPosition(movement.endPosition); + if (newPosition) { + this.activeCursorPosition = newPosition; } - }) + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); - const entityData = { - id, - type: 'point', - lat, - lng, - entity: entity, - color: this.defaultStyles.point.color, - size: this.defaultStyles.point.size, - label: `点 ${this.entityCounter}` - } + // 3. 鼠标点击事件 + this.drawingHandler.setInputAction((click) => { + const position = this.getClickPosition(click.position); + if (position) { + // --- 情况A:第一次点击(确定圆心) --- + if (this.drawingPoints.length === 0) { + this.drawingPoints.push(position); + this.activeCursorPosition = position; - this.allEntities.push(entityData) + // 创建动态预览半径线 + this.tempPreviewEntity = this.viewer.entities.add({ + polyline: { + positions: new Cesium.CallbackProperty(() => { + if (this.drawingPoints.length > 0 && this.activeCursorPosition) { + return [this.drawingPoints[0], this.activeCursorPosition]; + } + return []; + }, false), + width: this.defaultStyles.sector.width, + material: new Cesium.PolylineDashMaterialProperty({ + color: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), + dashLength: 16 + }), + clampToGround: true + } + }); + } + // --- 情况B:第二次点击(确定半径) --- + 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) { + this.viewer.entities.remove(this.tempPreviewEntity); + this.tempPreviewEntity = null; + } - // 添加点击事件 - entity.clickHandler = (e) => { - this.selectEntity(entityData) - e.stopPropagation() - } + // 创建动态预览扇形 + this.tempEntity = this.viewer.entities.add({ + polygon: { + hierarchy: new Cesium.CallbackProperty(() => { + 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, this.activeCursorPosition); + const positions = this.generateSectorPositions(centerPoint, fixedRadius, startAngle, endAngle); + return new Cesium.PolygonHierarchy(positions); + } + return new Cesium.PolygonHierarchy([]); + }, false), + material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color).withAlpha(0.5), + outline: true, + outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), + outlineWidth: this.defaultStyles.sector.width + } + }); + } + // --- 情况C:第三次点击(确定角度) --- + else if (this.drawingPoints.length === 2) { + this.drawingPoints.push(position); + this.activeCursorPosition = null; // 停止动态更新 - return entityData + // 传递角度点位置去结束绘制 + this.finishSectorDrawing(this.drawingPoints[0], this.drawingPoints[1], this.drawingPoints[2]); + } + } + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + // 4. 右键取消 + this.drawingHandler.setInputAction(() => { + this.cancelDrawing(); + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); }, - addLineEntity(positions) { - this.entityCounter++ - const id = `line_${this.entityCounter}` + // 完成扇形绘制 + finishSectorDrawing(centerPoint, radiusPoint, anglePoint) { + const radius = Cesium.Cartesian3.distance(centerPoint, radiusPoint); + const startAngle = this.calculatePointAngle(centerPoint, radiusPoint); + const endAngle = this.calculatePointAngle(centerPoint, anglePoint); + + // 1. 移除动态预览实体 + if (this.tempEntity) { + this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + } + + // 2. 生成扇形顶点 + const positions = this.generateSectorPositions(centerPoint, radius, startAngle, endAngle); + + // 3. 创建最终显示的静态实体 + const finalEntity = this.viewer.entities.add({ + id: 'sector-' + new Date().getTime(), + name: `扇形 ${this.entityCounter}`, + polygon: { + hierarchy: new Cesium.PolygonHierarchy(positions), + material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color).withAlpha(0.5), + outline: true, + outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), + outlineWidth: this.defaultStyles.sector.width, + perPositionHeight: false + } + }); + + // 4. 记录实体 + this.entityCounter++; + const entityData = { + id: `sector_${this.entityCounter}`, + type: 'sector', + center: this.cartesianToLatLng(centerPoint), + radius: radius, + startAngle: startAngle, + endAngle: endAngle, + positions: positions, + entity: finalEntity, + color: this.defaultStyles.sector.color, + opacity: 0.5, + width: this.defaultStyles.sector.width, + label: `扇形 ${this.entityCounter}` + }; + this.allEntities.push(entityData); + + // 5. 重置状态,保持绘制状态以继续绘制 + this.drawingPoints = []; + }, + + // 计算角度 + calculateAngle(center, start, end) { + const startLL = Cesium.Cartographic.fromCartesian(start); + const endLL = Cesium.Cartographic.fromCartesian(end); + const centerLL = Cesium.Cartographic.fromCartesian(center); + + // 计算两点相对于圆心的角度 + 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; + }, + + // 计算角度差 + calculateAngleDiff(center, start, end) { + const startLL = Cesium.Cartographic.fromCartesian(start); + const endLL = Cesium.Cartographic.fromCartesian(end); + const centerLL = Cesium.Cartographic.fromCartesian(center); + + // 计算两点相对于圆心的角度 + 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; + if (angleDiff < 0) { + angleDiff += 2 * Math.PI; + } + + // 确保角度差在合理范围内 + return Math.max(0.1, Math.min(Math.PI * 2, angleDiff)); + }, + + // 计算点相对于圆心的角度 + calculatePointAngle(center, point) { + const pointLL = Cesium.Cartographic.fromCartesian(point); + const centerLL = Cesium.Cartographic.fromCartesian(center); + + // 计算点相对于圆心的角度 + const angle = Math.atan2(pointLL.latitude - centerLL.latitude, pointLL.longitude - centerLL.longitude); + return angle; + }, + + // 生成扇形顶点位置 + generateSectorPositions(center, radius, startAngle, endAngle) { + const positions = []; + const centerLL = Cesium.Cartographic.fromCartesian(center); + + // 添加圆心 + positions.push(center); + + // 计算角度差(顺时针方向) + let angleDiff = startAngle - endAngle; + if (angleDiff < 0) { + angleDiff += 2 * Math.PI; + } + + // 确保角度差不为零 + angleDiff = Math.max(0.01, angleDiff); + + // 计算扇形的顶点数(根据角度差确定,确保平滑) + 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 distance = radius / 6378137; // 转换为弧度 + + 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); + } + + // 闭合扇形 + positions.push(center); + + return positions; + }, + + + + // 计算两点之间的距离(米) + calculateDistance(point1, point2) { + return Cesium.Cartesian3.distance(point1, point2); + }, + + // 绘制箭头 + startArrowDrawing() { + this.drawingPoints = []; // 存储起点和终点 + + // 1. 清理旧实体 + if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); + if (this.tempPreviewEntity) this.viewer.entities.remove(this.tempPreviewEntity); + this.tempEntity = null; + this.tempPreviewEntity = null; + + // 2. 鼠标移动事件 + this.drawingHandler.setInputAction((movement) => { + const newPosition = this.getClickPosition(movement.endPosition); + if (newPosition) { + this.activeCursorPosition = newPosition; + } + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + + // 3. 鼠标点击事件 + this.drawingHandler.setInputAction((click) => { + const position = this.getClickPosition(click.position); + if (position) { + this.drawingPoints.push(position); + + // --- 情况A:第一次点击(确定起点) --- + if (this.drawingPoints.length === 1) { + this.activeCursorPosition = position; // 初始化鼠标位置 + + // 创建动态预览箭头 + 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: 8, // 增加宽度以获得更大的箭头头部 + // 使用箭头材质 + material: new Cesium.PolylineArrowMaterialProperty( + Cesium.Color.fromCssColorString(this.defaultStyles.arrow.color) + ), + clampToGround: true, // 贴地 + widthInMeters: false // 使用像素宽度模式 + } + }); + } + // --- 情况B:第二次点击(确定终点) --- + else { + // 停止监听鼠标移动,因为形状已确定 + this.activeCursorPosition = null; + + // 调用完成逻辑 + this.finishArrowDrawing(); + } + } + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + // 4. 右键取消 + this.drawingHandler.setInputAction(() => { + this.cancelDrawing(); + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); + }, + + // 完成箭头绘制 + finishArrowDrawing() { + // 将预览箭头转换为最终箭头 + if (this.drawingPoints.length > 1) { + // 移除预览箭头 + if (this.tempPreviewEntity) { + this.viewer.entities.remove(this.tempPreviewEntity); + this.tempPreviewEntity = null; + } + + // 创建最终的箭头实体 + const entity = this.addArrowEntity([...this.drawingPoints]); + + // 重置绘制点数组,保持绘制状态以继续绘制 + this.drawingPoints = []; + return entity; + } else { + this.cancelDrawing(); + return null; + } + }, + + // 添加箭头实体 + addArrowEntity(positions) { + this.entityCounter++ + const id = `arrow_${this.entityCounter}` + + // 创建箭头实体,使用固定宽度以确保等比例放大 + const entity = this.viewer.entities.add({ + id: id, + name: `箭头 ${this.entityCounter}`, + polyline: { + positions: positions, + width: 8, // 增加宽度以获得更大的箭头头部 + material: new Cesium.PolylineArrowMaterialProperty( + Cesium.Color.fromCssColorString(this.defaultStyles.arrow.color) + ), + clampToGround: true, + // 使用像素宽度模式,确保箭头在缩放时保持比例 + widthInMeters: false + } + }) + + const entityData = { + id, + type: 'arrow', + points: positions.map(p => this.cartesianToLatLng(p)), + positions: positions, + entity: entity, + color: this.defaultStyles.arrow.color, + width: this.defaultStyles.arrow.width, + label: `箭头 ${this.entityCounter}` + } + + this.allEntities.push(entityData) + + entity.clickHandler = (e) => { + this.selectEntity(entityData) + e.stopPropagation() + } + + return entityData + }, + + // 绘制文本框 + startTextDrawing() { + // 1. 清理旧实体 + if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + + // 2. 鼠标点击事件 + this.drawingHandler.setInputAction((click) => { + const position = this.getClickPosition(click.position); + if (position) { + // 弹出输入框,让用户输入文本内容 + const text = prompt('请输入文本内容:', '文本'); + if (text) { + // 创建文本实体 + this.addTextEntity(position, text); + } + } + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + // 3. 右键取消 + this.drawingHandler.setInputAction(() => { + this.cancelDrawing(); + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); + }, + + // 添加文本实体 + addTextEntity(position, text) { + this.entityCounter++ + const id = `text_${this.entityCounter}` + + // 获取经纬度坐标 + const { lat, lng } = this.cartesianToLatLng(position) + + const entity = this.viewer.entities.add({ + id: id, + name: `文本 ${this.entityCounter}`, + position: position, + label: { + text: text, + font: this.defaultStyles.text.font, + fillColor: Cesium.Color.fromCssColorString(this.defaultStyles.text.color), + outlineColor: Cesium.Color.BLACK, + outlineWidth: 2, + style: Cesium.LabelStyle.FILL_AND_OUTLINE, + verticalOrigin: Cesium.VerticalOrigin.CENTER, + horizontalOrigin: Cesium.HorizontalOrigin.CENTER, + backgroundColor: Cesium.Color.fromCssColorString(this.defaultStyles.text.backgroundColor), + backgroundPadding: new Cesium.Cartesian2(10, 5), + pixelOffset: new Cesium.Cartesian2(0, 0), + // 随地图缩放调整大小 + scaleByDistance: new Cesium.NearFarScalar( + 1000, // 近距离(米) + 1.0, // 近距离时的缩放比例 + 500000, // 远距离(米) + 0.1 // 远距离时的缩放比例(不为0,保持可见) + ), + // 随地图缩放调整透明度 + translucencyByDistance: new Cesium.NearFarScalar( + 1000, // 近距离(米) + 1.0, // 近距离时的透明度 + 500000, // 远距离(米) + 0.3 // 远距离时的透明度 + ) + } + }) + + const entityData = { + id, + type: 'text', + lat, + lng, + text: text, + position: position, + entity: entity, + color: this.defaultStyles.text.color, + font: this.defaultStyles.text.font, + backgroundColor: this.defaultStyles.text.backgroundColor, + label: `文本 ${this.entityCounter}` + } + + this.allEntities.push(entityData) + + entity.clickHandler = (e) => { + this.selectEntity(entityData) + e.stopPropagation() + } + + return entityData + }, + + // 绘制图片 + startImageDrawing() { + // 1. 清理旧实体 + if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); + this.tempEntity = null; + + // 2. 鼠标点击事件 + this.drawingHandler.setInputAction((click) => { + const position = this.getClickPosition(click.position); + if (position) { + // 创建一个隐藏的文件输入元素 + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = 'image/*'; + + // 监听文件选择事件 + fileInput.onchange = (e) => { + const file = e.target.files[0]; + if (file) { + // 创建 FormData 对象用于上传 + const formData = new FormData(); + formData.append('file', file); + + // 上传文件到服务器 + this.uploadImage(formData, position); + } + }; + + // 触发文件选择对话框 + fileInput.click(); + } + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + // 3. 右键取消 + this.drawingHandler.setInputAction(() => { + this.cancelDrawing(); + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); + }, + + // 上传图片 + uploadImage(formData, position) { + // 显示加载状态 + this.$modal.loading('图片上传中,请稍候...'); + + try { + // 创建请求配置 + const config = { + headers: { + 'Content-Type': 'multipart/form-data' + } + }; + + // 发送请求 - 使用项目封装的 request 实例 + request.post('/common/upload', formData, config) + .then((response) => { + this.$modal.closeLoading(); + + if (response.code === 200) { + // 上传成功,获取图片 URL + let imageUrl = response.url; + // 创建图片实体 + this.addImageEntity(position, imageUrl); + } else { + this.$modal.msgError('图片上传失败:' + (response.msg || '未知错误')); + } + }) + .catch((error) => { + this.$modal.closeLoading(); + + if (error.response) { + // 服务器返回错误 + if (error.response.data && error.response.data.msg) { + this.$modal.msgError('图片上传失败:' + error.response.data.msg); + } else { + this.$modal.msgError('图片上传失败:服务器返回错误 ' + error.response.status); + } + } else if (error.request) { + // 请求发送但没有收到响应 + this.$modal.msgError('图片上传失败:无法连接到服务器,请检查网络'); + } else { + // 请求配置错误 + this.$modal.msgError('图片上传失败:' + error.message); + } + }); + } catch (error) { + this.$modal.closeLoading(); + this.$modal.msgError('图片上传失败:' + error.message); + } + }, + + // 添加图片实体 + addImageEntity(position, imageUrl) { + this.entityCounter++ + const id = `image_${this.entityCounter}` + + // 获取经纬度坐标 + const { lat, lng } = this.cartesianToLatLng(position) + + const entity = this.viewer.entities.add({ + id: id, + name: `图片 ${this.entityCounter}`, + position: position, + billboard: { + image: imageUrl, + width: this.defaultStyles.image.width, + height: this.defaultStyles.image.height, + verticalOrigin: Cesium.VerticalOrigin.CENTER, + horizontalOrigin: Cesium.HorizontalOrigin.CENTER, + // 随地图缩放调整大小 + scaleByDistance: new Cesium.NearFarScalar( + 1000, // 近距离(米) + 1.0, // 近距离时的缩放比例 + 500000, // 远距离(米) + 0.1 // 远距离时的缩放比例(不为0,保持可见) + ), + // 随地图缩放调整透明度 + translucencyByDistance: new Cesium.NearFarScalar( + 1000, // 近距离(米) + 1.0, // 近距离时的透明度 + 500000, // 远距离(米) + 0.3 // 远距离时的透明度 + ) + } + }) + + const entityData = { + id, + type: 'image', + lat, + lng, + imageUrl: imageUrl, + position: position, + entity: entity, + width: this.defaultStyles.image.width, + height: this.defaultStyles.image.height, + label: `图片 ${this.entityCounter}` + } + + this.allEntities.push(entityData) + + entity.clickHandler = (e) => { + this.selectEntity(entityData) + e.stopPropagation() + } + + return entityData + }, + + // ================== 实体创建方法 ================== + + addPointEntity(lat, lng) { + this.entityCounter++ + const id = `point_${this.entityCounter}` + + const entity = this.viewer.entities.add({ + id: id, + name: `点 ${this.entityCounter}`, + position: Cesium.Cartesian3.fromDegrees(lng, lat), + point: { + pixelSize: this.defaultStyles.point.size, + color: Cesium.Color.fromCssColorString(this.defaultStyles.point.color), + outlineColor: Cesium.Color.WHITE, + outlineWidth: 2 + }, + label: { + text: `${this.entityCounter}`, + font: '14px Arial', + fillColor: Cesium.Color.WHITE, + outlineColor: Cesium.Color.BLACK, + outlineWidth: 2, + style: Cesium.LabelStyle.FILL_AND_OUTLINE, + verticalOrigin: Cesium.VerticalOrigin.CENTER, + horizontalOrigin: Cesium.HorizontalOrigin.CENTER + } + }) + + const entityData = { + id, + type: 'point', + lat, + lng, + entity: entity, + color: this.defaultStyles.point.color, + size: this.defaultStyles.point.size, + label: `点 ${this.entityCounter}` + } + + this.allEntities.push(entityData) + + // 添加点击事件 + entity.clickHandler = (e) => { + this.selectEntity(entityData) + e.stopPropagation() + } + + return entityData + }, + + addLineEntity(positions, pointEntities = []) { + this.entityCounter++ + const id = `line_${this.entityCounter}` const entity = this.viewer.entities.add({ id: id, @@ -1102,6 +1984,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}` @@ -1280,6 +2163,7 @@ export default { }, + // ================== 实体管理 ================== selectEntity(entity) { @@ -1355,24 +2239,44 @@ 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 } } }, + clearAll() { // 1. 检查数组是否有内容 if (this.allEntities && this.allEntities.length > 0) { @@ -1392,6 +2296,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); } @@ -1424,7 +2335,11 @@ export default { line: '线', polygon: '面', rectangle: '矩形', - circle: '圆形' + circle: '圆形', + sector: '扇形', + arrow: '箭头', + text: '文本', + image: '图片' } return names[type] || type }, @@ -1588,12 +2503,11 @@ export default { case 'circle': // 检查半径是否有效 - let radius = entityData.data.radius || 1000 + const radius = entityData.data.radius || 1000 if (radius <= 0) { this.$message.error('圆形半径必须大于0') return } - radius = Math.max(radius, 1); // 最小半径为1米,避免0值错误 entity = this.viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(entityData.data.center.lng, entityData.data.center.lat), ellipse: { @@ -1707,6 +2621,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() { // ... 原有的比例尺更新代码保持不变 }, @@ -1715,6 +2744,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 diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index 8bf4f5e..3633faa 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -26,100 +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 }}
+
+
+ +
+
+
+
+
- -
- - 添加航点 - - - 取消 - -
-
-
@@ -189,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') }, @@ -317,6 +365,7 @@ export default { }, handleSelectRoute(route) { + // 确保航线有航点数据 this.$emit('select-route', route) }, @@ -328,6 +377,14 @@ export default { this.$emit('create-route') }, + handleCreateRouteForPlan(plan) { + this.$emit('create-route', plan) + }, + + handleDeletePlan(plan) { + // 暂时留空,后续实现 + }, + handleOpenPlanDialog(plan) { this.$emit('open-plan-dialog', plan) }, @@ -336,16 +393,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) { @@ -469,115 +537,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); +.tree-item.plan-item .tree-item-header { + background: rgba(255, 255, 255, 0.9) !important; } -.route-info { - flex: 1; +.tree-item.route-item .tree-item-header { + background: rgba(255, 255, 255, 0.8) !important; } -.route-name { - font-size: 14px; - font-weight: 500; - color: #333; +.tree-item.route-item:not(.active) .tree-item-header { + background: rgba(255, 255, 255, 0.8) !important; } -.route-meta { - font-size: 12px; - color: #999; -} - -.route-actions { - display: flex; - gap: 8px; -} - -.route-actions i { - cursor: pointer; - color: #008aff; - font-size: 14px; - padding: 4px; - border-radius: 4px; - transition: all 0.2s; +.tree-item.waypoint-item .tree-item-header { + background: rgba(224, 238, 255, 0.8); } -.route-actions i:hover { - background: rgba(0, 138, 255, 0.1); - transform: scale(1.2); -} - -.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; @@ -586,11 +629,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 b522d75..84a2431 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -106,6 +106,7 @@ ({ @@ -741,9 +744,7 @@ export default { this.isMenuHidden = true; }, - selectTopNav(item) { - console.log('选中顶部导航:', item); - }, + // 右侧面板操作 showRightPanel() { @@ -751,6 +752,11 @@ export default { this.$message.info('显示右侧面板'); }, + // 顶部导航栏操作 + selectTopNav(item) { + this.activeMenu = item.id; + }, + // 文件下拉菜单方法 savePlan() { this.$message.success('保存计划'); @@ -758,35 +764,43 @@ export default { importPlanFile() { this.$message.success('导入计划'); + // 这里可以添加导入计划文件的逻辑 }, importACD() { this.$message.success('导入ACD'); + // 这里可以添加导入ACD文件的逻辑 }, importATO() { this.$message.success('导入ATO'); + // 这里可以添加导入ATO文件的逻辑 }, importLayer() { this.$message.success('导入图层'); + // 这里可以添加导入图层的逻辑 }, importRoute() { this.$message.success('导入航线'); + // 这里可以添加导入航线的逻辑 }, exportPlan() { this.$message.success('导出计划'); + // 这里可以添加导出计划的逻辑 }, // 编辑下拉菜单方法 routeEdit() { this.$message.success('航线编辑'); + // 这里可以添加航线编辑的逻辑 }, militaryMarking() { this.$message.success('军事标绘'); + // 这里可以添加军事标绘的逻辑 }, iconEdit() { @@ -879,31 +893,38 @@ export default { timeSettings() { this.$message.success('时间设置'); + // 这里可以添加时间设置的逻辑 }, aircraftSettings() { this.$message.success('机型设置'); + // 这里可以添加机型设置的逻辑 }, keyEventEdit() { this.$message.success('关键事件编辑'); + // 这里可以添加关键事件编辑的逻辑 }, missileLaunch() { this.$message.success('导弹发射'); + // 这里可以添加导弹发射的逻辑 }, // 视图下拉菜单方法 toggle2D3D() { this.$message.success('2D/3D切换'); + // 这里可以添加2D/3D切换的逻辑 }, toggleRuler() { this.$message.success('显示/隐藏标尺'); + // 这里可以添加标尺显示/隐藏的逻辑 }, toggleGrid() { this.$message.success('显示/隐藏网格'); + // 这里可以添加网格显示/隐藏的逻辑 }, saveScale(scale) { @@ -915,10 +936,12 @@ export default { // 地图下拉菜单方法 loadTerrain() { this.$message.success('加载/切换地形'); + // 这里可以添加地形加载/切换的逻辑 }, changeProjection() { this.$message.success('投影'); + // 这里可以添加投影切换的逻辑 }, loadAeroChart() { @@ -938,18 +961,22 @@ export default { // 工具下拉菜单方法 routeCalculation() { this.$message.success('航线计算'); + // 这里可以添加航线计算的逻辑 }, conflictDisplay() { this.$message.success('冲突显示'); + // 这里可以添加冲突显示的逻辑 }, dataMaterials() { this.$message.success('数据资料'); + // 这里可以添加数据资料管理的逻辑 }, coordinateConversion() { this.$message.success('坐标换算'); + // 这里可以添加坐标换算的逻辑 }, // 选项下拉菜单方法 @@ -1013,17 +1040,20 @@ export default { systemDescription() { this.$message.success('系统说明'); + // 这里可以添加系统说明的逻辑 }, // 收藏下拉菜单方法 layerFavorites() { this.$message.success('图层收藏'); + // 这里可以添加图层收藏的逻辑 }, routeFavorites() { this.$message.success('航线收藏'); }, + hideRightPanel() { this.isRightPanelHidden = true; this.$message.info('隐藏右侧面板'); @@ -1035,6 +1065,11 @@ export default { this.handleMenuAction(item.action) } + // 点击方案、平台、冲突等菜单项时,停止地图绘制状态 + if (item.id === 'file' || item.id === 'start' || item.id === 'insert') { + this.drawDom = false; + } + // 点击左侧的方案、冲突、平台时,切换右侧面板内容 if (item.id === 'file') { // 如果当前已经是方案标签页,则关闭右侧面板 @@ -1062,14 +1097,19 @@ export default { } } else if(item.id === 'modify'){ this.drawDom = !this.drawDom + // 点击修改图标进行地图绘制时,自动收起右侧面板 + this.isRightPanelHidden = true; console.log(this.drawDom,999999) - } - if (item.id === 'deduction') { + } else if (item.id === 'deduction') { // 点击推演按钮,显示/隐藏K时弹出框 this.showKTimePopup = !this.showKTimePopup; - } - if (item.id === 'save') { - this.savePlan(); + // 点击推演时,也停止地图绘制状态 + this.drawDom = false; + } else { + // 点击其他菜单项时,也自动收起右侧面板 + this.isRightPanelHidden = true; + // 点击其他菜单项时,也停止地图绘制状态 + this.drawDom = false; } }, @@ -1161,49 +1201,83 @@ export default { const minutes = (val % 4) * 15; return `K+${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`; }, + // 航线操作 + selectPlan(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.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) { @@ -1218,13 +1292,7 @@ export default { this.$message.error('无法加载该航线的详细航点数据'); } }, - // 航线操作 - selectPlan(plan) { - this.selectedPlanId = plan.id; - this.selectedPlanDetails = plan; - this.selectedRouteId = null; - this.selectedRouteDetails = null; - }, + createPlan() { const newId = Date.now(); const newPlan = { @@ -1249,8 +1317,9 @@ export default { const count = this.selectedRouteDetails.waypoints.length + 1; this.selectedRouteDetails.waypoints.push({ name: `WP${count}`, - altitude: 1000, - speed: 250 + altitude: 5000, + speed: '800km/h', + eta: `K+01:${(count * 15).toString().padStart(2, '0')}:00` }); if (this.selectedPlanDetails) { const route = this.selectedPlanDetails.routes.find(r => r.id === this.selectedRouteId); @@ -1273,6 +1342,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; @@ -1304,7 +1411,6 @@ export default { \ No newline at end of file + diff --git a/ruoyi-ui/src/views/selectRoom/index.vue b/ruoyi-ui/src/views/selectRoom/index.vue index 0f4daef..13e5de4 100644 --- a/ruoyi-ui/src/views/selectRoom/index.vue +++ b/ruoyi-ui/src/views/selectRoom/index.vue @@ -46,7 +46,6 @@
-
-

暂无房间

点击下方按钮创建您的第一个房间

-
- -
删除房间
-