From 587f83d7e3c5d27192c579fb431160c7b835f4c3 Mon Sep 17 00:00:00 2001
From: cuitw <1051735452@qq.com>
Date: Mon, 30 Mar 2026 13:30:23 +0800
Subject: [PATCH 1/3] =?UTF-8?q?=E5=8F=B3=E9=94=AE=E8=8F=9C=E5=8D=95?=
=?UTF-8?q?=E6=A0=B7=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ruoyi-ui/src/layout/components/TagsView/index.vue | 26 +-
ruoyi-ui/src/views/cesiumMap/ContextMenu.vue | 618 ++++++++++++---------
ruoyi-ui/src/views/cesiumMap/MenuGlyph.vue | 244 ++++++++
ruoyi-ui/src/views/childRoom/SixStepsOverlay.vue | 33 +-
ruoyi-ui/src/views/childRoom/StepCanvasContent.vue | 15 +-
ruoyi-ui/src/views/childRoom/TaskPageContent.vue | 16 +-
.../views/childRoom/UnderstandingStepContent.vue | 17 +-
ruoyi-ui/src/views/selectRoom/index.vue | 35 +-
8 files changed, 675 insertions(+), 329 deletions(-)
create mode 100644 ruoyi-ui/src/views/cesiumMap/MenuGlyph.vue
diff --git a/ruoyi-ui/src/layout/components/TagsView/index.vue b/ruoyi-ui/src/layout/components/TagsView/index.vue
index f719f80..dfc8aef 100644
--- a/ruoyi-ui/src/layout/components/TagsView/index.vue
+++ b/ruoyi-ui/src/layout/components/TagsView/index.vue
@@ -294,22 +294,32 @@ export default {
.contextmenu {
margin: 0;
- background: #fff;
z-index: 3000;
position: absolute;
list-style-type: none;
- padding: 5px 0;
- border-radius: 4px;
- font-size: 12px;
- font-weight: 400;
+ padding: 8px 0;
+ min-width: 144px;
+ font-size: 13px;
+ font-weight: 500;
color: #333;
- box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Microsoft YaHei', sans-serif;
+ background: rgba(255, 255, 255, 0.72);
+ -webkit-backdrop-filter: blur(15px);
+ backdrop-filter: blur(15px);
+ border: 1px solid rgba(22, 93, 255, 0.12);
+ border-radius: 14px;
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
li {
margin: 0;
- padding: 7px 16px;
+ padding: 9px 20px;
cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ transition: background 0.2s ease, color 0.2s ease;
&:hover {
- background: #eee;
+ background: rgba(22, 93, 255, 0.08);
+ color: #165dff;
}
}
}
diff --git a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue
index 336c9d3..53acf2e 100644
--- a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue
+++ b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue
@@ -4,8 +4,8 @@
@@ -13,19 +13,19 @@
@@ -33,42 +33,42 @@
@@ -139,24 +139,24 @@
@@ -164,32 +164,32 @@
@@ -197,8 +197,8 @@
@@ -234,9 +234,9 @@
-
@@ -542,37 +544,37 @@
@@ -580,8 +582,8 @@
@@ -589,8 +591,8 @@
@@ -598,12 +600,12 @@
@@ -648,8 +650,11 @@
diff --git a/ruoyi-ui/src/views/cesiumMap/MenuGlyph.vue b/ruoyi-ui/src/views/cesiumMap/MenuGlyph.vue
new file mode 100644
index 0000000..527271e
--- /dev/null
+++ b/ruoyi-ui/src/views/cesiumMap/MenuGlyph.vue
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
diff --git a/ruoyi-ui/src/views/childRoom/SixStepsOverlay.vue b/ruoyi-ui/src/views/childRoom/SixStepsOverlay.vue
index cedd55d..6569c12 100644
--- a/ruoyi-ui/src/views/childRoom/SixStepsOverlay.vue
+++ b/ruoyi-ui/src/views/childRoom/SixStepsOverlay.vue
@@ -965,23 +965,28 @@ export default {
.sub-title-context-menu {
position: fixed;
z-index: 10000;
- min-width: 120px;
- padding: 4px 0;
- background: #fff;
- border-radius: 8px;
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
- border: 1px solid rgba(0, 0, 0, 0.08);
+ min-width: 144px;
+ padding: 8px 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Microsoft YaHei', sans-serif;
+ font-weight: 500;
+ color: #333;
+ background: rgba(255, 255, 255, 0.72);
+ -webkit-backdrop-filter: blur(15px);
+ backdrop-filter: blur(15px);
+ border: 1px solid rgba(22, 93, 255, 0.12);
+ border-radius: 14px;
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.sub-title-context-menu .context-menu-item {
display: flex;
align-items: center;
- gap: 8px;
- padding: 8px 16px;
+ gap: 10px;
+ padding: 9px 20px;
font-size: 14px;
- color: #1e293b;
+ color: #333;
cursor: pointer;
- transition: background 0.2s;
+ transition: background 0.2s ease, color 0.2s ease;
}
.sub-title-context-menu .context-menu-item:hover {
@@ -989,8 +994,12 @@ export default {
color: #165dff;
}
+.sub-title-context-menu .context-menu-item-danger {
+ color: #f56c6c;
+}
+
.sub-title-context-menu .context-menu-item-danger:hover {
- background: rgba(239, 68, 68, 0.08);
- color: #ef4444;
+ background: rgba(245, 108, 108, 0.1);
+ color: #f56c6c;
}
diff --git a/ruoyi-ui/src/views/childRoom/StepCanvasContent.vue b/ruoyi-ui/src/views/childRoom/StepCanvasContent.vue
index 05d9f53..b83b467 100644
--- a/ruoyi-ui/src/views/childRoom/StepCanvasContent.vue
+++ b/ruoyi-ui/src/views/childRoom/StepCanvasContent.vue
@@ -1084,12 +1084,15 @@ export default {
position: fixed;
display: flex;
align-items: center;
- gap: 6px;
- padding: 6px 10px;
- background: #fff;
- border: 1px solid #ddd;
- border-radius: 6px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ gap: 8px;
+ padding: 8px 12px;
+ color: #333;
+ background: rgba(255, 255, 255, 0.72);
+ -webkit-backdrop-filter: blur(15px);
+ backdrop-filter: blur(15px);
+ border: 1px solid rgba(22, 93, 255, 0.12);
+ border-radius: 14px;
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
z-index: 10000;
margin-top: 4px;
}
diff --git a/ruoyi-ui/src/views/childRoom/TaskPageContent.vue b/ruoyi-ui/src/views/childRoom/TaskPageContent.vue
index a504acb..e028d41 100644
--- a/ruoyi-ui/src/views/childRoom/TaskPageContent.vue
+++ b/ruoyi-ui/src/views/childRoom/TaskPageContent.vue
@@ -921,17 +921,19 @@ export default {
}
-/* Office 风格格式工具栏:仅选中文字右键时显示,固定定位在右键位置 */
.textbox-format-toolbar-fixed {
position: fixed;
display: flex;
align-items: center;
- gap: 6px;
- padding: 6px 10px;
- background: #fff;
- border: 1px solid #ddd;
- border-radius: 6px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ gap: 8px;
+ padding: 8px 12px;
+ color: #333;
+ background: rgba(255, 255, 255, 0.72);
+ -webkit-backdrop-filter: blur(15px);
+ backdrop-filter: blur(15px);
+ border: 1px solid rgba(22, 93, 255, 0.12);
+ border-radius: 14px;
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
z-index: 10000;
margin-top: 4px;
}
diff --git a/ruoyi-ui/src/views/childRoom/UnderstandingStepContent.vue b/ruoyi-ui/src/views/childRoom/UnderstandingStepContent.vue
index 3fc9066..f938a54 100644
--- a/ruoyi-ui/src/views/childRoom/UnderstandingStepContent.vue
+++ b/ruoyi-ui/src/views/childRoom/UnderstandingStepContent.vue
@@ -1162,17 +1162,20 @@ export default {
pointer-events: auto;
}
-/* 格式工具栏:仅选中文字右键时显示 */
+/* 格式工具栏:与地图 ContextMenu / TopHeader 一致 */
.textbox-format-toolbar-fixed {
position: fixed;
display: flex;
align-items: center;
- gap: 6px;
- padding: 6px 10px;
- background: #fff;
- border: 1px solid #ddd;
- border-radius: 6px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ gap: 8px;
+ padding: 8px 12px;
+ color: #333;
+ background: rgba(255, 255, 255, 0.72);
+ -webkit-backdrop-filter: blur(15px);
+ backdrop-filter: blur(15px);
+ border: 1px solid rgba(22, 93, 255, 0.12);
+ border-radius: 14px;
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
z-index: 10000;
margin-top: 4px;
}
diff --git a/ruoyi-ui/src/views/selectRoom/index.vue b/ruoyi-ui/src/views/selectRoom/index.vue
index 2b9dea7..94c37aa 100644
--- a/ruoyi-ui/src/views/selectRoom/index.vue
+++ b/ruoyi-ui/src/views/selectRoom/index.vue
@@ -573,41 +573,46 @@ export default {
color: #94a3b8;
}
-/* 右键菜单 */
+/* 右键菜单:与 TopHeader / ContextMenu 企业蓝一致 */
.context-menu {
position: fixed;
- background: white;
- border-radius: 10px;
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
- padding: 6px 0;
- min-width: 140px;
z-index: 2000;
- border: 1px solid #f1f5f9;
+ min-width: 152px;
+ padding: 8px 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Microsoft YaHei', sans-serif;
+ color: #333;
+ background: rgba(255, 255, 255, 0.72);
+ -webkit-backdrop-filter: blur(15px);
+ backdrop-filter: blur(15px);
+ border: 1px solid rgba(22, 93, 255, 0.12);
+ border-radius: 14px;
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.menu-item {
- padding: 10px 16px;
+ padding: 10px 20px;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
- font-size: 13px;
- color: #475569;
- transition: background 0.2s;
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+ transition: background 0.2s ease, color 0.2s ease;
}
.menu-item:hover {
- background-color: #f8fafc;
+ background: rgba(22, 93, 255, 0.08);
color: #165dff;
}
.menu-item-danger {
- color: #ef4444;
+ color: #f56c6c;
}
.menu-item-danger:hover {
- background-color: #fef2f2;
- color: #ef4444;
+ background: rgba(245, 108, 108, 0.1);
+ color: #f56c6c;
}
/* --- Element UI 局部重写 --- */
From 151efd679b70cfd9888e79384255abeb8c46a363 Mon Sep 17 00:00:00 2001
From: cuitw <1051735452@qq.com>
Date: Mon, 30 Mar 2026 15:34:47 +0800
Subject: [PATCH 2/3] =?UTF-8?q?=E6=93=8D=E4=BD=9C=E6=97=A5=E5=BF=97?=
=?UTF-8?q?=E5=AE=8C=E5=96=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../web/controller/RoomPlatformIconController.java | 64 +++-
.../com/ruoyi/web/controller/RoutesController.java | 76 ++++-
.../controller/RoomWebSocketController.java | 130 ++++++-
.../listener/WebSocketDisconnectListener.java | 6 +
.../websocket/service/RoomUserActivityService.java | 219 ++++++++++++
.../ruoyi/system/domain/ObjectOperationLog.java | 4 +
.../system/mapper/RoomPlatformIconMapper.java | 3 +
.../impl/ObjectOperationLogServiceImpl.java | 96 +++++-
.../mapper/system/RoomPlatformIconMapper.xml | 7 +
ruoyi-ui/src/lang/en.js | 11 +-
ruoyi-ui/src/lang/zh.js | 11 +-
ruoyi-ui/src/utils/websocket.js | 97 ++++--
ruoyi-ui/src/views/cesiumMap/ContextMenu.vue | 23 +-
ruoyi-ui/src/views/cesiumMap/index.vue | 10 +-
ruoyi-ui/src/views/childRoom/index.vue | 95 +++++-
ruoyi-ui/src/views/dialogs/OnlineMembersDialog.vue | 374 +++++++++++++++++++--
16 files changed, 1151 insertions(+), 75 deletions(-)
create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/websocket/service/RoomUserActivityService.java
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoomPlatformIconController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoomPlatformIconController.java
index e0b97b4..d702851 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoomPlatformIconController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoomPlatformIconController.java
@@ -5,11 +5,14 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
+import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.system.domain.ObjectOperationLog;
import com.ruoyi.system.domain.RoomPlatformIcon;
+import com.ruoyi.system.service.IObjectOperationLogService;
import com.ruoyi.system.service.IRoomPlatformIconService;
/**
@@ -25,6 +28,9 @@ public class RoomPlatformIconController extends BaseController {
@Autowired
private RedisTemplate redisTemplate;
+ @Autowired
+ private IObjectOperationLogService objectOperationLogService;
+
/**
* 按房间ID查询该房间下所有地图平台图标(不分页)
*/
@@ -43,6 +49,23 @@ public class RoomPlatformIconController extends BaseController {
@PostMapping
public AjaxResult add(@RequestBody RoomPlatformIcon roomPlatformIcon) {
int rows = roomPlatformIconService.insert(roomPlatformIcon);
+ if (rows > 0 && roomPlatformIcon.getId() != null) {
+ try {
+ ObjectOperationLog opLog = new ObjectOperationLog();
+ opLog.setRoomId(roomPlatformIcon.getRoomId());
+ opLog.setOperatorId(getUserId());
+ opLog.setOperatorName(getUsername());
+ opLog.setOperationType(ObjectOperationLog.TYPE_INSERT);
+ opLog.setObjectType(ObjectOperationLog.OBJ_ROOM_PLATFORM_ICON);
+ opLog.setObjectId(String.valueOf(roomPlatformIcon.getId()));
+ opLog.setObjectName(roomPlatformIcon.getPlatformName());
+ opLog.setDetail("在地图摆放平台:" + (roomPlatformIcon.getPlatformName() != null ? roomPlatformIcon.getPlatformName() : "") + "(实例ID=" + roomPlatformIcon.getId() + ")");
+ opLog.setSnapshotAfter(JSON.toJSONString(roomPlatformIcon));
+ objectOperationLogService.saveLog(opLog);
+ } catch (Exception e) {
+ logger.warn("记录房间地图平台摆放操作日志失败", e);
+ }
+ }
return rows > 0 ? success(roomPlatformIcon) : error("新增失败");
}
@@ -53,7 +76,29 @@ public class RoomPlatformIconController extends BaseController {
@Log(title = "房间地图平台图标", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody RoomPlatformIcon roomPlatformIcon) {
- return toAjax(roomPlatformIconService.update(roomPlatformIcon));
+ RoomPlatformIcon before = roomPlatformIcon.getId() != null
+ ? roomPlatformIconService.selectById(roomPlatformIcon.getId()) : null;
+ int rows = roomPlatformIconService.update(roomPlatformIcon);
+ if (rows > 0 && before != null) {
+ RoomPlatformIcon after = roomPlatformIconService.selectById(roomPlatformIcon.getId());
+ try {
+ ObjectOperationLog opLog = new ObjectOperationLog();
+ opLog.setRoomId(before.getRoomId());
+ opLog.setOperatorId(getUserId());
+ opLog.setOperatorName(getUsername());
+ opLog.setOperationType(ObjectOperationLog.TYPE_UPDATE);
+ opLog.setObjectType(ObjectOperationLog.OBJ_ROOM_PLATFORM_ICON);
+ opLog.setObjectId(String.valueOf(before.getId()));
+ opLog.setObjectName(after != null ? after.getPlatformName() : before.getPlatformName());
+ opLog.setDetail("调整地图平台位置/朝向/缩放:" + (before.getPlatformName() != null ? before.getPlatformName() : "") + "(实例ID=" + before.getId() + ")");
+ opLog.setSnapshotBefore(JSON.toJSONString(before));
+ opLog.setSnapshotAfter(after != null ? JSON.toJSONString(after) : JSON.toJSONString(roomPlatformIcon));
+ objectOperationLogService.saveLog(opLog);
+ } catch (Exception e) {
+ logger.warn("记录房间地图平台修改操作日志失败", e);
+ }
+ }
+ return toAjax(rows);
}
/**
@@ -64,6 +109,23 @@ public class RoomPlatformIconController extends BaseController {
@DeleteMapping("/{id}")
public AjaxResult remove(@PathVariable Long id) {
RoomPlatformIcon icon = roomPlatformIconService.selectById(id);
+ if (icon != null) {
+ try {
+ ObjectOperationLog opLog = new ObjectOperationLog();
+ opLog.setRoomId(icon.getRoomId());
+ opLog.setOperatorId(getUserId());
+ opLog.setOperatorName(getUsername());
+ opLog.setOperationType(ObjectOperationLog.TYPE_DELETE);
+ opLog.setObjectType(ObjectOperationLog.OBJ_ROOM_PLATFORM_ICON);
+ opLog.setObjectId(String.valueOf(id));
+ opLog.setObjectName(icon.getPlatformName());
+ opLog.setDetail("删除地图上的平台:" + (icon.getPlatformName() != null ? icon.getPlatformName() : "") + "(实例ID=" + id + ")");
+ opLog.setSnapshotBefore(JSON.toJSONString(icon));
+ objectOperationLogService.saveLog(opLog);
+ } catch (Exception e) {
+ logger.warn("记录房间地图平台删除操作日志失败", e);
+ }
+ }
if (icon != null && icon.getRoomId() != null) {
String key = "room:" + icon.getRoomId() + ":platformIcons:platforms";
redisTemplate.opsForHash().delete(key, String.valueOf(id));
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoutesController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoutesController.java
index 01ea4e3..061fbd0 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoutesController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/RoutesController.java
@@ -18,8 +18,10 @@ import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.ObjectOperationLog;
+import com.ruoyi.system.domain.RoomPlatformIcon;
import com.ruoyi.system.domain.Routes;
import com.ruoyi.system.service.IObjectOperationLogService;
+import com.ruoyi.system.service.IRoomPlatformIconService;
import com.ruoyi.system.service.IRoutesService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
@@ -47,6 +49,9 @@ public class RoutesController extends BaseController
private IRoutesService routesService;
@Autowired
+ private IRoomPlatformIconService roomPlatformIconService;
+
+ @Autowired
private IObjectOperationLogService objectOperationLogService;
@Autowired
@@ -85,7 +90,14 @@ public class RoutesController extends BaseController
String hashField = isPlatformIcon && dto.getPlatformIconInstanceId() != null
? String.valueOf(dto.getPlatformIconInstanceId())
: String.valueOf(dto.getPlatformId());
- redisTemplate.opsForHash().put(key, hashField, JSON.toJSONString(dto));
+ Object oldRedisVal = redisTemplate.opsForHash().get(key, hashField);
+ if (oldRedisVal == null && isPlatformIcon) {
+ String legacyKey = "room:" + dto.getRoomId() + ":route:0:platforms";
+ oldRedisVal = redisTemplate.opsForHash().get(legacyKey, hashField);
+ }
+ String snapshotBefore = oldRedisVal != null ? oldRedisVal.toString() : null;
+ String snapshotAfter = JSON.toJSONString(dto);
+ redisTemplate.opsForHash().put(key, hashField, snapshotAfter);
if (isPlatformIcon && dto.getPlatformIconInstanceId() != null) {
String oldKey = "room:" + dto.getRoomId() + ":route:0:platforms";
@@ -94,6 +106,41 @@ public class RoutesController extends BaseController
String oldKey = "room:" + dto.getRoomId() + ":route:0:platforms";
redisTemplate.opsForHash().delete(oldKey, String.valueOf(dto.getPlatformId()));
}
+
+ if (isPlatformIcon && dto.getPlatformIconInstanceId() != null) {
+ boolean styleUnchanged = snapshotBefore != null && snapshotBefore.equals(snapshotAfter);
+ try {
+ Long roomIdLong = null;
+ if (dto.getRoomId() != null && !dto.getRoomId().isEmpty()) {
+ roomIdLong = Long.valueOf(dto.getRoomId());
+ }
+ if (roomIdLong != null && !styleUnchanged) {
+ String opName = dto.getPlatformName();
+ if (opName == null || opName.isEmpty()) {
+ RoomPlatformIcon icon = roomPlatformIconService.selectById(dto.getPlatformIconInstanceId());
+ if (icon != null) {
+ opName = icon.getPlatformName();
+ }
+ }
+ ObjectOperationLog opLog = new ObjectOperationLog();
+ opLog.setRoomId(roomIdLong);
+ opLog.setOperatorId(getUserId());
+ opLog.setOperatorName(getUsername());
+ opLog.setOperationType(ObjectOperationLog.TYPE_UPDATE);
+ opLog.setObjectType(ObjectOperationLog.OBJ_ROOM_PLATFORM_ICON_STYLE);
+ opLog.setObjectId(String.valueOf(dto.getPlatformIconInstanceId()));
+ opLog.setObjectName(opName);
+ String colorHint = dto.getPlatformColor() != null && !dto.getPlatformColor().isEmpty()
+ ? ",图标颜色=" + dto.getPlatformColor() : "";
+ opLog.setDetail("修改地图平台样式(实例ID=" + dto.getPlatformIconInstanceId() + colorHint + ")");
+ opLog.setSnapshotBefore(snapshotBefore);
+ opLog.setSnapshotAfter(snapshotAfter);
+ objectOperationLogService.saveLog(opLog);
+ }
+ } catch (Exception e) {
+ logger.warn("记录地图平台样式操作日志失败", e);
+ }
+ }
return success();
}
@@ -145,10 +192,33 @@ public class RoutesController extends BaseController
if (roomId == null || platformIconInstanceId == null) {
return AjaxResult.error("roomId 与 platformIconInstanceId 不能为空");
}
+ String field = String.valueOf(platformIconInstanceId);
String key = "room:" + roomId + ":platformIcons:platforms";
- redisTemplate.opsForHash().delete(key, String.valueOf(platformIconInstanceId));
String oldKey = "room:" + roomId + ":route:0:platforms";
- redisTemplate.opsForHash().delete(oldKey, String.valueOf(platformIconInstanceId));
+ Object oldVal = redisTemplate.opsForHash().get(key, field);
+ if (oldVal == null) {
+ oldVal = redisTemplate.opsForHash().get(oldKey, field);
+ }
+ redisTemplate.opsForHash().delete(key, field);
+ redisTemplate.opsForHash().delete(oldKey, field);
+ if (oldVal != null) {
+ try {
+ RoomPlatformIcon icon = roomPlatformIconService.selectById(platformIconInstanceId);
+ ObjectOperationLog opLog = new ObjectOperationLog();
+ opLog.setRoomId(roomId);
+ opLog.setOperatorId(getUserId());
+ opLog.setOperatorName(getUsername());
+ opLog.setOperationType(ObjectOperationLog.TYPE_DELETE);
+ opLog.setObjectType(ObjectOperationLog.OBJ_ROOM_PLATFORM_ICON_STYLE);
+ opLog.setObjectId(field);
+ opLog.setObjectName(icon != null ? icon.getPlatformName() : null);
+ opLog.setDetail("清除地图平台样式(实例ID=" + platformIconInstanceId + ")");
+ opLog.setSnapshotBefore(oldVal.toString());
+ objectOperationLogService.saveLog(opLog);
+ } catch (Exception e) {
+ logger.warn("记录清除地图平台样式操作日志失败", e);
+ }
+ }
return success();
}
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 57e9bb3..76dcd4a 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,5 +1,6 @@
package com.ruoyi.websocket.controller;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -22,6 +23,7 @@ import com.ruoyi.system.service.IRoomUserProfileService;
import com.ruoyi.websocket.dto.RoomMemberDTO;
import com.ruoyi.websocket.service.RoomChatService;
import com.ruoyi.websocket.service.RoomOnlineMemberBroadcastService;
+import com.ruoyi.websocket.service.RoomUserActivityService;
import com.ruoyi.websocket.service.RoomWebSocketService;
/**
@@ -50,6 +52,9 @@ public class RoomWebSocketController {
@Autowired
private IRoomUserProfileService roomUserProfileService;
+ @Autowired
+ private RoomUserActivityService roomUserActivityService;
+
private static final String TYPE_JOIN = "JOIN";
private static final String TYPE_LEAVE = "LEAVE";
private static final String TYPE_PING = "PING";
@@ -71,6 +76,8 @@ public class RoomWebSocketController {
private static final String TYPE_OBJECT_EDIT_LOCK = "OBJECT_EDIT_LOCK";
/** 对象编辑解锁 */
private static final String TYPE_OBJECT_EDIT_UNLOCK = "OBJECT_EDIT_UNLOCK";
+ /** 地图选中状态同步(供「当前操作」展示) */
+ private static final String TYPE_USER_SELECTION = "USER_SELECTION";
/**
* 处理房间消息:JOIN、LEAVE、PING、CHAT、PRIVATE_CHAT、SYNC_*
@@ -118,6 +125,8 @@ public class RoomWebSocketController {
handleObjectEditLock(roomId, sessionId, loginUser, body);
} else if (TYPE_OBJECT_EDIT_UNLOCK.equals(type)) {
handleObjectEditUnlock(roomId, sessionId, loginUser, body);
+ } else if (TYPE_USER_SELECTION.equals(type)) {
+ handleUserSelection(roomId, sessionId, loginUser, body);
}
}
@@ -134,12 +143,22 @@ public class RoomWebSocketController {
editor.put("nickName", profile != null ? profile.getDisplayName() : loginUser.getUser().getNickName());
editor.put("sessionId", sessionId);
+ String objectName = "";
+ if (body != null && body.get("objectName") != null) {
+ objectName = String.valueOf(body.get("objectName"));
+ }
+ roomUserActivityService.onEditLock(roomId, sessionId, editor, objectType, objectIdObj, objectName);
+
Map msg = new HashMap<>();
msg.put("type", TYPE_OBJECT_EDIT_LOCK);
msg.put("objectType", objectType);
msg.put("objectId", objectIdObj);
msg.put("editor", editor);
+ if (!objectName.isEmpty()) {
+ msg.put("objectName", objectName);
+ }
messagingTemplate.convertAndSend("/topic/room/" + roomId, msg);
+ roomUserActivityService.broadcastSnapshot(roomId);
}
/** 广播:某成员解锁某对象(结束编辑) */
@@ -148,12 +167,51 @@ public class RoomWebSocketController {
Object objectIdObj = body != null ? body.get("objectId") : null;
if (objectType == null || objectIdObj == null) return;
+ roomUserActivityService.onEditUnlock(roomId, sessionId, objectType, objectIdObj);
+
Map msg = new HashMap<>();
msg.put("type", TYPE_OBJECT_EDIT_UNLOCK);
msg.put("objectType", objectType);
msg.put("objectId", objectIdObj);
msg.put("sessionId", sessionId);
messagingTemplate.convertAndSend("/topic/room/" + roomId, msg);
+ roomUserActivityService.broadcastSnapshot(roomId);
+ }
+
+ private void handleUserSelection(Long roomId, String sessionId, LoginUser loginUser, Map body) {
+ RoomUserProfile profile = roomUserProfileService.getOrInitByUser(loginUser.getUserId(), loginUser.getUsername());
+ Map editor = new HashMap<>();
+ editor.put("userId", loginUser.getUserId());
+ editor.put("userName", loginUser.getUsername());
+ editor.put("nickName", profile != null ? profile.getDisplayName() : loginUser.getUser().getNickName());
+ editor.put("sessionId", sessionId);
+
+ String summary = body != null && body.get("summary") != null ? String.valueOf(body.get("summary")) : "";
+ int count = 0;
+ if (body != null && body.get("selectedCount") instanceof Number) {
+ count = ((Number) body.get("selectedCount")).intValue();
+ } else if (body != null && body.get("selectedCount") != null) {
+ try {
+ count = Integer.parseInt(String.valueOf(body.get("selectedCount")));
+ } catch (NumberFormatException ignored) {
+ count = 0;
+ }
+ }
+ List