Browse Source

水文四预页面修改

WQQ 6 days ago
parent
commit
af3b701904

BIN
src/assets/images/台风.png


BIN
src/assets/images/强对流.png


BIN
src/assets/images/暴雨.png


BIN
src/assets/images/高温.png


+ 316 - 318
src/components/CesiumMap.vue

@@ -23,351 +23,349 @@
   </div>
 </template>
 
-<script setup>
-import { ref, onMounted, onUnmounted, computed } from 'vue'
+<script>
 import * as Cesium from 'cesium'
 import 'cesium/Build/CesiumUnminified/Widgets/widgets.css'
-import { useRouter } from 'vue-router'
 
-const emit = defineEmits(['toggleMap'])
-
-const viewer = ref(null)
-const showPopup = ref(false)
-const popupPosition = ref({ x: 0, y: 0 })
-
-const router = useRouter()
-
-const poiData = ref({
-  name: '黑林水文站',
-  waterLevel: '3.25',
-  flow: '12.5',
-  rainfall: '0.5',
-  historyWaterLevel: '3.10',
-  historyFlow: '11.8',
-  device: 'HL-Sensor-001'
-})
-
-const popupStyle = computed(() => {
-  return {
-    left: popupPosition.value.x + 'px',
-    top: popupPosition.value.y + 'px'
-  }
-})
-
-let blinkInterval = null
-
-const addPoiMarker = () => {
-    // 添加黑林水文站POI
-    const heilinPosition = Cesium.Cartesian3.fromDegrees(118.8770798, 35.0320627)
-    
-    // 创建一个不可见的点实体,用于扩大点击区域
-    const clickEntity = viewer.value.entities.add({
-      id: 'heilin-station-click',
-      position: heilinPosition,
-      point: {
-        pixelSize: 30,
-        color: Cesium.Color.TRANSPARENT,
-        outlineColor: Cesium.Color.TRANSPARENT,
-        outlineWidth: 0
-      }
-    })
-    
-    const entity = viewer.value.entities.add({
-      id: 'heilin-station',
-      position: heilinPosition,
-      billboard: {
-        image: '/src/assets/images/水文站.png',
-        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
-        horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
-        pixelOffset: new Cesium.Cartesian2(0, -15),
-        scale: 0.8,
-        width: 30,
-        height: 37.5,
-        disableDepthTestDistance: Number.POSITIVE_INFINITY
+export default {
+  name: 'CesiumMap',
+  props: {
+    simulationTime: {
+      type: Number,
+      default: 0
+    },
+    simulationData: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data() {
+    return {
+      viewer: null,
+      showPopup: false,
+      popupPosition: { x: 0, y: 0 },
+      poiData: {
+        name: '黑林水文站',
+        waterLevel: '3.25',
+        flow: '12.5',
+        rainfall: '0.5',
+        historyWaterLevel: '3.10',
+        historyFlow: '11.8',
+        device: 'HL-Sensor-001'
       },
-      label: {
-        text: '水位 3.25m\n流量 12.5m³/s\n降雨量 0.5mm/h',
-        font: 'bold 14px 黑体',
-        fillColor: Cesium.Color.WHITE,
-        outlineColor: Cesium.Color.fromCssColorString('#003860'),
-        outlineWidth: 2,
-        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
-        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
-        horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
-        pixelOffset: new Cesium.Cartesian2(0, -30),
-        showBackground: true,
-        backgroundColor: new Cesium.Color(0, 0.3, 0.5, 0.7),
-        backgroundPadding: new Cesium.Cartesian2(8, 4),
-        disableDepthTestDistance: Number.POSITIVE_INFINITY
-      }
-    })
-    
-    // 添加标题标签
-    viewer.value.entities.add({
-      id: 'heilin-station-title',
-      position: heilinPosition,
-      label: {
-        text: '黑林水文站',
-        font: 'bold 14px sans-serif',
-        fillColor: Cesium.Color.WHITE,
-        outlineColor: Cesium.Color.fromCssColorString('#003860'),
-        outlineWidth: 2,
-        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
-        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
-        horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
-        pixelOffset: new Cesium.Cartesian2(0, -85),
-        showBackground: true,
-        backgroundColor: new Cesium.Color(0, 0.2, 0.4, 0.9),
-        backgroundPadding: new Cesium.Cartesian2(22, 4),
-        disableDepthTestDistance: Number.POSITIVE_INFINITY
+      blinkInterval: null,
+      heilinEntity: null,
+      reservoirEntity: null
+    }
+  },
+  computed: {
+    popupStyle() {
+      return {
+        left: this.popupPosition.x + 'px',
+        top: this.popupPosition.y + 'px'
       }
-    })
-
-    // 添加塔山小水库POI
-    const reservoirPosition = Cesium.Cartesian3.fromDegrees(118.9540555, 34.9355329)
-    
-    const reservoirEntity = viewer.value.entities.add({
-      id: 'tashan-reservoir',
-      position: reservoirPosition,
-      billboard: {
-        image: '/src/assets/images/水库蓝.png',
-        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
-        horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
-        pixelOffset: new Cesium.Cartesian2(0, -15),
-        scale: 0.8,
-        width: 30,
-        height: 30,
-        disableDepthTestDistance: Number.POSITIVE_INFINITY
+    }
+  },
+  watch: {
+    simulationData: {
+      handler(newData) {
+        this.$nextTick(() => {
+          this.updateMapMarkers(newData)
+        })
       },
-      label: {
-        text: '库水位 18.5m\n库容 2350.8万m³\n汛限 20.0m',
-        font: 'bold 14px 黑体',
-        fillColor: Cesium.Color.WHITE,
-        outlineColor: Cesium.Color.fromCssColorString('#003860'),
-        outlineWidth: 2,
-        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
-        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
-        horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
-        pixelOffset: new Cesium.Cartesian2(0, -30),
-        showBackground: true,
-        backgroundColor: new Cesium.Color(0, 0.3, 0.5, 0.7),
-        backgroundPadding: new Cesium.Cartesian2(8, 4),
-        disableDepthTestDistance: Number.POSITIVE_INFINITY
-      }
-    })
-    
-    // 添加标题标签
-    viewer.value.entities.add({
-      id: 'tashan-reservoir-title',
-      position: reservoirPosition,
-      label: {
-        text: '小塔山水库',
-        font: 'bold 14px sans-serif',
-        fillColor: Cesium.Color.WHITE,
-        outlineColor: Cesium.Color.fromCssColorString('#003860'),
-        outlineWidth: 2,
-        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
-        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
-        horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
-        pixelOffset: new Cesium.Cartesian2(0, -85),
-        showBackground: true,
-        backgroundColor: new Cesium.Color(0, 0.2, 0.4, 0.9),
-        backgroundPadding: new Cesium.Cartesian2(22, 4),
-        disableDepthTestDistance: Number.POSITIVE_INFINITY
-      }
-    })
+      deep: true,
+      immediate: true
+    }
+  },
+  methods: {
+    addPoiMarker() {
+      const heilinPosition = Cesium.Cartesian3.fromDegrees(118.8770798, 35.0320627)
+      
+      const clickEntity = this.viewer.entities.add({
+        id: 'heilin-station-click',
+        position: heilinPosition,
+        point: {
+          pixelSize: 30,
+          color: Cesium.Color.TRANSPARENT,
+          outlineColor: Cesium.Color.TRANSPARENT,
+          outlineWidth: 0
+        }
+      })
+      
+      this.heilinEntity = this.viewer.entities.add({
+        id: 'heilin-station',
+        position: heilinPosition,
+        billboard: {
+          image: '/src/assets/images/水文站.png',
+          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
+          pixelOffset: new Cesium.Cartesian2(0, -15),
+          scale: 0.8,
+          width: 30,
+          height: 37.5,
+          disableDepthTestDistance: Number.POSITIVE_INFINITY
+        },
+        label: {
+          text: `水位 ${this.simulationData.heilinStation?.waterLevel || '3.25'}m\n流量 ${this.simulationData.heilinStation?.flow || '12.5'}m³/s\n降雨量 ${this.simulationData.heilinStation?.rainfall || '0.5'}mm/h`,
+          font: 'bold 14px 黑体',
+          fillColor: Cesium.Color.WHITE,
+          outlineColor: Cesium.Color.fromCssColorString('#003860'),
+          outlineWidth: 2,
+          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
+          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+          horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
+          pixelOffset: new Cesium.Cartesian2(0, -30),
+          showBackground: true,
+          backgroundColor: new Cesium.Color(0, 0.3, 0.5, 0.7),
+          backgroundPadding: new Cesium.Cartesian2(8, 4),
+          disableDepthTestDistance: Number.POSITIVE_INFINITY
+        }
+      })
+      
+      this.viewer.entities.add({
+        id: 'heilin-station-title',
+        position: heilinPosition,
+        label: {
+          text: '黑林水文站',
+          font: 'bold 14px sans-serif',
+          fillColor: Cesium.Color.WHITE,
+          outlineColor: Cesium.Color.fromCssColorString('#003860'),
+          outlineWidth: 2,
+          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
+          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+          horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
+          pixelOffset: new Cesium.Cartesian2(0, -85),
+          showBackground: true,
+          backgroundColor: new Cesium.Color(0, 0.2, 0.4, 0.9),
+          backgroundPadding: new Cesium.Cartesian2(22, 4),
+          disableDepthTestDistance: Number.POSITIVE_INFINITY
+        }
+      })
 
-    const handler = new Cesium.ScreenSpaceEventHandler(viewer.value.scene.canvas)
-    
-    handler.setInputAction((movement) => {
-      // 尝试拾取所有对象
-      const pickedObjects = viewer.value.scene.drillPick(movement.position)
-      for (let i = 0; i < pickedObjects.length; i++) {
-        const pickedObj = pickedObjects[i]
-        if (Cesium.defined(pickedObj) && pickedObj.id) {
-          if (pickedObj.id.id === 'heilin-station' || pickedObj.id.id === 'heilin-station-click') {
-            showPopup.value = true
-            popupPosition.value = { x: movement.position.x - 120, y: movement.position.y - 150 }
-            break
+      const reservoirPosition = Cesium.Cartesian3.fromDegrees(118.9540555, 34.9355329)
+      
+      this.reservoirEntity = this.viewer.entities.add({
+        id: 'tashan-reservoir',
+        position: reservoirPosition,
+        billboard: {
+          image: '/src/assets/images/水库蓝.png',
+          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+          horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
+          pixelOffset: new Cesium.Cartesian2(0, -15),
+          scale: 0.8,
+          width: 30,
+          height: 30,
+          disableDepthTestDistance: Number.POSITIVE_INFINITY
+        },
+        label: {
+          text: `库水位 ${this.simulationData.reservoir?.waterLevel || '18.5'}m\n库容 ${this.simulationData.reservoir?.storage || '2350.8'}万m³\n汛限 20.0m`,
+          font: 'bold 14px 黑体',
+          fillColor: Cesium.Color.WHITE,
+          outlineColor: Cesium.Color.fromCssColorString('#003860'),
+          outlineWidth: 2,
+          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
+          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+          horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
+          pixelOffset: new Cesium.Cartesian2(0, -30),
+          showBackground: true,
+          backgroundColor: new Cesium.Color(0, 0.3, 0.5, 0.7),
+          backgroundPadding: new Cesium.Cartesian2(8, 4),
+          disableDepthTestDistance: Number.POSITIVE_INFINITY
+        }
+      })
+      
+      this.viewer.entities.add({
+        id: 'tashan-reservoir-title',
+        position: reservoirPosition,
+        label: {
+          text: '小塔山水库',
+          font: 'bold 14px sans-serif',
+          fillColor: Cesium.Color.WHITE,
+          outlineColor: Cesium.Color.fromCssColorString('#003860'),
+          outlineWidth: 2,
+          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
+          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+          horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
+          pixelOffset: new Cesium.Cartesian2(0, -85),
+          showBackground: true,
+          backgroundColor: new Cesium.Color(0, 0.2, 0.4, 0.9),
+          backgroundPadding: new Cesium.Cartesian2(22, 4),
+          disableDepthTestDistance: Number.POSITIVE_INFINITY
+        }
+      })
+
+      const handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+      
+      handler.setInputAction((movement) => {
+        const pickedObjects = this.viewer.scene.drillPick(movement.position)
+        for (let i = 0; i < pickedObjects.length; i++) {
+          const pickedObj = pickedObjects[i]
+          if (Cesium.defined(pickedObj) && pickedObj.id) {
+            if (pickedObj.id.id === 'heilin-station' || pickedObj.id.id === 'heilin-station-click') {
+              this.showPopup = true
+              this.popupPosition = { x: movement.position.x - 120, y: movement.position.y - 150 }
+              break
+            }
           }
         }
+      }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
+    },
+    updateMapMarkers(data) {
+      if (!data) return
+      
+      if (this.heilinEntity && data.heilinStation) {
+        const waterLevel = data.heilinStation.waterLevel || '3.25'
+        const flow = data.heilinStation.flow || '12.5'
+        const rainfall = data.heilinStation.rainfall || '0.5'
+        
+        this.heilinEntity.label.text = `水位 ${waterLevel}m\n流量 ${flow}m³/s\n降雨量 ${rainfall}mm/h`
+        this.poiData.waterLevel = waterLevel
+        this.poiData.flow = flow
+        this.poiData.rainfall = rainfall
       }
-    }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
-
-    // 移除闪烁效果
-    entity.billboard.scale = 0.8
-    reservoirEntity.billboard.scale = 0.8
-  }
-
-const createPoiImage = () => {
-  const canvas = document.createElement('canvas')
-  canvas.width = 40
-  canvas.height = 50
-  const ctx = canvas.getContext('2d')
-  
-  ctx.beginPath()
-  ctx.moveTo(20, 0)
-  ctx.lineTo(40, 30)
-  ctx.lineTo(20, 50)
-  ctx.lineTo(0, 30)
-  ctx.closePath()
-  
-  const gradient = ctx.createRadialGradient(20, 25, 5, 20, 25, 25)
-  gradient.addColorStop(0, '#00ffff')
-  gradient.addColorStop(1, '#0066ff')
-  ctx.fillStyle = gradient
-  ctx.fill()
-  
-  ctx.strokeStyle = '#ffffff'
-  ctx.lineWidth = 2
-  ctx.stroke()
-  
-  return canvas
-}
-
-const closePopup = () => {
-  showPopup.value = false
-}
-
-const goToTwinStation = () => {
-  // 执行平滑flyto动画到黑林水文站位置
-  const heilinPosition = Cesium.Cartesian3.fromDegrees(118.8770798, 35.0320627, 10000)
-  viewer.value.camera.flyTo({
-    destination: heilinPosition,
-    duration: 2.0,
-    easingFunction: Cesium.EasingFunction.SINE_IN_OUT
-  })
-  
-  // 延迟跳转到水文站详情页面
-  setTimeout(() => {
-    router.push('/heilin-station')
-  }, 2000)
-  
-  closePopup()
-}
-
-onMounted(async () => {
-  viewer.value = new Cesium.Viewer('cesiumContainer', {
-    terrainProvider: await Cesium.createWorldTerrainAsync(),
-    animation: false,
-    baseLayerPicker: false,
-    fullscreenButton: false,
-    geocoder: false,
-    homeButton: false,
-    infoBox: false,
-    sceneModePicker: false,
-    selectionIndicator: false,
-    timeline: false,
-    navigationHelpButton: false,
-    navigationInstructionsInitiallyVisible: false,
-    shouldAnimate: false,
-    showRenderLoopErrors: false
-  })
-  
-  if (viewer.value.cesiumWidget.creditContainer) {
-    viewer.value.cesiumWidget.creditContainer.style.display = 'none'
-  }
-  
-  const toolbar = viewer.value._element.querySelector('.cesium-viewer-toolbar')
-  if (toolbar) toolbar.style.display = 'none'
-  
-  const animationContainer = viewer.value._element.querySelector('.cesium-viewer-animationContainer')
-  if (animationContainer) animationContainer.style.display = 'none'
-  
-  const timelineContainer = viewer.value._element.querySelector('.cesium-viewer-timelineContainer')
-  if (timelineContainer) timelineContainer.style.display = 'none'
-
-  try {
-    // 加载流域边界GeoJSON
-    const response = await fetch('/Heilin.geojson')
-    const geojson = await response.json()
-    
-    const dataSource = await Cesium.GeoJsonDataSource.load(geojson, {
-      stroke: Cesium.Color.fromCssColorString('#00d5ff'),
-      strokeWidth: 3,
-      fill: Cesium.Color.fromCssColorString('#00d5ff').withAlpha(0.1)
+      
+      if (this.reservoirEntity && data.reservoir) {
+        const waterLevel = data.reservoir.waterLevel || '18.5'
+        const storage = data.reservoir.storage || '2350.8'
+        
+        this.reservoirEntity.label.text = `库水位 ${waterLevel}m\n库容 ${storage}万m³\n汛限 20.0m`
+      }
+    },
+    closePopup() {
+      this.showPopup = false
+    },
+    goToTwinStation() {
+      const heilinPosition = Cesium.Cartesian3.fromDegrees(118.8770798, 35.0320627, 10000)
+      this.viewer.camera.flyTo({
+        destination: heilinPosition,
+        duration: 2.0,
+        easingFunction: Cesium.EasingFunction.SINE_IN_OUT
+      })
+      this.closePopup()
+    }
+  },
+  async mounted() {
+    this.viewer = new Cesium.Viewer('cesiumContainer', {
+      terrainProvider: await Cesium.createWorldTerrainAsync(),
+      animation: false,
+      baseLayerPicker: false,
+      fullscreenButton: false,
+      geocoder: false,
+      homeButton: false,
+      infoBox: false,
+      sceneModePicker: false,
+      selectionIndicator: false,
+      timeline: false,
+      navigationHelpButton: false,
+      navigationInstructionsInitiallyVisible: false,
+      shouldAnimate: false,
+      showRenderLoopErrors: false
     })
     
-    viewer.value.dataSources.add(dataSource)
+    if (this.viewer.cesiumWidget.creditContainer) {
+      this.viewer.cesiumWidget.creditContainer.style.display = 'none'
+    }
+    
+    const toolbar = this.viewer._element.querySelector('.cesium-viewer-toolbar')
+    if (toolbar) toolbar.style.display = 'none'
     
-    // 加载水系GeoJSON
+    const animationContainer = this.viewer._element.querySelector('.cesium-viewer-animationContainer')
+    if (animationContainer) animationContainer.style.display = 'none'
+    
+    const timelineContainer = this.viewer._element.querySelector('.cesium-viewer-timelineContainer')
+    if (timelineContainer) timelineContainer.style.display = 'none'
+
     try {
-      const waterResponse = await fetch('/黑林水系.geojson')
-      const waterGeojson = await waterResponse.json()
+      const response = await fetch('/Heilin.geojson')
+      const geojson = await response.json()
       
-      const waterDataSource = await Cesium.GeoJsonDataSource.load(waterGeojson, {
-        stroke: Cesium.Color.fromCssColorString('#62f6fb'),
-        strokeWidth: 2,
-        fill: Cesium.Color.fromCssColorString('#62f6fb').withAlpha(0.2)
+      const dataSource = await Cesium.GeoJsonDataSource.load(geojson, {
+        stroke: Cesium.Color.fromCssColorString('#00d5ff'),
+        strokeWidth: 3,
+        fill: Cesium.Color.fromCssColorString('#00d5ff').withAlpha(0.1)
       })
       
-      viewer.value.dataSources.add(waterDataSource)
-    } catch (waterError) {
-      console.error('加载水系GeoJSON失败:', waterError)
-    }
-    
-    // 加载小塔山水库GeoJSON
-    try {
-      const reservoirResponse = await fetch('/小塔山水库.geojson')
-      const reservoirGeojson = await reservoirResponse.json()
+      this.viewer.dataSources.add(dataSource)
       
-      const reservoirDataSource = await Cesium.GeoJsonDataSource.load(reservoirGeojson, {
-        stroke: Cesium.Color.fromCssColorString('#00d4ff'),
-        strokeWidth: 2,
-        fill: Cesium.Color.fromCssColorString('#00d4ff').withAlpha(0.3)
-      })
+      try {
+        const waterResponse = await fetch('/黑林水系.geojson')
+        const waterGeojson = await waterResponse.json()
+        
+        const waterDataSource = await Cesium.GeoJsonDataSource.load(waterGeojson, {
+          stroke: Cesium.Color.fromCssColorString('#62f6fb'),
+          strokeWidth: 2,
+          fill: Cesium.Color.fromCssColorString('#62f6fb').withAlpha(0.2)
+        })
+        
+        this.viewer.dataSources.add(waterDataSource)
+      } catch (waterError) {
+        console.error('加载水系GeoJSON失败:', waterError)
+      }
       
-      viewer.value.dataSources.add(reservoirDataSource)
-    } catch (reservoirError) {
-      console.error('加载小塔山水库GeoJSON失败:', reservoirError)
-    }
-    
-    const entities = dataSource.entities.values
-    if (entities.length > 0) {
-      let minLat = 90, maxLat = -90, minLon = 180, maxLon = -180
+      try {
+        const reservoirResponse = await fetch('/小塔山水库.geojson')
+        const reservoirGeojson = await reservoirResponse.json()
+        
+        const reservoirDataSource = await Cesium.GeoJsonDataSource.load(reservoirGeojson, {
+          stroke: Cesium.Color.fromCssColorString('#00d4ff'),
+          strokeWidth: 2,
+          fill: Cesium.Color.fromCssColorString('#00d4ff').withAlpha(0.3)
+        })
+        
+        this.viewer.dataSources.add(reservoirDataSource)
+      } catch (reservoirError) {
+        console.error('加载小塔山水库GeoJSON失败:', reservoirError)
+      }
       
-      for (const entity of entities) {
-        if (entity.polygon && entity.polygon.hierarchy) {
-          const hierarchy = entity.polygon.hierarchy.getValue()
-          const positions = hierarchy.positions
-          
-          for (const pos of positions) {
-            const cartographic = Cesium.Cartographic.fromCartesian(pos)
-            const lat = Cesium.Math.toDegrees(cartographic.latitude)
-            const lon = Cesium.Math.toDegrees(cartographic.longitude)
+      const entities = dataSource.entities.values
+      if (entities.length > 0) {
+        let minLat = 90, maxLat = -90, minLon = 180, maxLon = -180
+        
+        for (const entity of entities) {
+          if (entity.polygon && entity.polygon.hierarchy) {
+            const hierarchy = entity.polygon.hierarchy.getValue()
+            const positions = hierarchy.positions
             
-            minLat = Math.min(minLat, lat)
-            maxLat = Math.max(maxLat, lat)
-            minLon = Math.min(minLon, lon)
-            maxLon = Math.max(maxLon, lon)
+            for (const pos of positions) {
+              const cartographic = Cesium.Cartographic.fromCartesian(pos)
+              const lat = Cesium.Math.toDegrees(cartographic.latitude)
+              const lon = Cesium.Math.toDegrees(cartographic.longitude)
+              
+              minLat = Math.min(minLat, lat)
+              maxLat = Math.max(maxLat, lat)
+              minLon = Math.min(minLon, lon)
+              maxLon = Math.max(maxLon, lon)
+            }
           }
         }
+        
+        this.viewer.camera.flyTo({
+          destination: Cesium.Cartesian3.fromDegrees(118.9019, 34.985, 33000),
+          duration: 0
+        })
       }
-      
-      viewer.value.camera.flyTo({
-        destination: Cesium.Cartesian3.fromDegrees(118.9019, 34.985, 33000),
-        duration: 0
-      })
+    } catch (error) {
+      console.error('加载GeoJSON失败:', error)
+      this.viewer.camera.flyTo({
+          destination: Cesium.Cartesian3.fromDegrees(118.9019, 34.985, 33000),
+          duration: 0
+        })
     }
-  } catch (error) {
-    console.error('加载GeoJSON失败:', error)
-    viewer.value.camera.flyTo({
-        destination: Cesium.Cartesian3.fromDegrees(118.9019, 34.985, 33000),
-        duration: 0
-      })
-  }
-
-  addPoiMarker()
-})
 
-onUnmounted(() => {
-  if (blinkInterval) {
-    clearInterval(blinkInterval)
-  }
-  if (viewer.value) {
-    viewer.value.destroy()
+    this.addPoiMarker()
+    this.$nextTick(() => {
+      this.updateMapMarkers(this.simulationData)
+    })
+  },
+  beforeUnmount() {
+    if (this.blinkInterval) {
+      clearInterval(this.blinkInterval)
+    }
+    if (this.viewer) {
+      this.viewer.destroy()
+    }
   }
-})
+}
 </script>
 
 <style scoped>

+ 235 - 61
src/components/HydrologyForecastPanel.vue

@@ -1,5 +1,24 @@
 <template>
   <div class="forecast-panel">
+    <div class="top-timeline">
+      <div class="timeline-container">
+        <div class="timeline-controls">
+          <button class="control-btn" @click="togglePlay">
+            {{ isPlaying ? '⏸' : '▶' }}
+          </button>
+          <button class="control-btn" @click="resetTimeline">↺</button>
+        </div>
+        <div class="timeline-progress">
+          <div class="timeline-track">
+            <div class="timeline-fill" :style="{ width: timelineProgress + '%' }"></div>
+            <div class="timeline-marker" :style="{ left: timelineProgress + '%' }"></div>
+          </div>
+        </div>
+        <div class="timeline-time">
+          <span>{{ currentTimeLabel }}</span>
+        </div>
+      </div>
+    </div>
     <div class="left-sidebar">
       <div class="data-card">
         <div class="card-header" @click="toggleDistrictRainfall">
@@ -145,7 +164,7 @@
       <div class="sidebar-wrapper">
         <div class="time-range-dropdown">
           <div class="dropdown-toggle" @click="toggleTimeRangeDropdown">
-            <span class="selected-text">{{ selectedTimeRange === '24h' ? '24h' : '72h' }}</span>
+            <span class="selected-text">{{ selectedTimeRange === '24h' ? '24h' : (selectedTimeRange === '48h' ? '48h' : '72h') }}</span>
             <span class="dropdown-arrow">{{ timeRangeDropdownOpen ? '▲' : '▼' }}</span>
           </div>
           <div v-if="timeRangeDropdownOpen" class="dropdown-menu" @click.stop>
@@ -156,6 +175,13 @@
             >
               24h
             </div>
+            <div 
+              class="dropdown-item" 
+              :class="{ active: selectedTimeRange === '48h' }"
+              @click="selectTimeRange('48h')"
+            >
+              48h
+            </div>
             <div 
               class="dropdown-item" 
               :class="{ active: selectedTimeRange === '72h' }"
@@ -193,7 +219,7 @@
               <canvas id="capacityPieChart" width="300" height="180"></canvas>
             </div>
             <div class="forecast-info">
-              预报 {{ selectedTimeRange === '24h' ? '24h' : '72h' }} 后蓄水量 {{ selectedTimeRange === '24h' ? '235' : '242' }} 万 m³,占用库容 {{ selectedTimeRange === '24h' ? '78' : '81' }}%
+              预报 {{ selectedTimeRange === '24h' ? '24h' : (selectedTimeRange === '48h' ? '48h' : '72h') }} 后蓄水量 {{ selectedTimeRange === '24h' ? '235' : (selectedTimeRange === '48h' ? '238' : '242') }} 万 m³,占用库容 {{ selectedTimeRange === '24h' ? '78' : (selectedTimeRange === '48h' ? '79' : '81') }}%
             </div>
           </div>
         </div>
@@ -233,6 +259,10 @@ export default {
       storageCapacityChart: null,
       capacityPieChart: null,
       rainfallMapImage: rainfallMapImage,
+      isPlaying: false,
+      playInterval: null,
+      currentTimeIndex: 0,
+      timelineProgress: 0,
       stationData: {
         heilin: {
           data: [2.5, 2.8, 3.5, 4.2, 3.8],
@@ -261,6 +291,17 @@ export default {
       ]
     }
   },
+  computed: {
+    currentTimeLabel() {
+      const hours = this.selectedTimeRange === '24h' ? 24 : (this.selectedTimeRange === '48h' ? 48 : 72)
+      const timePoints = this.getTimePoints()
+      return timePoints[this.currentTimeIndex] || timePoints[0]
+    },
+    totalTimePoints() {
+      const hours = this.selectedTimeRange === '24h' ? 24 : (this.selectedTimeRange === '48h' ? 48 : 72)
+      return Math.floor(hours / 6) + 1
+    }
+  },
   mounted() {
     const now = new Date()
     const month = now.getMonth() + 1
@@ -274,10 +315,56 @@ export default {
     document.addEventListener('click', this.handleOutsideClick)
   },
   beforeUnmount() {
+    this.stopPlayback()
     this.destroyCharts()
     document.removeEventListener('click', this.handleOutsideClick)
   },
   methods: {
+    getTimePoints() {
+      const is24h = this.selectedTimeRange === '24h'
+      const is48h = this.selectedTimeRange === '48h'
+      if (is24h) {
+        return ['00:00', '06:00', '12:00', '18:00', '次日00:00']
+      } else if (is48h) {
+        return ['00:00', '12:00', '24:00', '36:00', '48:00']
+      } else {
+        return ['00:00', '12:00', '24:00', '36:00', '48:00', '60:00', '72:00']
+      }
+    },
+    togglePlay() {
+      if (this.isPlaying) {
+        this.stopPlayback()
+      } else {
+        this.startPlayback()
+      }
+    },
+    startPlayback() {
+      this.isPlaying = true
+      this.playInterval = setInterval(() => {
+        this.currentTimeIndex++
+        if (this.currentTimeIndex >= this.totalTimePoints) {
+          this.currentTimeIndex = 0
+        }
+        this.timelineProgress = (this.currentTimeIndex / (this.totalTimePoints - 1)) * 100
+        this.updateChartsForTime()
+      }, 1000)
+    },
+    stopPlayback() {
+      this.isPlaying = false
+      if (this.playInterval) {
+        clearInterval(this.playInterval)
+        this.playInterval = null
+      }
+    },
+    resetTimeline() {
+      this.stopPlayback()
+      this.currentTimeIndex = 0
+      this.timelineProgress = 0
+      this.updateChartsForTime()
+    },
+    updateChartsForTime() {
+      this.initCharts()
+    },
     toggleHydrologyForecast() {
       this.hydrologyForecastExpanded = !this.hydrologyForecastExpanded
       if (this.hydrologyForecastExpanded) {
@@ -321,6 +408,7 @@ export default {
     selectTimeRange(timeRange) {
       this.selectedTimeRange = timeRange
       this.timeRangeDropdownOpen = false
+      this.resetTimeline()
       setTimeout(() => {
         this.initForecastWaterLevelChart()
         this.initForecastFlowChart()
@@ -374,26 +462,30 @@ export default {
         const currentData = this.stationData[this.selectedStation]
         
         const is24h = this.selectedTimeRange === '24h'
+        const is48h = this.selectedTimeRange === '48h'
         
         const xAxisData24h = ['00:00', '06:00', '12:00', '18:00', '次日00:00']
+        const xAxisData48h = ['00:00', '12:00', '24:00', '36:00', '48:00']
         const xAxisData72h = ['00:00', '12:00', '24:00', '36:00', '48:00', '60:00', '72:00']
         
         const heilinData24h = [2.5, 2.8, 3.5, 4.2, 3.8]
+        const heilinData48h = [2.5, 2.8, 3.5, 4.2, 3.9]
         const heilinData72h = [2.5, 2.8, 3.5, 4.2, 3.9, 3.5, 3.2]
         
         const xiaotaishanData24h = [18.2, 18.3, 18.5, 18.8, 18.6]
+        const xiaotaishanData48h = [18.2, 18.3, 18.5, 18.8, 18.7]
         const xiaotaishanData72h = [18.2, 18.3, 18.5, 18.8, 18.7, 18.5, 18.4]
         
         let waterLevelData
         if (this.selectedStation === 'heilin') {
-          waterLevelData = is24h ? heilinData24h : heilinData72h
+          waterLevelData = is24h ? heilinData24h : (is48h ? heilinData48h : heilinData72h)
         } else {
-          waterLevelData = is24h ? xiaotaishanData24h : xiaotaishanData72h
+          waterLevelData = is24h ? xiaotaishanData24h : (is48h ? xiaotaishanData48h : xiaotaishanData72h)
         }
         
         const warningLevelData = is24h 
           ? [currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel]
-          : [currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel]
+          : (is48h ? [currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel] : [currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel, currentData.warningLevel])
         
         const option = {
           animation: false,
@@ -410,7 +502,7 @@ export default {
           xAxis: {
             type: 'category',
             boundaryGap: false,
-            data: is24h ? xAxisData24h : xAxisData72h,
+            data: is24h ? xAxisData24h : (is48h ? xAxisData48h : xAxisData72h),
             axisLine: {
               lineStyle: {
                 color: '#7bbef6'
@@ -482,11 +574,14 @@ export default {
         this.forecastFlowChart = echarts.init(document.getElementById('forecastFlowChart'))
         
         const is24h = this.selectedTimeRange === '24h'
+        const is48h = this.selectedTimeRange === '48h'
         
         const xAxisData24h = ['00:00', '06:00', '12:00', '18:00', '次日00:00']
+        const xAxisData48h = ['00:00', '12:00', '24:00', '36:00', '48:00']
         const xAxisData72h = ['00:00', '12:00', '24:00', '36:00', '48:00', '60:00', '72:00']
         
         const flowData24h = [45.2, 52.8, 68.5, 62.3, 58.0]
+        const flowData48h = [45.2, 52.8, 68.5, 62.3, 56.5]
         const flowData72h = [45.2, 52.8, 68.5, 62.3, 55.7, 48.2, 42.5]
         
         const option = {
@@ -504,7 +599,7 @@ export default {
           xAxis: {
             type: 'category',
             boundaryGap: false,
-            data: is24h ? xAxisData24h : xAxisData72h,
+            data: is24h ? xAxisData24h : (is48h ? xAxisData48h : xAxisData72h),
             axisLine: {
               lineStyle: {
                 color: '#7bbef6'
@@ -540,7 +635,7 @@ export default {
             {
               name: '预报流量',
               type: 'line',
-              data: is24h ? flowData24h : flowData72h,
+              data: is24h ? flowData24h : (is48h ? flowData48h : flowData72h),
               lineStyle: {
                 color: '#00d4ff',
                 width: 2
@@ -565,20 +660,26 @@ export default {
         this.inflowForecastChart = echarts.init(document.getElementById('inflowForecastChart'))
         
         const is24h = this.selectedTimeRange === '24h'
+        const is48h = this.selectedTimeRange === '48h'
         
         const xAxisData24h = ['00:00', '06:00', '12:00', '18:00']
+        const xAxisData48h = ['00:00', '12:00', '24:00', '36:00', '48:00']
         const xAxisData72h = ['00:00', '12:00', '24:00', '36:00', '48:00', '60:00', '72:00']
         
         const totalInflowData24h = [45.2, 52.8, 68.5, 62.3]
+        const totalInflowData48h = [45.2, 52.8, 68.5, 62.3, 58.4]
         const totalInflowData72h = [45.2, 52.8, 68.5, 62.3, 58.4, 52.1, 46.8]
         
         const rainfallRunoff24h = [25, 30, 40, 36]
+        const rainfallRunoff48h = [25, 30, 40, 36, 34]
         const rainfallRunoff72h = [25, 30, 40, 36, 34, 30, 27]
         
         const undergroundRunoff24h = [12, 14, 16, 14]
+        const undergroundRunoff48h = [12, 14, 16, 14, 15]
         const undergroundRunoff72h = [12, 14, 16, 14, 15, 14, 13]
         
         const intervalRunoff24h = [8.2, 8.8, 12.5, 12.3]
+        const intervalRunoff48h = [8.2, 8.8, 12.5, 12.3, 9.4]
         const intervalRunoff72h = [8.2, 8.8, 12.5, 12.3, 9.4, 8.1, 6.8]
         
         const option = {
@@ -616,7 +717,7 @@ export default {
           xAxis: {
             type: 'category',
             boundaryGap: true,
-            data: is24h ? xAxisData24h : xAxisData72h,
+            data: is24h ? xAxisData24h : (is48h ? xAxisData48h : xAxisData72h),
             axisLine: {
               lineStyle: {
                 color: '#7bbef6'
@@ -650,7 +751,7 @@ export default {
             {
               name: '总入库',
               type: 'line',
-              data: is24h ? totalInflowData24h : totalInflowData72h,
+              data: is24h ? totalInflowData24h : (is48h ? totalInflowData48h : totalInflowData72h),
               lineStyle: {
                 color: '#62f6fb',
                 width: 2
@@ -667,7 +768,7 @@ export default {
               type: 'bar',
               stack: 'total',
               barWidth: '35%',
-              data: is24h ? rainfallRunoff24h : rainfallRunoff72h,
+              data: is24h ? rainfallRunoff24h : (is48h ? rainfallRunoff48h : rainfallRunoff72h),
               itemStyle: {
                 color: '#62f6fb'
               }
@@ -677,7 +778,7 @@ export default {
               type: 'bar',
               stack: 'total',
               barWidth: '35%',
-              data: is24h ? undergroundRunoff24h : undergroundRunoff72h,
+              data: is24h ? undergroundRunoff24h : (is48h ? undergroundRunoff48h : undergroundRunoff72h),
               itemStyle: {
                 color: '#38bdf8'
               }
@@ -687,7 +788,7 @@ export default {
               type: 'bar',
               stack: 'total',
               barWidth: '35%',
-              data: is24h ? intervalRunoff24h : intervalRunoff72h,
+              data: is24h ? intervalRunoff24h : (is48h ? intervalRunoff48h : intervalRunoff72h),
               itemStyle: {
                 color: '#3b82f6'
               }
@@ -705,23 +806,26 @@ export default {
         this.outflowCombinedChart = echarts.init(document.getElementById('outflowCombinedChart'))
         
         const is24h = this.selectedTimeRange === '24h'
+        const is48h = this.selectedTimeRange === '48h'
         
         const xAxisData24h = ['00:00', '06:00', '12:00', '18:00']
+        const xAxisData48h = ['00:00', '12:00', '24:00', '36:00', '48:00']
         const xAxisData72h = ['00:00', '12:00', '24:00', '36:00', '48:00', '60:00', '72:00']
         
         const totalOutflowData24h = [38.5, 45.2, 55.8, 50.3]
+        const totalOutflowData48h = [38.5, 45.2, 55.8, 50.3, 46.2]
         const totalOutflowData72h = [38.5, 45.2, 55.8, 50.3, 46.2, 42.1, 38.5]
         
-        const powerGen24h = [12, 15, 18, 16]
-        const powerGen72h = [12, 15, 18, 16, 15, 14, 12]
-        
-        const irrigation24h = [18, 20, 22, 20]
-        const irrigation72h = [18, 20, 22, 20, 19, 18, 18]
+        const domesticWater24h = [8, 10, 12, 11]
+        const domesticWater48h = [8, 10, 12, 11, 10]
+        const domesticWater72h = [8, 10, 12, 11, 10, 9, 8]
         
-        const ecology24h = [5, 6, 7, 6]
-        const ecology72h = [5, 6, 7, 6, 6, 6, 5]
+        const agriculturalIrrigation24h = [15, 18, 20, 18]
+        const agriculturalIrrigation48h = [15, 18, 20, 18, 17]
+        const agriculturalIrrigation72h = [15, 18, 20, 18, 17, 16, 15]
         
         const floodDischarge24h = [3.5, 4.2, 8.8, 8.3]
+        const floodDischarge48h = [3.5, 4.2, 8.8, 8.3, 6.2]
         const floodDischarge72h = [3.5, 4.2, 8.8, 8.3, 6.2, 4.1, 3.5]
         
         const option = {
@@ -742,7 +846,7 @@ export default {
             }
           },
           legend: {
-            data: ['总出库', '发电', '灌溉', '生态', '泄洪'],
+            data: ['总出库', '生活供水', '农业灌溉', '泄洪'],
             textStyle: {
               color: '#7bbef6',
               fontSize: 9
@@ -759,7 +863,7 @@ export default {
           xAxis: {
             type: 'category',
             boundaryGap: true,
-            data: is24h ? xAxisData24h : xAxisData72h,
+            data: is24h ? xAxisData24h : (is48h ? xAxisData48h : xAxisData72h),
             axisLine: {
               lineStyle: {
                 color: '#7bbef6'
@@ -793,7 +897,7 @@ export default {
             {
               name: '总出库',
               type: 'line',
-              data: is24h ? totalOutflowData24h : totalOutflowData72h,
+              data: is24h ? totalOutflowData24h : (is48h ? totalOutflowData48h : totalOutflowData72h),
               lineStyle: {
                 color: '#00d4ff',
                 width: 2
@@ -806,41 +910,31 @@ export default {
               }
             },
             {
-              name: '发电',
+              name: '生活供水',
               type: 'bar',
               stack: 'total',
               barWidth: '35%',
-              data: is24h ? powerGen24h : powerGen72h,
+              data: is24h ? domesticWater24h : (is48h ? domesticWater48h : domesticWater72h),
               itemStyle: {
                 color: '#62f6fb'
               }
             },
             {
-              name: '灌溉',
+              name: '农业灌溉',
               type: 'bar',
               stack: 'total',
               barWidth: '35%',
-              data: is24h ? irrigation24h : irrigation72h,
+              data: is24h ? agriculturalIrrigation24h : (is48h ? agriculturalIrrigation48h : agriculturalIrrigation72h),
               itemStyle: {
                 color: '#38bdf8'
               }
             },
-            {
-              name: '生态',
-              type: 'bar',
-              stack: 'total',
-              barWidth: '35%',
-              data: is24h ? ecology24h : ecology72h,
-              itemStyle: {
-                color: '#3b82f6'
-              }
-            },
             {
               name: '泄洪',
               type: 'bar',
               stack: 'total',
               barWidth: '35%',
-              data: is24h ? floodDischarge24h : floodDischarge72h,
+              data: is24h ? floodDischarge24h : (is48h ? floodDischarge48h : floodDischarge72h),
               itemStyle: {
                 color: '#f97316'
               }
@@ -858,14 +952,18 @@ export default {
         this.storageCapacityChart = echarts.init(document.getElementById('storageCapacityChart'))
         
         const is24h = this.selectedTimeRange === '24h'
+        const is48h = this.selectedTimeRange === '48h'
         
         const xAxisData24h = ['00:00', '06:00', '12:00', '18:00', '次日00:00']
+        const xAxisData48h = ['00:00', '12:00', '24:00', '36:00', '48:00']
         const xAxisData72h = ['00:00', '12:00', '24:00', '36:00', '48:00', '60:00', '72:00']
         
         const waterLevelData24h = [18.2, 18.3, 18.5, 18.8, 18.6]
+        const waterLevelData48h = [18.2, 18.3, 18.5, 18.8, 18.7]
         const waterLevelData72h = [18.2, 18.3, 18.5, 18.8, 18.7, 18.5, 18.4]
         
         const storageData24h = [220, 225, 230, 235, 232]
+        const storageData48h = [220, 225, 230, 235, 238]
         const storageData72h = [220, 225, 230, 235, 242, 240, 238]
         
         const option = {
@@ -911,7 +1009,7 @@ export default {
           xAxis: {
             type: 'category',
             boundaryGap: false,
-            data: is24h ? xAxisData24h : xAxisData72h,
+            data: is24h ? xAxisData24h : (is48h ? xAxisData48h : xAxisData72h),
             axisLine: {
               lineStyle: {
                 color: '#7bbef6'
@@ -966,7 +1064,7 @@ export default {
               name: '水位',
               type: 'line',
               yAxisIndex: 0,
-              data: is24h ? waterLevelData24h : waterLevelData72h,
+              data: is24h ? waterLevelData24h : (is48h ? waterLevelData48h : waterLevelData72h),
               lineStyle: {
                 color: '#ffd93d',
                 width: 2
@@ -979,7 +1077,7 @@ export default {
               name: '蓄水量',
               type: 'line',
               yAxisIndex: 1,
-              data: is24h ? storageData24h : storageData72h,
+              data: is24h ? storageData24h : (is48h ? storageData48h : storageData72h),
               lineStyle: {
                 color: '#62f6fb',
                 width: 2
@@ -1001,8 +1099,10 @@ export default {
         this.capacityPieChart = echarts.init(document.getElementById('capacityPieChart'))
         
         const is24h = this.selectedTimeRange === '24h'
+        const is48h = this.selectedTimeRange === '48h'
         
         const forecastStorage24h = 235
+        const forecastStorage48h = 238
         const forecastStorage72h = 242
         
         const option = {
@@ -1012,6 +1112,7 @@ export default {
             formatter: '{b}: {c}万m³ ({d}%)'
           },
           legend: {
+            data: ['已蓄水量', '剩余库容'],
             orient: 'vertical',
             right: 5,
             top: 'center',
@@ -1053,31 +1154,17 @@ export default {
               },
               data: [
                 {
-                  value: 50,
-                  name: '死库容',
-                  itemStyle: {
-                    color: 'rgba(123, 190, 246, 0.3)'
-                  }
-                },
-                {
-                  value: is24h ? forecastStorage24h : forecastStorage72h,
-                  name: '已预报蓄水量',
+                  value: is24h ? forecastStorage24h : (is48h ? forecastStorage48h : forecastStorage72h),
+                  name: '已蓄水量',
                   itemStyle: {
                     color: '#62f6fb'
                   }
                 },
                 {
-                  value: is24h ? 80 : 73,
-                  name: '兴利库容剩余',
-                  itemStyle: {
-                    color: '#38bdf8'
-                  }
-                },
-                {
-                  value: is24h ? 35 : 30,
-                  name: '防洪库容剩余',
+                  value: is24h ? 165 : (is48h ? 162 : 158),
+                  name: '剩余库容',
                   itemStyle: {
-                    color: '#f97316'
+                    color: 'rgba(123, 190, 246, 0.3)'
                   }
                 }
               ]
@@ -1097,6 +1184,93 @@ export default {
   height: 100%;
 }
 
+.top-timeline {
+  position: absolute;
+  top: 120px;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 50%;
+  z-index: 10;
+}
+
+.timeline-container {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 6px 15px;
+  background: rgba(0, 20, 40, 0.8);
+  border: 1px solid rgba(0, 212, 255, 0.3);
+  border-radius: 6px;
+}
+
+.timeline-controls {
+  display: flex;
+  gap: 6px;
+}
+
+.control-btn {
+  width: 30px;
+  height: 30px;
+  background: rgba(0, 212, 255, 0.2);
+  border: 1px solid rgba(0, 212, 255, 0.5);
+  border-radius: 4px;
+  color: #62f6fb;
+  font-size: 14px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: all 0.3s ease;
+}
+
+.control-btn:hover {
+  background: rgba(0, 212, 255, 0.3);
+  border-color: #62f6fb;
+}
+
+.timeline-progress {
+  flex: 1;
+  position: relative;
+}
+
+.timeline-track {
+  width: 100%;
+  height: 6px;
+  background: rgba(0, 50, 80, 0.5);
+  border-radius: 3px;
+  position: relative;
+}
+
+.timeline-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #3b82f6, #62f6fb);
+  border-radius: 3px;
+  transition: width 0.3s ease;
+}
+
+.timeline-marker {
+  position: absolute;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  width: 12px;
+  height: 12px;
+  background: #62f6fb;
+  border-radius: 50%;
+  box-shadow: 0 0 8px rgba(98, 246, 251, 0.5);
+}
+
+.timeline-time {
+  min-width: 80px;
+  text-align: center;
+}
+
+.timeline-time span {
+  color: #62f6fb;
+  font-size: 12px;
+  font-weight: bold;
+  font-family: monospace;
+}
+
 .left-sidebar {
   position: absolute;
   left: 20px;
@@ -1598,7 +1772,7 @@ export default {
 
 .time-range-dropdown {
   position: absolute;
-  top: 0;
+  top: 50px;
   left: -70px;
   z-index: 10;
 }

+ 657 - 115
src/components/HydrologyPlanPanel.vue

@@ -3,39 +3,20 @@
     <div class="left-sidebar">
       <div class="data-card">
         <div class="card-header">
-          <h3 class="card-title">预案类型</h3>
+          <h3 class="card-title">计算方案</h3>
         </div>
         <div class="card-body">
-          <div class="plan-types">
-            <div class="plan-type active">
-              <div class="plan-name">防汛应急预案</div>
-              <div class="plan-desc">蓝色预警级别响应</div>
-            </div>
-            <div class="plan-type">
-              <div class="plan-name">水库调度预案</div>
-              <div class="plan-desc">汛期调洪方案</div>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <div class="data-card mt-20">
-        <div class="card-header">
-          <h3 class="card-title">责任单位</h3>
-        </div>
-        <div class="card-body">
-          <div class="unit-list">
-            <div class="unit-item">
-              <div class="unit-name">黑林镇防汛办</div>
-              <div class="unit-contact">张主任</div>
-            </div>
-            <div class="unit-item">
-              <div class="unit-name">小塔山水库管理处</div>
-              <div class="unit-contact">李处长</div>
-            </div>
-            <div class="unit-item">
-              <div class="unit-name">黑林水文站</div>
-              <div class="unit-contact">王站长</div>
+          <div class="section-title">预报方案</div>
+          <div class="plan-checkbox-list">
+            <div 
+              v-for="(plan, index) in planList" 
+              :key="index"
+              class="plan-checkbox-item"
+              :class="{ active: selectedPlans.includes(plan) }"
+              @click="togglePlan(plan)"
+            >
+              <div class="plan-checkbox" :class="{ checked: selectedPlans.includes(plan) }"></div>
+              <div class="plan-name">{{ plan }}</div>
             </div>
           </div>
         </div>
@@ -45,58 +26,424 @@
     <div class="right-sidebar">
       <div class="data-card">
         <div class="card-header">
-          <h3 class="card-title">预案内容</h3>
+          <h3 class="card-title">方案信息</h3>
         </div>
         <div class="card-body">
-          <div class="plan-content-list">
-            <div class="plan-content-item">
-              <div class="content-title">预警信息发布</div>
-              <div class="content-desc">2小时内发布预警通知</div>
-            </div>
-            <div class="plan-content-item">
-              <div class="content-title">人员转移</div>
-              <div class="content-desc">危险区域人员撤离</div>
+          <div class="fixed-fact-section">
+            <div class="plan-card-title">实况</div>
+            
+            <div class="fact-info-list">
+              <div class="fact-info-row">
+                <div class="fact-info-item">
+                  <div class="fact-label">最新水位</div>
+                  <div class="fact-value">{{ planData[selectedPlans[0]]?.fact?.latestLevel }}</div>
+                  <div class="fact-unit">m</div>
+                </div>
+                <div class="fact-info-item">
+                  <div class="fact-label">发生时间</div>
+                  <div class="fact-value">{{ planData[selectedPlans[0]]?.fact?.latestLevelTime }}</div>
+                </div>
+              </div>
+              <div class="fact-info-row">
+                <div class="fact-info-item full">
+                  <div class="fact-label">今日雨量</div>
+                  <div class="fact-value">{{ planData[selectedPlans[0]]?.fact?.todayRain }}</div>
+                  <div class="fact-unit">mm</div>
+                </div>
+              </div>
             </div>
-            <div class="plan-content-item">
-              <div class="content-title">物资调配</div>
-              <div class="content-desc">应急物资准备到位</div>
+          </div>
+          
+          <div class="section-divider"></div>
+          
+          <div class="scroll-container">
+            <div 
+              v-for="(planName, index) in selectedPlans" 
+              :key="index"
+              class="plan-card"
+            >
+              <div class="plan-card-title">方案{{ index + 1 }}</div>
+              <div class="plan-info-list">
+                <div class="plan-info-item">
+                  <div class="info-label">预报方案名称</div>
+                  <div class="info-value">{{ planData[planName]?.name }}</div>
+                </div>
+                <div class="plan-info-item">
+                  <div class="info-label">作业预报时间</div>
+                  <div class="info-value">{{ planData[planName]?.workTime }}</div>
+                </div>
+                <div class="plan-info-item">
+                  <div class="info-label">预报结束时间</div>
+                  <div class="info-value">{{ planData[planName]?.endTime }}</div>
+                </div>
+                <div class="plan-info-item">
+                  <div class="info-label">预报员</div>
+                  <div class="info-value">{{ planData[planName]?.forecaster }}</div>
+                </div>
+                <div class="plan-info-item">
+                  <div class="info-label">预报操作时间</div>
+                  <div class="info-value">{{ planData[planName]?.operateTime }}</div>
+                </div>
+              </div>
+              
+              <div class="section-divider"></div>
+              <div class="section-title">预报</div>
+              
+              <div class="forecast-info-list">
+                <div class="forecast-info-row">
+                  <div class="forecast-info-item">
+                    <div class="forecast-label">最大入库</div>
+                    <div class="forecast-value">{{ planData[planName]?.forecast?.maxInflow }}</div>
+                    <div class="forecast-unit">m³/s</div>
+                  </div>
+                  <div class="forecast-info-item">
+                    <div class="forecast-label">发生时间</div>
+                    <div class="forecast-value">{{ planData[planName]?.forecast?.maxInflowTime }}</div>
+                  </div>
+                </div>
+                <div class="forecast-info-row">
+                  <div class="forecast-info-item full">
+                    <div class="forecast-label">入库水量</div>
+                    <div class="forecast-value">{{ planData[planName]?.forecast?.inflowVolume }}</div>
+                    <div class="forecast-unit">万m³</div>
+                  </div>
+                </div>
+                <div class="forecast-info-row">
+                  <div class="forecast-info-item">
+                    <div class="forecast-label">最大出库</div>
+                    <div class="forecast-value">{{ planData[planName]?.forecast?.maxOutflow }}</div>
+                    <div class="forecast-unit">m³/s</div>
+                  </div>
+                  <div class="forecast-info-item">
+                    <div class="forecast-label">发生时间</div>
+                    <div class="forecast-value">{{ planData[planName]?.forecast?.maxOutflowTime }}</div>
+                  </div>
+                </div>
+                <div class="forecast-info-row">
+                  <div class="forecast-info-item full">
+                    <div class="forecast-label">出库水量</div>
+                    <div class="forecast-value">{{ planData[planName]?.forecast?.outflowVolume }}</div>
+                    <div class="forecast-unit">万m³</div>
+                  </div>
+                </div>
+                <div class="forecast-info-row">
+                  <div class="forecast-info-item">
+                    <div class="forecast-label">最高水位</div>
+                    <div class="forecast-value">{{ planData[planName]?.forecast?.highestLevel }}</div>
+                    <div class="forecast-unit">m</div>
+                  </div>
+                  <div class="forecast-info-item">
+                    <div class="forecast-label">期末水位</div>
+                    <div class="forecast-value">{{ planData[planName]?.forecast?.endLevel }}</div>
+                    <div class="forecast-unit">m</div>
+                  </div>
+                </div>
+              </div>
             </div>
           </div>
         </div>
       </div>
+    </div>
 
-      <div class="data-card mt-20">
-        <div class="card-header">
-          <h3 class="card-title">预案状态</h3>
-        </div>
-        <div class="card-body">
-          <div class="plan-status">
-            <div class="status-item">
-              <div class="status-label">预案等级</div>
-              <div class="status-value blue">IV级</div>
-            </div>
-            <div class="status-item">
-              <div class="status-label">更新时间</div>
-              <div class="status-value">2026-03-09</div>
-            </div>
-            <div class="status-item">
-              <div class="status-label">执行状态</div>
-              <div class="status-value">待命</div>
-            </div>
-          </div>
+    <div class="bottom-chart">
+      <div class="chart-header">
+        <h4 class="chart-title">调度综合过程图</h4>
+      </div>
+      <div class="chart-content">
+        <div class="chart-time-info">
+          <span>作业预报时间:2022-06-25 10:00</span>
+          <span>预报调度时间:06-24 00:00-06-29 23:00</span>
         </div>
+        <div id="planProcessChart" class="process-chart"></div>
       </div>
     </div>
   </div>
 </template>
 
 <script>
-import DataCard from './DataCard.vue'
+import * as echarts from 'echarts'
 
 export default {
   name: 'HydrologyPlanPanel',
-  components: {
-    DataCard
+  data() {
+    return {
+      processChart: null,
+      selectedPlans: ['小塔山水库预报方案'],
+      planList: ['小塔山水库预报方案', '黑林水文站预报方案'],
+      planColors: ['#ff6b6b', '#a855f7', '#ec4899', '#f59e0b', '#10b981'],
+      planData: {
+        '小塔山水库预报方案': {
+          name: '小塔山水库预报方案',
+          workTime: '2022-06-25 15:00',
+          endTime: '2022-06-27 15:00',
+          forecaster: 'admin',
+          operateTime: '2024-06-02 14:59',
+          fact: {
+            latestLevel: '31.29',
+            latestLevelTime: '06-27 15:00',
+            todayRain: '—'
+          },
+          forecast: {
+            maxInflow: '438',
+            maxInflowTime: '06-27 10:00',
+            inflowVolume: '1893.34',
+            maxOutflow: '10.0',
+            maxOutflowTime: '06-25 16:00',
+            outflowVolume: '171.00',
+            highestLevel: '31.37',
+            endLevel: '31.37',
+            forecastLevelData: [null, null, 36.8, 37.2, 37.5, 37.8, 38.2, 38.5, 38.3, 37.8, 37.2, 36.8]
+          }
+        },
+        '黑林水文站预报方案': {
+          name: '黑林水文站预报方案',
+          workTime: '2022-06-25 16:00',
+          endTime: '2022-06-27 16:00',
+          forecaster: 'admin',
+          operateTime: '2024-06-02 15:00',
+          fact: {
+            latestLevel: '28.50',
+            latestLevelTime: '06-27 15:30',
+            todayRain: '12.5'
+          },
+          forecast: {
+            maxInflow: '320',
+            maxInflowTime: '06-27 11:00',
+            inflowVolume: '1250.00',
+            maxOutflow: '8.5',
+            maxOutflowTime: '06-25 17:00',
+            outflowVolume: '120.00',
+            highestLevel: '29.20',
+            endLevel: '28.80',
+            forecastLevelData: [null, null, 35.5, 36.0, 36.3, 36.8, 37.5, 37.2, 36.8, 36.3, 35.8, 35.5]
+          }
+        }
+      }
+    }
+  },
+  watch: {
+    selectedPlans: {
+      handler() {
+        this.$nextTick(() => {
+          this.initProcessChart()
+        })
+      },
+      deep: true
+    }
+  },
+  mounted() {
+    setTimeout(() => {
+      this.initProcessChart()
+    }, 100)
+  },
+  beforeUnmount() {
+    if (this.processChart) {
+      this.processChart.dispose()
+    }
+  },
+  methods: {
+    togglePlan(plan) {
+      const index = this.selectedPlans.indexOf(plan)
+      if (index > -1) {
+        this.selectedPlans.splice(index, 1)
+      } else {
+        this.selectedPlans.push(plan)
+      }
+    },
+    initProcessChart() {
+      if (document.getElementById('planProcessChart')) {
+        if (this.processChart) {
+          this.processChart.dispose()
+        }
+        this.processChart = echarts.init(document.getElementById('planProcessChart'))
+        
+        const legendData = ['水位', '入库流量', '泄洪流量', '汛限水位', '警戒水位', '保证水位']
+        this.selectedPlans.forEach((planName, index) => {
+          legendData.push(planName)
+        })
+        
+        const series = [
+          {
+            name: '水位',
+            type: 'line',
+            yAxisIndex: 0,
+            data: [36.2, 36.5, 36.8, 37.2, 37.5, 37.8, 38.0, 37.8, 37.5, 37.2, 36.8, 36.5],
+            lineStyle: {
+              color: '#ffd93d',
+              width: 2
+            },
+            itemStyle: {
+              color: '#ffd93d'
+            }
+          },
+          {
+            name: '入库流量',
+            type: 'line',
+            yAxisIndex: 1,
+            data: [45, 52, 60, 75, 85, 78, 65, 58, 52, 48, 45, 42],
+            lineStyle: {
+              color: '#62f6fb',
+              width: 2
+            },
+            itemStyle: {
+              color: '#62f6fb'
+            }
+          },
+          {
+            name: '泄洪流量',
+            type: 'bar',
+            yAxisIndex: 1,
+            data: [0, 0, 0, 20, 30, 25, 15, 10, 5, 0, 0, 0],
+            itemStyle: {
+              color: '#f97316'
+            }
+          },
+          {
+            name: '汛限水位',
+            type: 'line',
+            yAxisIndex: 0,
+            data: [36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36],
+            lineStyle: {
+              color: '#3b82f6',
+              width: 1,
+              type: 'dashed'
+            },
+            symbol: 'none'
+          },
+          {
+            name: '警戒水位',
+            type: 'line',
+            yAxisIndex: 0,
+            data: [38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38],
+            lineStyle: {
+              color: '#ffd93d',
+              width: 1,
+              type: 'dashed'
+            },
+            symbol: 'none'
+          },
+          {
+            name: '保证水位',
+            type: 'line',
+            yAxisIndex: 0,
+            data: [39.5, 39.5, 39.5, 39.5, 39.5, 39.5, 39.5, 39.5, 39.5, 39.5, 39.5, 39.5],
+            lineStyle: {
+              color: '#ff6b6b',
+              width: 1,
+              type: 'dashed'
+            },
+            symbol: 'none'
+          }
+        ]
+        
+        this.selectedPlans.forEach((planName, index) => {
+          const plan = this.planData[planName]
+          if (plan && plan.forecast && plan.forecast.forecastLevelData) {
+            series.push({
+              name: planName,
+              type: 'line',
+              yAxisIndex: 0,
+              data: plan.forecast.forecastLevelData,
+              lineStyle: {
+                color: this.planColors[index % this.planColors.length],
+                width: 2,
+                type: 'dashed'
+              },
+              itemStyle: {
+                color: this.planColors[index % this.planColors.length]
+              }
+            })
+          }
+        })
+        
+        const option = {
+          animation: false,
+          tooltip: {
+            trigger: 'axis'
+          },
+          legend: {
+            data: legendData,
+            textStyle: {
+              color: '#7bbef6',
+              fontSize: 10
+            },
+            top: 10
+          },
+          grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '10%',
+            top: '60px',
+            containLabel: true
+          },
+          xAxis: {
+            type: 'category',
+            boundaryGap: false,
+            data: ['06-24 00:00', '06-24 12:00', '06-25 00:00', '06-25 12:00', '06-26 00:00', '06-26 12:00', '06-27 00:00', '06-27 12:00', '06-28 00:00', '06-28 12:00', '06-29 00:00', '06-29 12:00'],
+            axisLine: {
+              lineStyle: {
+                color: '#7bbef6'
+              }
+            },
+            axisLabel: {
+              color: '#7bbef6',
+              fontSize: 9
+            }
+          },
+          yAxis: [
+            {
+              type: 'value',
+              name: '水位(m)',
+              nameTextStyle: {
+                color: '#ffd93d',
+                fontSize: 10
+              },
+              min: 32,
+              max: 42,
+              axisLine: {
+                lineStyle: {
+                  color: '#ffd93d'
+                }
+              },
+              axisLabel: {
+                color: '#ffd93d',
+                fontSize: 9
+              },
+              splitLine: {
+                lineStyle: {
+                  color: 'rgba(123, 190, 246, 0.2)'
+                }
+              }
+            },
+            {
+              type: 'value',
+              name: '流量(m³/s)',
+              nameTextStyle: {
+                color: '#62f6fb',
+                fontSize: 10
+              },
+              min: 0,
+              max: 120,
+              position: 'right',
+              axisLine: {
+                lineStyle: {
+                  color: '#62f6fb'
+                }
+              },
+              axisLabel: {
+                color: '#62f6fb',
+                fontSize: 9
+              },
+              splitLine: {
+                show: false
+              }
+            }
+          ],
+          series: series
+        }
+        this.processChart.setOption(option)
+      }
+    }
   }
 }
 </script>
@@ -123,8 +470,51 @@ export default {
   z-index: 5;
 }
 
+.bottom-chart {
+  position: absolute;
+  left: 390px;
+  right: 390px;
+  bottom: 80px;
+  height: 280px;
+  z-index: 5;
+}
+
+.chart-header {
+  text-align: center;
+  margin-bottom: 5px;
+}
+
+.chart-title {
+  font-size: 14px;
+  color: #e0fcff;
+  margin: 0;
+  font-weight: bold;
+}
+
+.chart-content {
+  width: 100%;
+  height: 240px;
+  background: rgba(0, 20, 40, 0.7);
+  border: 1px solid rgba(0, 212, 255, 0.3);
+  border-radius: 4px;
+  padding: 10px;
+}
+
+.chart-time-info {
+  display: flex;
+  justify-content: space-around;
+  margin-bottom: 5px;
+  font-size: 11px;
+  color: #7bbef6;
+}
+
+.process-chart {
+  width: 100%;
+  height: 190px;
+}
+
 .mt-20 {
-  margin-top: 20px;
+  margin-top: 15px;
 }
 
 .data-card {
@@ -143,6 +533,7 @@ export default {
   background-repeat: no-repeat;
   display: flex;
   align-items: flex-start;
+  justify-content: space-between;
   padding: 6px 20px 0;
 }
 
@@ -163,119 +554,270 @@ export default {
   margin-top: -10px;
 }
 
-.plan-types {
+.scroll-container {
+  max-height: 500px;
+  overflow-y: auto;
+}
+
+.fixed-fact-section {
+  margin-bottom: 15px;
+  padding: 0;
+  border: none;
+  background: transparent;
+}
+
+.plan-card {
+  margin-bottom: 15px;
+  border: 1px solid rgba(0, 212, 255, 0.3);
+  border-radius: 6px;
+  background: rgba(0, 30, 60, 0.6);
+  padding: 12px;
+}
+
+.plan-card:last-child {
+  margin-bottom: 0;
+}
+
+.plan-card-title {
+  font-size: 14px;
+  font-weight: bold;
+  color: #62f6fb;
+  margin-bottom: 12px;
+  padding-bottom: 8px;
+  border-bottom: 1px solid rgba(0, 212, 255, 0.3);
+}
+
+.scroll-container::-webkit-scrollbar {
+  width: 6px;
+}
+
+.scroll-container::-webkit-scrollbar-track {
+  background: rgba(0, 20, 40, 0.5);
+  border-radius: 3px;
+}
+
+.scroll-container::-webkit-scrollbar-thumb {
+  background: rgba(0, 212, 255, 0.4);
+  border-radius: 3px;
+}
+
+.scroll-container::-webkit-scrollbar-thumb:hover {
+  background: rgba(0, 212, 255, 0.6);
+}
+
+.section-title {
+  font-size: 14px;
+  color: #7bbef6;
+  margin: 8px 0 10px;
+  font-weight: bold;
+}
+
+.plan-checkbox-list {
   display: flex;
   flex-direction: column;
-  gap: 10px;
+  gap: 8px;
 }
 
-.plan-type {
-  padding: 12px;
+.plan-checkbox-item {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 10px 12px;
   background: rgba(0, 20, 40, 0.5);
-  border-radius: 4px;
   border: 1px solid rgba(0, 212, 255, 0.2);
+  border-radius: 4px;
   cursor: pointer;
   transition: all 0.3s ease;
 }
 
-.plan-type:hover,
-.plan-type.active {
+.plan-checkbox-item:hover,
+.plan-checkbox-item.active {
   border-color: #62f6fb;
   background: rgba(98, 246, 251, 0.1);
 }
 
+.plan-checkbox {
+  width: 16px;
+  height: 16px;
+  border: 2px solid rgba(0, 212, 255, 0.4);
+  border-radius: 3px;
+  background: transparent;
+  flex-shrink: 0;
+  transition: all 0.2s ease;
+}
+
+.plan-checkbox.checked {
+  background: #62f6fb;
+  border-color: #62f6fb;
+  position: relative;
+}
+
+.plan-checkbox.checked::after {
+  content: '';
+  position: absolute;
+  left: 4px;
+  top: 1px;
+  width: 5px;
+  height: 9px;
+  border: solid #000;
+  border-width: 0 2px 2px 0;
+  transform: rotate(45deg);
+}
+
 .plan-name {
   color: #e0fcff;
-  font-size: 15px;
+  font-size: 13px;
   font-weight: bold;
 }
 
-.plan-desc {
+.water-info-list,
+.flow-info-list {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.water-info-item,
+.flow-info-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 6px 8px;
+  background: rgba(0, 50, 80, 0.5);
+  border: 1px solid rgba(0, 212, 255, 0.2);
+  border-radius: 3px;
+}
+
+.info-label,
+.flow-label {
   color: #7bbef6;
-  font-size: 13px;
-  margin-top: 4px;
+  font-size: 11px;
 }
 
-.unit-list {
+.info-value,
+.flow-value {
+  color: #e0fcff;
+  font-size: 12px;
+  font-weight: bold;
+}
+
+.gate-list {
   display: flex;
   flex-direction: column;
-  gap: 10px;
+  gap: 6px;
 }
 
-.unit-item {
+.gate-item {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 10px 12px;
-  background: rgba(0, 20, 40, 0.5);
-  border-radius: 4px;
+  padding: 6px 8px;
+  background: rgba(0, 50, 80, 0.5);
   border: 1px solid rgba(0, 212, 255, 0.2);
+  border-radius: 3px;
 }
 
-.unit-name {
-  color: #e0fcff;
-  font-size: 14px;
+.gate-name {
+  color: #7bbef6;
+  font-size: 11px;
+}
+
+.gate-status {
+  padding: 2px 8px;
+  border-radius: 3px;
+  font-size: 10px;
   font-weight: bold;
 }
 
-.unit-contact {
-  color: #7bbef6;
-  font-size: 13px;
+.gate-status.open {
+  background: #22c55e;
+  color: #fff;
+}
+
+.gate-status.closed {
+  background: #64748b;
+  color: #fff;
 }
 
-.plan-content-list {
+.plan-info-list {
   display: flex;
   flex-direction: column;
-  gap: 10px;
+  gap: 8px;
 }
 
-.plan-content-item {
-  padding: 10px 12px;
-  background: rgba(0, 20, 40, 0.5);
-  border-radius: 4px;
-  border: 1px solid rgba(0, 212, 255, 0.2);
+.plan-info-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.plan-info-item .info-label {
+  color: #7bbef6;
+  font-size: 13px;
+  flex-shrink: 0;
 }
 
-.content-title {
+.plan-info-item .info-value {
   color: #e0fcff;
   font-size: 14px;
   font-weight: bold;
+  flex: 1;
 }
 
-.content-desc {
-  color: #7bbef6;
-  font-size: 13px;
-  margin-top: 4px;
+.section-divider {
+  height: 1px;
+  background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.4), transparent);
+  margin: 15px 0;
 }
 
-.plan-status {
+.fact-info-list,
+.forecast-info-list {
   display: flex;
   flex-direction: column;
-  gap: 10px;
+  gap: 5px;
 }
 
-.status-item {
+.fact-info-row,
+.forecast-info-row {
   display: flex;
-  justify-content: space-between;
+  gap: 8px;
+}
+
+.fact-info-item,
+.forecast-info-item {
+  flex: 1;
+  display: flex;
+  flex-wrap: wrap;
   align-items: center;
-  padding: 10px 12px;
-  background: rgba(0, 20, 40, 0.5);
-  border-radius: 4px;
-  border: 1px solid rgba(0, 212, 255, 0.2);
+  gap: 4px;
+  padding: 4px 0;
+  background: transparent;
+  border: none;
+  border-radius: 0;
+}
+
+.fact-info-item.full,
+.forecast-info-item.full {
+  flex: 1 1 100%;
 }
 
-.status-label {
+.fact-label,
+.forecast-label {
   color: #7bbef6;
-  font-size: 14px;
+  font-size: 13px;
+  flex-shrink: 0;
 }
 
-.status-value {
-  color: #e0fcff;
+.fact-value,
+.forecast-value {
+  color: #62f6fb;
   font-size: 14px;
   font-weight: bold;
 }
 
-.status-value.blue {
-  color: #3b82f6;
+.fact-unit,
+.forecast-unit {
+  color: #7bbef6;
+  font-size: 11px;
 }
 </style>

File diff suppressed because it is too large
+ 668 - 160
src/components/HydrologySimulationPanel.vue


+ 303 - 100
src/components/HydrologyWarningPanel.vue

@@ -3,54 +3,55 @@
     <div class="left-sidebar">
       <div class="data-card">
         <div class="card-header">
-          <h3 class="card-title">当前预警</h3>
+          <h3 class="card-title">气象预警</h3>
         </div>
         <div class="card-body">
-          <div class="warning-current">
-            <div class="warning-level-indicator blue"></div>
-            <div class="warning-text">
-              <div class="warning-title">蓝色预警</div>
-              <div class="warning-desc">水位接近警戒,请密切关注</div>
+          <div class="weather-warning-container">
+            <div 
+              v-for="(warning, index) in weatherWarnings" 
+              :key="index"
+              class="weather-warning-item"
+            >
+              <div class="warning-name">{{ warning.name }}</div>
+              <img :src="warning.image" :alt="warning.name" class="warning-image">
+              <div class="warning-time">{{ warning.time }}</div>
             </div>
           </div>
         </div>
       </div>
-
       <div class="data-card mt-20">
         <div class="card-header">
-          <h3 class="card-title">预警站点</h3>
+          <h3 class="card-title">预警统计</h3>
         </div>
         <div class="card-body">
-          <div class="warning-stations">
-            <div class="warning-station">
-              <div class="station-name">陡沟站</div>
-              <div class="station-level warning">3.2m</div>
-              <div class="station-status">黄色</div>
+          <div class="pie-chart-container">
+            <div id="warningPieChart" class="pie-chart"></div>
+            <div class="pie-center-text">{{ activeWarningName || '预警统计' }}</div>
+          </div>
+          <div class="warning-summary">
+            <div class="warning-summary-item warning">
+              <span class="warning-summary-label">超警戒:</span>
+              <span class="warning-summary-value">{{ warningStatsData[1].value }}个</span>
             </div>
-            <div class="warning-station">
-              <div class="station-name">黑林站</div>
-              <div class="station-level normal">3.8m</div>
-              <div class="station-status">正常</div>
+            <div class="warning-summary-item danger">
+              <span class="warning-summary-label">超保证:</span>
+              <span class="warning-summary-value">{{ warningStatsData[2].value }}个</span>
             </div>
           </div>
-        </div>
-      </div>
-    </div>
-
-    <div class="right-sidebar">
-      <div class="data-card">
-        <div class="card-header">
-          <h3 class="card-title">预警历史</h3>
-        </div>
-        <div class="card-body">
-          <div class="warning-history">
-            <div class="history-item">
-              <div class="history-time">2026-03-09 14:30</div>
-              <div class="history-level blue">蓝色预警</div>
-            </div>
-            <div class="history-item">
-              <div class="history-time">2026-03-08 18:20</div>
-              <div class="history-level blue">蓝色预警</div>
+          <div class="warning-list">
+            <div 
+              v-for="(item, index) in warningList" 
+              :key="index"
+              class="warning-list-item"
+              :class="item.level"
+            >
+              <div class="warning-list-time">{{ item.time }}</div>
+              <div class="warning-list-location">{{ item.location }}</div>
+              <div class="warning-list-content">
+                {{ item.content }}
+                <span v-if="item.isWarning" class="warning-badge">预警</span>
+              </div>
+              <div class="warning-list-level">{{ item.levelText }}</div>
             </div>
           </div>
         </div>
@@ -60,12 +61,147 @@
 </template>
 
 <script>
-import DataCard from './DataCard.vue'
+import * as echarts from 'echarts'
+import rainstormImage from '../assets/images/暴雨.png'
+import highTempImage from '../assets/images/高温.png'
+import convectionImage from '../assets/images/强对流.png'
+import typhoonImage from '../assets/images/台风.png'
 
 export default {
   name: 'HydrologyWarningPanel',
-  components: {
-    DataCard
+  data() {
+    return {
+      weatherWarnings: [
+        {
+          name: '暴雨',
+          image: rainstormImage,
+          time: '3月10日 14:30'
+        },
+        {
+          name: '高温',
+          image: highTempImage,
+          time: '3月10日 12:00'
+        },
+        {
+          name: '强对流',
+          image: convectionImage,
+          time: '3月10日 16:20'
+        },
+        {
+          name: '台风',
+          image: typhoonImage,
+          time: '3月10日 08:15'
+        }
+      ],
+      warningList: [
+        {
+          time: '3月10日 14:30',
+          location: '黑林水文站',
+          content: '水位接近警戒',
+          level: 'yellow',
+          levelText: '黄色',
+          isWarning: true
+        },
+        {
+          time: '3月10日 12:15',
+          location: '小塔山水库',
+          content: '入库流量突增',
+          level: 'orange',
+          levelText: '橙色',
+          isWarning: true
+        },
+        {
+          time: '3月10日 10:00',
+          location: '陡沟站',
+          content: '流量超阈值',
+          level: 'red',
+          levelText: '红色',
+          isWarning: true
+        }
+      ],
+      warningPieChart: null,
+      warningStatsData: [
+        { value: 5, name: '正常' },
+        { value: 2, name: '超警戒' },
+        { value: 1, name: '超保证' }
+      ],
+      activeWarningName: ''
+    }
+  },
+  mounted() {
+    setTimeout(() => {
+      this.initWarningPieChart()
+    }, 100)
+  },
+  beforeUnmount() {
+    if (this.warningPieChart) {
+      this.warningPieChart.dispose()
+    }
+  },
+  methods: {
+    initWarningPieChart() {
+      if (document.getElementById('warningPieChart')) {
+        if (this.warningPieChart) {
+          this.warningPieChart.dispose()
+        }
+        this.warningPieChart = echarts.init(document.getElementById('warningPieChart'))
+        const option = {
+          animation: false,
+          tooltip: {
+            trigger: 'item',
+            formatter: '{b}: {c}个 ({d}%)'
+          },
+          legend: {
+            orient: 'vertical',
+            right: 10,
+            top: 'center',
+            textStyle: {
+              color: '#7bbef6',
+              fontSize: 12
+            }
+          },
+          series: [
+            {
+              name: '预警统计',
+              type: 'pie',
+              radius: ['45%', '70%'],
+              center: ['35%', '50%'],
+              avoidLabelOverlap: false,
+              label: {
+                show: false
+              },
+              emphasis: {
+                label: {
+                  show: true,
+                  fontSize: 14,
+                  fontWeight: 'bold',
+                  color: '#e0fcff'
+                }
+              },
+              labelLine: {
+                show: false
+              },
+              data: this.warningStatsData,
+              itemStyle: {
+                color: function(params) {
+                  const colors = ['#22c55e', '#ffd93d', '#ff6b6b']
+                  return colors[params.dataIndex]
+                }
+              }
+            }
+          ]
+        }
+        this.warningPieChart.setOption(option)
+        
+        this.warningPieChart.on('mouseover', (params) => {
+          this.activeWarningName = params.name
+        })
+        
+        this.warningPieChart.on('mouseout', () => {
+          this.activeWarningName = ''
+        })
+      }
+    }
   }
 }
 </script>
@@ -96,6 +232,80 @@ export default {
   margin-top: 20px;
 }
 
+.pie-chart-container {
+  position: relative;
+  width: 100%;
+  height: 200px;
+}
+
+.pie-chart {
+  width: 100%;
+  height: 100%;
+}
+
+.pie-center-text {
+  position: absolute;
+  top: 50%;
+  left: 35%;
+  transform: translate(-50%, -50%);
+  font-size: 16px;
+  font-weight: bold;
+  color: #e0fcff;
+  pointer-events: none;
+}
+
+.warning-summary {
+  display: flex;
+  justify-content: center;
+  gap: 30px;
+  margin: 15px 0;
+  padding: 10px;
+  background: rgba(0, 20, 40, 0.5);
+  border-radius: 4px;
+}
+
+.warning-summary-item {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+}
+
+.warning-summary-label {
+  color: #7bbef6;
+  font-size: 14px;
+}
+
+.warning-summary-value {
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.warning-summary-item.warning .warning-summary-value {
+  color: #ffd93d;
+}
+
+.warning-summary-item.danger .warning-summary-value {
+  color: #ff6b6b;
+}
+
+.warning-list {
+  margin-top: 15px;
+  padding-top: 15px;
+  border-top: 1px solid rgba(0, 212, 255, 0.2);
+}
+
+.warning-badge {
+  display: inline-block;
+  margin-left: 8px;
+  padding: 2px 6px;
+  font-size: 10px;
+  font-weight: bold;
+  color: #fff;
+  background: #ff6b6b;
+  border-radius: 3px;
+  vertical-align: middle;
+}
+
 .data-card {
   width: 100%;
   background: rgba(0, 20, 40, 0.7);
@@ -132,109 +342,102 @@ export default {
   margin-top: -10px;
 }
 
-.warning-current {
+.weather-warning-container {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 12px;
+}
+
+.weather-warning-item {
   display: flex;
+  flex-direction: column;
   align-items: center;
-  gap: 15px;
-  padding: 15px;
+  justify-content: center;
+  padding: 10px;
   background: rgba(0, 20, 40, 0.5);
   border-radius: 4px;
   border: 1px solid rgba(0, 212, 255, 0.2);
 }
 
-.warning-level-indicator {
-  width: 40px;
-  height: 40px;
-  border-radius: 8px;
-}
-
-.warning-level-indicator.blue {
-  background-color: #3b82f6;
-}
-
-.warning-text {
-  flex: 1;
+.warning-name {
+  color: #62f6fb;
+  font-size: 14px;
+  font-weight: bold;
+  margin-bottom: 8px;
 }
 
-.warning-title {
-  color: #e0fcff;
-  font-size: 18px;
-  font-weight: bold;
+.warning-image {
+  width: 80px;
+  height: 80px;
+  object-fit: contain;
 }
 
-.warning-desc {
+.warning-time {
   color: #7bbef6;
-  font-size: 14px;
-  margin-top: 4px;
+  font-size: 12px;
+  margin-top: 8px;
 }
 
-.warning-stations {
+.warning-list {
   display: flex;
   flex-direction: column;
-  gap: 10px;
+  gap: 8px;
 }
 
-.warning-station {
-  display: flex;
-  justify-content: space-between;
+.warning-list-item {
+  display: grid;
+  grid-template-columns: 80px 80px 1fr 50px;
+  gap: 8px;
   align-items: center;
-  padding: 10px 12px;
+  padding: 8px 10px;
   background: rgba(0, 20, 40, 0.5);
   border-radius: 4px;
   border: 1px solid rgba(0, 212, 255, 0.2);
 }
 
-.station-name {
-  color: #e0fcff;
-  font-size: 14px;
-  font-weight: bold;
+.warning-list-item.yellow {
+  border-color: #ffd93d;
 }
 
-.station-level {
-  font-size: 14px;
-  font-weight: bold;
+.warning-list-item.orange {
+  border-color: #f97316;
 }
 
-.station-level.normal {
-  color: #62f6fb;
+.warning-list-item.red {
+  border-color: #ff6b6b;
 }
 
-.station-level.warning {
-  color: #ffd93d;
+.warning-list-time {
+  font-size: 11px;
+  color: #7bbef6;
 }
 
-.station-status {
-  font-size: 13px;
-  color: #7bbef6;
+.warning-list-location {
+  font-size: 11px;
+  color: #62f6fb;
+  font-weight: bold;
 }
 
-.warning-history {
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
+.warning-list-content {
+  font-size: 11px;
+  color: #e0fcff;
 }
 
-.history-item {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 10px 12px;
-  background: rgba(0, 20, 40, 0.5);
-  border-radius: 4px;
-  border: 1px solid rgba(0, 212, 255, 0.2);
+.warning-list-level {
+  font-size: 11px;
+  font-weight: bold;
+  text-align: right;
 }
 
-.history-time {
-  color: #7bbef6;
-  font-size: 13px;
+.warning-list-item.yellow .warning-list-level {
+  color: #ffd93d;
 }
 
-.history-level {
-  font-size: 14px;
-  font-weight: bold;
+.warning-list-item.orange .warning-list-level {
+  color: #f97316;
 }
 
-.history-level.blue {
-  color: #3b82f6;
+.warning-list-item.red .warning-list-level {
+  color: #ff6b6b;
 }
 </style>

+ 28 - 2
src/views/HomeView.vue

@@ -7,7 +7,11 @@
     
     <!-- 全局地图 -->
     <div class="map-container" v-if="showMap">
-      <CesiumMap @toggleMap="toggleMap" />
+      <CesiumMap 
+        @toggleMap="toggleMap" 
+        :simulationTime="simulationTime"
+        :simulationData="simulationData"
+      />
     </div>
     
     <!-- 渐变装饰层(四周暗角) -->
@@ -56,7 +60,11 @@
     <HydrologyForecastView 
       v-if="activeTab === '水文四预'" 
       :activeSecondaryTab="activeSecondaryTab"
+      :simulationTime="simulationTime"
+      :simulationData="simulationData"
       @selectSecondaryTab="selectSecondaryTab"
+      @updateSimulationTime="updateSimulationTime"
+      @updateSimulationData="updateSimulationData"
     />
     
     <!-- 水资源调配视图 -->
@@ -88,7 +96,19 @@ export default {
     return {
       activeTab: '流域总览',
       activeSecondaryTab: '水文预报',
-      showMap: true // 控制显示Cesium地图还是图片底图
+      showMap: true,
+      simulationTime: 0,
+      simulationData: {
+        heilinStation: {
+          waterLevel: '3.25',
+          flow: '12.5',
+          rainfall: '0.5'
+        },
+        reservoir: {
+          waterLevel: '18.5',
+          storage: '2350.8'
+        }
+      }
     }
   },
   methods: {
@@ -105,6 +125,12 @@ export default {
     },
     toggleMap(show) {
       this.showMap = show
+    },
+    updateSimulationTime(time) {
+      this.simulationTime = time
+    },
+    updateSimulationData(data) {
+      this.simulationData = { ...this.simulationData, ...data }
     }
   }
 }

+ 16 - 2
src/views/HydrologyForecastView.vue

@@ -33,7 +33,13 @@
 
     <HydrologyForecastPanel v-if="activeSecondaryTab === '水文预报'" />
     <HydrologyWarningPanel v-if="activeSecondaryTab === '水文预警'" />
-    <HydrologySimulationPanel v-if="activeSecondaryTab === '水文预演'" />
+    <HydrologySimulationPanel 
+      v-if="activeSecondaryTab === '水文预演'" 
+      :simulationTime="simulationTime"
+      :simulationData="simulationData"
+      @updateSimulationTime="$emit('updateSimulationTime', $event)"
+      @updateSimulationData="$emit('updateSimulationData', $event)"
+    />
     <HydrologyPlanPanel v-if="activeSecondaryTab === '水文预案'" />
   </div>
 </template>
@@ -56,9 +62,17 @@ export default {
     activeSecondaryTab: {
       type: String,
       default: '水文预报'
+    },
+    simulationTime: {
+      type: Number,
+      default: 0
+    },
+    simulationData: {
+      type: Object,
+      default: () => ({})
     }
   },
-  emits: ['selectSecondaryTab'],
+  emits: ['selectSecondaryTab', 'updateSimulationTime', 'updateSimulationData'],
   methods: {
     selectSecondaryTab(tab) {
       this.$emit('selectSecondaryTab', tab)

+ 7 - 43
src/views/OverviewView.vue

@@ -705,15 +705,7 @@ export default {
         const xiaotaishanCombinedOption = {
           animation: false,
           tooltip: {
-            trigger: 'axis',
-            formatter: function(params) {
-              let result = params[0].axisValue + '<br/>';
-              params.forEach(function(item) {
-                let unit = item.seriesName === '水位' ? 'm' : '10⁶m³';
-                result += item.marker + item.seriesName + ': ' + item.value + ' ' + unit + '<br/>';
-              });
-              return result;
-            }
+            trigger: 'axis'
           },
           legend: {
             data: ['水位', '库容'],
@@ -1103,13 +1095,6 @@ export default {
                   itemStyle: {
                     color: '#38bdf8'
                   }
-                },
-                {
-                  value: 120,
-                  name: '其他调水入库',
-                  itemStyle: {
-                    color: 'rgba(123, 190, 246, 0.5)'
-                  }
                 }
               ]
             }
@@ -1177,44 +1162,23 @@ export default {
               data: [
                 {
                   value: 220,
-                  name: '工业供水',
+                  name: '生活供水',
                   itemStyle: {
-                    color: '#f97316'
+                    color: '#62f6fb'
                   }
                 },
                 {
                   value: 300,
-                  name: '农业灌溉用水',
-                  itemStyle: {
-                    color: '#eab308'
-                  }
-                },
-                {
-                  value: 120,
-                  name: '发电用水',
+                  name: '农业灌溉',
                   itemStyle: {
-                    color: '#84cc16'
-                  }
-                },
-                {
-                  value: 80,
-                  name: '生态流量下放',
-                  itemStyle: {
-                    color: '#22c55e'
+                    color: '#38bdf8'
                   }
                 },
                 {
                   value: 50,
-                  name: '泄洪/排沙',
+                  name: '泄洪',
                   itemStyle: {
-                    color: '#ef4444'
-                  }
-                },
-                {
-                  value: 30,
-                  name: '蒸发渗漏损失',
-                  itemStyle: {
-                    color: 'rgba(123, 190, 246, 0.3)'
+                    color: '#f97316'
                   }
                 }
               ]

Some files were not shown because too many files changed in this diff