浏览代码

水文测站页面开发

BAI 1 月之前
父节点
当前提交
822aaaf587

二进制
src/assets/昆山水文站.mp4


+ 1 - 1
src/views/map/components/ElectricityUsage.vue

@@ -136,7 +136,7 @@ const stationData = ref([
 }
 
 .station-list {
-  height: 100%;
+  height: calc(500px - 60px);
   display: flex;
   flex-direction: column;
   padding: 0 10px;

+ 107 - 2
src/views/map/index.vue

@@ -1,13 +1,28 @@
 <template>
   <div class="large-screen">
     <!-- 地图 -->
-    <mapScene ref="mapSceneRef" v-show="state.activeIndex === '1'"></mapScene>
+    <mapScene ref="mapSceneRef" v-show="(state.activeIndex === '1' || state.activeIndex === '3') && !state.showVideoPlayer"></mapScene>
     <div class="fusion-bg" v-show="state.activeIndex === '2'">
       <img src="@/assets/images/昆山水文站.jpg" alt="昆山水文站" />
     </div>
     <div class="history-bg" v-show="state.activeIndex === '4'">
       <img src="@/assets/images/昆山水文站.jpg" alt="昆山水文站" />
     </div>
+    <!-- 视频背景 -->
+    <div class="video-background" v-if="state.showVideoPlayer">
+      <video 
+        ref="videoPlayerRef"
+        class="video-bg-player"
+        src="@/assets/昆山水文站.mp4" 
+        autoplay
+        loop
+        muted
+        @ended="closeVideoPlayer"
+      ></video>
+      <div class="video-close-btn" @click="closeVideoPlayer">
+        <span>返回</span>
+      </div>
+    </div>
     <div class="vignette-overlay"></div>
     <div class="large-screen-wrap" id="large-screen">
       <m-header title="昆山水务水文系统" sub-text="Hydrological Visualization System">
@@ -94,6 +109,9 @@
       <!-- 融合体系内容 -->
       <WaterResourceContent v-if="state.activeIndex === '2'" />
 
+      <!-- 水文测站内容 -->
+      <WaterStationContent v-if="state.activeIndex === '3'" />
+
       <!-- 历史沿革内容 -->
       <HistoryContent v-if="state.activeIndex === '4'" />
       <!-- 左右装饰线 -->
@@ -120,7 +138,7 @@
   </div>
 </template>
 <script setup>
-import { shallowRef, ref, reactive, onMounted, onBeforeUnmount, nextTick } from "vue"
+import { shallowRef, ref, reactive, onMounted, onBeforeUnmount, nextTick, provide } from "vue"
 import mapScene from "./map.vue"
 import mHeader from "@/components/mHeader/index.vue"
 import mCountCard from "@/components/mCountCard/index.vue"
@@ -137,6 +155,7 @@ import ProportionPopulationConsumption from "./components/ProportionPopulationCo
 import ElectricityUsage from "./components/ElectricityUsage.vue"
 import QuarterlyGrowthSituation from "./components/QuarterlyGrowthSituation.vue"
 import WaterResourceContent from "@/views/waterResource/index.vue"
+import WaterStationContent from "@/views/waterStation/index.vue"
 import HistoryContent from "@/views/history/index.vue"
 
 import { Assets } from "./assets.js"
@@ -146,11 +165,17 @@ import autofit from "autofit.js"
 
 const assets = shallowRef(null)
 const mapSceneRef = ref(null)
+
+// 提供资源给子组件
+provide("assets", assets)
+
 const state = reactive({
   // 进度
   progress: 0,
   // 当前顶部导航索引
   activeIndex: "1",
+  // 是否显示视频播放器
+  showVideoPlayer: false,
   // 卡片统计数据
   totalView: [
     {
@@ -182,6 +207,8 @@ const state = reactive({
 onMounted(() => {
   // 监听地图播放完成,执行事件
   emitter.$on("mapPlayComplete", handleMapPlayComplete)
+  // 监听水文站图标点击事件
+  emitter.$on("waterStationClick", handleWaterStationClick)
   // 自动适配
   assets.value = autofit.init({
     dh: 1080,
@@ -201,7 +228,24 @@ onMounted(() => {
 })
 onBeforeUnmount(() => {
   emitter.$off("mapPlayComplete", handleMapPlayComplete)
+  emitter.$off("waterStationClick", handleWaterStationClick)
 })
+
+// 处理水文站图标点击事件
+function handleWaterStationClick(stationInfo) {
+  console.log('播放水文站视频:', stationInfo.name)
+  state.showVideoPlayer = true
+}
+
+// 关闭视频播放器
+function closeVideoPlayer() {
+  state.showVideoPlayer = false
+  // 暂停视频播放
+  const videoPlayer = document.querySelector('.video-player')
+  if (videoPlayer) {
+    videoPlayer.pause()
+  }
+}
 // 初始化加载资源
 function initAssets(onLoadCallback) {
   assets.value = new Assets()
@@ -252,6 +296,16 @@ async function hideLoading() {
 
 function handleMenuSelect(index) {
   state.activeIndex = index
+  
+  // 切换水文站图标显示
+  if (index === "3") {
+    // 在水文测站页面显示图标
+    emitter.$emit("toggleWaterStations", true)
+  } else {
+    // 其他页面隐藏图标
+    emitter.$emit("toggleWaterStations", false)
+  }
+  
   nextTick(() => {
     if (index === "1") {
       gsap.to(".left-card", { x: 0, opacity: 1, duration: 0.5, stagger: 0.1 })
@@ -261,6 +315,9 @@ function handleMenuSelect(index) {
       gsap.to(".bottom-radar", { y: 0, opacity: 1, duration: 0.5 })
     } else if (index === "2") {
       gsap.to(".water-resource-content .module-card", { x: 0, opacity: 1, duration: 0.5, stagger: 0.1 })
+    } else if (index === "3") {
+      gsap.to(".water-station-content .station-card", { x: 0, opacity: 1, duration: 0.5 })
+      gsap.to(".water-station-content .bottom-container", { y: 0, opacity: 1, duration: 0.5, delay: 0.2 })
     } else if (index === "4") {
       gsap.to(".history-content .event-card", { x: 0, opacity: 1, duration: 0.5 })
       gsap.to(".history-content .timeline-container", { y: 0, opacity: 1, duration: 0.5, delay: 0.2 })
@@ -502,4 +559,52 @@ function handleMapPlayComplete() {
     opacity: 0;
   }
 }
+
+// 视频背景样式
+.video-background {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  z-index: 1;
+  
+  .video-bg-player {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+  
+  .video-close-btn {
+    position: absolute;
+    top: 100px;
+    right: 40px;
+    padding: 8px 20px;
+    background: rgba(0, 20, 40, 0.9);
+    border: 1px solid rgba(48, 220, 255, 0.5);
+    border-radius: 4px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    z-index: 10;
+    transition: all 0.3s ease;
+    
+    span {
+      color: #30dcff;
+      font-size: 14px;
+      font-weight: bold;
+    }
+    
+    &:hover {
+      background: rgba(48, 220, 255, 0.2);
+      border-color: rgba(48, 220, 255, 0.8);
+      box-shadow: 0 0 20px rgba(48, 220, 255, 0.5);
+      
+      span {
+        color: #00ffff;
+      }
+    }
+  }
+}
 </style>

+ 112 - 1
src/views/map/map.js

@@ -110,6 +110,10 @@ export class World extends Mini3d {
       center: [120.98422, 31.39244],
     }
     
+    // 是否显示水文站图标
+    this.showWaterStations = false
+    this.waterStationLabels = [] // 存储水文站标签
+    
     // 交互状态
     this.clicked = false // 是否点击
     
@@ -1168,7 +1172,7 @@ export class World extends Mini3d {
       const [x, y] = self.geoProjection(province.center) // 地理坐标转换
       label.init(
         `<div class="other-label"><img class="label-icon" src="${labelIcon}">${province.name}</div>`,
-        new Vector3(x, -y, 0.4) // 标签位置
+        new Vector3(x, -y, 0.8) // 标签位置
       )
       label3d.setLabelStyle(label, 0.02, "x") // 设置标签样式
       label.setParent(labelGroup) // 设置父级
@@ -1242,6 +1246,113 @@ export class World extends Mini3d {
       return label
     }
   }
+  
+  /**
+   * 切换水文站图标显示/隐藏
+   * @param {boolean} show - 是否显示
+   */
+  toggleWaterStations(show) {
+    this.showWaterStations = show
+    
+    // 显示/隐藏水文站图标
+    this.waterStationLabels.forEach(label => {
+      if (show) {
+        label.show()
+      } else {
+        label.hide()
+      }
+    })
+    
+    // 如果还没有创建水文站图标,则创建
+    if (show && this.waterStationLabels.length === 0) {
+      this.createWaterStationIcons()
+    }
+    
+    // 显示/隐藏柱状图(与水文站图标相反)
+    if (this.barGroup) {
+      this.barGroup.visible = !show
+    }
+    // 显示/隐藏柱状图标签
+    if (this.allProvinceLabel) {
+      this.allProvinceLabel.forEach(label => {
+        if (!show) {
+          label.show()
+        } else {
+          label.hide()
+        }
+      })
+    }
+  }
+  
+  /**
+   * 创建水文站图标(供 toggleWaterStations 调用)
+   */
+  createWaterStationIcons() {
+    let self = this
+    let labelGroup = this.labelGroup
+    let label3d = this.label3d
+    
+    // 水文站数据 - 参考柱状图坐标分散布局
+    const waterStations = [
+      { name: '昆山水文站', code: '30101200', position: [121.0286375, 31.3675229] },
+      { name: '玉山站', code: '30101201', position: [120.9147662, 31.3817447] },
+      { name: '花桥站', code: '30101202', position: [121.0951479, 31.3048198] },
+      { name: '周市站', code: '30101203', position: [120.9890589, 31.4476539] },
+      { name: '陆家站', code: '30101204', position: [121.0358312, 31.3257908] },
+      { name: '张浦站', code: '30101205', position: [120.9310144, 31.2704097] },
+      { name: '周庄站', code: '30101206', position: [120.8374877, 31.1541608] },
+      { name: '巴城站', code: '30101207', position: [120.8705844, 31.4592578] },
+      { name: '淀山湖站', code: '30101208', position: [121.0167915, 31.1743784] },
+      { name: '千灯站', code: '30101209', position: [121.0104264, 31.2483020] },
+      { name: '锦溪站', code: '30101210', position: [120.9087587, 31.1784504] }
+    ]
+    
+    waterStations.forEach((station, index) => {
+      const [x, y] = self.geoProjection(station.position)
+      
+      // 创建水文站标签 - 使用蓝色倒三角
+      let stationLabel = label3d.create("", "water-station-icon", false)
+      stationLabel.init(
+        `<div class="water-station-marker" data-station="${station.code}">
+          <div class="station-triangle"></div>
+          <div class="station-label">${station.name}</div>
+        </div>`,
+        new Vector3(x, -y, 1.2)
+      )
+
+      // 设置标签样式 - 启用点击事件
+      label3d.setLabelStyle(stationLabel, 0.022, "x", Math.PI / 2, "auto")
+      stationLabel.setParent(labelGroup)
+      
+      // 添加点击事件
+      const markerElement = stationLabel.element.querySelector('.water-station-marker')
+      if (markerElement) {
+        markerElement.style.cursor = 'pointer'
+        markerElement.addEventListener('click', (e) => {
+          e.stopPropagation()
+          e.preventDefault()
+          console.log('水文站图标被点击:', station.name)
+          // 触发水文站点击事件
+          emitter.$emit('waterStationClick', {
+            name: station.name,
+            code: station.code,
+            position: station.position
+          })
+        })
+        
+        markerElement.addEventListener('mouseenter', () => {
+          markerElement.style.transform = 'scale(1.3)'
+          markerElement.style.transition = 'transform 0.2s ease'
+        })
+        
+        markerElement.addEventListener('mouseleave', () => {
+          markerElement.style.transform = 'scale(1)'
+        })
+      }
+      
+      self.waterStationLabels.push(stationLabel)
+    })
+  }
   /**
    * 创建旋转圆环
    * 功能:在地图中心创建两个旋转的圆环,增强视觉效果和动态感

+ 58 - 0
src/views/map/map.vue

@@ -7,18 +7,26 @@
 import { onMounted, shallowRef, onBeforeUnmount } from "vue";
 import { World } from "./map.js";
 import emitter from "@/utils/emitter";
+
 const canvasMap = shallowRef(null);
 onMounted(() => {
   emitter.$on("loadMap", loadMap);
+  emitter.$on("toggleWaterStations", toggleWaterStations);
 });
 onBeforeUnmount(() => {
   canvasMap.value && canvasMap.value.destroy();
   emitter.$off("loadMap", loadMap);
+  emitter.$off("toggleWaterStations", toggleWaterStations);
 });
 function loadMap(assets) {
   canvasMap.value = new World(document.getElementById("canvasMap"), assets);
   canvasMap.value.time.pause();
 }
+function toggleWaterStations(show) {
+  if (canvasMap.value) {
+    canvasMap.value.toggleWaterStations(show);
+  }
+}
 async function play() {
   canvasMap.value.time.resume();
   canvasMap.value.animateTl.timeScale(1); // 设置播放速度正常
@@ -237,5 +245,55 @@ defineExpose({
     background: none;
     will-change: transform;
   }
+  
+  // 水文站图标样式 - 蓝色倒三角
+  .water-station-icon {
+    .water-station-marker {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      cursor: pointer;
+      pointer-events: auto;
+      
+      .station-triangle {
+        width: 0;
+        height: 15;
+        border-left: 12px solid transparent;
+        border-right: 12px solid transparent;
+        border-top: 18px solid #0066cc;
+        filter: drop-shadow(0 0 6px rgba(0, 102, 204, 0.8));
+        transition: all 0.2s ease;
+        margin-bottom: 3px;
+      }
+      
+      .station-label {
+        margin-top: 0;
+        padding: 2px 6px;
+        background: rgba(0, 20, 40, 0.85);
+        border: 1px solid rgba(0, 102, 204, 0.4);
+        border-radius: 2px;
+        color: #0066cc;
+        font-size: 10px;
+        font-weight: normal;
+        white-space: nowrap;
+        text-shadow: 0 0 6px rgba(0, 102, 204, 0.5);
+        transform: scale(0.95);
+        transition: all 0.2s ease;
+      }
+      
+      &:hover {
+        .station-triangle {
+          border-top-color: #0088ff;
+          filter: drop-shadow(0 0 8px rgba(0, 102, 204, 1));
+        }
+        
+        .station-label {
+          background: rgba(0, 102, 204, 0.2);
+          border-color: rgba(0, 102, 204, 0.8);
+          color: #0088ff;
+        }
+      }
+    }
+  }
 }
 </style>

+ 531 - 0
src/views/waterStation/index.vue

@@ -0,0 +1,531 @@
+<template>
+  <div class="water-station-content">
+    <!-- 测站信息面板 - 右侧 -->
+    <div class="right-panel">
+      <div class="station-card">
+        <div class="card-header">
+          <div class="header-icon">📍</div>
+          <div class="header-title">水文测站</div>
+        </div>
+        <div class="station-list">
+          <div 
+            v-for="(station, index) in stations" 
+            :key="index" 
+            class="station-item"
+            :class="{ active: selectedIndex === index }"
+            @click="selectStation(index)"
+          >
+            <div class="station-name">{{ station.name }}</div>
+            <div class="station-type">{{ station.type }}</div>
+          </div>
+        </div>
+        <div class="station-detail">
+          <div class="detail-header">
+            <div class="detail-name">{{ currentStation.name }}</div>
+            <div class="detail-type">{{ currentStation.type }}</div>
+          </div>
+          <div class="detail-info">
+            <div class="info-item">
+              <span class="label">站点编号</span>
+              <span class="value">{{ currentStation.code }}</span>
+            </div>
+            <div class="info-item">
+              <span class="label">所在河流</span>
+              <span class="value">{{ currentStation.river }}</span>
+            </div>
+            <div class="info-item">
+              <span class="label">建站时间</span>
+              <span class="value">{{ currentStation.establishDate }}</span>
+            </div>
+            <div class="info-item">
+              <span class="label">监测要素</span>
+              <span class="value">{{ currentStation.elements }}</span>
+            </div>
+          </div>
+          <div class="detail-data">
+            <div class="data-title">实时数据</div>
+            <div class="data-grid">
+              <div class="data-item">
+                <div class="data-value">{{ currentStation.waterLevel }}</div>
+                <div class="data-unit">m</div>
+                <div class="data-label">水位</div>
+              </div>
+              <div class="data-item">
+                <div class="data-value">{{ currentStation.flow }}</div>
+                <div class="data-unit">m³/s</div>
+                <div class="data-label">流量</div>
+              </div>
+              <div class="data-item">
+                <div class="data-value">{{ currentStation.rainfall }}</div>
+                <div class="data-unit">mm</div>
+                <div class="data-label">降雨量</div>
+              </div>
+              <div class="data-item">
+                <div class="data-value">{{ currentStation.quality }}</div>
+                <div class="data-unit">类</div>
+                <div class="data-label">水质等级</div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 底部测站统计 -->
+    <div class="bottom-panel">
+      <div class="bottom-container">
+        <div class="stat-item">
+          <div class="stat-value">57</div>
+          <div class="stat-label">测站总数</div>
+        </div>
+        <div class="stat-item">
+          <div class="stat-value">42</div>
+          <div class="stat-label">河道站</div>
+        </div>
+        <div class="stat-item">
+          <div class="stat-value">8</div>
+          <div class="stat-label">雨量站</div>
+        </div>
+        <div class="stat-item">
+          <div class="stat-value">7</div>
+          <div class="stat-label">水质站</div>
+        </div>
+        <div class="stat-item">
+          <div class="stat-value">100%</div>
+          <div class="stat-label">在线率</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onBeforeUnmount } from "vue"
+import emitter from "@/utils/emitter"
+
+const stations = ref([
+  {
+    name: "昆山水文站",
+    type: "国家基本站",
+    code: "30101200",
+    river: "吴淞江",
+    establishDate: "1954-01",
+    elements: "水位、流量、水质",
+    waterLevel: "2.85",
+    flow: "128.5",
+    rainfall: "12.5",
+    quality: "II"
+  },
+  {
+    name: "玉山站",
+    type: "省级重点站",
+    code: "30101201",
+    river: "娄江",
+    establishDate: "1978-06",
+    elements: "水位、流量",
+    waterLevel: "2.72",
+    flow: "92.3",
+    rainfall: "12.5",
+    quality: "II"
+  },
+  {
+    name: "花桥站",
+    type: "省级重点站",
+    code: "30101202",
+    river: "吴淞江",
+    establishDate: "1985-03",
+    elements: "水位、流量、水质",
+    waterLevel: "2.68",
+    flow: "105.6",
+    rainfall: "12.5",
+    quality: "II"
+  },
+  {
+    name: "周市站",
+    type: "省级重点站",
+    code: "30101203",
+    river: "杨林塘",
+    establishDate: "1982-07",
+    elements: "水位、流量",
+    waterLevel: "2.75",
+    flow: "88.4",
+    rainfall: "12.5",
+    quality: "III"
+  },
+  {
+    name: "陆家站",
+    type: "一般站",
+    code: "30101204",
+    river: "吴淞江",
+    establishDate: "1990-05",
+    elements: "水位、水质",
+    waterLevel: "2.66",
+    flow: "--",
+    rainfall: "12.5",
+    quality: "II"
+  },
+  {
+    name: "张浦站",
+    type: "一般站",
+    code: "30101205",
+    river: "淀山湖",
+    establishDate: "1988-09",
+    elements: "水位、水质",
+    waterLevel: "2.78",
+    flow: "--",
+    rainfall: "12.5",
+    quality: "II"
+  },
+  {
+    name: "周庄站",
+    type: "旅游监测站",
+    code: "30101206",
+    river: "急水港",
+    establishDate: "1995-04",
+    elements: "水位、流量、水质",
+    waterLevel: "2.82",
+    flow: "45.2",
+    rainfall: "12.5",
+    quality: "I"
+  },
+  {
+    name: "巴城站",
+    type: "一般站",
+    code: "30101207",
+    river: "阳澄湖",
+    establishDate: "1986-08",
+    elements: "水位、水质",
+    waterLevel: "2.74",
+    flow: "--",
+    rainfall: "12.5",
+    quality: "II"
+  },
+  {
+    name: "淀山湖站",
+    type: "湖泊站",
+    code: "30101208",
+    river: "淀山湖",
+    establishDate: "1965-04",
+    elements: "水位、水质",
+    waterLevel: "2.76",
+    flow: "--",
+    rainfall: "12.5",
+    quality: "III"
+  },
+  {
+    name: "千灯站",
+    type: "一般站",
+    code: "30101209",
+    river: "千灯浦",
+    establishDate: "1992-06",
+    elements: "水位、流量",
+    waterLevel: "2.71",
+    flow: "68.5",
+    rainfall: "12.5",
+    quality: "II"
+  },
+  {
+    name: "锦溪站",
+    type: "旅游监测站",
+    code: "30101210",
+    river: "五保湖",
+    establishDate: "1998-03",
+    elements: "水位、水质",
+    waterLevel: "2.79",
+    flow: "--",
+    rainfall: "12.5",
+    quality: "I"
+  }
+])
+
+const selectedIndex = ref(0)
+
+const currentStation = computed(() => stations.value[selectedIndex.value])
+
+function selectStation(index) {
+  selectedIndex.value = index
+}
+
+// 监听地图上图标点击事件
+onMounted(() => {
+  emitter.$on("waterStationClick", handleWaterStationClick)
+})
+
+onBeforeUnmount(() => {
+  emitter.$off("waterStationClick", handleWaterStationClick)
+})
+
+function handleWaterStationClick(data) {
+  console.log("收到水文站点击事件:", data)
+  // 根据点击的水文站代码找到对应的索引
+  const index = stations.value.findIndex(station => station.code === data.code)
+  if (index !== -1) {
+    selectedIndex.value = index
+  }
+}
+</script>
+
+<style lang="scss">
+.water-station-content {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 2;
+  pointer-events: none;
+
+  .map {
+    z-index: 1;
+  }
+}
+
+.right-panel {
+  position: absolute;
+  z-index: 4;
+  width: 380px;
+  right: 32px;
+  top: 120px;
+  bottom: 120px;
+  perspective: 800px;
+  perspective-origin: 50% 50%;
+}
+
+.station-card {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 20, 40, 0.85);
+  border: 1px solid rgba(48, 220, 255, 0.3);
+  border-radius: 10px;
+  box-shadow: 0 0 20px rgba(48, 220, 255, 0.1);
+  transform: translate3d(0px, 0px, 0px) scaleX(1) scaleY(1) rotateX(0deg) rotateY(-6deg) rotateZ(0deg) skewX(0deg) skewY(0deg);
+  padding: 15px;
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  pointer-events: auto;
+  transform: translateX(150%);
+  opacity: 0;
+}
+
+.card-header {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid rgba(48, 220, 255, 0.2);
+  .header-icon {
+    font-size: 24px;
+  }
+  .header-title {
+    font-size: 18px;
+    font-weight: bold;
+    color: #30dcff;
+    letter-spacing: 2px;
+  }
+}
+
+.station-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  max-height: 200px;
+  overflow-y: auto;
+  
+  &::-webkit-scrollbar {
+    width: 4px;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: rgba(48, 220, 255, 0.3);
+    border-radius: 2px;
+  }
+  
+  .station-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 10px 12px;
+    background: rgba(48, 220, 255, 0.1);
+    border: 1px solid rgba(48, 220, 255, 0.2);
+    border-radius: 6px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    pointer-events: auto;
+    
+    &:hover {
+      background: rgba(48, 220, 255, 0.2);
+      border-color: rgba(48, 220, 255, 0.4);
+    }
+    
+    &.active {
+      background: rgba(48, 220, 255, 0.3);
+      border-color: rgba(48, 220, 255, 0.6);
+      box-shadow: 0 0 10px rgba(48, 220, 255, 0.3);
+    }
+    
+    .station-name {
+      font-size: 14px;
+      color: #c4f3fe;
+      font-weight: bold;
+    }
+    
+    .station-type {
+      font-size: 12px;
+      color: #00bfff;
+    }
+  }
+}
+
+.station-detail {
+  flex: 1;
+  background: rgba(0, 180, 255, 0.05);
+  border-radius: 8px;
+  padding: 15px;
+  border: 1px solid rgba(48, 220, 255, 0.1);
+  overflow-y: auto;
+  
+  &::-webkit-scrollbar {
+    width: 4px;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: rgba(48, 220, 255, 0.3);
+    border-radius: 2px;
+  }
+  
+  .detail-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 15px;
+    padding-bottom: 10px;
+    border-bottom: 1px solid rgba(48, 220, 255, 0.2);
+    
+    .detail-name {
+      font-size: 18px;
+      font-weight: bold;
+      color: #30dcff;
+    }
+    
+    .detail-type {
+      font-size: 12px;
+      color: #43e97b;
+      padding: 4px 8px;
+      background: rgba(67, 233, 123, 0.1);
+      border-radius: 4px;
+    }
+  }
+  
+  .detail-info {
+    margin-bottom: 15px;
+    
+    .info-item {
+      display: flex;
+      justify-content: space-between;
+      padding: 8px 0;
+      border-bottom: 1px solid rgba(48, 220, 255, 0.1);
+      
+      &:last-child {
+        border-bottom: none;
+      }
+      
+      .label {
+        font-size: 13px;
+        color: #a3dcde;
+      }
+      
+      .value {
+        font-size: 13px;
+        color: #c4f3fe;
+      }
+    }
+  }
+  
+  .detail-data {
+    padding-top: 15px;
+    border-top: 1px solid rgba(48, 220, 255, 0.2);
+    
+    .data-title {
+      font-size: 14px;
+      color: #30dcff;
+      margin-bottom: 12px;
+      font-weight: bold;
+    }
+    
+    .data-grid {
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+      gap: 10px;
+      
+      .data-item {
+        background: rgba(48, 220, 255, 0.1);
+        border-radius: 6px;
+        padding: 12px;
+        text-align: center;
+        border: 1px solid rgba(48, 220, 255, 0.2);
+        
+        .data-value {
+          font-size: 24px;
+          font-weight: bold;
+          color: #30dcff;
+          line-height: 1;
+        }
+        
+        .data-unit {
+          font-size: 11px;
+          color: #a3dcde;
+          margin-top: 2px;
+        }
+        
+        .data-label {
+          font-size: 12px;
+          color: #c4f3fe;
+          margin-top: 6px;
+        }
+      }
+    }
+  }
+}
+
+.bottom-panel {
+  position: absolute;
+  left: 100px;
+  right: 430px;
+  bottom: 20px;
+  height: 70px;
+  z-index: 4;
+}
+
+.bottom-container {
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  height: 100%;
+  background: rgba(0, 20, 40, 0.85);
+  border: 1px solid rgba(48, 220, 255, 0.3);
+  border-radius: 10px;
+  padding: 0 30px;
+  pointer-events: auto;
+  transform: translateY(100%);
+  opacity: 0;
+  
+  .stat-item {
+    text-align: center;
+    
+    .stat-value {
+      font-size: 28px;
+      font-weight: bold;
+      color: #30dcff;
+      text-shadow: 0 0 10px rgba(48, 220, 255, 0.5);
+    }
+    
+    .stat-label {
+      font-size: 13px;
+      color: #a3dcde;
+      margin-top: 5px;
+    }
+  }
+}
+</style>