|  | @@ -1,20 +1,20 @@
 | 
	
		
			
				|  |  |  <template>
 | 
	
		
			
				|  |  |    <div id="cesiumContainer" style="height: 100%;width: 100%;"></div>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    <!-- 控制按钮 -->
 | 
	
		
			
				|  |  | -    <div class="control-buttons">
 | 
	
		
			
				|  |  | -      <button class="control-btn" @click="goToHomeView" title="返回首页视角">
 | 
	
		
			
				|  |  | -        <i class="fas fa-home"></i>
 | 
	
		
			
				|  |  | -      </button>
 | 
	
		
			
				|  |  | -      <button class="control-btn" @click="togglePOIDisplay" title="显示/隐藏POI点">
 | 
	
		
			
				|  |  | -        <i class="fas fa-map-marker-alt" :class="{ 'active': poiVisible }"></i>
 | 
	
		
			
				|  |  | -      </button>
 | 
	
		
			
				|  |  | -      <button class="control-btn" @click="toggleTyphoon" title="显示/隐藏台风并切换视角">
 | 
	
		
			
				|  |  | -        <i class="fas fa-wind" :class="{ 'active': typhoonVisible }"></i>
 | 
	
		
			
				|  |  | -      </button>
 | 
	
		
			
				|  |  | -    </div>
 | 
	
		
			
				|  |  | +  <!-- 控制按钮 -->
 | 
	
		
			
				|  |  | +  <div class="control-buttons">
 | 
	
		
			
				|  |  | +    <button class="control-btn" @click="goToHomeView" title="返回首页视角">
 | 
	
		
			
				|  |  | +      <i class="fas fa-home"></i>
 | 
	
		
			
				|  |  | +    </button>
 | 
	
		
			
				|  |  | +    <button class="control-btn" @click="togglePOIDisplay" title="显示/隐藏POI点">
 | 
	
		
			
				|  |  | +      <i class="fas fa-map-marker-alt" :class="{ 'active': poiVisible }"></i>
 | 
	
		
			
				|  |  | +    </button>
 | 
	
		
			
				|  |  | +    <button class="control-btn" @click="toggleTyphoon" title="显示/隐藏台风并切换视角">
 | 
	
		
			
				|  |  | +      <i class="fas fa-wind" :class="{ 'active': typhoonVisible }"></i>
 | 
	
		
			
				|  |  | +    </button>
 | 
	
		
			
				|  |  | +  </div>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  <!-- 自定义弹框组件 -->
 | 
	
		
			
				|  |  | +  <!-- 自定义弹框组件 - POI点 -->
 | 
	
		
			
				|  |  |    <div v-if="selectedPoint" class="custom-popup" :style="{
 | 
	
		
			
				|  |  |      left: `${popupPosition.x}px`,
 | 
	
		
			
				|  |  |      top: `${popupPosition.y}px`
 | 
	
	
		
			
				|  | @@ -26,9 +26,28 @@
 | 
	
		
			
				|  |  |        <div class="popup-arrow"></div>
 | 
	
		
			
				|  |  |      </div>
 | 
	
		
			
				|  |  |    </div>
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | -  <!-- 台风图例面板 -->
 | 
	
		
			
				|  |  | -  <ul class="legend">
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  <!-- 台风路径点信息弹窗 -->
 | 
	
		
			
				|  |  | +  <div v-if="typhoonInfoVisible && typhoonVisible" class="typhoon-popup" :style="{
 | 
	
		
			
				|  |  | +    left: `${typhoonPopupPosition.x}px`,
 | 
	
		
			
				|  |  | +    top: `${typhoonPopupPosition.y}px`
 | 
	
		
			
				|  |  | +  }">
 | 
	
		
			
				|  |  | +    <div class="popup-content">
 | 
	
		
			
				|  |  | +      <h3>{{ typhoonInfo.name }}({{ typhoonInfo.enname }})</h3>
 | 
	
		
			
				|  |  | +      <p>{{ typhoonInfo.time }}</p>
 | 
	
		
			
				|  |  | +      <p><strong>中心位置:</strong> {{ typhoonInfo.lng }}° / {{ typhoonInfo.lat }}°</p>
 | 
	
		
			
				|  |  | +      <p><strong>风速风力:</strong> {{ typhoonInfo.speed }} m/s ({{ typhoonInfo.power }}级)</p>
 | 
	
		
			
				|  |  | +      <p><strong>中心气压:</strong> {{ typhoonInfo.pressure }} hPa</p>
 | 
	
		
			
				|  |  | +      <p><strong>移速移向:</strong> {{ typhoonInfo.movedirection }} {{ typhoonInfo.movespeed }} km/h</p>
 | 
	
		
			
				|  |  | +      <p><strong>七级半径:</strong> {{ typhoonInfo.radius7 || '--' }}</p>
 | 
	
		
			
				|  |  | +      <p><strong>十级半径:</strong> {{ typhoonInfo.radius10 || '--' }}</p>
 | 
	
		
			
				|  |  | +      <p><strong>十二级半径:</strong> {{ typhoonInfo.radius12 || '--' }}</p>
 | 
	
		
			
				|  |  | +      <div class="popup-arrow"></div>
 | 
	
		
			
				|  |  | +    </div>
 | 
	
		
			
				|  |  | +  </div>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  <!-- 台风图例面板 - 添加v-if控制显示 -->
 | 
	
		
			
				|  |  | +  <ul class="legend" v-if="typhoonVisible">
 | 
	
		
			
				|  |  |      <li>
 | 
	
		
			
				|  |  |        <span class="dot green"></span>
 | 
	
		
			
				|  |  |        <span>热带低压</span>
 | 
	
	
		
			
				|  | @@ -53,7 +72,7 @@
 | 
	
		
			
				|  |  |  </template>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  <script setup>
 | 
	
		
			
				|  |  | -import { ref, onMounted, onUnmounted, watch } from 'vue'
 | 
	
		
			
				|  |  | +import { ref, onMounted, onUnmounted } from 'vue'
 | 
	
		
			
				|  |  |  import * as Cesium from 'cesium';
 | 
	
		
			
				|  |  |  import "cesium/Build/CesiumUnminified/Widgets/widgets.css";
 | 
	
		
			
				|  |  |  import JYLData from '@/assets/Data/THJYL.json'
 | 
	
	
		
			
				|  | @@ -66,6 +85,12 @@ const popupPosition = ref({ x: 0, y: 0 });
 | 
	
		
			
				|  |  |  let handler = null;
 | 
	
		
			
				|  |  |  let viewer = null;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// 新增台风信息弹窗变量
 | 
	
		
			
				|  |  | +const typhoonInfoVisible = ref(false);
 | 
	
		
			
				|  |  | +const typhoonInfo = ref({});
 | 
	
		
			
				|  |  | +const typhoonPopupPosition = ref({ x: 0, y: 0 });
 | 
	
		
			
				|  |  | +const typhoonPointDataMap = ref(new Map()); // 存储台风点与数据的映射
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  // 控制按钮相关变量
 | 
	
		
			
				|  |  |  const poiVisible = ref(true); // POI点显示状态
 | 
	
		
			
				|  |  |  const typhoonVisible = ref(true); // 台风显示状态
 | 
	
	
		
			
				|  | @@ -120,45 +145,50 @@ const togglePOIDisplay = () => {
 | 
	
		
			
				|  |  |  // 切换台风显示/隐藏并跳转视角
 | 
	
		
			
				|  |  |  const toggleTyphoon = () => {
 | 
	
		
			
				|  |  |    typhoonVisible.value = !typhoonVisible.value;
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 控制台风相关实体显示/隐藏
 | 
	
		
			
				|  |  |    if (myEntityCollection.value) {
 | 
	
		
			
				|  |  |      myEntityCollection.value.show = typhoonVisible.value;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 控制风圈显示/隐藏
 | 
	
		
			
				|  |  |    fengquanLayers.value.forEach(entity => {
 | 
	
		
			
				|  |  |      if (entity) {
 | 
	
		
			
				|  |  |        entity.show = typhoonVisible.value;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 控制路径线显示/隐藏
 | 
	
		
			
				|  |  |    typhoonRelatedEntities.value.paths.forEach(entity => {
 | 
	
		
			
				|  |  |      if (entity) {
 | 
	
		
			
				|  |  |        entity.show = typhoonVisible.value;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 控制预报路径显示/隐藏
 | 
	
		
			
				|  |  |    typhoonRelatedEntities.value.forecasts.forEach(entity => {
 | 
	
		
			
				|  |  |      if (entity) {
 | 
	
		
			
				|  |  |        entity.show = typhoonVisible.value;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 控制警戒线显示/隐藏
 | 
	
		
			
				|  |  |    typhoonRelatedEntities.value.warnings.forEach(entity => {
 | 
	
		
			
				|  |  |      if (entity) {
 | 
	
		
			
				|  |  |        entity.show = typhoonVisible.value;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 控制台风标记显示/隐藏
 | 
	
		
			
				|  |  |    if (tbentity.value) {
 | 
	
		
			
				|  |  |      tbentity.value.show = typhoonVisible.value;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // 如果台风隐藏,同时隐藏信息弹窗
 | 
	
		
			
				|  |  | +  if (!typhoonVisible.value) {
 | 
	
		
			
				|  |  | +    typhoonInfoVisible.value = false;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 如果显示台风,则跳转到台风视角
 | 
	
		
			
				|  |  |    if (typhoonVisible.value) {
 | 
	
		
			
				|  |  |      viewer.camera.flyTo({
 | 
	
	
		
			
				|  | @@ -174,7 +204,7 @@ onMounted(async () => {
 | 
	
		
			
				|  |  |    document.body.style.height = '100%';
 | 
	
		
			
				|  |  |    document.body.style.margin = '0';
 | 
	
		
			
				|  |  |    document.body.style.padding = '0';
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 初始化Cesium viewer
 | 
	
		
			
				|  |  |    viewer = new Cesium.Viewer('cesiumContainer', {
 | 
	
		
			
				|  |  |      timeline: false,
 | 
	
	
		
			
				|  | @@ -245,7 +275,7 @@ onMounted(async () => {
 | 
	
		
			
				|  |  |    JYLData.forEach((item) => {
 | 
	
		
			
				|  |  |      const position = Cesium.Cartesian3.fromDegrees(
 | 
	
		
			
				|  |  |        parseFloat(item.LGTD),
 | 
	
		
			
				|  |  | -      parseFloat(item.LTTD) 
 | 
	
		
			
				|  |  | +      parseFloat(item.LTTD)
 | 
	
		
			
				|  |  |      );
 | 
	
		
			
				|  |  |      const entity = viewer.entities.add({
 | 
	
		
			
				|  |  |        position: position,
 | 
	
	
		
			
				|  | @@ -326,6 +356,9 @@ onMounted(async () => {
 | 
	
		
			
				|  |  |      const correctedY = click.position.y / scaleRatio;
 | 
	
		
			
				|  |  |      const pickedObject = viewer.scene.pick(new Cesium.Cartesian2(correctedX, correctedY));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    // 隐藏台风信息弹窗
 | 
	
		
			
				|  |  | +    typhoonInfoVisible.value = false;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
 | 
	
		
			
				|  |  |        const entityId = pickedObject.id.id;
 | 
	
		
			
				|  |  |        const data = entityDataMap.get(entityId) || pickedObject.id.properties?.data?.getValue();
 | 
	
	
		
			
				|  | @@ -349,9 +382,50 @@ onMounted(async () => {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +  // 添加鼠标移动事件处理 - 用于台风路径点悬停
 | 
	
		
			
				|  |  | +  handler.setInputAction((movement) => {
 | 
	
		
			
				|  |  | +    // 只有台风可见时才处理台风信息弹窗
 | 
	
		
			
				|  |  | +    if (!typhoonVisible.value) {
 | 
	
		
			
				|  |  | +      typhoonInfoVisible.value = false;
 | 
	
		
			
				|  |  | +      return;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    const scaleRatio = getScaleRatio();
 | 
	
		
			
				|  |  | +    const correctedX = movement.endPosition.x / scaleRatio;
 | 
	
		
			
				|  |  | +    const correctedY = movement.endPosition.y / scaleRatio;
 | 
	
		
			
				|  |  | +    const pickedObject = viewer.scene.pick(new Cesium.Cartesian2(correctedX, correctedY));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // 检查是否悬停在台风路径点上
 | 
	
		
			
				|  |  | +    if (Cesium.defined(pickedObject) && Cesium.defined(pickedObject.id)) {
 | 
	
		
			
				|  |  | +      const entityId = pickedObject.id.id;
 | 
	
		
			
				|  |  | +      // 检查是否是台风路径点
 | 
	
		
			
				|  |  | +      if (entityId && entityId.startsWith('typhoon-point-')) {
 | 
	
		
			
				|  |  | +        const typhoonData = typhoonPointDataMap.value.get(entityId);
 | 
	
		
			
				|  |  | +        if (typhoonData) {
 | 
	
		
			
				|  |  | +          // 显示台风信息弹窗
 | 
	
		
			
				|  |  | +          typhoonInfo.value = typhoonData;
 | 
	
		
			
				|  |  | +          typhoonInfoVisible.value = true;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          // 计算弹窗位置
 | 
	
		
			
				|  |  | +          const entityPosition = viewer.scene.cartesianToCanvasCoordinates(pickedObject.id.position._value);
 | 
	
		
			
				|  |  | +          if (entityPosition) {
 | 
	
		
			
				|  |  | +            typhoonPopupPosition.value = {
 | 
	
		
			
				|  |  | +              x: (entityPosition.x / scaleRatio) - 120,
 | 
	
		
			
				|  |  | +              y: (entityPosition.y / scaleRatio) - 200
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          return; // 找到台风点后不再处理其他逻辑
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // 如果不是台风路径点,隐藏台风信息弹窗
 | 
	
		
			
				|  |  | +    typhoonInfoVisible.value = false;
 | 
	
		
			
				|  |  | +  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 初始化台风相关功能
 | 
	
		
			
				|  |  |    initTyphoonVisualization();
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 监听窗口大小变化,确保地图铺满
 | 
	
		
			
				|  |  |    window.addEventListener('resize', handleResize);
 | 
	
		
			
				|  |  |    // 初始触发一次 resize 确保地图正确显示
 | 
	
	
		
			
				|  | @@ -362,14 +436,14 @@ onMounted(async () => {
 | 
	
		
			
				|  |  |  const initTyphoonVisualization = () => {
 | 
	
		
			
				|  |  |    // 设置Cesium Ion访问令牌
 | 
	
		
			
				|  |  |    Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1ODIwOGQ2Ny1hMTFhLTQ4OGQtODJhZi0wNmMzZGNhNjU5OWMiLCJpZCI6NTkzMTMsImlhdCI6MTYyMzk4ODQ4NX0.40CU0i0LswshdxVXAXEJgfEDJN3EK_jPbo_S8lece9E';
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 创建台风数据源
 | 
	
		
			
				|  |  |    myEntityCollection.value = new Cesium.CustomDataSource("typhoonPoints");
 | 
	
		
			
				|  |  |    viewer.dataSources.add(myEntityCollection.value);
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 初始化警戒线
 | 
	
		
			
				|  |  |    initJJ();
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 加载台风数据
 | 
	
		
			
				|  |  |    initPoints();
 | 
	
		
			
				|  |  |  };
 | 
	
	
		
			
				|  | @@ -378,53 +452,77 @@ const initTyphoonVisualization = () => {
 | 
	
		
			
				|  |  |  const initPoints = () => {
 | 
	
		
			
				|  |  |    // 加载台风JSON数据
 | 
	
		
			
				|  |  |    const jsonUrl = new URL('../assets/Data/202508.json', import.meta.url).href;
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    axios.get(jsonUrl)
 | 
	
		
			
				|  |  |      .then(response => {
 | 
	
		
			
				|  |  | -      const points = response.data.points;
 | 
	
		
			
				|  |  | -      processPoints(points);
 | 
	
		
			
				|  |  | +      const typhoonData = response.data;
 | 
	
		
			
				|  |  | +      // 处理台风数据,使用response.data.points
 | 
	
		
			
				|  |  | +      processPoints(typhoonData.points, typhoonData);
 | 
	
		
			
				|  |  |      })
 | 
	
		
			
				|  |  |      .catch(error => {
 | 
	
		
			
				|  |  |        console.error('加载台风JSON数据失败,使用测试数据', error);
 | 
	
		
			
				|  |  | -      // 测试数据
 | 
	
		
			
				|  |  | -      const testPoints = [
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -          "lng": 120,
 | 
	
		
			
				|  |  | -          "lat": 20,
 | 
	
		
			
				|  |  | -          "strong": "台风",
 | 
	
		
			
				|  |  | -          "forecast": [
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -              "forecastpoints": [
 | 
	
		
			
				|  |  | -                {"lng": 121, "lat": 19},
 | 
	
		
			
				|  |  | -                {"lng": 122, "lat": 18}
 | 
	
		
			
				|  |  | -              ]
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -          ]
 | 
	
		
			
				|  |  | -        },
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -          "lng": 121,
 | 
	
		
			
				|  |  | -          "lat": 19.5,
 | 
	
		
			
				|  |  | -          "strong": "强台风",
 | 
	
		
			
				|  |  | -          "forecast": []
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -      ];
 | 
	
		
			
				|  |  | -      processPoints(testPoints);
 | 
	
		
			
				|  |  | +      // 测试数据 - 包含详细台风信息
 | 
	
		
			
				|  |  | +      const testTyphoonData = {
 | 
	
		
			
				|  |  | +        "name": "竹节草",
 | 
	
		
			
				|  |  | +        "enname": "CO-MAY",
 | 
	
		
			
				|  |  | +        "points": [
 | 
	
		
			
				|  |  | +          {
 | 
	
		
			
				|  |  | +            "time": "2025-07-23 14:00:00",
 | 
	
		
			
				|  |  | +            "lng": "119.20",
 | 
	
		
			
				|  |  | +            "lat": "18.40",
 | 
	
		
			
				|  |  | +            "strong": "热带低压",
 | 
	
		
			
				|  |  | +            "power": "7",
 | 
	
		
			
				|  |  | +            "speed": "15",
 | 
	
		
			
				|  |  | +            "pressure": "998",
 | 
	
		
			
				|  |  | +            "movespeed": "14",
 | 
	
		
			
				|  |  | +            "movedirection": "南西",
 | 
	
		
			
				|  |  | +            "radius7": "150-280公里",
 | 
	
		
			
				|  |  | +            "radius10": "--",
 | 
	
		
			
				|  |  | +            "radius12": "--",
 | 
	
		
			
				|  |  | +            "forecast": [
 | 
	
		
			
				|  |  | +              {
 | 
	
		
			
				|  |  | +                "tm": "中国",
 | 
	
		
			
				|  |  | +                "forecastpoints": [
 | 
	
		
			
				|  |  | +                  { "lng": 121, "lat": 19 },
 | 
	
		
			
				|  |  | +                  { "lng": 122, "lat": 18 }
 | 
	
		
			
				|  |  | +                ]
 | 
	
		
			
				|  |  | +              }
 | 
	
		
			
				|  |  | +            ]
 | 
	
		
			
				|  |  | +          },
 | 
	
		
			
				|  |  | +          {
 | 
	
		
			
				|  |  | +            "time": "2025-07-23 17:00:00",
 | 
	
		
			
				|  |  | +            "lng": "119.10",
 | 
	
		
			
				|  |  | +            "lat": "18.00",
 | 
	
		
			
				|  |  | +            "strong": "热带低压",
 | 
	
		
			
				|  |  | +            "power": "7",
 | 
	
		
			
				|  |  | +            "speed": "15",
 | 
	
		
			
				|  |  | +            "pressure": "996",
 | 
	
		
			
				|  |  | +            "movespeed": "13",
 | 
	
		
			
				|  |  | +            "movedirection": "南南西",
 | 
	
		
			
				|  |  | +            "radius7": "180-300公里",
 | 
	
		
			
				|  |  | +            "radius10": "50-80公里",
 | 
	
		
			
				|  |  | +            "radius12": "--",
 | 
	
		
			
				|  |  | +            "forecast": []
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        ]
 | 
	
		
			
				|  |  | +      };
 | 
	
		
			
				|  |  | +      processPoints(testTyphoonData.points, testTyphoonData);
 | 
	
		
			
				|  |  |      });
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // 处理台风数据
 | 
	
		
			
				|  |  | -const processPoints = (points) => {
 | 
	
		
			
				|  |  | +const processPoints = (points, typhoonData) => {
 | 
	
		
			
				|  |  |    const lineArr = [];
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | -  points.forEach(element => {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  points.forEach((element, index) => {
 | 
	
		
			
				|  |  |      // 强制转换为数字类型
 | 
	
		
			
				|  |  |      const lng = Number(element.lng);
 | 
	
		
			
				|  |  |      const lat = Number(element.lat);
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      let color = Cesium.Color.RED;
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      // 根据台风强度设置颜色
 | 
	
		
			
				|  |  | -    switch(element.strong) {
 | 
	
		
			
				|  |  | +    switch (element.strong) {
 | 
	
		
			
				|  |  |        case "热带低压":
 | 
	
		
			
				|  |  |          color = Cesium.Color.GREEN;
 | 
	
		
			
				|  |  |          break;
 | 
	
	
		
			
				|  | @@ -444,18 +542,40 @@ const processPoints = (points) => {
 | 
	
		
			
				|  |  |          color = Cesium.Color.RED;
 | 
	
		
			
				|  |  |          break;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      lineArr.push(lng, lat);
 | 
	
		
			
				|  |  | +    // 为每个台风路径点创建唯一ID
 | 
	
		
			
				|  |  | +    const pointId = `typhoon-point-${index}`;
 | 
	
		
			
				|  |  |      const entity = new Cesium.Entity({
 | 
	
		
			
				|  |  | +      id: pointId,
 | 
	
		
			
				|  |  |        position: Cesium.Cartesian3.fromDegrees(lng, lat),
 | 
	
		
			
				|  |  |        point: {
 | 
	
		
			
				|  |  | -        pixelSize: 5,
 | 
	
		
			
				|  |  | -        color: color
 | 
	
		
			
				|  |  | +        pixelSize: 8,  // 增大点的尺寸,更容易被鼠标选中
 | 
	
		
			
				|  |  | +        color: color,
 | 
	
		
			
				|  |  | +        outlineColor: Cesium.Color.WHITE,
 | 
	
		
			
				|  |  | +        outlineWidth: 2
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      });
 | 
	
		
			
				|  |  |      myEntityCollection.value.entities.add(entity);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // 存储台风点与数据的映射关系,使用正确的属性名
 | 
	
		
			
				|  |  | +    typhoonPointDataMap.value.set(pointId, {
 | 
	
		
			
				|  |  | +      name: typhoonData.name || '',
 | 
	
		
			
				|  |  | +      enname: typhoonData.enname || '',
 | 
	
		
			
				|  |  | +      time: element.time || '',
 | 
	
		
			
				|  |  | +      lng: element.lng,
 | 
	
		
			
				|  |  | +      lat: element.lat,
 | 
	
		
			
				|  |  | +      speed: element.speed,
 | 
	
		
			
				|  |  | +      power: element.power,
 | 
	
		
			
				|  |  | +      pressure: element.pressure,
 | 
	
		
			
				|  |  | +      movedirection: element.movedirection,
 | 
	
		
			
				|  |  | +      movespeed: element.movespeed,
 | 
	
		
			
				|  |  | +      radius7: element.radius7 || '--',
 | 
	
		
			
				|  |  | +      radius10: element.radius10 || '--',
 | 
	
		
			
				|  |  | +      radius12: element.radius12 || '--'
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 添加台风路径线
 | 
	
		
			
				|  |  |    const pathEntity = viewer.entities.add({
 | 
	
		
			
				|  |  |      polyline: {
 | 
	
	
		
			
				|  | @@ -466,12 +586,39 @@ const processPoints = (points) => {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  |    typhoonRelatedEntities.value.paths.push(pathEntity);
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // 添加台风登陆点标记
 | 
	
		
			
				|  |  | +  if (typhoonData.land && typhoonData.land.length > 0) {
 | 
	
		
			
				|  |  | +    typhoonData.land.forEach((landPoint, index) => {
 | 
	
		
			
				|  |  | +      const landEntity = viewer.entities.add({
 | 
	
		
			
				|  |  | +        position: Cesium.Cartesian3.fromDegrees(
 | 
	
		
			
				|  |  | +          Number(landPoint.lng), 
 | 
	
		
			
				|  |  | +          Number(landPoint.lat)
 | 
	
		
			
				|  |  | +        ),
 | 
	
		
			
				|  |  | +        point: {
 | 
	
		
			
				|  |  | +          pixelSize: 12,
 | 
	
		
			
				|  |  | +          color: Cesium.Color.BLACK,
 | 
	
		
			
				|  |  | +          outlineColor: Cesium.Color.YELLOW,
 | 
	
		
			
				|  |  | +          outlineWidth: 2
 | 
	
		
			
				|  |  | +        },
 | 
	
		
			
				|  |  | +        label: {
 | 
	
		
			
				|  |  | +          text: `登陆 ${index + 1}`,
 | 
	
		
			
				|  |  | +          font: '14px 微软雅黑',
 | 
	
		
			
				|  |  | +          fillColor: Cesium.Color.YELLOW,
 | 
	
		
			
				|  |  | +          backgroundColor: Cesium.Color.BLACK.withAlpha(0.7),
 | 
	
		
			
				|  |  | +          showBackground: true,
 | 
	
		
			
				|  |  | +          pixelOffset: new Cesium.Cartesian2(0, -20)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  | +      myEntityCollection.value.entities.add(landEntity);
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    if (points.length > 0) {
 | 
	
		
			
				|  |  |      // 初始化预报路径
 | 
	
		
			
				|  |  |      initForeast(points[points.length - 1]);
 | 
	
		
			
				|  |  |      // 添加台风动画
 | 
	
		
			
				|  |  | -    adds(points);
 | 
	
		
			
				|  |  | +    adds(points, typhoonData);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -485,37 +632,40 @@ const initForeast = (data) => {
 | 
	
		
			
				|  |  |      Cesium.Color.fromCssColorString("#E76F15"),
 | 
	
		
			
				|  |  |      Cesium.Color.fromCssColorString("#15D9E7")
 | 
	
		
			
				|  |  |    ];
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    forecast.forEach((ele, ii) => {
 | 
	
		
			
				|  |  | -    const lineArr = [];
 | 
	
		
			
				|  |  | -    ele.forecastpoints.forEach(e => {
 | 
	
		
			
				|  |  | -      // 强制转换为数字
 | 
	
		
			
				|  |  | -      const lng = Number(e.lng);
 | 
	
		
			
				|  |  | -      const lat = Number(e.lat);
 | 
	
		
			
				|  |  | -      lineArr.push(lng, lat);
 | 
	
		
			
				|  |  | -      
 | 
	
		
			
				|  |  | -      const entity = new Cesium.Entity({
 | 
	
		
			
				|  |  | -        position: Cesium.Cartesian3.fromDegrees(lng, lat),
 | 
	
		
			
				|  |  | -        point: {
 | 
	
		
			
				|  |  | -          pixelSize: 7,
 | 
	
		
			
				|  |  | -          color: colorArr[ii]
 | 
	
		
			
				|  |  | +    // 适配新的数据结构,预报点在tm对象下的forecastpoints中
 | 
	
		
			
				|  |  | +    if (ele.forecastpoints && ele.forecastpoints.length > 0) {
 | 
	
		
			
				|  |  | +      const lineArr = [];
 | 
	
		
			
				|  |  | +      ele.forecastpoints.forEach(e => {
 | 
	
		
			
				|  |  | +        // 强制转换为数字
 | 
	
		
			
				|  |  | +        const lng = Number(e.lng);
 | 
	
		
			
				|  |  | +        const lat = Number(e.lat);
 | 
	
		
			
				|  |  | +        lineArr.push(lng, lat);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        const entity = new Cesium.Entity({
 | 
	
		
			
				|  |  | +          position: Cesium.Cartesian3.fromDegrees(lng, lat),
 | 
	
		
			
				|  |  | +          point: {
 | 
	
		
			
				|  |  | +            pixelSize: 7,
 | 
	
		
			
				|  |  | +            color: colorArr[ii % colorArr.length]
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        myEntityCollection.value.entities.add(entity);
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      // 添加预报路径线
 | 
	
		
			
				|  |  | +      const forecastEntity = viewer.entities.add({
 | 
	
		
			
				|  |  | +        polyline: {
 | 
	
		
			
				|  |  | +          positions: Cesium.Cartesian3.fromDegreesArray(lineArr),
 | 
	
		
			
				|  |  | +          width: 2,
 | 
	
		
			
				|  |  | +          clampToGround: true,
 | 
	
		
			
				|  |  | +          material: new Cesium.PolylineDashMaterialProperty({
 | 
	
		
			
				|  |  | +            color: colorArr[ii % colorArr.length]
 | 
	
		
			
				|  |  | +          })
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |        });
 | 
	
		
			
				|  |  | -      myEntityCollection.value.entities.add(entity);
 | 
	
		
			
				|  |  | -    });
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | -    // 添加预报路径线
 | 
	
		
			
				|  |  | -    const forecastEntity = viewer.entities.add({
 | 
	
		
			
				|  |  | -      polyline: {
 | 
	
		
			
				|  |  | -        positions: Cesium.Cartesian3.fromDegreesArray(lineArr),
 | 
	
		
			
				|  |  | -        width: 2,
 | 
	
		
			
				|  |  | -        clampToGround: true,
 | 
	
		
			
				|  |  | -        material: new Cesium.PolylineDashMaterialProperty({
 | 
	
		
			
				|  |  | -          color: colorArr[ii]
 | 
	
		
			
				|  |  | -        })
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -    });
 | 
	
		
			
				|  |  | -    typhoonRelatedEntities.value.forecasts.push(forecastEntity);
 | 
	
		
			
				|  |  | +      typhoonRelatedEntities.value.forecasts.push(forecastEntity);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -534,7 +684,7 @@ const initJJ = () => {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  |    typhoonRelatedEntities.value.warnings.push(line24h);
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 48小时警戒线
 | 
	
		
			
				|  |  |    const line48h = viewer.entities.add({
 | 
	
		
			
				|  |  |      name: '48小时警戒线',
 | 
	
	
		
			
				|  | @@ -548,7 +698,7 @@ const initJJ = () => {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  |    typhoonRelatedEntities.value.warnings.push(line48h);
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 警戒线标签
 | 
	
		
			
				|  |  |    const label24h = viewer.entities.add({
 | 
	
		
			
				|  |  |      position: Cesium.Cartesian3.fromDegrees(126.129019, 29.104287),
 | 
	
	
		
			
				|  | @@ -559,7 +709,7 @@ const initJJ = () => {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  |    typhoonRelatedEntities.value.warnings.push(label24h);
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    const label48h = viewer.entities.add({
 | 
	
		
			
				|  |  |      position: Cesium.Cartesian3.fromDegrees(132, 20),
 | 
	
		
			
				|  |  |      label: {
 | 
	
	
		
			
				|  | @@ -572,20 +722,23 @@ const initJJ = () => {
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // 添加台风动画
 | 
	
		
			
				|  |  | -const adds = (data) => {
 | 
	
		
			
				|  |  | -  addTB();
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +const adds = (data, typhoonData) => {
 | 
	
		
			
				|  |  | +  addTB(typhoonData);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 清除可能存在的旧定时器
 | 
	
		
			
				|  |  |    if (typhoonInterval) {
 | 
	
		
			
				|  |  |      clearInterval(typhoonInterval);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 设置台风动画定时器
 | 
	
		
			
				|  |  |    typhoonInterval = setInterval(() => {
 | 
	
		
			
				|  |  | +    // 只有台风可见时才更新动画
 | 
	
		
			
				|  |  | +    if (!typhoonVisible.value) return;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      if (iii.value >= data.length) {
 | 
	
		
			
				|  |  |        iii.value = 0;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      const kkk = iii.value * 2;
 | 
	
		
			
				|  |  |      // 确保经纬度是数字类型
 | 
	
		
			
				|  |  |      const currentData = data[iii.value];
 | 
	
	
		
			
				|  | @@ -611,14 +764,14 @@ const adds = (data) => {
 | 
	
		
			
				|  |  |          radius4: 170 - kkk
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      if (tbentity.value) {
 | 
	
		
			
				|  |  |        tbentity.value.position = Cesium.Cartesian3.fromDegrees(
 | 
	
		
			
				|  |  |          Number(currentData.lng),
 | 
	
		
			
				|  |  |          Number(currentData.lat)
 | 
	
		
			
				|  |  |        );
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      iii.value = (iii.value + 1) % data.length;
 | 
	
		
			
				|  |  |      removeTFLayer();
 | 
	
		
			
				|  |  |      // 只有当台风可见时才添加风圈
 | 
	
	
		
			
				|  | @@ -629,24 +782,28 @@ const adds = (data) => {
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // 添加台风标记
 | 
	
		
			
				|  |  | -const addTB = () => {
 | 
	
		
			
				|  |  | +const addTB = (typhoonData) => {
 | 
	
		
			
				|  |  |    // 尝试使用GIF动画标记
 | 
	
		
			
				|  |  |    const SuperGif = window.SuperGif;
 | 
	
		
			
				|  |  |    if (typeof SuperGif !== 'function') {
 | 
	
		
			
				|  |  |      console.warn('SuperGif未加载成功,使用默认标记');
 | 
	
		
			
				|  |  | -    useDefaultMarker();
 | 
	
		
			
				|  |  | +    useDefaultMarker(typhoonData);
 | 
	
		
			
				|  |  |      return;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    const img = document.createElement('img');
 | 
	
		
			
				|  |  |    img.src = '/tf.gif'; // public目录下的GIF
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    img.onload = () => {
 | 
	
		
			
				|  |  |      try {
 | 
	
		
			
				|  |  |        const rub = new SuperGif({ gif: img });
 | 
	
		
			
				|  |  |        rub.load(() => {
 | 
	
		
			
				|  |  |          tbentity.value = viewer.entities.add({
 | 
	
		
			
				|  |  | -          position: Cesium.Cartesian3.fromDegrees(75.166493, 39.9060534),
 | 
	
		
			
				|  |  | +          // 使用台风的中心经纬度作为初始位置
 | 
	
		
			
				|  |  | +          position: Cesium.Cartesian3.fromDegrees(
 | 
	
		
			
				|  |  | +            Number(typhoonData.centerlng || 123.75), 
 | 
	
		
			
				|  |  | +            Number(typhoonData.centerlat || 28.95)
 | 
	
		
			
				|  |  | +          ),
 | 
	
		
			
				|  |  |            billboard: {
 | 
	
		
			
				|  |  |              image: new Cesium.CallbackProperty(() => {
 | 
	
		
			
				|  |  |                return rub.get_canvas().toDataURL('image/png');
 | 
	
	
		
			
				|  | @@ -657,20 +814,23 @@ const addTB = () => {
 | 
	
		
			
				|  |  |        });
 | 
	
		
			
				|  |  |      } catch (error) {
 | 
	
		
			
				|  |  |        console.error('GIF处理失败,使用默认标记', error);
 | 
	
		
			
				|  |  | -      useDefaultMarker();
 | 
	
		
			
				|  |  | +      useDefaultMarker(typhoonData);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    img.onerror = () => {
 | 
	
		
			
				|  |  |      console.error('GIF加载失败,使用默认标记');
 | 
	
		
			
				|  |  | -    useDefaultMarker();
 | 
	
		
			
				|  |  | +    useDefaultMarker(typhoonData);
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // 使用默认标记
 | 
	
		
			
				|  |  | -const useDefaultMarker = () => {
 | 
	
		
			
				|  |  | +const useDefaultMarker = (typhoonData) => {
 | 
	
		
			
				|  |  |    tbentity.value = viewer.entities.add({
 | 
	
		
			
				|  |  | -    position: Cesium.Cartesian3.fromDegrees(75.166493, 39.9060534),
 | 
	
		
			
				|  |  | +    position: Cesium.Cartesian3.fromDegrees(
 | 
	
		
			
				|  |  | +      Number(typhoonData.centerlng || 123.75), 
 | 
	
		
			
				|  |  | +      Number(typhoonData.centerlat || 28.95)
 | 
	
		
			
				|  |  | +    ),
 | 
	
		
			
				|  |  |      point: {
 | 
	
		
			
				|  |  |        pixelSize: 15,
 | 
	
		
			
				|  |  |        color: Cesium.Color.RED,
 | 
	
	
		
			
				|  | @@ -691,7 +851,7 @@ const removeTFLayer = () => {
 | 
	
		
			
				|  |  |  // 添加台风风圈
 | 
	
		
			
				|  |  |  const addTyphoonCircle = () => {
 | 
	
		
			
				|  |  |    if (!currentPointObj.value || !typhoonVisible.value) return;
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    const circles = ['circle7', 'circle10', 'circle12'];
 | 
	
		
			
				|  |  |    circles.forEach(item => {
 | 
	
		
			
				|  |  |      const entity = viewer.entities.add({
 | 
	
	
		
			
				|  | @@ -739,12 +899,12 @@ const getTyphoonPolygonPoints = (pointObj, cNum) => {
 | 
	
		
			
				|  |  |    ];
 | 
	
		
			
				|  |  |    const startAngleList = [0, 90, 180, 270];
 | 
	
		
			
				|  |  |    let fx, fy;
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    startAngleList.forEach((startAngle, index) => {
 | 
	
		
			
				|  |  |      const radius = radiusList[index] / 100;
 | 
	
		
			
				|  |  |      const pointNum = 90;
 | 
	
		
			
				|  |  |      const endAngle = startAngle + 90;
 | 
	
		
			
				|  |  | -    
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      for (let i = 0; i <= pointNum; i++) {
 | 
	
		
			
				|  |  |        const angle = startAngle + ((endAngle - startAngle) * i) / pointNum;
 | 
	
		
			
				|  |  |        const sin = Math.sin((angle * Math.PI) / 180);
 | 
	
	
		
			
				|  | @@ -752,34 +912,41 @@ const getTyphoonPolygonPoints = (pointObj, cNum) => {
 | 
	
		
			
				|  |  |        const x = center[0] + radius * sin;
 | 
	
		
			
				|  |  |        const y = center[1] + radius * cos;
 | 
	
		
			
				|  |  |        points.push(x, y);
 | 
	
		
			
				|  |  | -      
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |        if (startAngle === 0 && i === 0) {
 | 
	
		
			
				|  |  |          fx = x;
 | 
	
		
			
				|  |  |          fy = y;
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    points.push(fx, fy);
 | 
	
		
			
				|  |  |    return points;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// 处理窗口大小变化
 | 
	
		
			
				|  |  | +const handleResize = () => {
 | 
	
		
			
				|  |  | +  if (viewer) {
 | 
	
		
			
				|  |  | +    viewer.resize();
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  // 清理函数
 | 
	
		
			
				|  |  |  onUnmounted(() => {
 | 
	
		
			
				|  |  |    // 清除台风动画定时器
 | 
	
		
			
				|  |  |    if (typhoonInterval) {
 | 
	
		
			
				|  |  |      clearInterval(typhoonInterval);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 移除窗口大小变化监听
 | 
	
		
			
				|  |  |    window.removeEventListener('resize', handleResize);
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 销毁事件处理器
 | 
	
		
			
				|  |  |    if (handler) {
 | 
	
		
			
				|  |  |      handler.destroy();
 | 
	
		
			
				|  |  |      handler = null;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    // 销毁viewer
 | 
	
		
			
				|  |  |    if (viewer && !viewer.isDestroyed()) {
 | 
	
		
			
				|  |  |      viewer.destroy();
 | 
	
	
		
			
				|  | @@ -788,7 +955,6 @@ onUnmounted(() => {
 | 
	
		
			
				|  |  |  </script>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  <style scoped>
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |  /* 控制按钮样式 */
 | 
	
		
			
				|  |  |  .control-buttons {
 | 
	
		
			
				|  |  |    position: absolute;
 | 
	
	
		
			
				|  | @@ -825,7 +991,8 @@ onUnmounted(() => {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -.custom-popup {
 | 
	
		
			
				|  |  | +.custom-popup,
 | 
	
		
			
				|  |  | +.typhoon-popup {
 | 
	
		
			
				|  |  |    position: absolute;
 | 
	
		
			
				|  |  |    z-index: 1000;
 | 
	
		
			
				|  |  |    display: block;
 | 
	
	
		
			
				|  | @@ -837,7 +1004,7 @@ onUnmounted(() => {
 | 
	
		
			
				|  |  |    border-radius: 5px;
 | 
	
		
			
				|  |  |    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
 | 
	
		
			
				|  |  |    padding: 10px 15px;
 | 
	
		
			
				|  |  | -  width: 200px;
 | 
	
		
			
				|  |  | +  width: 240px;
 | 
	
		
			
				|  |  |    pointer-events: all;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -857,6 +1024,7 @@ onUnmounted(() => {
 | 
	
		
			
				|  |  |    margin-top: 0;
 | 
	
		
			
				|  |  |    margin-bottom: 8px;
 | 
	
		
			
				|  |  |    color: #333;
 | 
	
		
			
				|  |  | +  font-size: 16px;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  .popup-content p {
 | 
	
	
		
			
				|  | @@ -877,6 +1045,7 @@ onUnmounted(() => {
 | 
	
		
			
				|  |  |    padding: 10px 15px;
 | 
	
		
			
				|  |  |    border-radius: 4px;
 | 
	
		
			
				|  |  |    margin: 0;
 | 
	
		
			
				|  |  | +  transition: opacity 0.3s ease, transform 0.3s ease;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  .legend li {
 | 
	
	
		
			
				|  | @@ -913,3 +1082,4 @@ onUnmounted(() => {
 | 
	
		
			
				|  |  |    background: red;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  </style>
 | 
	
		
			
				|  |  | +    
 |