From c3757800528a6b3b458194eb3a6aabec8475e335 Mon Sep 17 00:00:00 2001 From: cuitw <1051735452@qq.com> Date: Wed, 4 Mar 2026 15:11:07 +0800 Subject: [PATCH] =?UTF-8?q?=E8=81=94=E7=BD=91=E5=86=85=E5=AE=B9=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RoomWebSocketController.java | 33 +++++++- ruoyi-ui/src/utils/websocket.js | 4 + ruoyi-ui/src/views/childRoom/RightPanel.vue | 10 +-- ruoyi-ui/src/views/childRoom/index.vue | 90 +++++++++++++++++++--- 4 files changed, 117 insertions(+), 20 deletions(-) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/websocket/controller/RoomWebSocketController.java b/ruoyi-admin/src/main/java/com/ruoyi/websocket/controller/RoomWebSocketController.java index 1a41837..f7818c0 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/websocket/controller/RoomWebSocketController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/websocket/controller/RoomWebSocketController.java @@ -1,8 +1,10 @@ package com.ruoyi.websocket.controller; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.DestinationVariable; @@ -19,6 +21,7 @@ import com.ruoyi.system.domain.Rooms; import com.ruoyi.system.service.IRoomsService; import com.ruoyi.websocket.dto.RoomMemberDTO; import com.ruoyi.websocket.service.RoomChatService; +import com.ruoyi.websocket.service.RoomRoomStateService; import com.ruoyi.websocket.service.RoomWebSocketService; /** @@ -41,6 +44,9 @@ public class RoomWebSocketController { @Autowired private IRoomsService roomsService; + @Autowired + private RoomRoomStateService roomRoomStateService; + private static final String TYPE_JOIN = "JOIN"; private static final String TYPE_LEAVE = "LEAVE"; private static final String TYPE_PING = "PING"; @@ -58,6 +64,8 @@ public class RoomWebSocketController { private static final String TYPE_SYNC_PLATFORM_ICONS = "SYNC_PLATFORM_ICONS"; private static final String TYPE_SYNC_ROOM_DRAWINGS = "SYNC_ROOM_DRAWINGS"; private static final String TYPE_SYNC_PLATFORM_STYLES = "SYNC_PLATFORM_STYLES"; + /** 新用户加入时下发的房间状态(可见航线等) */ + private static final String TYPE_ROOM_STATE = "ROOM_STATE"; /** 对象编辑锁定:某成员进入编辑,其他人看到锁定 */ private static final String TYPE_OBJECT_EDIT_LOCK = "OBJECT_EDIT_LOCK"; /** 对象编辑解锁 */ @@ -179,6 +187,14 @@ public class RoomWebSocketController { chatHistoryMsg.put("type", TYPE_CHAT_HISTORY); chatHistoryMsg.put("messages", chatHistory); messagingTemplate.convertAndSendToUser(loginUser.getUsername(), "/queue/private", chatHistoryMsg); + + Set visibleRouteIds = roomRoomStateService.getVisibleRouteIds(roomId); + if (visibleRouteIds != null && !visibleRouteIds.isEmpty()) { + Map roomStateMsg = new HashMap<>(); + roomStateMsg.put("type", TYPE_ROOM_STATE); + roomStateMsg.put("visibleRouteIds", new ArrayList<>(visibleRouteIds)); + messagingTemplate.convertAndSendToUser(loginUser.getUsername(), "/queue/private", roomStateMsg); + } } private void handleLeave(Long roomId, String sessionId, LoginUser loginUser) { @@ -280,13 +296,26 @@ public class RoomWebSocketController { messagingTemplate.convertAndSendToUser(loginUser.getUsername(), "/queue/private", resp); } - /** 广播航线显隐变更,供其他设备实时同步 */ + /** 广播航线显隐变更,供其他设备实时同步;并持久化到 Redis 供新加入用户同步 */ private void handleSyncRouteVisibility(Long roomId, Map body, String sessionId) { if (body == null || !body.containsKey("routeId")) return; + Object routeIdObj = body.get("routeId"); + boolean visible = body.get("visible") != null && Boolean.TRUE.equals(body.get("visible")); + Long routeId = null; + if (routeIdObj instanceof Number) { + routeId = ((Number) routeIdObj).longValue(); + } else if (routeIdObj != null) { + try { + routeId = Long.parseLong(routeIdObj.toString()); + } catch (NumberFormatException ignored) {} + } + if (routeId != null) { + roomRoomStateService.updateRouteVisibility(roomId, routeId, visible); + } Map msg = new HashMap<>(); msg.put("type", TYPE_SYNC_ROUTE_VISIBILITY); msg.put("routeId", body.get("routeId")); - msg.put("visible", body.get("visible") != null && Boolean.TRUE.equals(body.get("visible"))); + msg.put("visible", visible); msg.put("senderSessionId", sessionId); messagingTemplate.convertAndSend("/topic/room/" + roomId, msg); } diff --git a/ruoyi-ui/src/utils/websocket.js b/ruoyi-ui/src/utils/websocket.js index c65e40c..6a4495c 100644 --- a/ruoyi-ui/src/utils/websocket.js +++ b/ruoyi-ui/src/utils/websocket.js @@ -23,6 +23,7 @@ const WS_BASE = process.env.VUE_APP_BASE_API || '/dev-api' * @param {Function} options.onSyncPlatformIcons - 平台图标变更同步回调 (senderUserId) => {} * @param {Function} options.onSyncRoomDrawings - 空域图形变更同步回调 (senderUserId) => {} * @param {Function} options.onSyncPlatformStyles - 探测区/威力区样式变更同步回调 (senderUserId) => {} + * @param {Function} options.onRoomState - 新加入时收到的房间状态 (visibleRouteIds: number[]) => {} * @param {Function} options.onObjectEditLock - 对象被某成员编辑锁定 (msg: { objectType, objectId, editor }) => {} * @param {Function} options.onObjectEditUnlock - 对象编辑解锁 (msg: { objectType, objectId, sessionId }) => {} * @param {Function} options.onConnected - 连接成功回调 @@ -45,6 +46,7 @@ export function createRoomWebSocket(options) { onSyncPlatformIcons, onSyncRoomDrawings, onSyncPlatformStyles, + onRoomState, onObjectEditLock, onObjectEditUnlock, onConnected, @@ -241,6 +243,8 @@ export function createRoomWebSocket(options) { onChatHistory && onChatHistory(body.messages) } else if (type === 'PRIVATE_CHAT_HISTORY' && body.targetUserId != null && Array.isArray(body.messages)) { onPrivateChatHistory && onPrivateChatHistory(body.targetUserId, body.messages) + } else if (type === 'ROOM_STATE' && Array.isArray(body.visibleRouteIds)) { + onRoomState && onRoomState(body.visibleRouteIds) } } catch (e) { console.warn('[WebSocket] parse private message error:', e) diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index c9cd3f3..5f063ca 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -412,17 +412,9 @@ export default { }) this.$emit('select-plan', { id: null }) } else { - // 展开新方案前,先清空所有已显示的航线和已展开的方案(仅本地清除,不广播,避免影响其他账号) - this.activeRouteIds.forEach(activeId => { - const activeRoute = this.routes.find(r => r.id === activeId); - if (activeRoute) { - this.$emit('toggle-route-visibility', activeRoute, { fromPlanSwitch: true }); - } - }); - // 重置展开状态,确保只有一个方案是展开的 + // 展开新方案:不再隐藏航线,保留已打开的航线显示;仅切换展开状态并选中方案 this.expandedPlans = []; this.expandedRoutes = []; - // --- 正常的展开逻辑 --- this.expandedPlans.push(planId) const plan = this.plans.find(p => p.id === planId) if (plan) { diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 9585a92..d9efc8a 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -623,6 +623,8 @@ export default { plans: [], activeRightTab: 'plan', activeRouteIds: [], // 存储当前所有选中的航线ID + /** 新加入时收到的房间可见航线 ID,若 routes 未加载则暂存,getList 完成后应用 */ + roomStatePendingVisibleRouteIds: [], /** 航线上锁状态:routeId -> true 上锁,与地图右键及右侧列表锁图标同步 */ routeLocked: {}, // 冲突数据(由 runConflictCheck 根据当前航线与时间轴计算真实问题) @@ -1417,6 +1419,9 @@ export default { if (this.isMySyncSession(senderSessionId)) return; this.applySyncPlatformStyles(); }, + onRoomState: (visibleRouteIds) => { + this.applyRoomStateVisibleRoutes(visibleRouteIds); + }, onConnected: () => {}, onDisconnected: () => { this.onlineCount = 0; @@ -1436,12 +1441,31 @@ export default { this.wsConnection.disconnect(); this.wsConnection = null; } + this.roomStatePendingVisibleRouteIds = []; this.wsOnlineMembers = []; this.mySyncSessionIds = []; this.onlineCount = 0; this.chatMessages = []; this.privateChatMessages = {}; }, + /** 应用房间状态中的可见航线(新加入用户同步;若 routes 未加载则暂存) */ + async applyRoomStateVisibleRoutes(visibleRouteIds) { + if (!visibleRouteIds || !Array.isArray(visibleRouteIds) || visibleRouteIds.length === 0) return; + if (!this.routes || this.routes.length === 0) { + this.roomStatePendingVisibleRouteIds = visibleRouteIds; + return; + } + for (const routeId of visibleRouteIds) { + await this.applySyncRouteVisibility(routeId, true); + } + }, + /** 应用暂存的房间可见航线(getList 完成后调用) */ + async applyRoomStatePending() { + if (this.roomStatePendingVisibleRouteIds.length === 0) return; + const pending = [...this.roomStatePendingVisibleRouteIds]; + this.roomStatePendingVisibleRouteIds = []; + await this.applyRoomStateVisibleRoutes(pending); + }, /** 判断是否为当前连接发出的同步消息(避免自己发的消息再应用一次) */ isMySyncSession(senderSessionId) { if (!senderSessionId) return false; @@ -1920,6 +1944,7 @@ export default { }).catch(() => {}); } } + this.$nextTick(() => this.applyRoomStatePending()); } } catch (error) { console.error("数据加载失败:", error); @@ -3613,25 +3638,72 @@ export default { const minutes = absMin % 60; return `K${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:00`; }, - selectPlan(plan) { + async 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; + return; } - // 重置状态 - this.selectedRouteId = null; - this.selectedRouteDetails = null; - this.activeRouteIds = []; + // 清空地图上的航点/航线及探测区、威力区(按 routeId 逐个移除,避免误删其他实体) + if (this.$refs.cesiumMap) { + [...this.activeRouteIds].forEach(routeId => { + this.$refs.cesiumMap.removeRouteById(routeId); + }); + } - // 物理清场(仅航点/航线;空域图形与房间绑定,不随方案切换) - if (this.$refs.cesiumMap && this.$refs.cesiumMap.clearAllWaypoints) { - this.$refs.cesiumMap.clearAllWaypoints(); + // 保留 activeRouteIds,恢复显示该方案下已打开的航线(含联网同步的) + const planRouteIds = this.activeRouteIds.filter(id => { + const r = this.routes.find(x => x.id === id); + return r && r.scenarioId === plan.id; + }); + if (planRouteIds.length > 0) { + for (const routeId of planRouteIds) { + const route = this.routes.find(r => r.id === routeId); + if (!route) continue; + try { + const res = await getRoutes(route.id); + if (res.code !== 200 || !res.data) continue; + const waypoints = res.data.waypoints || []; + const routeIndex = this.routes.findIndex(r => r.id === route.id); + if (routeIndex > -1) { + this.$set(this.routes, routeIndex, { ...this.routes[routeIndex], waypoints }); + } + if (waypoints.length > 0 && this.$refs.cesiumMap) { + const roomId = this.currentRoomId; + if (roomId && route.platformId) { + try { + const styleRes = await getPlatformStyle({ roomId, routeId: route.id, platformId: route.platformId }); + if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(route.id, styleRes.data); + } catch (_) {} + } + this.$refs.cesiumMap.renderRouteWaypoints(waypoints, route.id, route.platformId, route.platform, this.parseRouteStyle(route.attributes)); + } + } catch (_) {} + } + const firstId = planRouteIds[0]; + const firstRoute = this.routes.find(r => r.id === firstId); + if (firstRoute && firstRoute.waypoints) { + this.selectedRouteId = firstRoute.id; + this.selectedRouteDetails = { + id: firstRoute.id, + name: firstRoute.callSign || firstRoute.name, + waypoints: firstRoute.waypoints, + platformId: firstRoute.platformId, + platform: firstRoute.platform, + attributes: firstRoute.attributes + }; + } + } else { + this.selectedRouteId = null; + this.selectedRouteDetails = null; } - console.log(`>>> [切换成功] 已进入方案: ${plan && plan.name},地图已清空,列表已展开。`); + console.log(`>>> [切换成功] 已进入方案: ${plan.name},已恢复显示 ${planRouteIds.length} 条航线。`); }, /** 切换航线:实现多选/开关逻辑 */ async selectRoute(route) {