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 18a57c5..f9c29ef 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 @@ -20,6 +20,9 @@ import com.ruoyi.system.domain.Routes; import com.ruoyi.system.service.IRoutesService; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.system.domain.dto.PlatformStyleDTO; +import com.alibaba.fastjson2.JSON; +import org.springframework.data.redis.core.RedisTemplate; /** * 实体部署与航线Controller @@ -34,6 +37,42 @@ public class RoutesController extends BaseController @Autowired private IRoutesService routesService; + @Autowired + private RedisTemplate redisTemplate; + + /** + * 保存平台样式到 Redis + */ + @PreAuthorize("@ss.hasPermi('system:routes:edit')") + @PostMapping("/savePlatformStyle") + public AjaxResult savePlatformStyle(@RequestBody PlatformStyleDTO dto) + { + if (dto.getRoomId() == null || dto.getRouteId() == null || dto.getPlatformId() == null) { + return AjaxResult.error("参数不完整"); + } + String key = "room:" + dto.getRoomId() + ":route:" + dto.getRouteId() + ":platforms"; + redisTemplate.opsForHash().put(key, String.valueOf(dto.getPlatformId()), JSON.toJSONString(dto)); + return success(); + } + + /** + * 从 Redis 获取平台样式 + */ + @PreAuthorize("@ss.hasPermi('system:routes:query')") + @GetMapping("/getPlatformStyle") + public AjaxResult getPlatformStyle(PlatformStyleDTO dto) + { + if (dto.getRoomId() == null || dto.getRouteId() == null || dto.getPlatformId() == null) { + return AjaxResult.error("参数不完整"); + } + String key = "room:" + dto.getRoomId() + ":route:" + dto.getRouteId() + ":platforms"; + Object val = redisTemplate.opsForHash().get(key, String.valueOf(dto.getPlatformId())); + if (val != null) { + return success(JSON.parseObject(val.toString(), PlatformStyleDTO.class)); + } + return success(); + } + /** * 查询实体部署与航线列表 */ diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java index 3f4f485..113521b 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/RedisConfig.java @@ -25,7 +25,31 @@ public class RedisConfig extends CachingConfigurerSupport RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); - FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer<>(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } + + /** + * 配置 RedisTemplate + * 解决 RoutesController 中注入 RedisTemplate 失败的问题 + */ + @Bean + public RedisTemplate stringObjectRedisTemplate(RedisConnectionFactory connectionFactory) + { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer<>(Object.class); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); diff --git a/ruoyi-ui/src/api/system/routes.js b/ruoyi-ui/src/api/system/routes.js index 36b9db4..cf6ad48 100644 --- a/ruoyi-ui/src/api/system/routes.js +++ b/ruoyi-ui/src/api/system/routes.js @@ -42,3 +42,21 @@ export function delRoutes(id) { method: 'delete' }) } + +// 保存平台样式 +export function savePlatformStyle(data) { + return request({ + url: '/system/routes/savePlatformStyle', + method: 'post', + data: data + }) +} + +// 获取平台样式 +export function getPlatformStyle(query) { + return request({ + url: '/system/routes/getPlatformStyle', + method: 'get', + params: query + }) +} diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index bf79670..4fddb47 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -169,6 +169,8 @@ import RadiusDialog from '../dialogs/RadiusDialog.vue' import axios from 'axios' import request from '@/utils/request' import { getToken } from '@/utils/auth' +import { savePlatformStyle, getRoutes, getPlatformStyle } from '@/api/system/routes' + export default { name: 'CesiumMap', props: { @@ -295,6 +297,8 @@ export default { // 航线上锁状态由父组件通过 prop routeLocked 传入,与右侧列表锁图标同步 // 从平台右键进入的航线绘制:{ platformInfo: { platformId, platform }, mode: 'before'|'after' } platformRouteDrawing: null, + // 航线平台自定义样式缓存:routeId -> { labelFontSize, labelFontColor, platformSize, platformColor } + platformCustomStyles: {}, // 默认样式 defaultStyles: { point: { color: '#FF0000', size: 12 }, @@ -367,6 +371,27 @@ export default { LocateDialog, RadiusDialog }, + watch: { + // 监听 routeLabelVisible 的变化,当航线显示状态改变时,重新应用样式 + // 这样当从隐藏切换为显示时,可以确保样式被正确应用 + routeLabelVisible: { + handler(newVal) { + // 使用 debounce 或 setTimeout 避免频繁调用 + if (this.applyStylesTimer) clearTimeout(this.applyStylesTimer); + this.applyStylesTimer = setTimeout(() => { + this.applyRedisPlatformStyles(); + }, 300); + }, + deep: true + }, + // 监听 allEntities 变化,当有新实体添加时尝试应用样式(主要针对从隐藏到显示的场景) + allEntities(newVal) { + if (this.applyStylesTimer) clearTimeout(this.applyStylesTimer); + this.applyStylesTimer = setTimeout(() => { + this.applyRedisPlatformStyles(); + }, 500); + } + }, mounted() { console.log(this.drawDomClick,999999) // this.initMap() @@ -1616,7 +1641,10 @@ export default { id: platformBillboardId, name: (platform && platform.name) || '平台', position: originalPositions[0], - properties: { routeId: routeId }, + properties: { + routeId: routeId, + platformId: (platform && platform.id) || 0 + }, billboard: { image: fullUrl, width: 144, @@ -1636,6 +1664,15 @@ export default { if (target && target.billboard) { target.billboard.image = whiteImage; // 此时 target.billboard.color 已在创建时设为 BLACK(航线飞机默认黑色),染色后显示为黑色图标 + + // 如果在图片处理期间已经加载了自定义样式(Redis),需要重新应用一次颜色否则这里会保持默认黑色,覆盖掉 applyRedisPlatformStyles 的效果(如果它先执行了) + + if (this.platformCustomStyles && this.platformCustomStyles[routeId]) { + const style = this.platformCustomStyles[routeId]; + if (style.platformColor) { + target.billboard.color = Cesium.Color.fromCssColorString(style.platformColor); + } + } // 触发一次渲染刷新 if (this.viewer.scene.requestRenderMode) { @@ -2327,9 +2364,18 @@ export default { if (!labelEntity.labelDataCache) labelEntity.labelDataCache = {}; Object.assign(labelEntity.labelDataCache, labelData); - if (labelEntity.billboard) { - const style = this.routeLabelStyles[routeId] || { fontSize: 16, fontColor: '#000000' }; + // 优先使用 Redis 自定义样式,如果没有则使用 routeLabelStyles(可能为空或旧值) + const customStyle = this.platformCustomStyles[routeId]; + let style = { fontSize: 16, fontColor: '#000000' }; + + if (customStyle) { + style.fontSize = customStyle.labelFontSize || 16; + style.fontColor = customStyle.labelFontColor || '#000000'; + } else if (this.routeLabelStyles[routeId]) { + style = this.routeLabelStyles[routeId]; + } + if (labelEntity.billboard) { // 预处理显示数据(取整),既解决了小数点过长问题,也用于差异检测 const displayData = { name: labelData.name || '平台', @@ -2720,12 +2766,29 @@ export default { const idStr = typeof pickedEntity.id === 'string' ? pickedEntity.id : (pickedEntity.id || '') let entityData = null // 航线上的飞机(平台图标):右键可切换标牌显示/隐藏 - if (idStr.startsWith('route-platform-') && !idStr.startsWith('route-platform-label-')) { - const routeId = idStr.replace('route-platform-', '') + if (idStr.startsWith('route-platform-')) { + const routeId = idStr.replace('route-platform-', '').replace('route-platform-label-', '') + // 尝试获取该实体绑定的 platformId 和 platformName + // 通常这些信息绑定在 entity.properties 中 + let platformId = 0 + let platformName = '平台' + if (pickedEntity.properties) { + const now = Cesium.JulianDate.now(); + // 尝试直接获取 + if (pickedEntity.properties.platformId) { + platformId = pickedEntity.properties.platformId.getValue ? pickedEntity.properties.platformId.getValue(now) : pickedEntity.properties.platformId + } + if (pickedEntity.properties.platformName) { + platformName = pickedEntity.properties.platformName.getValue ? pickedEntity.properties.platformName.getValue(now) : pickedEntity.properties.platformName + } + } + entityData = { type: 'routePlatform', routeId, entity: pickedEntity, + platformId, + platformName, labelVisible: this.routeLabelVisible[routeId] !== false } } @@ -5274,6 +5337,17 @@ export default { const platformEntity = this.viewer.entities.getById(`route-platform-${routeId}`) let iconSize = 144 let iconColor = '#ffffff' + let platformName = '平台' // 默认名称 + + // 尝试获取平台名称 + if (labelEntity && labelEntity.label && labelEntity.label.text) { + const now = Cesium.JulianDate.now() + const text = labelEntity.label.text.getValue ? labelEntity.label.text.getValue(now) : labelEntity.label.text + if (text) platformName = text + } else if (ed.platformName) { + platformName = ed.platformName + } + if (platformEntity && platformEntity.billboard) { const now = Cesium.JulianDate.now() const widthValue = platformEntity.billboard.width && platformEntity.billboard.width.getValue @@ -5294,6 +5368,23 @@ export default { this.editPlatformForm.fontColor = fontColor this.editPlatformForm.iconSize = iconSize this.editPlatformForm.iconColor = iconColor + this.editPlatformForm.platformName = platformName + this.editPlatformForm.platformId = ed.platformId || 0 + + // 异步获取最新航线信息,更新 platformId 和 platformName + if (routeId) { + getRoutes(routeId).then(response => { + const route = response.data; + if (route && route.platformId) { + this.editPlatformForm.platformId = route.platformId; + if (route.platform && route.platform.name) { + this.editPlatformForm.platformName = route.platform.name; + } + } + }).catch(err => { + console.error("获取航线信息失败", err); + }); + } this.contextMenu.visible = false this.editPlatformDialogVisible = true @@ -5352,6 +5443,35 @@ export default { platformEntity.billboard.height = size platformEntity.billboard.color = Cesium.Color.fromCssColorString(color) } + + // 更新样式缓存 + this.$set(this.platformCustomStyles, routeId, { + labelFontSize: fontSize, + labelFontColor: fontColor, + platformSize: Math.max(48, Math.min(256, Number(this.editPlatformForm.iconSize) || 144)), + platformColor: this.editPlatformForm.iconColor || '#ffffff' + }); + + // 调用后端接口保存样式到Redis + const currentRoomId = this.$route.query.roomId || (this.$parent && this.$parent.currentRoomId); + + if (currentRoomId) { + const styleData = { + roomId: String(currentRoomId), + routeId: routeId, + platformId: this.editPlatformForm.platformId, // 需要确保 openEditPlatformDialog 中设置了 platformId + platformName: this.editPlatformForm.platformName, // 需要确保 openEditPlatformDialog 中设置了 platformName + labelFontSize: fontSize, + labelFontColor: fontColor, + platformSize: Math.max(48, Math.min(256, Number(this.editPlatformForm.iconSize) || 144)), + platformColor: this.editPlatformForm.iconColor || '#ffffff' + }; + savePlatformStyle(styleData).then(() => { + // console.log("样式保存成功"); + }).catch(err => { + console.error("样式保存失败", err); + }); + } if (this.viewer.scene && this.viewer.scene.requestRenderMode) { this.viewer.scene.requestRender() @@ -5434,6 +5554,84 @@ export default { this.contextMenu.visible = false } }, + + /** + * 批量获取并应用航线平台样式(Redis) + * 遍历 allEntities 中的所有航线平台,调用 getPlatformStyle + */ + applyRedisPlatformStyles() { + // 获取 roomId + const currentRoomId = this.$route.query.roomId || (this.$parent && this.$parent.currentRoomId); + if (!currentRoomId) return; + + // 遍历查找所有的航线平台实体 + const entities = this.viewer.entities.values; + for (const entity of entities) { + const idStr = entity.id || ''; + if (idStr.startsWith('route-platform-') && !idStr.startsWith('route-platform-label-')) { + const routeId = idStr.replace('route-platform-', ''); + + // 获取 platformId + let platformId = 0; + if (entity.properties && entity.properties.platformId) { + const now = Cesium.JulianDate.now(); + platformId = entity.properties.platformId.getValue ? entity.properties.platformId.getValue(now) : entity.properties.platformId; + } + + // 确保 platformId 存在(可能是字符串或数字,需转为数字判断) + if (Number(platformId) > 0) { + getPlatformStyle({ + roomId: currentRoomId, + routeId: routeId, + platformId: platformId + }).then(res => { + if (res.data) { + const style = res.data; + + // 缓存样式 + this.$set(this.platformCustomStyles, routeId, { + labelFontSize: style.labelFontSize, + labelFontColor: style.labelFontColor, + platformSize: style.platformSize, + platformColor: style.platformColor + }); + + // 应用标牌样式 + const labelEntity = this.viewer.entities.getById(`route-platform-label-${routeId}`); + if (labelEntity) { + // 触发重绘(借助 updatePlatformPosition 中的逻辑) + // 这里手动设置一下样式,updatePlatformPosition 会在下次更新时使用 + this.$set(this.routeLabelStyles, routeId, { + fontSize: style.labelFontSize || 16, + fontColor: style.labelFontColor || '#333333' + }); + // 强制更新标牌内容 + if (labelEntity.labelDataCache) { + // 清除最后更新时间,强制刷新 + labelEntity._lastUpdateTime = 0; + this.updatePlatformPosition(routeId, entity.position.getValue(Cesium.JulianDate.now()), null, labelEntity.labelDataCache); + } + } + + // 应用平台图标样式 + if (entity.billboard) { + const size = Math.max(48, Math.min(256, Number(style.platformSize) || 144)); + const color = style.platformColor || '#ffffff'; + entity.billboard.width = size; + entity.billboard.height = size; + entity.billboard.color = Cesium.Color.fromCssColorString(color); + } + + if (this.viewer.scene.requestRenderMode) { + this.viewer.scene.requestRender(); + } + } + }); + } + } + } + }, + // 更新实体属性 updateEntityProperty(property, value) { if (this.contextMenu.entityData) {