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 be3e9a4..1a41837 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 @@ -58,10 +58,6 @@ 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_OBJECT_VIEW = "OBJECT_VIEW"; - /** 取消对象查看 */ - private static final String TYPE_OBJECT_VIEW_CLEAR = "OBJECT_VIEW_CLEAR"; /** 对象编辑锁定:某成员进入编辑,其他人看到锁定 */ private static final String TYPE_OBJECT_EDIT_LOCK = "OBJECT_EDIT_LOCK"; /** 对象编辑解锁 */ @@ -109,10 +105,6 @@ public class RoomWebSocketController { handleSyncRoomDrawings(roomId, body, sessionId); } else if (TYPE_SYNC_PLATFORM_STYLES.equals(type)) { handleSyncPlatformStyles(roomId, body, sessionId); - } else if (TYPE_OBJECT_VIEW.equals(type)) { - handleObjectView(roomId, sessionId, loginUser, body); - } else if (TYPE_OBJECT_VIEW_CLEAR.equals(type)) { - handleObjectViewClear(roomId, sessionId, loginUser, body); } else if (TYPE_OBJECT_EDIT_LOCK.equals(type)) { handleObjectEditLock(roomId, sessionId, loginUser, body); } else if (TYPE_OBJECT_EDIT_UNLOCK.equals(type)) { @@ -120,40 +112,6 @@ public class RoomWebSocketController { } } - /** 广播:某成员正在查看某对象(如航线) */ - private void handleObjectView(Long roomId, String sessionId, LoginUser loginUser, Map body) { - String objectType = body != null ? String.valueOf(body.get("objectType")) : null; - Object objectIdObj = body != null ? body.get("objectId") : null; - if (objectType == null || objectIdObj == null) return; - - Map viewer = new HashMap<>(); - viewer.put("userId", loginUser.getUserId()); - viewer.put("userName", loginUser.getUsername()); - viewer.put("nickName", loginUser.getUser().getNickName()); - viewer.put("sessionId", sessionId); - - Map msg = new HashMap<>(); - msg.put("type", TYPE_OBJECT_VIEW); - msg.put("objectType", objectType); - msg.put("objectId", objectIdObj); - msg.put("viewer", viewer); - messagingTemplate.convertAndSend("/topic/room/" + roomId, msg); - } - - /** 广播:某成员取消查看某对象 */ - private void handleObjectViewClear(Long roomId, String sessionId, LoginUser loginUser, Map body) { - String objectType = body != null ? String.valueOf(body.get("objectType")) : null; - Object objectIdObj = body != null ? body.get("objectId") : null; - if (objectType == null || objectIdObj == null) return; - - Map msg = new HashMap<>(); - msg.put("type", TYPE_OBJECT_VIEW_CLEAR); - msg.put("objectType", objectType); - msg.put("objectId", objectIdObj); - msg.put("sessionId", sessionId); - messagingTemplate.convertAndSend("/topic/room/" + roomId, msg); - } - /** 广播:某成员锁定某对象进入编辑 */ private void handleObjectEditLock(Long roomId, String sessionId, LoginUser loginUser, Map body) { String objectType = body != null ? String.valueOf(body.get("objectType")) : null; diff --git a/ruoyi-ui/src/utils/websocket.js b/ruoyi-ui/src/utils/websocket.js index cbc8e88..c65e40c 100644 --- a/ruoyi-ui/src/utils/websocket.js +++ b/ruoyi-ui/src/utils/websocket.js @@ -23,8 +23,6 @@ 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.onObjectViewing - 对象被某成员查看 (msg: { objectType, objectId, viewer }) => {} - * @param {Function} options.onObjectViewClear - 取消对象查看 (msg: { objectType, objectId, sessionId }) => {} * @param {Function} options.onObjectEditLock - 对象被某成员编辑锁定 (msg: { objectType, objectId, editor }) => {} * @param {Function} options.onObjectEditUnlock - 对象编辑解锁 (msg: { objectType, objectId, sessionId }) => {} * @param {Function} options.onConnected - 连接成功回调 @@ -47,8 +45,6 @@ export function createRoomWebSocket(options) { onSyncPlatformIcons, onSyncRoomDrawings, onSyncPlatformStyles, - onObjectViewing, - onObjectViewClear, onObjectEditLock, onObjectEditUnlock, onConnected, @@ -171,26 +167,6 @@ export function createRoomWebSocket(options) { } } - /** 发送:当前成员正在查看某对象(如航线) */ - function sendObjectView(objectType, objectId) { - if (client && client.connected) { - client.publish({ - destination: '/app/room/' + roomId, - body: JSON.stringify({ type: 'OBJECT_VIEW', objectType, objectId }) - }) - } - } - - /** 发送:取消查看某对象 */ - function sendObjectViewClear(objectType, objectId) { - if (client && client.connected) { - client.publish({ - destination: '/app/room/' + roomId, - body: JSON.stringify({ type: 'OBJECT_VIEW_CLEAR', objectType, objectId }) - }) - } - } - /** 发送:当前成员锁定某对象进入编辑 */ function sendObjectEditLock(objectType, objectId) { if (client && client.connected) { @@ -245,10 +221,6 @@ export function createRoomWebSocket(options) { onSyncRoomDrawings && onSyncRoomDrawings(body.senderSessionId) } else if (type === 'SYNC_PLATFORM_STYLES') { onSyncPlatformStyles && onSyncPlatformStyles(body.senderSessionId) - } else if (type === 'OBJECT_VIEW' && body.objectType != null && body.objectId != null && body.viewer) { - onObjectViewing && onObjectViewing(body) - } else if (type === 'OBJECT_VIEW_CLEAR' && body.objectType != null && body.objectId != null) { - onObjectViewClear && onObjectViewClear(body) } else if (type === 'OBJECT_EDIT_LOCK' && body.objectType != null && body.objectId != null && body.editor) { onObjectEditLock && onObjectEditLock(body) } else if (type === 'OBJECT_EDIT_UNLOCK' && body.objectType != null && body.objectId != null) { @@ -349,8 +321,6 @@ export function createRoomWebSocket(options) { sendSyncPlatformIcons, sendSyncRoomDrawings, sendSyncPlatformStyles, - sendObjectView, - sendObjectViewClear, sendObjectEditLock, sendObjectEditUnlock, get connected() { diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index ff77ed0..ce76970 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -361,6 +361,11 @@ export default { type: Object, default: () => ({}) }, + /** 被其他成员编辑锁定的航线 ID 列表(禁止拖拽航点等) */ + routeLockedByOtherIds: { + type: Array, + default: () => [] + }, /** 推演时间轴:相对 K 的分钟数(用于导弹按时间轴显示/隐藏与位置插值) */ deductionTimeMinutes: { type: Number, @@ -3598,6 +3603,8 @@ export default { const dbId = props.dbId && (props.dbId.getValue ? props.dbId.getValue() : props.dbId); if (routeId == null || dbId == null) return; if (this.routeLocked[routeId]) return; + const rid = Number(routeId); + if (Array.isArray(this.routeLockedByOtherIds) && this.routeLockedByOtherIds.indexOf(rid) !== -1) return; const entity = pickedObject.id; const carto = Cesium.Cartographic.fromCartesian(entity.position.getValue(Cesium.JulianDate.now())); this.waypointDragPending = { diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue index 34c9e56..c9cd3f3 100644 --- a/ruoyi-ui/src/views/childRoom/RightPanel.vue +++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue @@ -59,9 +59,6 @@
{{ route.name }}
{{ route.points }}{{ $t('rightPanel.points') }}
-
- {{ routeViewingName(route.id) }} 正在查看 -
{{ routeLockedByName(route.id) }} 正在编辑
@@ -318,11 +315,6 @@ export default { type: Object, default: () => ({}) }, - /** 协作:谁正在查看该航线 routeId -> { userId, userName, nickName, sessionId } */ - routeViewingBy: { - type: Object, - default: () => ({}) - }, /** 协作:谁正在编辑(锁定)该航线 routeId -> { userId, userName, nickName, sessionId } */ routeLockedBy: { type: Object, @@ -541,22 +533,9 @@ export default { getRouteClasses(routeId) { return { active: this.activeRouteIds.includes(routeId), - 'viewing-by-other': this.routeViewingByOther(routeId), 'locked-by-other': this.routeLockedByOther(routeId) } }, - /** 是否有其他成员正在查看该航线(非自己) */ - routeViewingByOther(routeId) { - const v = this.routeViewingBy[routeId] - if (!v) return false - const myId = this.currentUserId != null ? Number(this.currentUserId) : null - const uid = v.userId != null ? Number(v.userId) : null - return myId !== uid - }, - routeViewingName(routeId) { - const v = this.routeViewingBy[routeId] - return (v && (v.nickName || v.userName)) || '' - }, /** 是否有其他成员正在编辑(锁定)该航线 */ routeLockedByOther(routeId) { const e = this.routeLockedBy[routeId] @@ -804,12 +783,6 @@ export default { color: #999; } -.route-viewing-tag { - font-size: 11px; - color: #008aff; - margin-top: 2px; -} - .route-locked-tag { font-size: 11px; color: #909399; diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 8677d2c..9585a92 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -15,6 +15,7 @@ :coordinateFormat="coordinateFormat" :bottomPanelVisible="bottomPanelVisible" :route-locked="routeLocked" + :route-locked-by-other-ids="routeLockedByOtherRouteIds" :deduction-time-minutes="deductionMinutesFromK" :room-id="currentRoomId" @draw-complete="handleMapDrawComplete" @@ -176,7 +177,6 @@ :routes="routes" :active-route-ids="activeRouteIds" :route-locked="routeLocked" - :route-viewing-by="routeViewingBy" :route-locked-by="routeLockedBy" :current-user-id="currentUserId" :selected-route-details="selectedRouteDetails" @@ -514,8 +514,7 @@ export default { screenshotDataUrl: '', screenshotFileName: '', - // 协作:谁正在查看/编辑哪条航线(来自 WebSocket 广播) - routeViewingBy: {}, // routeId -> { userId, userName, nickName, sessionId } + // 协作:谁正在编辑(锁定)哪条航线(来自 WebSocket 广播) routeLockedBy: {}, // routeId -> { userId, userName, nickName, sessionId } routeEditLockedId: null, // 本端当前编辑锁定的航线 ID,关闭弹窗时发送解锁 @@ -688,14 +687,6 @@ export default { }); } }, - /** 选中航线变化时:广播“正在查看”或取消查看,供其他成员显示 */ - selectedRouteId: { - handler(newId, oldId) { - if (!this.wsConnection || !this.wsConnection.sendObjectViewClear || !this.wsConnection.sendObjectView) return; - if (oldId != null) this.wsConnection.sendObjectViewClear('route', oldId); - if (newId != null) this.wsConnection.sendObjectView('route', newId); - } - }, /** 航线编辑弹窗关闭时:发送编辑解锁,让其他成员可编辑 */ showRouteDialog(visible) { if (visible) return; @@ -744,6 +735,17 @@ export default { if (!this.addHoldContext) return ''; if (this.addHoldContext.mode === 'drawing') return '在最后两航点之间插入盘旋,保存航线时生效。'; return `在 ${this.addHoldContext.fromName} 与 ${this.addHoldContext.toName} 之间添加盘旋,到计划时间后沿切线飞往下一航点(原「下一格」航点将被移除)。`; + }, + /** 被其他成员编辑锁定的航线 ID 列表,供地图禁止拖拽等 */ + routeLockedByOtherRouteIds() { + const myId = this.currentUserId; + if (myId == null) return Object.keys(this.routeLockedBy).map(id => Number(id)); + const ids = []; + Object.keys(this.routeLockedBy).forEach(rid => { + const editor = this.routeLockedBy[rid]; + if (editor && Number(editor.userId) !== Number(myId)) ids.push(Number(rid)); + }); + return ids; } }, mounted() { @@ -784,6 +786,10 @@ export default { }, // 处理从地图点击传来的编辑请求(支持 wpId 或 waypointIndex,如右键盘旋弧时仅有 waypointIndex) async handleOpenWaypointEdit(wpId, routeId, waypointIndex) { + if (routeId != null && this.isRouteLockedByOther(routeId)) { + this.$message.warning('该航线正被其他成员编辑,无法修改航点'); + return; + } if (waypointIndex != null && (wpId == null || wpId === undefined)) { try { const response = await getRoutes(routeId); @@ -866,18 +872,22 @@ export default { }); }, + /** 该航线是否被其他成员编辑锁定(非自己) */ + isRouteLockedByOther(routeId) { + const lockedBy = this.routeLockedBy[routeId]; + if (!lockedBy) return false; + const myId = this.currentUserId != null ? Number(this.currentUserId) : null; + const uid = lockedBy.userId != null ? Number(lockedBy.userId) : null; + return myId !== uid; + }, // 处理从地图点击传来的航线编辑请求 async handleOpenRouteEdit(routeId) { console.log(`>>> [父组件接收] 航线 ID: ${routeId}`); - const lockedBy = this.routeLockedBy[routeId]; - if (lockedBy) { - const myId = this.currentUserId != null ? Number(this.currentUserId) : null; - const uid = lockedBy.userId != null ? Number(lockedBy.userId) : null; - if (myId !== uid) { - const name = lockedBy.nickName || lockedBy.userName || '其他成员'; - this.$message.warning(`${name} 正在编辑该航线,请稍后再试`); - return; - } + if (this.isRouteLockedByOther(routeId)) { + const lockedBy = this.routeLockedBy[routeId]; + const name = lockedBy && (lockedBy.nickName || lockedBy.userName) || '其他成员'; + this.$message.warning(`${name} 正在编辑该航线,请稍后再试`); + return; } try { const response = await getRoutes(routeId); @@ -905,6 +915,10 @@ export default { /** 右键航点“向前/向后增加航点”:进入放置模式,传入 waypoints 给地图预览 */ handleAddWaypointAt({ routeId, waypointIndex, mode }) { + if (this.isRouteLockedByOther(routeId)) { + this.$message.warning('该航线正被其他成员编辑,无法添加航点'); + return; + } if (this.routeLocked[routeId]) { this.$message.info('该航线已上锁,请先解锁'); return; @@ -1032,6 +1046,10 @@ export default { /** 右键航点“切换盘旋航点”:普通航点设为盘旋(圆形默认),盘旋航点设为普通;支持首尾航点 */ async handleToggleWaypointHold({ routeId, dbId, waypointIndex }) { + if (this.isRouteLockedByOther(routeId)) { + this.$message.warning('该航线正被其他成员编辑,无法修改'); + return; + } if (this.routeLocked[routeId]) { this.$message.info('该航线已上锁,请先解锁'); return; @@ -1168,6 +1186,10 @@ export default { /** 地图上拖拽航点结束:将新位置写回数据库并刷新显示 */ async handleWaypointPositionChanged({ dbId, routeId, lat, lng, alt }) { + if (this.isRouteLockedByOther(routeId)) { + this.$message.warning('该航线正被其他成员编辑,无法拖拽修改'); + return; + } let waypoints = null; let route = null; if (this.selectedRouteDetails && this.selectedRouteDetails.id === routeId) { @@ -1337,27 +1359,13 @@ export default { this.wsOnlineMembers = this.wsOnlineMembers.filter(m => m.id !== sessionId && m.id !== member.sessionId); this.onlineCount = this.wsOnlineMembers.length; this.mySyncSessionIds = this.mySyncSessionIds.filter(s => s !== sessionId && s !== member.sessionId); - // 该成员离开后清除其查看/编辑状态 - Object.keys(this.routeViewingBy).forEach(rid => { - if (this.routeViewingBy[rid] && this.routeViewingBy[rid].sessionId === sessionId) { - this.$delete(this.routeViewingBy, rid); - } - }); + // 该成员离开后清除其编辑锁定状态 Object.keys(this.routeLockedBy).forEach(rid => { if (this.routeLockedBy[rid] && this.routeLockedBy[rid].sessionId === sessionId) { this.$delete(this.routeLockedBy, rid); } }); }, - onObjectViewing: (msg) => { - if (msg.objectType !== 'route' || msg.objectId == null) return; - this.$set(this.routeViewingBy, msg.objectId, msg.viewer); - }, - onObjectViewClear: (msg) => { - if (msg.objectType !== 'route' || msg.objectId == null) return; - const cur = this.routeViewingBy[msg.objectId]; - if (cur && cur.sessionId === msg.sessionId) this.$delete(this.routeViewingBy, msg.objectId); - }, onObjectEditLock: (msg) => { if (msg.objectType !== 'route' || msg.objectId == null) return; this.$set(this.routeLockedBy, msg.objectId, msg.editor); @@ -1416,7 +1424,6 @@ export default { this.mySyncSessionIds = []; this.chatMessages = []; this.privateChatMessages = {}; - this.routeViewingBy = {}; this.routeLockedBy = {}; }, onError: (err) => { @@ -1694,6 +1701,10 @@ export default { }, // 更新航线数据(含航点表编辑后的批量持久化) async updateRoute(updatedRoute) { + if (this.isRouteLockedByOther(updatedRoute.id)) { + this.$message.warning('该航线正被其他成员编辑,无法保存'); + return; + } const index = this.routes.findIndex(r => r.id === updatedRoute.id); if (index === -1) return; try { @@ -1802,6 +1813,10 @@ export default { } }, async handleDeleteRoute(route) { + if (this.isRouteLockedByOther(route.id)) { + this.$message.warning('该航线正被其他成员编辑,无法删除'); + return; + } try { // 二次确认,防止误删 await this.$confirm(`确定要彻底删除航线 "${route.name}" 吗?`, '提示', { @@ -2059,6 +2074,10 @@ export default { // 航点编辑弹窗相关方法 openWaypointDialog(data) { console.log(">>> [父组件接收] 编辑航点详情:", data); + if (data && data.routeId != null && this.isRouteLockedByOther(data.routeId)) { + this.$message.warning('该航线正被其他成员编辑,无法修改'); + return; + } // 将整个对象赋值给弹窗绑定的变量 this.selectedWaypoint = data; this.showWaypointDialog = true; @@ -2066,6 +2085,11 @@ export default { /** 航点编辑保存:更新数据库并同步地图显示 */ async updateWaypoint(updatedWaypoint) { if (!this.selectedRouteDetails || !this.selectedRouteDetails.waypoints) return; + const routeId = updatedWaypoint.routeId != null ? updatedWaypoint.routeId : this.selectedRouteDetails.id; + if (this.isRouteLockedByOther(routeId)) { + this.$message.warning('该航线正被其他成员编辑,无法保存'); + return; + } try { if (this.$refs.cesiumMap && updatedWaypoint.turnAngle > 0) { updatedWaypoint.turnRadius = this.$refs.cesiumMap.getWaypointRadius(updatedWaypoint); diff --git a/ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue b/ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue index 879ed38..f729a6a 100644 --- a/ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue +++ b/ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue @@ -331,11 +331,14 @@ export default { }, computed: { displayOnlineMembers() { - return (this.onlineMembers && this.onlineMembers.length > 0) ? this.onlineMembers : this._mockOnlineMembers; + const list = (this.onlineMembers && this.onlineMembers.length > 0) ? this.onlineMembers : this._mockOnlineMembers; + return Array.isArray(list) ? list : []; }, chatableMembers() { + const list = this.displayOnlineMembers; + if (!Array.isArray(list)) return []; const cur = this.currentUserId != null ? Number(this.currentUserId) : null; - return this.displayOnlineMembers.filter(m => m.userId != null && Number(m.userId) !== cur); + return list.filter(m => m.userId != null && Number(m.userId) !== cur); }, displayChatMessages() { return this.chatMessages || [];