diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/PlatformStyleDTO.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/PlatformStyleDTO.java index f39f7c7..16b2766 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/PlatformStyleDTO.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/dto/PlatformStyleDTO.java @@ -27,6 +27,12 @@ public class PlatformStyleDTO implements Serializable { /** 平台颜色 */ private String platformColor; + /** 威力区半径(千米) */ + private Double powerZoneRadius; + + /** 威力区填充颜色 */ + private String powerZoneColor; + public String getRoomId() { return roomId; } @@ -90,4 +96,20 @@ public class PlatformStyleDTO implements Serializable { public void setPlatformColor(String platformColor) { this.platformColor = platformColor; } + + public Double getPowerZoneRadius() { + return powerZoneRadius; + } + + public void setPowerZoneRadius(Double powerZoneRadius) { + this.powerZoneRadius = powerZoneRadius; + } + + public String getPowerZoneColor() { + return powerZoneColor; + } + + public void setPowerZoneColor(String powerZoneColor) { + this.powerZoneColor = powerZoneColor; + } } diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue index 358b0e8..a4fe3d9 100644 --- a/ruoyi-ui/src/views/cesiumMap/index.vue +++ b/ruoyi-ui/src/views/cesiumMap/index.vue @@ -92,7 +92,11 @@ /> - + 平台设置 @@ -106,7 +110,11 @@ /> - + @@ -214,30 +222,7 @@ export default { default: () => ({}) } }, - watch: { - drawDomClick: { - immediate: true, // 组件初始化时立即执行一次 - handler(newVal, oldVal) { - // 可选:如果需要在值变化时执行额外逻辑(比如初始化地图) - if (newVal) { - // this.initMap() - } - } - }, - scaleConfig: { - deep: true, - handler(newVal) { - if (newVal) { - this.updateScaleFromConfig(newVal) - } - } - }, - coordinateFormat: { - handler(newVal) { - this.updateCoordinatesDisplay() - } - } - }, + data() { return { viewer: null, @@ -287,6 +272,13 @@ export default { iconSize: 144, iconColor: '#ffffff' }, + // 编辑平台属性:字体颜色、平台颜色预选 + presetColors: [ + '#000000', '#333333', '#666666', '#999999', '#FFFFFF', + '#FF0000', '#FF6600', '#FFA500', '#FFFF00', '#00FF00', + '#00FFFF', '#0066FF', '#0000FF', '#6600FF', '#FF00FF', + '#800080', '#FF69B4', '#A52A2A', '#228B22' + ], powerZoneDialogVisible: false, powerZoneForm: { routeId: null, @@ -379,6 +371,28 @@ export default { watch: { // 监听 routeLabelVisible 的变化,当航线显示状态改变时,重新应用样式 // 这样当从隐藏切换为显示时,可以确保样式被正确应用 + drawDomClick: { + immediate: true, // 组件初始化时立即执行一次 + handler(newVal, oldVal) { + // 可选:如果需要在值变化时执行额外逻辑(比如初始化地图) + if (newVal) { + // this.initMap() + } + } + }, + scaleConfig: { + deep: true, + handler(newVal) { + if (newVal) { + this.updateScaleFromConfig(newVal) + } + } + }, + coordinateFormat: { + handler(newVal) { + this.updateCoordinatesDisplay() + } + }, routeLabelVisible: { handler(newVal) { // 使用 debounce 或 setTimeout 避免频繁调用 @@ -394,7 +408,7 @@ export default { if (this.applyStylesTimer) clearTimeout(this.applyStylesTimer); this.applyStylesTimer = setTimeout(() => { this.applyRedisPlatformStyles(); - }, 500); + }, 80); } }, mounted() { @@ -1637,55 +1651,87 @@ export default { if (iconUrl && originalPositions.length > 0) { const platformBillboardId = `route-platform-${routeId}`; const fullUrl = this.formatPlatformIconUrl(iconUrl); + const platId = (platform && platform.id) || 0; let initialRotation; const pathData = this.getRoutePathWithSegmentIndices(waypoints); if (pathData.path && pathData.path.length >= 2) { const heading = this.computeHeadingFromPositions(pathData.path[0], pathData.path[1]); if (heading !== undefined) initialRotation = Math.PI / 2 - heading; } - this.viewer.entities.add({ - id: platformBillboardId, - name: (platform && platform.name) || '平台', - position: originalPositions[0], - properties: { - routeId: routeId, - platformId: (platform && platform.id) || 0 - }, - billboard: { - image: fullUrl, - width: 144, - height: 144, - color: Cesium.Color.BLACK, // 航线飞机默认黑色,可通过编辑飞机颜色功能修改 - verticalOrigin: Cesium.VerticalOrigin.CENTER, - horizontalOrigin: Cesium.HorizontalOrigin.CENTER, - scaleByDistance: new Cesium.NearFarScalar(500, 2.0, 200000, 0.4), - translucencyByDistance: new Cesium.NearFarScalar(1000, 1.0, 500000, 0.6), - ...(initialRotation !== undefined && { rotation: initialRotation }) - } - }); - - // 异步加载并处理图片为白色,以便支持后续染色 - this.loadAndWhitenImage(fullUrl).then(whiteImage => { - const target = this.viewer.entities.getById(platformBillboardId); - if (target && target.billboard) { - target.billboard.image = whiteImage; - // 此时 target.billboard.color 已在创建时设为 BLACK(航线飞机默认黑色),染色后显示为黑色图标 - - // 如果在图片处理期间已经加载了自定义样式(Redis),需要重新应用一次颜色否则这里会保持默认黑色,覆盖掉 applyRedisPlatformStyles 的效果(如果它先执行了) + const currentRoomId = this.$route.query.roomId || (this.$parent && this.$parent.currentRoomId); + const cachedStyle = this.platformCustomStyles && this.platformCustomStyles[routeId]; - if (this.platformCustomStyles && this.platformCustomStyles[routeId]) { - const style = this.platformCustomStyles[routeId]; - if (style.platformColor) { - target.billboard.color = Cesium.Color.fromCssColorString(style.platformColor); - } + const addPlatformBillboard = (initialColor, initialSize) => { + this.viewer.entities.add({ + id: platformBillboardId, + name: (platform && platform.name) || '平台', + position: originalPositions[0], + properties: { + routeId: routeId, + platformId: platId + }, + billboard: { + image: fullUrl, + width: initialSize, + height: initialSize, + color: Cesium.Color.fromCssColorString(initialColor), + verticalOrigin: Cesium.VerticalOrigin.CENTER, + horizontalOrigin: Cesium.HorizontalOrigin.CENTER, + scaleByDistance: new Cesium.NearFarScalar(500, 2.0, 200000, 0.4), + translucencyByDistance: new Cesium.NearFarScalar(1000, 1.0, 500000, 0.6), + ...(initialRotation !== undefined && { rotation: initialRotation }) } + }); - // 触发一次渲染刷新 - if (this.viewer.scene.requestRenderMode) { - this.viewer.scene.requestRender(); + this.loadAndWhitenImage(fullUrl).then(whiteImage => { + const target = this.viewer.entities.getById(platformBillboardId); + if (target && target.billboard) { + target.billboard.image = whiteImage; + if (this.platformCustomStyles && this.platformCustomStyles[routeId] && this.platformCustomStyles[routeId].platformColor) { + target.billboard.color = Cesium.Color.fromCssColorString(this.platformCustomStyles[routeId].platformColor); + } + if (this.viewer.scene.requestRenderMode) { + this.viewer.scene.requestRender(); + } } + }); + }; + + if (cachedStyle && cachedStyle.platformColor != null) { + const size = (cachedStyle.platformSize != null) ? Math.max(48, Math.min(256, Number(cachedStyle.platformSize))) : 144; + addPlatformBillboard(cachedStyle.platformColor, size); + if (cachedStyle.powerZoneRadius != null && Number(cachedStyle.powerZoneRadius) > 0) { + this.$nextTick(() => { + this.ensurePowerZoneForRoute(routeId, cachedStyle.powerZoneRadius, cachedStyle.powerZoneColor || 'rgba(255, 0, 0, 0.3)'); + }); } - }); + } else if (currentRoomId && Number(platId) > 0) { + getPlatformStyle({ + roomId: currentRoomId, + routeId: routeId, + platformId: platId + }).then(res => { + const style = res.data; + const color = (style && style.platformColor) ? style.platformColor : '#ffffff'; + const size = (style && style.platformSize != null) ? Math.max(48, Math.min(256, Number(style.platformSize))) : 144; + this.$set(this.platformCustomStyles, routeId, { + labelFontSize: style && style.labelFontSize, + labelFontColor: style && style.labelFontColor, + platformSize: size, + platformColor: color, + powerZoneRadius: style && style.powerZoneRadius, + powerZoneColor: style && style.powerZoneColor + }); + addPlatformBillboard(color, size); + if (style && style.powerZoneRadius != null && Number(style.powerZoneRadius) > 0) { + this.ensurePowerZoneForRoute(routeId, style.powerZoneRadius, style.powerZoneColor || 'rgba(255, 0, 0, 0.3)'); + } + }).catch(() => { + addPlatformBillboard('#ffffff', 144); + }); + } else { + addPlatformBillboard('#ffffff', 144); + } // 飞机标牌:显示名字、高度、速度、航向,随飞机实时运动 const firstWp = waypoints[0]; @@ -5498,36 +5544,28 @@ export default { this.contextMenu.visible = false return } - // 重置表单,保留上次的 routeId(半径单位为千米) + const routeId = ed.routeId + const cached = this.platformCustomStyles && this.platformCustomStyles[routeId] this.powerZoneForm = { - routeId: ed.routeId, - radius: 10, - color: 'rgba(255, 0, 0, 0.3)' + routeId, + radius: (cached && cached.powerZoneRadius != null) ? Number(cached.powerZoneRadius) : 10, + color: (cached && cached.powerZoneColor) ? cached.powerZoneColor : 'rgba(255, 0, 0, 0.3)' } this.contextMenu.visible = false this.powerZoneDialogVisible = true }, - /** 应用威力区:以当前平台位置为中心画圆 */ - applyPowerZone() { - const routeId = this.powerZoneForm.routeId - if (!routeId || !this.viewer) { - this.powerZoneDialogVisible = false - return - } + /** + * 以平台位置为中心绘制威力区圆(供 applyPowerZone 与 applyRedisPlatformStyles 复用) + */ + ensurePowerZoneForRoute(routeId, radiusKm, color) { + if (!routeId || !this.viewer) return const platformEntity = this.viewer.entities.getById(`route-platform-${routeId}`) - if (!platformEntity) { - this.$message.error('未找到对应平台,无法添加威力区') - this.powerZoneDialogVisible = false - return - } - - // 移除旧的威力区(如果存在) + if (!platformEntity) return const oldZoneId = `power-zone-${routeId}` this.viewer.entities.removeById(oldZoneId) - - // 添加新威力区 - // 使用 CallbackProperty 绑定位置,使其随飞机移动 + const radius = Number(radiusKm) || 10 + const fillColor = color || 'rgba(255, 0, 0, 0.3)' this.viewer.entities.add({ id: oldZoneId, position: new Cesium.CallbackProperty(() => { @@ -5535,20 +5573,67 @@ export default { return platformEntity.position.getValue(now) }, false), ellipse: { - semiMinorAxis: this.powerZoneForm.radius * 1000, - semiMajorAxis: this.powerZoneForm.radius * 1000, - material: Cesium.Color.fromCssColorString(this.powerZoneForm.color), + semiMinorAxis: radius * 1000, + semiMajorAxis: radius * 1000, + material: Cesium.Color.fromCssColorString(fillColor), outline: true, - outlineColor: Cesium.Color.fromCssColorString(this.powerZoneForm.color).withAlpha(1.0), + outlineColor: Cesium.Color.fromCssColorString(fillColor).withAlpha(1.0), outlineWidth: 2 } }) - if (this.viewer.scene.requestRenderMode) { this.viewer.scene.requestRender() } - this.powerZoneDialogVisible = false - this.$message.success(`已添加半径 ${this.powerZoneForm.radius} 千米的威力区`) + }, + + /** 应用威力区:以当前平台位置为中心画圆,并保存到 Redis */ + applyPowerZone() { + const routeId = this.powerZoneForm.routeId + if (!routeId || !this.viewer) { + this.powerZoneDialogVisible = false + return + } + const platformEntity = this.viewer.entities.getById(`route-platform-${routeId}`) + if (!platformEntity) { + this.$message.error('未找到对应平台,无法添加威力区') + this.powerZoneDialogVisible = false + return + } + const currentRoomId = this.$route.query.roomId || (this.$parent && this.$parent.currentRoomId) + let platformId = 0 + if (platformEntity.properties && platformEntity.properties.platformId) { + const now = Cesium.JulianDate.now() + platformId = platformEntity.properties.platformId.getValue ? platformEntity.properties.platformId.getValue(now) : platformEntity.properties.platformId + } + const radius = this.powerZoneForm.radius + const color = this.powerZoneForm.color + + const doDrawAndClose = () => { + this.ensurePowerZoneForRoute(routeId, radius, color) + this.powerZoneDialogVisible = false + this.$message.success(`已添加半径 ${radius} 千米的威力区`) + } + + if (currentRoomId && Number(platformId) > 0) { + getPlatformStyle({ roomId: currentRoomId, routeId, platformId }).then(res => { + const existing = res.data || {} + const styleData = { + roomId: String(currentRoomId), + routeId, + platformId, + platformName: existing.platformName || undefined, + labelFontSize: existing.labelFontSize, + labelFontColor: existing.labelFontColor, + platformSize: existing.platformSize, + platformColor: existing.platformColor, + powerZoneRadius: radius, + powerZoneColor: color + } + savePlatformStyle(styleData).then(doDrawAndClose).catch(() => doDrawAndClose()) + }).catch(() => doDrawAndClose()) + } else { + doDrawAndClose() + } }, // 从右键菜单删除实体 deleteEntityFromContextMenu() { @@ -5565,6 +5650,23 @@ export default { }, /** + * 由父组件在渲染航线前调用,预先写入平台样式,使 renderRouteWaypoints 创建实体时可直接用该颜色(无延迟) + * @param {string} routeId - 航线 id + * @param {Object} style - { labelFontSize, labelFontColor, platformSize, platformColor, powerZoneRadius, powerZoneColor } + */ + setPlatformStyle(routeId, style) { + if (!routeId || !style) return; + this.$set(this.platformCustomStyles, routeId, { + labelFontSize: style.labelFontSize, + labelFontColor: style.labelFontColor, + platformSize: style.platformSize != null ? style.platformSize : 144, + platformColor: style.platformColor != null ? style.platformColor : '#ffffff', + powerZoneRadius: style.powerZoneRadius, + powerZoneColor: style.powerZoneColor + }); + }, + + /** * 批量获取并应用航线平台样式(Redis) * 遍历 allEntities 中的所有航线平台,调用 getPlatformStyle */ @@ -5597,12 +5699,14 @@ export default { if (res.data) { const style = res.data; - // 缓存样式 + // 缓存样式(含威力区) this.$set(this.platformCustomStyles, routeId, { labelFontSize: style.labelFontSize, labelFontColor: style.labelFontColor, platformSize: style.platformSize, - platformColor: style.platformColor + platformColor: style.platformColor, + powerZoneRadius: style.powerZoneRadius, + powerZoneColor: style.powerZoneColor }); // 应用标牌样式 @@ -5631,6 +5735,11 @@ export default { entity.billboard.color = Cesium.Color.fromCssColorString(color); } + // 应用威力区(半径与颜色从 Redis 读出) + if (style.powerZoneRadius != null && style.powerZoneRadius > 0) { + this.ensurePowerZoneForRoute(routeId, style.powerZoneRadius, style.powerZoneColor || 'rgba(255, 0, 0, 0.3)'); + } + if (this.viewer.scene.requestRenderMode) { this.viewer.scene.requestRender(); } diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue index 72528ad..316d537 100644 --- a/ruoyi-ui/src/views/childRoom/index.vue +++ b/ruoyi-ui/src/views/childRoom/index.vue @@ -422,7 +422,7 @@ import RightPanel from './RightPanel' import BottomLeftPanel from './BottomLeftPanel' import TopHeader from './TopHeader' import { listScenario, addScenario, delScenario } from "@/api/system/scenario"; -import { listRoutes, getRoutes, addRoutes, updateRoutes, delRoutes } from "@/api/system/routes"; +import { listRoutes, getRoutes, addRoutes, updateRoutes, delRoutes, getPlatformStyle } from "@/api/system/routes"; import { updateWaypoints, addWaypoints, delWaypoints } from "@/api/system/waypoints"; import { listLib,addLib,delLib} from "@/api/system/lib"; import { getRooms, updateRooms } from "@/api/system/rooms"; @@ -893,8 +893,14 @@ export default { if (i !== -1) this.selectedRouteDetails.waypoints.splice(i, 1, merged); } if (this.$refs.cesiumMap) { - // 平台信息来自 list 的 route(selectedRouteDetails 来自 getRoutes 可能不含 platformId/platform) const routeForPlatform = this.routes.find(r => r.id === routeId) || route; + const roomId = this.currentRoomId; + if (roomId && routeForPlatform.platformId) { + try { + const styleRes = await getPlatformStyle({ roomId, routeId, platformId: routeForPlatform.platformId }); + if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(routeId, styleRes.data); + } catch (_) {} + } this.$refs.cesiumMap.renderRouteWaypoints( waypoints, routeId, @@ -1059,6 +1065,13 @@ export default { if (this.$refs.cesiumMap && this.activeRouteIds.includes(updatedRoute.id)) { const route = this.routes.find(r => r.id === updatedRoute.id); if (route && route.waypoints && route.waypoints.length > 0) { + const roomId = this.currentRoomId; + if (roomId && route.platformId) { + try { + const styleRes = await getPlatformStyle({ roomId, routeId: updatedRoute.id, platformId: route.platformId }); + if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(updatedRoute.id, styleRes.data); + } catch (_) {} + } this.$refs.cesiumMap.removeRouteById(updatedRoute.id); this.$refs.cesiumMap.renderRouteWaypoints(route.waypoints, updatedRoute.id, route.platformId, route.platform, routeStyle); // 切换平台后按当前推演时间把图标更新到对应位置,避免图标回到起点 @@ -1159,17 +1172,25 @@ export default { }); this.routes = allRoutes; - // 直接根据 activeRouteIds 同步地图即可 - if (this.activeRouteIds.length > 0) { + // 先预取所有展示中航线的平台样式,再渲染,避免平台图标先黑后变色 + if (this.activeRouteIds.length > 0 && this.$refs.cesiumMap) { + const roomId = this.currentRoomId; + await Promise.all(this.activeRouteIds.map(async (id) => { + const route = this.routes.find(r => r.id === id); + if (!route || !route.waypoints || route.waypoints.length === 0) return; + if (roomId && route.platformId) { + try { + const res = await getPlatformStyle({ roomId, routeId: id, platformId: route.platformId }); + if (res.data) this.$refs.cesiumMap.setPlatformStyle(id, res.data); + } catch (_) {} + } + })); this.$nextTick(() => { this.activeRouteIds.forEach(id => { const route = this.routes.find(r => r.id === id); - // 只要数据里有点位,直接指挥地图画线 - if (route && route.waypoints && route.waypoints.length > 0) { - if (this.$refs.cesiumMap) { - this.$refs.cesiumMap.removeRouteById(id); - this.$refs.cesiumMap.renderRouteWaypoints(route.waypoints, id, route.platformId, route.platform, this.parseRouteStyle(route.attributes)); - } + if (route && route.waypoints && route.waypoints.length > 0 && this.$refs.cesiumMap) { + this.$refs.cesiumMap.removeRouteById(id); + this.$refs.cesiumMap.renderRouteWaypoints(route.waypoints, id, route.platformId, route.platform, this.parseRouteStyle(route.attributes)); } }); }); @@ -1381,13 +1402,21 @@ export default { if (idxInList !== -1) routeInList.waypoints.splice(idxInList, 1, merged); } if (this.$refs.cesiumMap) { + const roomId = this.currentRoomId; + const sd = this.selectedRouteDetails; + if (roomId && sd.platformId) { + try { + const styleRes = await getPlatformStyle({ roomId, routeId: sd.id, platformId: sd.platformId }); + if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(sd.id, styleRes.data); + } catch (_) {} + } this.$refs.cesiumMap.updateWaypointGraphicById(updatedWaypoint.id, updatedWaypoint.name); this.$refs.cesiumMap.renderRouteWaypoints( - this.selectedRouteDetails.waypoints, - this.selectedRouteDetails.id, - this.selectedRouteDetails.platformId, - this.selectedRouteDetails.platform, - this.parseRouteStyle(this.selectedRouteDetails.attributes) + sd.waypoints, + sd.id, + sd.platformId, + sd.platform, + this.parseRouteStyle(sd.attributes) ); } this.showWaypointDialog = false; @@ -2711,6 +2740,13 @@ export default { await this.getList(); const updated = this.routes.find(r => r.id === routeId); if (updated && updated.waypoints && this.activeRouteIds.includes(routeId) && this.$refs.cesiumMap) { + const roomId = this.currentRoomId; + if (roomId && updated.platformId) { + try { + const styleRes = await getPlatformStyle({ roomId, routeId, platformId: updated.platformId }); + if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(routeId, styleRes.data); + } catch (_) {} + } this.$refs.cesiumMap.removeRouteById(routeId); this.$refs.cesiumMap.renderRouteWaypoints(updated.waypoints, routeId, updated.platformId, updated.platform, this.parseRouteStyle(updated.attributes)); this.$nextTick(() => this.updateDeductionPositions()); @@ -2837,8 +2873,14 @@ export default { } if (waypoints.length > 0) { - // 通知地图渲染 if (this.$refs.cesiumMap) { + const roomId = this.currentRoomId; + if (roomId && route.platformId) { + try { + const styleRes = await getPlatformStyle({ roomId, routeId: route.id, platformId: route.platformId }); + if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(route.id, styleRes.data); + } catch (_) {} + } this.$refs.cesiumMap.renderRouteWaypoints(waypoints, route.id, route.platformId, route.platform, this.parseRouteStyle(route.attributes)); } } else {