Browse Source

编辑/选中状态2.0

mh
menghao 1 month ago
parent
commit
347d791d39
  1. 42
      ruoyi-admin/src/main/java/com/ruoyi/websocket/controller/RoomWebSocketController.java
  2. 30
      ruoyi-ui/src/utils/websocket.js
  3. 7
      ruoyi-ui/src/views/cesiumMap/index.vue
  4. 27
      ruoyi-ui/src/views/childRoom/RightPanel.vue
  5. 96
      ruoyi-ui/src/views/childRoom/index.vue
  6. 7
      ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue

42
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<String, Object> 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<String, Object> viewer = new HashMap<>();
viewer.put("userId", loginUser.getUserId());
viewer.put("userName", loginUser.getUsername());
viewer.put("nickName", loginUser.getUser().getNickName());
viewer.put("sessionId", sessionId);
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> body) {
String objectType = body != null ? String.valueOf(body.get("objectType")) : null;

30
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() {

7
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 = {

27
ruoyi-ui/src/views/childRoom/RightPanel.vue

@ -59,9 +59,6 @@
<div class="tree-item-info">
<div class="tree-item-name">{{ route.name }}</div>
<div class="tree-item-meta">{{ route.points }}{{ $t('rightPanel.points') }}</div>
<div v-if="routeViewingByOther(route.id)" class="route-viewing-tag">
{{ routeViewingName(route.id) }} 正在查看
</div>
<div v-if="routeLockedByOther(route.id)" class="route-locked-tag">
<i class="el-icon-lock"></i> {{ routeLockedByName(route.id) }} 正在编辑
</div>
@ -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;

96
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);

7
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 || [];

Loading…
Cancel
Save