From b9f3daa8c6c9c856a92810b370b472027775b04b Mon Sep 17 00:00:00 2001 From: ctw <1051735452@qq.com> Date: Sun, 8 Feb 2026 14:53:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=AF=E6=8B=96=E6=8B=BD=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui/.env.development | 2 +- ruoyi-ui/src/views/cesiumMap/ContextMenu.vue | 30 ++ ruoyi-ui/src/views/cesiumMap/index.vue | 526 ++++++++++++++++++++++++++- ruoyi-ui/src/views/childRoom/RightPanel.vue | 58 ++- ruoyi-ui/src/views/childRoom/index.vue | 87 +++-- ruoyi-ui/vue.config.js | 2 +- 6 files changed, 659 insertions(+), 46 deletions(-) diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development index 4d098ba..0e7127a 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://127.0.0.1:8080' # 路由懒加载 VUE_CLI_BABEL_TRANSPILE_MODULES = true diff --git a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue index 796c828..096d43b 100644 --- a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue +++ b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue @@ -272,6 +272,24 @@ + + + @@ -324,6 +342,18 @@ export default { this.$emit('delete') }, + handleShowTransformBox() { + this.$emit('show-transform-box') + }, + + handleEditPlatformPosition() { + this.$emit('edit-platform-position') + }, + + handleEditPlatformHeading() { + this.$emit('edit-platform-heading') + }, + toggleColorPicker(property) { if (this.showColorPickerFor === property) { this.showColorPickerFor = null diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 9843259..f25a40e 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -34,6 +34,9 @@ :entity-data="contextMenu.entityData" @delete="deleteEntityFromContextMenu" @update-property="updateEntityProperty" + @edit-platform-position="openPlatformIconPositionDialog" + @edit-platform-heading="openPlatformIconHeadingDialog" + @show-transform-box="showPlatformIconTransformBox" /> @@ -44,6 +47,11 @@ @cancel="handleLocateCancel" /> + +
+ {{ platformIconRotateTip }} +
+
@@ -174,7 +182,25 @@ export default { currentScaleUnit: 'm', isApplyingScale: false, // 定位相关 - locateDialogVisible: false + locateDialogVisible: false, + // 平台图标图形化编辑:拖拽移动、旋转、伸缩框 + draggingPlatformIcon: null, + rotatingPlatformIcon: null, + platformIconRotateTip: '', + platformIconDragCameraEnabled: true, + selectedPlatformIcon: null, + pendingDragIcon: null, + dragStartScreenPos: null, + draggingRotateHandle: null, + draggingScaleHandle: null, + clickedOnEmpty: false, + PLATFORM_ICON_BASE_SIZE: 72, + PLATFORM_SCALE_BOX_HALF_DEG: 0.0005, + PLATFORM_SCALE_BOX_MIN_HALF_DEG: 0.0007, + PLATFORM_ROTATE_HANDLE_OFFSET_DEG: 0.0009, + PLATFORM_DRAG_THRESHOLD_PX: 10, + DESIRED_BOX_HALF_PX: 58, + DESIRED_ROTATE_OFFSET_PX: 50 } }, components: { @@ -538,6 +564,234 @@ export default { const backendUrl = process.env.VUE_APP_BACKEND_URL || ''; return backendUrl + cleanPath; }, + + /** 默认平台图标(无 imageUrl 时使用):简单飞机剪影 SVG */ + getDefaultPlatformIconDataUrl() { + const svg = ''; + return 'data:image/svg+xml,' + encodeURIComponent(svg); + }, + + /** 伸缩框旋转手柄图标 SVG:蓝底、白边、白色弧形箭头 */ + getRotationHandleIconDataUrl() { + const svg = '' + + '' + + '' + + '' + + ''; + return 'data:image/svg+xml,' + encodeURIComponent(svg); + }, + + /** 从手柄实体 id 解析出平台图标 entityData 与类型(rotate / scale-0~3) */ + getPlatformIconDataFromHandleId(handleEntityId) { + if (!handleEntityId || typeof handleEntityId !== 'string') return null; + if (handleEntityId.endsWith('-rotate-handle')) { + const baseId = handleEntityId.replace(/-rotate-handle$/, ''); + const ed = this.allEntities.find(e => e.type === 'platformIcon' && e.id === baseId); + return ed ? { entityData: ed, type: 'rotate' } : null; + } + const scaleIdx = handleEntityId.lastIndexOf('-scale-'); + if (scaleIdx !== -1) { + const baseId = handleEntityId.substring(0, scaleIdx); + const cornerIndex = parseInt(handleEntityId.substring(scaleIdx + 7), 10); + if (!isNaN(cornerIndex) && cornerIndex >= 0 && cornerIndex <= 3) { + const ed = this.allEntities.find(e => e.type === 'platformIcon' && e.id === baseId); + return ed ? { entityData: ed, type: 'scale', cornerIndex } : null; + } + } + return null; + }, + + /** 在当前视野下,图标位置处 1 像素对应的经纬度(用于伸缩框固定屏幕尺寸) */ + getDegreesPerPixelAt(lng, lat) { + if (!this.viewer || this.viewer.scene.mode !== Cesium.SceneMode.SCENE2D) { + return { degPerPxLng: this.PLATFORM_SCALE_BOX_HALF_DEG / this.DESIRED_BOX_HALF_PX, degPerPxLat: this.PLATFORM_SCALE_BOX_HALF_DEG / this.DESIRED_BOX_HALF_PX }; + } + const center = Cesium.Cartesian3.fromDegrees(lng, lat); + const east = Cesium.Cartesian3.fromDegrees(lng + 0.005, lat); + const north = Cesium.Cartesian3.fromDegrees(lng, lat + 0.005); + const sc = Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene, center); + const se = Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene, east); + const sn = Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene, north); + if (!sc || !se || !sn) { + return { degPerPxLng: this.PLATFORM_SCALE_BOX_HALF_DEG / this.DESIRED_BOX_HALF_PX, degPerPxLat: this.PLATFORM_SCALE_BOX_HALF_DEG / this.DESIRED_BOX_HALF_PX }; + } + const pxLng = Math.max(1, Math.abs(se.x - sc.x)); + const pxLat = Math.max(1, Math.abs(sn.y - sc.y)); + return { + degPerPxLng: 0.005 / pxLng, + degPerPxLat: 0.005 / pxLat + }; + }, + + /** 更新平台图标 billboard 的宽高(根据 iconScale) */ + updatePlatformIconBillboardSize(entityData) { + if (!entityData || !entityData.entity || !entityData.entity.billboard) return; + const scale = Math.max(0.2, Math.min(3, entityData.iconScale || 1)); + entityData.iconScale = scale; + const size = this.PLATFORM_ICON_BASE_SIZE * scale; + entityData.entity.billboard.width = new Cesium.ConstantProperty(size); + entityData.entity.billboard.height = new Cesium.ConstantProperty(size); + }, + + /** 显示伸缩框:旋转手柄 + 四角缩放手柄 + 矩形边线(按屏幕像素固定尺寸,任意缩放都易点) */ + showTransformHandles(entityData) { + if (!this.viewer || !entityData || entityData.type !== 'platformIcon') return; + this.removeTransformHandles(entityData); + const id = entityData.id; + const lng = entityData.lng; + const lat = entityData.lat; + const dpp = this.getDegreesPerPixelAt(lng, lat); + const baseHalfDeg = this.DESIRED_BOX_HALF_PX * Math.min(dpp.degPerPxLng, dpp.degPerPxLat); + const half = Math.max((entityData.iconScale || 1) * baseHalfDeg, baseHalfDeg * 0.5); + const rotOffsetLat = this.DESIRED_ROTATE_OFFSET_PX * dpp.degPerPxLat; + const rotOffset = Math.max(rotOffsetLat, this.PLATFORM_ROTATE_HANDLE_OFFSET_DEG); + const rotationHandle = this.viewer.entities.add({ + id: id + '-rotate-handle', + position: Cesium.Cartesian3.fromDegrees(lng, lat + rotOffset), + billboard: { + image: this.getRotationHandleIconDataUrl(), + width: 36, + height: 36, + verticalOrigin: Cesium.VerticalOrigin.BOTTOM, + horizontalOrigin: Cesium.HorizontalOrigin.CENTER, + disableDepthTestDistance: Number.POSITIVE_INFINITY + } + }); + const corners = [ + Cesium.Cartesian3.fromDegrees(lng + half, lat + half), + Cesium.Cartesian3.fromDegrees(lng - half, lat + half), + Cesium.Cartesian3.fromDegrees(lng - half, lat - half), + Cesium.Cartesian3.fromDegrees(lng + half, lat - half) + ]; + const scaleHandles = corners.map((pos, i) => + this.viewer.entities.add({ + id: id + '-scale-' + i, + position: pos, + point: { + pixelSize: 14, + color: Cesium.Color.WHITE, + outlineColor: Cesium.Color.fromCssColorString('#008aff'), + outlineWidth: 3, + disableDepthTestDistance: Number.POSITIVE_INFINITY + } + }) + ); + const linePositions = [corners[0], corners[1], corners[2], corners[3], corners[0]]; + const frameLine = this.viewer.entities.add({ + id: id + '-scale-frame', + polyline: { + positions: linePositions, + width: 3, + material: Cesium.Color.fromCssColorString('#008aff'), + clampToGround: true, + disableDepthTestDistance: Number.POSITIVE_INFINITY + } + }); + entityData.transformHandles = { + rotation: rotationHandle, + scale: scaleHandles, + frame: frameLine + }; + }, + + /** 移除伸缩框手柄与边线 */ + removeTransformHandles(entityData) { + if (!entityData || !entityData.transformHandles) return; + const h = entityData.transformHandles; + if (h.rotation) this.viewer.entities.remove(h.rotation); + if (h.scale) h.scale.forEach(e => this.viewer.entities.remove(e)); + if (h.frame) this.viewer.entities.remove(h.frame); + entityData.transformHandles = null; + }, + + /** 根据图标当前位置、iconScale 与当前视野更新伸缩框(保持固定像素尺寸) */ + updateTransformHandlePositions(entityData) { + if (!entityData || !entityData.transformHandles) return; + const lng = entityData.lng; + const lat = entityData.lat; + const dpp = this.getDegreesPerPixelAt(lng, lat); + const baseHalfDeg = this.DESIRED_BOX_HALF_PX * Math.min(dpp.degPerPxLng, dpp.degPerPxLat); + const half = Math.max((entityData.iconScale || 1) * baseHalfDeg, baseHalfDeg * 0.5); + const rotOffsetLat = this.DESIRED_ROTATE_OFFSET_PX * dpp.degPerPxLat; + const rotOffset = Math.max(rotOffsetLat, this.PLATFORM_ROTATE_HANDLE_OFFSET_DEG); + entityData.transformHandles.rotation.position = Cesium.Cartesian3.fromDegrees(lng, lat + rotOffset); + const corners = [ + Cesium.Cartesian3.fromDegrees(lng + half, lat + half), + Cesium.Cartesian3.fromDegrees(lng - half, lat + half), + Cesium.Cartesian3.fromDegrees(lng - half, lat - half), + Cesium.Cartesian3.fromDegrees(lng + half, lat - half) + ]; + entityData.transformHandles.scale.forEach((ent, i) => { ent.position = corners[i]; }); + const linePositions = [corners[0], corners[1], corners[2], corners[3], corners[0]]; + entityData.transformHandles.frame.polyline.positions = new Cesium.ConstantProperty(linePositions); + this.viewer.scene.requestRender(); + }, + + /** + * 从右侧平台列表拖拽放置到地图:在放置点添加平台图标实体,支持后续修改位置与朝向。 + * @param {Object} platform - 平台数据 { id, name, type, imageUrl, iconUrl, icon, color } + * @param {number} clientX - 放置点的视口 X + * @param {number} clientY - 放置点的视口 Y + */ + addPlatformIconFromDrag(platform, clientX, clientY) { + if (!this.viewer || !platform) return; + const canvas = this.viewer.scene.canvas; + const rect = canvas.getBoundingClientRect(); + const x = clientX - rect.left; + const y = clientY - rect.top; + const cartesian = this.viewer.camera.pickEllipsoid(new Cesium.Cartesian2(x, y), this.viewer.scene.globe.ellipsoid); + if (!cartesian) { + this.$message && this.$message.warning('请将图标放置到地图有效区域内'); + return; + } + const iconUrl = platform.imageUrl || platform.iconUrl; + const imageSrc = iconUrl ? this.formatPlatformIconUrl(iconUrl) : this.getDefaultPlatformIconDataUrl(); + this.entityCounter++; + const id = `platformIcon_${this.entityCounter}`; + const headingDeg = 0; + const rotation = Math.PI / 2 - (headingDeg * Math.PI / 180); + const iconScale = 1.0; + const size = this.PLATFORM_ICON_BASE_SIZE * iconScale; + const entity = this.viewer.entities.add({ + id, + name: platform.name || '平台', + position: cartesian, + billboard: { + image: imageSrc, + width: size, + height: size, + verticalOrigin: Cesium.VerticalOrigin.CENTER, + horizontalOrigin: Cesium.HorizontalOrigin.CENTER, + rotation, + scaleByDistance: new Cesium.NearFarScalar(500, 1.2, 200000, 0.35), + translucencyByDistance: new Cesium.NearFarScalar(1000, 1.0, 500000, 0.6) + } + }); + const { lat, lng } = this.cartesianToLatLng(cartesian); + const entityData = { + id, + type: 'platformIcon', + platformId: platform.id, + platform, + name: platform.name || '平台', + heading: headingDeg, + lat, + lng, + entity, + imageUrl: iconUrl, + label: platform.name || '平台', + iconScale, + transformHandles: null + }; + this.allEntities.push(entityData); + this.$nextTick(() => { + if (this.selectedPlatformIcon) this.removeTransformHandles(this.selectedPlatformIcon); + this.selectedPlatformIcon = entityData; + this.showTransformHandles(entityData); + this.$message && this.$message.success('已放置。上方箭头旋转、四角调大小、拖动图标移动;点击空白收起'); + }); + }, //正式航线渲染函数 renderRouteWaypoints(waypoints, routeId = 'default', platformId, platform, style) { if (!waypoints || waypoints.length < 1) return; @@ -1238,6 +1492,7 @@ export default { }) this.initScaleBar() this.initPointMovement() + this.initPlatformIconInteraction() this.initRightClickHandler() this.initHoverHandler() this.initMouseCoordinates() @@ -1254,6 +1509,10 @@ export default { const pickedObject = this.viewer.scene.pick(click.position); if (Cesium.defined(pickedObject) && pickedObject.id) { const entity = pickedObject.id; + const idStr = (entity && entity.id) ? entity.id : ''; + if (idStr && (idStr.endsWith('-rotate-handle') || idStr.indexOf('-scale-') !== -1)) return; + const platformIconData = this.allEntities.find(e => e.type === 'platformIcon' && e.entity === entity); + if (platformIconData) return; // --- 修正后的安全日志 --- console.log(">>> [点击检测] 实体ID:", entity.id); @@ -1328,6 +1587,183 @@ export default { } }, Cesium.ScreenSpaceEventType.RIGHT_CLICK) }, + + /** 平台图标图形化操作:伸缩框(旋转手柄 + 四角缩放)、拖拽移动、单击选中 */ + initPlatformIconInteraction() { + const canvas = this.viewer.scene.canvas; + this.platformIconHandler = new Cesium.ScreenSpaceEventHandler(canvas); + + this.platformIconHandler.setInputAction((click) => { + if (this.isDrawing || this.rotatingPlatformIcon) return; + const picked = this.viewer.scene.pick(click.position); + this.clickedOnEmpty = !Cesium.defined(picked) || !picked.id; + if (picked && picked.id) { + const idStr = typeof picked.id === 'string' ? picked.id : (picked.id.id || ''); + if (idStr.endsWith('-scale-frame')) { + this.clickedOnEmpty = false; + return; + } + const handleInfo = this.getPlatformIconDataFromHandleId(idStr); + if (handleInfo) { + this.clickedOnEmpty = false; + if (handleInfo.type === 'rotate') { + this.draggingRotateHandle = handleInfo.entityData; + this.platformIconDragCameraEnabled = this.viewer.scene.screenSpaceCameraController.enableInputs; + this.viewer.scene.screenSpaceCameraController.enableInputs = false; + return; + } + if (handleInfo.type === 'scale') { + this.draggingScaleHandle = { entityData: handleInfo.entityData, cornerIndex: handleInfo.cornerIndex }; + this.platformIconDragCameraEnabled = this.viewer.scene.screenSpaceCameraController.enableInputs; + this.viewer.scene.screenSpaceCameraController.enableInputs = false; + return; + } + } + const entityData = this.allEntities.find(e => e.type === 'platformIcon' && e.entity === picked.id); + if (entityData) { + this.pendingDragIcon = entityData; + this.dragStartScreenPos = { x: click.position.x, y: click.position.y }; + return; + } + } + }, Cesium.ScreenSpaceEventType.LEFT_DOWN); + + this.platformIconHandler.setInputAction((movement) => { + if (this.draggingRotateHandle) { + const ed = this.draggingRotateHandle; + if (ed.entity && ed.entity.position) { + const now = Cesium.JulianDate.now(); + const position = ed.entity.position.getValue(now); + if (position) { + const screenPos = Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene, position); + if (screenPos) { + const dx = movement.endPosition.x - screenPos.x; + const dy = movement.endPosition.y - screenPos.y; + const screenAngle = Math.atan2(dy, dx); + ed.entity.billboard.rotation = -screenAngle; + let headingDeg = (screenAngle + Math.PI / 2) * (180 / Math.PI); + if (headingDeg < 0) headingDeg += 360; + if (headingDeg >= 360) headingDeg -= 360; + ed.heading = Math.round(headingDeg); + } + } + } + this.viewer.scene.requestRender(); + return; + } + if (this.draggingScaleHandle) { + const { entityData: ed, cornerIndex } = this.draggingScaleHandle; + const cartesian = this.viewer.camera.pickEllipsoid(movement.endPosition, this.viewer.scene.globe.ellipsoid); + if (cartesian) { + const { lat: newLat, lng: newLng } = this.cartesianToLatLng(cartesian); + const lng = ed.lng; + const lat = ed.lat; + const dpp = this.getDegreesPerPixelAt(lng, lat); + const baseHalf = this.DESIRED_BOX_HALF_PX * Math.min(dpp.degPerPxLng, dpp.degPerPxLat); + let newHalfDeg; + if (cornerIndex === 0) newHalfDeg = Math.min(newLng - lng, newLat - lat); + else if (cornerIndex === 1) newHalfDeg = Math.min(lng - newLng, newLat - lat); + else if (cornerIndex === 2) newHalfDeg = Math.min(lng - newLng, lat - newLat); + else newHalfDeg = Math.min(newLng - lng, lat - newLat); + if (newHalfDeg > 0.0001 && baseHalf > 1e-10) { + ed.iconScale = Math.max(0.2, Math.min(3, newHalfDeg / baseHalf)); + this.updatePlatformIconBillboardSize(ed); + this.updateTransformHandlePositions(ed); + } + } + return; + } + if (this.pendingDragIcon) { + const dx = movement.endPosition.x - this.dragStartScreenPos.x; + const dy = movement.endPosition.y - this.dragStartScreenPos.y; + if (Math.sqrt(dx * dx + dy * dy) > (this.PLATFORM_DRAG_THRESHOLD_PX || 10)) { + this.draggingPlatformIcon = this.pendingDragIcon; + this.pendingDragIcon = null; + this.platformIconDragCameraEnabled = this.viewer.scene.screenSpaceCameraController.enableInputs; + this.viewer.scene.screenSpaceCameraController.enableInputs = false; + } + } + if (this.draggingPlatformIcon) { + const cartesian = this.viewer.camera.pickEllipsoid(movement.endPosition, this.viewer.scene.globe.ellipsoid); + if (cartesian) { + this.draggingPlatformIcon.entity.position = cartesian; + const { lat, lng } = this.cartesianToLatLng(cartesian); + this.draggingPlatformIcon.lat = lat; + this.draggingPlatformIcon.lng = lng; + if (this.selectedPlatformIcon === this.draggingPlatformIcon) { + this.updateTransformHandlePositions(this.draggingPlatformIcon); + } + } + this.viewer.scene.requestRender(); + } + if (this.rotatingPlatformIcon && this.rotatingPlatformIcon.entity && this.rotatingPlatformIcon.entity.position) { + const now = Cesium.JulianDate.now(); + const position = this.rotatingPlatformIcon.entity.position.getValue(now); + if (position) { + const screenPos = Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene, position); + if (screenPos) { + const dx = movement.endPosition.x - screenPos.x; + const dy = movement.endPosition.y - screenPos.y; + const screenAngle = Math.atan2(dy, dx); + this.rotatingPlatformIcon.entity.billboard.rotation = -screenAngle; + let headingDeg = (screenAngle + Math.PI / 2) * (180 / Math.PI); + if (headingDeg < 0) headingDeg += 360; + if (headingDeg >= 360) headingDeg -= 360; + this.rotatingPlatformIcon.heading = Math.round(headingDeg); + } + } + this.viewer.scene.requestRender(); + } + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + + this.platformIconHandler.setInputAction(() => { + if (this.pendingDragIcon) { + if (this.selectedPlatformIcon === this.pendingDragIcon) { + this.removeTransformHandles(this.selectedPlatformIcon); + this.selectedPlatformIcon = null; + } else { + if (this.selectedPlatformIcon) this.removeTransformHandles(this.selectedPlatformIcon); + this.selectedPlatformIcon = this.pendingDragIcon; + this.showTransformHandles(this.pendingDragIcon); + } + this.pendingDragIcon = null; + this.dragStartScreenPos = null; + } + if (this.clickedOnEmpty && this.selectedPlatformIcon) { + this.removeTransformHandles(this.selectedPlatformIcon); + this.selectedPlatformIcon = null; + } + this.clickedOnEmpty = false; + if (this.draggingRotateHandle) { + this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; + this.draggingRotateHandle = null; + } + if (this.draggingScaleHandle) { + this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; + this.draggingScaleHandle = null; + } + if (this.draggingPlatformIcon) { + this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; + this.draggingPlatformIcon = null; + } + if (this.rotatingPlatformIcon) { + this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; + this.rotatingPlatformIcon = null; + this.platformIconRotateTip = ''; + } + }, Cesium.ScreenSpaceEventType.LEFT_UP); + + const onCameraMoveEnd = () => { + if (this.selectedPlatformIcon && this.selectedPlatformIcon.transformHandles) { + this.updateTransformHandlePositions(this.selectedPlatformIcon); + } + }; + this.viewer.camera.moveEnd.addEventListener(onCameraMoveEnd); + this.platformIconCameraListener = () => { + this.viewer.camera.moveEnd.removeEventListener(onCameraMoveEnd); + }; + }, + // 初始化鼠标悬停事件处理器 initHoverHandler() { // 创建屏幕空间事件处理器 @@ -3385,6 +3821,53 @@ export default { this.contextMenu.visible = false } }, + + /** 右键「显示伸缩框」:选中该图标并显示旋转/缩放手柄 */ + showPlatformIconTransformBox() { + const fromMenu = this.contextMenu.entityData + if (!fromMenu || fromMenu.type !== 'platformIcon' || !fromMenu.entity) { + this.contextMenu.visible = false + return + } + const ed = this.allEntities.find(e => e.type === 'platformIcon' && e.id === fromMenu.id) || fromMenu + if (!ed.entity) { + this.contextMenu.visible = false + return + } + if (ed.lat == null || ed.lng == null) { + const now = Cesium.JulianDate.now() + const pos = ed.entity.position && ed.entity.position.getValue(now) + if (pos) { + const ll = this.cartesianToLatLng(pos) + ed.lat = ll.lat + ed.lng = ll.lng + } + } + if (this.selectedPlatformIcon) this.removeTransformHandles(this.selectedPlatformIcon) + this.selectedPlatformIcon = ed + this.showTransformHandles(ed) + this.contextMenu.visible = false + this.viewer.scene.requestRender() + this.$message && this.$message.success('已显示伸缩框') + }, + + /** 位置改为图形化:直接拖动图标即可,无需弹窗 */ + openPlatformIconPositionDialog() { + this.contextMenu.visible = false + this.$message && this.$message.info('请直接拖动图标以修改位置') + }, + + /** 进入旋转模式:移动鼠标设置朝向,单击结束(期间锁定地图) */ + openPlatformIconHeadingDialog() { + const ed = this.contextMenu.entityData + if (!ed || ed.type !== 'platformIcon' || !ed.entity) return + this.rotatingPlatformIcon = ed + this.platformIconRotateTip = '移动鼠标设置朝向,单击结束' + this.platformIconDragCameraEnabled = this.viewer.scene.screenSpaceCameraController.enableInputs + this.viewer.scene.screenSpaceCameraController.enableInputs = false + this.contextMenu.visible = false + this.$message && this.$message.info('移动鼠标可调整朝向,单击地图任意处结束') + }, removeEntity(id) { // 查找对应的实体数据 const index = this.allEntities.findIndex(e => @@ -3394,23 +3877,23 @@ export default { ) if (index > -1) { const entity = this.allEntities[index] + // 平台图标:移除伸缩框并清除选中 + if (entity.type === 'platformIcon') { + this.removeTransformHandles(entity) + if (this.selectedPlatformIcon === entity) this.selectedPlatformIcon = null + } // 从地图中移除 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 || (this.selectedEntity.entity && this.selectedEntity.entity.id === id))) { this.selectedEntity = null } @@ -3466,12 +3949,15 @@ export default { this.viewer.entities.remove(entity); } - // 移除线实体相关的点实体 if (item.type === 'line' && item.pointEntities) { item.pointEntities.forEach(pointEntity => { this.viewer.entities.remove(pointEntity); }); } + if (item.type === 'platformIcon') { + this.removeTransformHandles(item); + if (this.selectedPlatformIcon === item) this.selectedPlatformIcon = null; + } } catch (e) { console.warn('删除实体失败:', e); } @@ -4093,6 +4579,15 @@ export default { this.pointMovementHandler = null } + if (this.platformIconHandler) { + this.platformIconHandler.destroy() + this.platformIconHandler = null + } + if (typeof this.platformIconCameraListener === 'function') { + this.platformIconCameraListener() + this.platformIconCameraListener = null + } + if (this.rightClickHandler) { this.rightClickHandler.destroy() this.rightClickHandler = null @@ -4209,6 +4704,23 @@ export default { display: none !important; } +/* 平台图标旋转模式提示条:放在顶部菜单下方,避免被遮挡(顶部栏约 60px) */ +.platform-icon-rotate-tip { + position: absolute; + top: 72px; + left: 50%; + transform: translateX(-50%); + z-index: 99; + background: rgba(0, 138, 255, 0.95); + color: #fff; + padding: 10px 20px; + border-radius: 6px; + font-size: 14px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25); + pointer-events: none; + white-space: nowrap; +} + /* 地图右下角信息面板:比例尺在上、经纬度在下,整体略下移减少遮挡 */ .map-info-panel { position: absolute; diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index 17eaaa0..27c6bbb 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -164,8 +164,10 @@
{ this.platformJustDragged = false }, 300) + try { + ev.dataTransfer.setData('application/json', JSON.stringify({ + id: platform.id, + name: platform.name, + type: platform.type, + imageUrl: platform.imageUrl, + iconUrl: platform.iconUrl, + icon: platform.icon, + color: platform.color + })) + ev.dataTransfer.effectAllowed = 'copy' + } catch (e) { + console.warn('Platform drag start failed', e) + } } } } @@ -829,6 +865,14 @@ export default { box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15); } +.platform-item-draggable { + cursor: grab; +} + +.platform-item-draggable:active { + cursor: grabbing; +} + .platform-icon { width: 40px; height: 40px; diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 09fe537..5c8a99c 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -1,8 +1,13 @@