menghao 1 month ago
parent
commit
057b059b78
  1. 33
      ruoyi-admin/src/main/java/com/ruoyi/websocket/controller/RoomWebSocketController.java
  2. 4
      ruoyi-ui/src/utils/websocket.js
  3. 10
      ruoyi-ui/src/views/childRoom/RightPanel.vue
  4. 90
      ruoyi-ui/src/views/childRoom/index.vue

33
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<Long> visibleRouteIds = roomRoomStateService.getVisibleRouteIds(roomId);
if (visibleRouteIds != null && !visibleRouteIds.isEmpty()) {
Map<String, Object> 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<String, Object> 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<String, Object> 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);
}

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

10
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) {

90
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) {

Loading…
Cancel
Save