|
|
|
@ -2065,12 +2065,19 @@ export default { |
|
|
|
const styleRes = await getPlatformStyle({ roomId: rId, routeId, platformId: route.platformId }); |
|
|
|
if (styleRes.data && this.$refs.cesiumMap) { |
|
|
|
this.$refs.cesiumMap.setPlatformStyle(routeId, styleRes.data); |
|
|
|
if (styleRes.data.detectionZoneVisible !== false && styleRes.data.detectionZoneRadius != null && Number(styleRes.data.detectionZoneRadius) > 0) { |
|
|
|
this.$refs.cesiumMap.ensureDetectionZoneForRoute(routeId, styleRes.data.detectionZoneRadius, styleRes.data.detectionZoneColor || 'rgba(0, 150, 255, 0.35)', styleRes.data.detectionZoneOpacity); |
|
|
|
} |
|
|
|
if (styleRes.data.powerZoneVisible !== false && styleRes.data.powerZoneRadius != null && Number(styleRes.data.powerZoneRadius) > 0) { |
|
|
|
this.$refs.cesiumMap.ensurePowerZoneForRoute(routeId, styleRes.data.powerZoneRadius, styleRes.data.powerZoneAngle ?? 120, styleRes.data.powerZoneColor || 'rgba(255, 0, 0, 0.3)', styleRes.data.powerZoneOpacity); |
|
|
|
} |
|
|
|
const normalized = this.$refs.cesiumMap.normalizeZonesFromStyle(styleRes.data); |
|
|
|
(normalized.detectionZones || []).forEach(dz => { |
|
|
|
if (!dz) return; |
|
|
|
if (dz.radiusKm == null || Number(dz.radiusKm) <= 0) return; |
|
|
|
const opacity = dz.visible === false ? 0 : dz.opacity; |
|
|
|
this.$refs.cesiumMap.ensureDetectionZoneForRoute(routeId, dz.zoneId, dz.radiusKm, dz.color, opacity); |
|
|
|
}); |
|
|
|
(normalized.powerZones || []).forEach(pz => { |
|
|
|
if (!pz) return; |
|
|
|
if (pz.radiusKm == null || Number(pz.radiusKm) <= 0) return; |
|
|
|
const opacity = pz.visible === false ? 0 : pz.opacity; |
|
|
|
this.$refs.cesiumMap.ensurePowerZoneForRoute(routeId, pz.zoneId, pz.radiusKm, pz.angleDeg ?? 120, pz.color, opacity); |
|
|
|
}); |
|
|
|
} |
|
|
|
} catch (_) {} |
|
|
|
} |
|
|
|
@ -2421,6 +2428,26 @@ export default { |
|
|
|
}); |
|
|
|
|
|
|
|
this.routes = allRoutes; |
|
|
|
|
|
|
|
// 回滚/删除后后端可能不存在某些旧 routeId:右侧列表会消失,但地图实体如果不清理就会残留。 |
|
|
|
// 这里先把 activeRouteIds 与后端现存 routes 做一致性校正,并同步移除地图上“已消失的航线实体”。 |
|
|
|
const existingIdSet = new Set(allRoutes.map(r => String(r.id))); |
|
|
|
const missingRouteIds = (this.activeRouteIds || []).filter(id => !existingIdSet.has(String(id))); |
|
|
|
if (missingRouteIds.length > 0 && this.$refs.cesiumMap) { |
|
|
|
missingRouteIds.forEach((routeId) => { |
|
|
|
// removeRouteById 负责移除主航线/航点/盘旋与转弯半径弧等实体 |
|
|
|
this.$refs.cesiumMap.removeRouteById(routeId); |
|
|
|
// 同时清理探测区/威力区(removeRouteById 不覆盖这两类时显式保持一致) |
|
|
|
this.$refs.cesiumMap.removeDetectionZoneByRouteId(routeId); |
|
|
|
this.$refs.cesiumMap.removePowerZoneByRouteId(routeId); |
|
|
|
}); |
|
|
|
} |
|
|
|
this.activeRouteIds = (this.activeRouteIds || []).filter(id => existingIdSet.has(String(id))); |
|
|
|
// 如果当前选中航线也已回滚消失,则清空选中状态,避免右侧面板/地图再引用旧数据 |
|
|
|
if (this.selectedRouteId != null && !existingIdSet.has(String(this.selectedRouteId))) { |
|
|
|
this.selectedRouteId = null; |
|
|
|
this.selectedRouteDetails = null; |
|
|
|
} |
|
|
|
// 先预取所有展示中航线的平台样式,再渲染,避免平台图标先黑后变色(大房间时用方案所属子房间的 roomId) |
|
|
|
if (this.activeRouteIds.length > 0 && this.$refs.cesiumMap) { |
|
|
|
const roomId = this.currentRoomId; |
|
|
|
@ -3768,7 +3795,7 @@ export default { |
|
|
|
map.setPlatformIconServerId(entityData.id, res.data.id, this.currentRoomId) |
|
|
|
entityData.serverId = res.data.id |
|
|
|
entityData.roomId = this.currentRoomId |
|
|
|
this.$message.success('平台图标已保存到当前房间') |
|
|
|
// 保存成功提示会遮挡地图,当前禁用该提示 |
|
|
|
this.wsConnection?.sendSyncPlatformIcons?.() |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
@ -5272,6 +5299,62 @@ export default { |
|
|
|
if (cache) { |
|
|
|
routeIdToTimeline[routeId] = cache; |
|
|
|
routeIdsWithTimeline.push(routeId); |
|
|
|
|
|
|
|
// 关键修复:缓存命中时也要生成“时间类冲突”,否则第二次点击会只剩空间类冲突导致空结果 |
|
|
|
const { earlyArrivalLegs, lateArrivalLegs, holdDelayConflicts } = cache; |
|
|
|
const routeName = route.name || `航线${route.id}`; |
|
|
|
(earlyArrivalLegs || []).forEach(leg => { |
|
|
|
const earlyMin = leg.earlyMinutes != null ? Math.round(leg.earlyMinutes * 10) / 10 : 0; |
|
|
|
const earlyStr = earlyMin >= 0.1 ? `约 ${earlyMin} 分钟` : `约 ${Math.round(earlyMin * 60)} 秒`; |
|
|
|
const speedStr = leg.suggestedSpeedKmh != null && Number.isFinite(leg.suggestedSpeedKmh) ? `约 ${leg.suggestedSpeedKmh} km/h` : '(按计划时间反算)'; |
|
|
|
const kTimeStr = this.minutesToStartTime(leg.scheduled); |
|
|
|
allRaw.push({ |
|
|
|
type: CONFLICT_TYPE.TIME, |
|
|
|
subType: 'early_arrival', |
|
|
|
title: '提前到达', |
|
|
|
routeName, |
|
|
|
routeIds: [routeId], |
|
|
|
fromWaypoint: leg.fromName, |
|
|
|
toWaypoint: leg.toName, |
|
|
|
time: this.minutesToStartTime(leg.actualArrival), |
|
|
|
suggestion: `① 将本段速度降至 ${speedStr} ② 若下一航点为盘旋点,可盘旋等待 ${earlyStr} ③ 将下一航点相对K时调至 ${kTimeStr} 或更晚`, |
|
|
|
severity: 'high' |
|
|
|
}); |
|
|
|
}); |
|
|
|
(lateArrivalLegs || []).forEach(leg => { |
|
|
|
const kTimeStr = leg.actualArrival != null && Number.isFinite(leg.actualArrival) ? this.minutesToStartTime(leg.actualArrival) : ''; |
|
|
|
const part2 = kTimeStr ? ` ② 或将下一航点相对K时调至 ${kTimeStr} 或更晚` : ' ② 或将下一航点相对K时调晚'; |
|
|
|
allRaw.push({ |
|
|
|
type: CONFLICT_TYPE.TIME, |
|
|
|
subType: 'late_arrival', |
|
|
|
title: '无法按时到达', |
|
|
|
routeName, |
|
|
|
routeIds: [routeId], |
|
|
|
fromWaypoint: leg.fromName, |
|
|
|
toWaypoint: leg.toName, |
|
|
|
suggestion: `① 将本段速度提升至 ≥${leg.requiredSpeedKmh} km/h${part2} ③ 调整上游航段速度或时间`, |
|
|
|
severity: 'high' |
|
|
|
}); |
|
|
|
}); |
|
|
|
(holdDelayConflicts || []).forEach(conf => { |
|
|
|
allRaw.push({ |
|
|
|
type: CONFLICT_TYPE.TIME, |
|
|
|
subType: 'hold_delay', |
|
|
|
title: '盘旋时间不足', |
|
|
|
routeName, |
|
|
|
routeIds: [routeId], |
|
|
|
fromWaypoint: conf.fromName, |
|
|
|
toWaypoint: conf.toName, |
|
|
|
time: this.minutesToStartTime(conf.setExitTime), |
|
|
|
position: conf.holdCenter ? `经度 ${conf.holdCenter.lng.toFixed(5)}°, 纬度 ${conf.holdCenter.lat.toFixed(5)}°` : undefined, |
|
|
|
suggestion: `实际切出将延迟 ${conf.delaySeconds} 秒。① 延长该盘旋点相对K时 ② 定时盘旋调转弯半径,非定时调上一航点速度或本点相对K时 ③ 微调上下游航点相对K时`, |
|
|
|
severity: 'high', |
|
|
|
holdCenter: conf.holdCenter, |
|
|
|
positionLng: conf.holdCenter && conf.holdCenter.lng, |
|
|
|
positionLat: conf.holdCenter && conf.holdCenter.lat, |
|
|
|
positionAlt: conf.holdCenter && conf.holdCenter.alt |
|
|
|
}); |
|
|
|
}); |
|
|
|
} else { |
|
|
|
let pathData = null; |
|
|
|
if (this.$refs.cesiumMap && this.$refs.cesiumMap.getRoutePathWithSegmentIndices) { |
|
|
|
@ -5284,7 +5367,11 @@ export default { |
|
|
|
routeIdToTimeline[routeId] = { |
|
|
|
segments: timeline.segments, |
|
|
|
path: pathData && pathData.path ? pathData.path : null, |
|
|
|
segmentEndIndices: pathData && pathData.segmentEndIndices ? pathData.segmentEndIndices : null |
|
|
|
segmentEndIndices: pathData && pathData.segmentEndIndices ? pathData.segmentEndIndices : null, |
|
|
|
// 缓存时间类冲突的中间结果,避免第二次点击时“只算空间不算时间” |
|
|
|
earlyArrivalLegs: timeline.earlyArrivalLegs || [], |
|
|
|
lateArrivalLegs: timeline.lateArrivalLegs || [], |
|
|
|
holdDelayConflicts: timeline.holdDelayConflicts || [] |
|
|
|
}; |
|
|
|
this._setConflictTimelineCache(routeId, route.waypoints, minMinutes, maxMinutes, routeIdToTimeline[routeId]); |
|
|
|
routeIdsWithTimeline.push(routeId); |
|
|
|
|