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 d702851..0869b0d 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 @@ -10,6 +10,7 @@ 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.common.core.page.TableDataInfo; import com.ruoyi.system.domain.ObjectOperationLog; import com.ruoyi.system.domain.RoomPlatformIcon; import com.ruoyi.system.service.IObjectOperationLogService; @@ -32,16 +33,46 @@ public class RoomPlatformIconController extends BaseController { private IObjectOperationLogService objectOperationLogService; /** - * 按房间ID查询该房间下所有地图平台图标(不分页) + * 查询房间地图平台图标列表(分页) */ @PreAuthorize("@ss.hasPermi('system:roomPlatformIcon:list')") @GetMapping("/list") - public AjaxResult list(@RequestParam Long roomId) { + public TableDataInfo list(RoomPlatformIcon roomPlatformIcon) { + // 兜底:若前端将未选择的筛选值传为 0,则按“未传入”处理,避免 where room_id=0 导致空表 + if (roomPlatformIcon != null) { + if (roomPlatformIcon.getRoomId() != null && roomPlatformIcon.getRoomId() <= 0) { + roomPlatformIcon.setRoomId(null); + } + if (roomPlatformIcon.getPlatformId() != null && roomPlatformIcon.getPlatformId() <= 0) { + roomPlatformIcon.setPlatformId(null); + } + } + startPage(); + List list = roomPlatformIconService.selectRoomPlatformIconList(roomPlatformIcon); + return getDataTable(list); + } + + /** + * 按房间ID查询该房间下所有地图平台图标(不分页,用于 Cesium 地图渲染) + */ + @PreAuthorize("@ss.hasPermi('system:roomPlatformIcon:list')") + @GetMapping("/listByRoomId") + public AjaxResult listByRoomId(@RequestParam Long roomId) { List list = roomPlatformIconService.selectListByRoomId(roomId); return success(list); } /** + * 查询房间地图平台图标详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:roomPlatformIcon:list')") + @GetMapping("/{id}") + public AjaxResult getInfo(@PathVariable Long id) { + RoomPlatformIcon icon = roomPlatformIconService.selectById(id); + return success(icon); + } + + /** * 新增 */ @PreAuthorize("@ss.hasPermi('system:roomPlatformIcon:add')") @@ -106,32 +137,39 @@ public class RoomPlatformIconController extends BaseController { */ @PreAuthorize("@ss.hasPermi('system:roomPlatformIcon:remove')") @Log(title = "房间地图平台图标", businessType = BusinessType.DELETE) - @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); + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + int rows = 0; + if (ids == null || ids.length == 0) return success(); + + for (Long id : ids) { + if (id == null) continue; + RoomPlatformIcon icon = roomPlatformIconService.selectById(id); + if (icon != null && icon.getRoomId() != null) { + String key = "room:" + icon.getRoomId() + ":platformIcons:platforms"; + redisTemplate.opsForHash().delete(key, String.valueOf(id)); + String oldKey = "room:" + icon.getRoomId() + ":route:0:platforms"; + redisTemplate.opsForHash().delete(oldKey, String.valueOf(id)); + } + rows += roomPlatformIconService.deleteById(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)); - String oldKey = "room:" + icon.getRoomId() + ":route:0:platforms"; - redisTemplate.opsForHash().delete(oldKey, String.valueOf(id)); - } - return toAjax(roomPlatformIconService.deleteById(id)); - } + return toAjax(rows); +} } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java index a269b65..dcaf40f 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/RouteWaypointsMapper.java @@ -29,7 +29,7 @@ public interface RouteWaypointsMapper public List selectRouteWaypointsList(RouteWaypoints routeWaypoints); /** 查询指定航线下最大的序号 */ - public Integer selectMaxSeqByRouteId(Long routeId); + public Integer selectMaxSeqByRouteId(@Param("routeId") Long routeId); /** 将指定航线中 seq >= targetSeq 的航点序号均加 1,用于在指定位置插入新航点 */ int incrementSeqFrom(@Param("routeId") Long routeId, @Param("seq") Long targetSeq); @@ -56,7 +56,7 @@ public interface RouteWaypointsMapper * @param id 航线具体航点明细主键 * @return 结果 */ - public int deleteRouteWaypointsById(Long id); + public int deleteRouteWaypointsById(@Param("id") Long id); /** * 删除航线具体航点明细 @@ -64,7 +64,7 @@ public interface RouteWaypointsMapper * @param routeId 航线主键 * @return 结果 */ - public int deleteRouteWaypointsByRouteId(Long routeId); + public int deleteRouteWaypointsByRouteId(@Param("routeId") Long routeId); /** * 批量删除航线具体航点明细 @@ -72,5 +72,5 @@ public interface RouteWaypointsMapper * @param ids 需要删除的数据主键集合 * @return 结果 */ - public int deleteRouteWaypointsByIds(Long[] ids); + public int deleteRouteWaypointsByIds(@Param("ids") Long[] ids); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IRoomPlatformIconService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IRoomPlatformIconService.java index 9c7f7f8..3763c34 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/IRoomPlatformIconService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IRoomPlatformIconService.java @@ -10,6 +10,11 @@ public interface IRoomPlatformIconService { List selectListByRoomId(Long roomId); + /** + * 查询房间地图平台图标列表(支持按 roomId/platformId 过滤,由分页控制) + */ + List selectRoomPlatformIconList(RoomPlatformIcon roomPlatformIcon); + RoomPlatformIcon selectById(Long id); int insert(RoomPlatformIcon roomPlatformIcon); diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoomPlatformIconServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoomPlatformIconServiceImpl.java index 4cfea80..d1d3d3f 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoomPlatformIconServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoomPlatformIconServiceImpl.java @@ -25,6 +25,11 @@ public class RoomPlatformIconServiceImpl implements IRoomPlatformIconService { } @Override + public List selectRoomPlatformIconList(RoomPlatformIcon roomPlatformIcon) { + return roomPlatformIconMapper.selectRoomPlatformIconList(roomPlatformIcon); + } + + @Override public RoomPlatformIcon selectById(Long id) { return roomPlatformIconMapper.selectRoomPlatformIconById(id); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java index 16466c9..e4b939a 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java @@ -462,6 +462,17 @@ public class SysMenuServiceImpl implements ISysMenuService { component = menu.getComponent(); } + // 兜底:二级菜单(menuType=C)若 component 为空,会导致前端无法加载页面(白屏且无任何请求) + // 按若依约定:system//index + else if (StringUtils.isEmpty(menu.getComponent()) + && menu.getParentId().intValue() != MENU_ROOT_ID + && UserConstants.TYPE_MENU.equals(menu.getMenuType()) + && StringUtils.isNotEmpty(menu.getPath()) + && !isInnerLink(menu) + && !isParentView(menu)) + { + component = "system/" + menu.getPath() + "/index"; + } else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != MENU_ROOT_ID && isInnerLink(menu)) { component = UserConstants.INNER_LINK; diff --git a/ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml b/ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml index 908e959..a9269ab 100644 --- a/ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/RouteWaypointsMapper.xml @@ -114,9 +114,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" delete from route_waypoints where route_id = #{routeId} - + delete from route_waypoints where id in - + #{id} diff --git a/ruoyi-ui/src/api/system/roomPlatformIcon.js b/ruoyi-ui/src/api/system/roomPlatformIcon.js index d160f78..7261924 100644 --- a/ruoyi-ui/src/api/system/roomPlatformIcon.js +++ b/ruoyi-ui/src/api/system/roomPlatformIcon.js @@ -3,12 +3,29 @@ import request from '@/utils/request' /** 按房间ID查询该房间下所有地图平台图标 */ export function listByRoomId(roomId) { return request({ - url: '/system/roomPlatformIcon/list', + url: '/system/roomPlatformIcon/listByRoomId', method: 'get', params: { roomId } }) } +/** 查询房间地图平台图标列表(分页) */ +export function listRoomPlatformIcon(query) { + return request({ + url: '/system/roomPlatformIcon/list', + method: 'get', + params: query + }) +} + +/** 查询房间地图平台图标详细 */ +export function getRoomPlatformIcon(id) { + return request({ + url: '/system/roomPlatformIcon/' + id, + method: 'get' + }) +} + /** 新增房间地图平台图标 */ export function addRoomPlatformIcon(data) { return request({ diff --git a/ruoyi-ui/src/assets/styles/element-ui.scss b/ruoyi-ui/src/assets/styles/element-ui.scss index 363092a..3a72d23 100644 --- a/ruoyi-ui/src/assets/styles/element-ui.scss +++ b/ruoyi-ui/src/assets/styles/element-ui.scss @@ -47,11 +47,51 @@ } // to fixed https://github.com/ElemeFE/element/issues/2461 +// 与「编辑航线」自定义面板一致:白底、8px 圆角、标题字重与头部分割线 .el-dialog { transform: none; left: 0; position: relative; margin: 0 auto; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + background: #fff; +} + +.el-dialog__header { + padding: 14px 20px 12px; + border-bottom: 1px solid #ebeef5; +} + +.el-dialog__title { + font-size: 16px; + font-weight: 700; + color: #303133; + line-height: 1.4; +} + +.el-dialog__headerbtn { + top: 14px; + right: 16px; +} + +.el-dialog__headerbtn .el-dialog__close { + color: #909399; +} + +.el-dialog__headerbtn:focus .el-dialog__close, +.el-dialog__headerbtn:hover .el-dialog__close { + color: #606266; +} + +.el-dialog__body { + padding: 12px 20px 16px; +} + +.el-dialog__footer { + padding: 10px 20px 16px; + border-top: 1px solid #ebeef5; } // refine element ui upload diff --git a/ruoyi-ui/src/lang/en.js b/ruoyi-ui/src/lang/en.js index af38a05..b53a7f4 100644 --- a/ruoyi-ui/src/lang/en.js +++ b/ruoyi-ui/src/lang/en.js @@ -53,7 +53,8 @@ export default { }, airspace: { powerZone: 'Power Zone', - threatZone: 'Threat Zone' + threatZone: 'Threat Zone', + generateAirspace: 'Generate Airspace' }, options: { @@ -181,5 +182,38 @@ export default { sendImage: 'Send image', imageUploadFailed: 'Image upload failed, please try again', imageTooLarge: 'Image must be 5MB or smaller' + }, + generateAirspace: { + title: 'Generate Airspace', + shapeType: 'Shape', + polygon: 'Polygon', + rectangle: 'Rectangle', + circle: 'Circle', + sector: 'Sector', + name: 'Name', + namePlaceholder: 'Label on map (optional)', + color: 'Fill color', + borderWidth: 'Outline width', + vertices: 'Vertices', + polygonPlaceholder: 'At least 3 vertices, decimal degrees. One pair per line, or one line: (121.47,31.23), (120.15,30.28) separated by comma/semicolon', + rectangleSwCorner: 'SW corner (lon, lat)', + rectangleNeCorner: 'NE corner (lon, lat)', + cornerLonLatPlaceholder: '(longitude, latitude) e.g. (116.39, 39.90)', + centerLonLat: 'Center (lon, lat)', + radiusM: 'Radius', + radiusUnit: 'km', + startBearing: 'Start bearing (°)', + endBearing: 'End bearing (°)', + cancel: 'Cancel', + confirm: 'Create', + defaultLabel: 'Airspace', + errPolygonPoints: 'Polygon needs at least 3 valid lng,lat vertices', + errRectNumbers: 'Enter SW and NE corners as (longitude, latitude)', + errCircle: 'Enter center as (longitude, latitude) and radius in km', + errSector: 'Enter center as (longitude, latitude) and radius in km', + errBearing: 'Enter valid bearings', + needRoom: 'Enter a mission room first', + successMsg: 'Airspace created; it will be saved with the room', + errImport: 'Failed to create; check coordinates and parameters' } } diff --git a/ruoyi-ui/src/lang/zh.js b/ruoyi-ui/src/lang/zh.js index d733d4e..228b5a7 100644 --- a/ruoyi-ui/src/lang/zh.js +++ b/ruoyi-ui/src/lang/zh.js @@ -53,7 +53,8 @@ export default { }, airspace: { powerZone: '威力区', - threatZone: '威胁区' + threatZone: '威胁区', + generateAirspace: '生成空域' }, options: { @@ -181,5 +182,38 @@ export default { sendImage: '发送图片', imageUploadFailed: '图片上传失败,请重试', imageTooLarge: '图片不能超过 5MB' + }, + generateAirspace: { + title: '生成空域', + shapeType: '形状类型', + polygon: '多边形', + rectangle: '矩形', + circle: '圆形', + sector: '扇形', + name: '名称', + namePlaceholder: '地图上显示的名称(可选)', + color: '填充颜色', + borderWidth: '边线宽度', + vertices: '顶点坐标', + polygonPlaceholder: '至少3个顶点,可每行一对(经度,纬度)或用顿号分隔', + rectangleSwCorner: '西南角经纬度', + rectangleNeCorner: '东北角经纬度', + cornerLonLatPlaceholder: '(经度,纬度)例如 (116.39, 39.90)', + centerLonLat: '圆心经纬度', + radiusM: '半径', + radiusUnit: '千米', + startBearing: '起始方位角(°)', + endBearing: '终止方位角(°)', + cancel: '取消', + confirm: '生成', + defaultLabel: '空域', + errPolygonPoints: '多边形至少需要3个有效顶点(经度,纬度)', + errRectNumbers: '请按(经度,纬度)格式填写有效的西南角与东北角', + errCircle: '请按(经度,纬度)填写有效的圆心与半径(千米)', + errSector: '请按(经度,纬度)填写有效的圆心、半径(千米)', + errBearing: '请填写有效的方位角', + needRoom: '请先进入任务房间', + successMsg: '空域已生成,将随房间自动保存', + errImport: '生成失败,请检查坐标与参数' } } diff --git a/ruoyi-ui/src/store/modules/permission.js b/ruoyi-ui/src/store/modules/permission.js index f21d328..efea480 100644 --- a/ruoyi-ui/src/store/modules/permission.js +++ b/ruoyi-ui/src/store/modules/permission.js @@ -38,13 +38,16 @@ const permission = { const rdata = JSON.parse(JSON.stringify(res.data)) const sidebarRoutes = filterAsyncRouter(sdata) const rewriteRoutes = filterAsyncRouter(rdata, false, true) + // 二次排序:有图标的菜单优先展示;无图标的在下方。 + // 说明:后端按 orderNum 排序,但若部分菜单 icon 为空会显得“乱套”。 + const orderedSidebarRoutes = sortRoutesByIconPresence(sidebarRoutes) const asyncRoutes = filterDynamicRoutes(dynamicRoutes) rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true }) router.addRoutes(asyncRoutes) commit('SET_ROUTES', rewriteRoutes) - commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes)) - commit('SET_DEFAULT_ROUTES', sidebarRoutes) - commit('SET_TOPBAR_ROUTES', sidebarRoutes) + commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(orderedSidebarRoutes)) + commit('SET_DEFAULT_ROUTES', orderedSidebarRoutes) + commit('SET_TOPBAR_ROUTES', orderedSidebarRoutes) resolve(rewriteRoutes) }) }) @@ -103,6 +106,38 @@ function filterChildren(childrenMap, lastRouter = false) { return children } +/** + * 侧边栏排序:优先展示带图标的菜单;不带图标的排后。 + * 稳定排序:同组内相对顺序保持不变。 + */ +function sortRoutesByIconPresence(routes) { + if (!Array.isArray(routes) || routes.length === 0) return routes + + const withIcon = [] + const withoutIcon = [] + routes.forEach(r => { + // 图标字段来源: + // - 标准若依:meta.icon + // - 个别定制:直接放在 icon + const icon = + (r && r.meta && r.meta.icon) != null ? r.meta.icon + : (r && r.icon) != null ? r.icon + : '' + const iconStr = String(icon).trim() + // 约定:'#' 代表“未设置图标”,视为无图标 + if (iconStr !== '' && iconStr !== '#') withIcon.push(r) + else withoutIcon.push(r) + }) + + const sorted = withIcon.concat(withoutIcon) + sorted.forEach(r => { + if (r && Array.isArray(r.children) && r.children.length > 0) { + r.children = sortRoutesByIconPresence(r.children) + } + }) + return sorted +} + // 动态路由遍历,验证是否具备权限 export function filterDynamicRoutes(routes) { const res = [] diff --git a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue index 98bb4e8..73f324f 100644 --- a/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue +++ b/ruoyi-ui/src/views/cesiumMap/ContextMenu.vue @@ -22,7 +22,7 @@ - - -