Browse Source

修改水文科普页

WQQ 1 month ago
parent
commit
0f7caa0e63

BIN
src/assets/images/背景图.png


BIN
src/assets/video/水文化.mp4


+ 15 - 1
src/views/map/index.vue

@@ -11,6 +11,14 @@
       </video>
       <img v-else class="history-bg-image" :src="state.historyImageSrc" alt="历史背景" />
     </div>
+    <div class="culture-bg" v-show="state.activeIndex === '5'">
+      <video class="culture-bg-video" loop autoplay muted>
+        <source src="@/assets/video/水文化.mp4" type="video/mp4">
+      </video>
+    </div>
+    <div class="science-bg" v-show="state.activeIndex === '5'">
+      <img src="@/assets/images/背景图.png" alt="背景图" />
+    </div>
     <!-- 视频背景 -->
     <div class="video-background" v-if="state.showVideoPlayer">
       <video 
@@ -117,6 +125,9 @@
 
       <!-- 历史沿革内容 -->
       <HistoryContent v-if="state.activeIndex === '4'" @changeVideo="handleVideoChange" />
+
+      <!-- 水文科普内容 -->
+      <ScienceContent v-if="state.activeIndex === '5'" />
       <!-- 左右装饰线 -->
       <div class="large-screen-left-zsline"></div>
       <div class="large-screen-right-zsline"></div>
@@ -160,6 +171,7 @@ 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 ScienceContent from "@/views/science/index.vue"
 
 import { Assets } from "./assets.js"
 import emitter from "@/utils/emitter"
@@ -331,6 +343,8 @@ function handleMenuSelect(index) {
     } 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 })
+    } else if (index === "5") {
+      gsap.to(".science-content .science-card", { y: 0, opacity: 1, duration: 0.6, stagger: 0.15 })
     }
   })
 }
@@ -518,7 +532,7 @@ function handleMapPlayComplete() {
   opacity: 0;
 }
 
-.fusion-bg, .history-bg {
+.fusion-bg, .history-bg, .culture-bg, .science-bg {
   position: absolute;
   z-index: 1;
   left: 0;

+ 604 - 0
src/views/science/GuidePanel.vue

@@ -0,0 +1,604 @@
+<template>
+  <div class="guide-panel">
+    <div class="guide-container">
+      <div class="guide-header">
+        <div class="guide-title">
+          <span class="title-icon">🤖</span>
+          <span class="title-text">数字人导览</span>
+        </div>
+        <div class="guide-close" @click="$emit('close')">×</div>
+      </div>
+
+      <div class="guide-main">
+        <div class="guide-avatar-section">
+          <div class="avatar-wrapper">
+            <div class="avatar-glow"></div>
+            <div class="avatar-scan"></div>
+            <div class="avatar-body">
+              <div class="body-head">👤</div>
+              <div class="body-torso"></div>
+              <div class="body-legs">
+                <div class="leg left"></div>
+                <div class="leg right"></div>
+              </div>
+            </div>
+            <div class="avatar-wave" v-if="isSpeaking">
+              <span v-for="i in 7" :key="i" class="wave-bar" :style="{ animationDelay: `${i * 0.08}s` }"></span>
+            </div>
+          </div>
+          <div class="avatar-info">
+            <div class="avatar-name">水文智能导览员</div>
+            <div class="avatar-status" :class="{ speaking: isSpeaking }">
+              <span class="status-dot"></span>
+              <span class="status-text">{{ isSpeaking ? '正在回复...' : '等待提问' }}</span>
+            </div>
+          </div>
+        </div>
+
+        <div class="guide-chat-section">
+          <div class="chat-header">
+            <span class="chat-title">💬 智能问答</span>
+            <span class="chat-tip">输入您的问题,数字人将为您解答</span>
+          </div>
+          
+          <div class="chat-messages" ref="chatMessagesRef">
+            <div class="chat-message ai" v-if="chatMessages.length === 0">
+              <div class="message-avatar">🤖</div>
+              <div class="message-content">
+                <div class="message-text">您好!我是水文智能导览员,请问有什么关于水文知识的问题需要咨询吗?</div>
+              </div>
+            </div>
+            <template v-for="(msg, index) in chatMessages" :key="index">
+              <div class="chat-message user">
+                <div class="message-avatar">👤</div>
+                <div class="message-content">
+                  <div class="message-text">{{ msg.question }}</div>
+                </div>
+              </div>
+              <div class="chat-message ai">
+                <div class="message-avatar">🤖</div>
+                <div class="message-content">
+                  <div class="message-text">{{ msg.answer }}</div>
+                </div>
+              </div>
+            </template>
+            <div class="chat-message ai typing" v-if="isTyping">
+              <div class="message-avatar">🤖</div>
+              <div class="message-content">
+                <div class="typing-indicator">
+                  <span></span>
+                  <span></span>
+                  <span></span>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <div class="chat-input-area">
+            <div class="quick-questions">
+              <span class="quick-label">快捷问题:</span>
+              <button 
+                v-for="(q, index) in quickQuestions" 
+                :key="index"
+                class="quick-btn"
+                @click="sendQuickQuestion(q)"
+              >
+                {{ q }}
+              </button>
+            </div>
+            <div class="input-wrapper">
+              <input 
+                type="text" 
+                v-model="inputMessage" 
+                placeholder="请输入您的问题..."
+                @keyup.enter="sendMessage"
+                class="chat-input"
+              />
+              <button class="send-btn" @click="sendMessage" :disabled="!inputMessage.trim()">
+                发送
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, nextTick } from "vue"
+
+defineEmits(["close"])
+
+const chatMessagesRef = ref(null)
+const inputMessage = ref("")
+const chatMessages = ref([])
+const isSpeaking = ref(false)
+const isTyping = ref(false)
+
+const quickQuestions = [
+  "什么是水文?",
+  "水位怎么测量?",
+  "洪水来了怎么办?",
+  "如何节约用水?"
+]
+
+const knowledgeBase = {
+  "什么是水文": "水文是研究地球上水的起源、存在、分布、循环、运动等变化规律的科学。水文工作主要包括水位、流量、水质等要素的监测和分析,为水资源管理、防洪减灾等提供科学依据。",
+  "水位怎么测量": "水位测量常用的方法有:1. 水尺读数法:通过读取水尺刻度获取水位;2. 自记水位计:自动记录水位变化;3. 雷达水位计:利用雷达波测量水位,精度高。",
+  "洪水来了怎么办": "遇到洪水时:1. 保持冷静,迅速向高处转移;2. 远离低洼地区、河道和危险建筑;3. 不要游泳逃生,不要攀爬带电设施;4. 如被洪水包围,设法发出求救信号等待救援。",
+  "如何节约用水": "节水小技巧:1. 随手关闭水龙头;2. 使用节水器具;3. 一水多用,如洗菜水浇花;4. 缩短洗澡时间;5. 及时修复漏水设施。每个人的小行动,汇聚成节水大力量!"
+}
+
+function getAnswer(question) {
+  const keywords = {
+    "水文": "水文是研究地球上水的起源、存在、分布、循环、运动等变化规律的科学。水文工作为水资源管理和防洪减灾提供重要支撑。",
+    "水位": "水位是指水体表面相对于某一基准面的高度。测量水位的方法有水尺、自记水位计、雷达水位计等。水位数据是水文监测的基础数据。",
+    "洪水": "洪水是由暴雨、融雪等引起的江河湖泊水量迅猛增加的现象。防洪措施包括工程措施(堤防、水库)和非工程措施(预报预警、应急预案)。",
+    "节水": "节约用水是保护水资源的重要方式。可以通过使用节水器具、一水多用、减少浪费等方式实现节水。",
+    "水资源": "水资源是指可资利用或有可能被利用的水源。我国水资源总量丰富,但人均占有量仅为世界平均水平的四分之一,需要珍惜保护。",
+    "水质": "水质是指水的物理、化学和生物特性。水质监测包括pH值、溶解氧、浊度、各类污染物等指标的检测,保障饮用水安全。",
+    "降雨": "降雨量使用雨量筒或雨量计测量,单位为毫米。降雨数据对于洪水预报、水资源计算等具有重要意义。",
+    "防汛": "防汛工作包括:完善防洪工程体系、加强监测预报预警、制定应急预案、开展防汛演练、普及防灾知识等。"
+  }
+  
+  for (const [key, answer] of Object.entries(keywords)) {
+    if (question.includes(key)) {
+      return answer
+    }
+  }
+  
+  return "感谢您的提问!关于" + question + ",建议您查阅相关的水文资料或咨询专业人士获取更详细的信息。我会持续学习,努力为您提供更好的服务。"
+}
+
+function sendMessage() {
+  const message = inputMessage.value.trim()
+  if (!message) return
+  
+  chatMessages.value.push({
+    question: message,
+    answer: ""
+  })
+  
+  inputMessage.value = ""
+  isTyping.value = true
+  
+  scrollToBottom()
+  
+  setTimeout(() => {
+    const answer = getAnswer(message)
+    chatMessages.value[chatMessages.value.length - 1].answer = answer
+    isTyping.value = false
+    isSpeaking.value = true
+    
+    setTimeout(() => {
+      isSpeaking.value = false
+    }, 2000)
+    
+    scrollToBottom()
+  }, 1000 + Math.random() * 1000)
+}
+
+function sendQuickQuestion(question) {
+  inputMessage.value = question
+  sendMessage()
+}
+
+function scrollToBottom() {
+  nextTick(() => {
+    if (chatMessagesRef.value) {
+      chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight
+    }
+  })
+}
+</script>
+
+<style lang="scss">
+.guide-panel {
+  position: absolute;
+  top: 80px;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 100;
+  background: rgba(0, 0, 0, 0.85);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  pointer-events: auto;
+}
+
+.guide-container {
+  position: relative;
+  width: 95%;
+  max-width: 1600px;
+  height: 90%;
+  background: linear-gradient(135deg, rgba(0, 30, 60, 0.95) 0%, rgba(0, 50, 80, 0.95) 100%);
+  border: 2px solid rgba(48, 220, 255, 0.5);
+  border-radius: 10px;
+  box-shadow: 0 0 30px rgba(48, 220, 255, 0.3);
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+.guide-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 20px 30px;
+  background: linear-gradient(90deg, rgba(48, 220, 255, 0.1) 0%, transparent 100%);
+  border-bottom: 1px solid rgba(48, 220, 255, 0.2);
+}
+
+.guide-title {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  .title-icon {
+    font-size: 28px;
+  }
+  .title-text {
+    font-size: 22px;
+    font-weight: bold;
+    color: #fff;
+    letter-spacing: 2px;
+  }
+}
+
+.guide-close {
+  width: 36px;
+  height: 36px;
+  background: rgba(0, 20, 40, 0.8);
+  border: 1px solid rgba(48, 220, 255, 0.5);
+  border-radius: 50%;
+  color: #30dcff;
+  font-size: 24px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: all 0.3s ease;
+  &:hover {
+    background: rgba(48, 220, 255, 0.3);
+    transform: scale(1.1);
+  }
+}
+
+.guide-main {
+  flex: 1;
+  display: flex;
+  padding: 30px;
+  gap: 30px;
+  overflow: hidden;
+}
+
+.guide-avatar-section {
+  width: 250px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  .avatar-wrapper {
+    position: relative;
+    width: 180px;
+    height: 280px;
+    .avatar-glow {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 160px;
+      height: 260px;
+      background: radial-gradient(ellipse, rgba(48, 220, 255, 0.15) 0%, transparent 70%);
+      animation: glowPulse 3s ease-in-out infinite;
+    }
+    .avatar-scan {
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      height: 2px;
+      background: linear-gradient(90deg, transparent, rgba(48, 220, 255, 0.8), transparent);
+      animation: scanLine 3s linear infinite;
+    }
+    .avatar-body {
+      position: relative;
+      width: 100%;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      .body-head {
+        width: 70px;
+        height: 70px;
+        background: linear-gradient(135deg, rgba(48, 220, 255, 0.3) 0%, rgba(0, 100, 150, 0.3) 100%);
+        border: 2px solid rgba(48, 220, 255, 0.5);
+        border-radius: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 36px;
+        margin-bottom: 10px;
+      }
+      .body-torso {
+        width: 80px;
+        height: 100px;
+        background: linear-gradient(180deg, rgba(48, 220, 255, 0.25) 0%, rgba(48, 220, 255, 0.15) 100%);
+        border: 2px solid rgba(48, 220, 255, 0.4);
+        border-radius: 40px 40px 20px 20px;
+        margin-bottom: 10px;
+      }
+      .body-legs {
+        display: flex;
+        gap: 15px;
+        .leg {
+          width: 25px;
+          height: 80px;
+          background: linear-gradient(180deg, rgba(48, 220, 255, 0.2) 0%, rgba(48, 220, 255, 0.1) 100%);
+          border: 2px solid rgba(48, 220, 255, 0.35);
+          border-radius: 12px 12px 8px 8px;
+        }
+      }
+    }
+    .avatar-wave {
+      position: absolute;
+      bottom: -30px;
+      left: 50%;
+      transform: translateX(-50%);
+      display: flex;
+      gap: 4px;
+      align-items: flex-end;
+      .wave-bar {
+        width: 5px;
+        background: linear-gradient(180deg, #30dcff, rgba(48, 220, 255, 0.3));
+        border-radius: 3px;
+        animation: waveAnim 0.4s ease-in-out infinite alternate;
+        &:nth-child(1) { height: 15px; }
+        &:nth-child(2) { height: 25px; }
+        &:nth-child(3) { height: 35px; }
+        &:nth-child(4) { height: 40px; }
+        &:nth-child(5) { height: 35px; }
+        &:nth-child(6) { height: 25px; }
+        &:nth-child(7) { height: 15px; }
+      }
+    }
+  }
+  .avatar-info {
+    margin-top: 40px;
+    text-align: center;
+    .avatar-name {
+      font-size: 18px;
+      font-weight: bold;
+      color: #fff;
+      margin-bottom: 10px;
+    }
+    .avatar-status {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 8px;
+      padding: 8px 20px;
+      background: rgba(100, 100, 100, 0.3);
+      border-radius: 20px;
+      color: rgba(255, 255, 255, 0.6);
+      font-size: 14px;
+      .status-dot {
+        width: 8px;
+        height: 8px;
+        background: rgba(255, 255, 255, 0.4);
+        border-radius: 50%;
+      }
+      &.speaking {
+        background: rgba(48, 220, 255, 0.2);
+        color: #30dcff;
+        .status-dot {
+          background: #30dcff;
+          box-shadow: 0 0 10px rgba(48, 220, 255, 0.8);
+        }
+      }
+    }
+  }
+}
+
+@keyframes glowPulse {
+  0%, 100% { opacity: 0.5; transform: translate(-50%, -50%) scale(1); }
+  50% { opacity: 1; transform: translate(-50%, -50%) scale(1.1); }
+}
+
+@keyframes scanLine {
+  0% { top: 0; opacity: 0; }
+  10% { opacity: 1; }
+  90% { opacity: 1; }
+  100% { top: 100%; opacity: 0; }
+}
+
+@keyframes waveAnim {
+  0% { transform: scaleY(0.6); }
+  100% { transform: scaleY(1); }
+}
+
+.guide-chat-section {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+}
+
+.chat-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding-bottom: 15px;
+  border-bottom: 1px solid rgba(48, 220, 255, 0.1);
+  .chat-title {
+    font-size: 16px;
+    font-weight: bold;
+    color: #30dcff;
+  }
+  .chat-tip {
+    font-size: 12px;
+    color: rgba(255, 255, 255, 0.5);
+  }
+}
+
+.chat-messages {
+  flex: 1;
+  overflow-y: auto;
+  padding-right: 10px;
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+  &::-webkit-scrollbar-track {
+    background: rgba(48, 220, 255, 0.1);
+    border-radius: 3px;
+  }
+  &::-webkit-scrollbar-thumb {
+    background: rgba(48, 220, 255, 0.3);
+    border-radius: 3px;
+  }
+}
+
+.chat-message {
+  display: flex;
+  gap: 12px;
+  margin-bottom: 15px;
+  &.user {
+    flex-direction: row-reverse;
+  }
+  .message-avatar {
+    width: 36px;
+    height: 36px;
+    background: linear-gradient(135deg, rgba(48, 220, 255, 0.3) 0%, rgba(0, 100, 150, 0.3) 100%);
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 18px;
+    flex-shrink: 0;
+  }
+  &.user .message-avatar {
+    background: linear-gradient(135deg, rgba(0, 255, 136, 0.3) 0%, rgba(0, 150, 100, 0.3) 100%);
+  }
+  .message-content {
+    max-width: 80%;
+    .message-text {
+      padding: 12px 16px;
+      background: rgba(48, 220, 255, 0.1);
+      border: 1px solid rgba(48, 220, 255, 0.2);
+      border-radius: 12px;
+      border-top-left-radius: 4px;
+      font-size: 14px;
+      color: #fff;
+      line-height: 1.6;
+    }
+  }
+  &.user .message-text {
+    background: rgba(0, 255, 136, 0.1);
+    border-color: rgba(0, 255, 136, 0.2);
+    border-top-left-radius: 12px;
+    border-top-right-radius: 4px;
+  }
+}
+
+.typing-indicator {
+  display: flex;
+  gap: 4px;
+  padding: 16px;
+  background: rgba(48, 220, 255, 0.1);
+  border: 1px solid rgba(48, 220, 255, 0.2);
+  border-radius: 12px;
+  border-top-left-radius: 4px;
+  span {
+    width: 8px;
+    height: 8px;
+    background: rgba(48, 220, 255, 0.6);
+    border-radius: 50%;
+    animation: typingBounce 0.6s ease-in-out infinite;
+    &:nth-child(2) {
+      animation-delay: 0.1s;
+    }
+    &:nth-child(3) {
+      animation-delay: 0.2s;
+    }
+  }
+}
+
+@keyframes typingBounce {
+  0%, 100% {
+    transform: translateY(0);
+  }
+  50% {
+    transform: translateY(-8px);
+  }
+}
+
+.chat-input-area {
+  padding-top: 15px;
+  border-top: 1px solid rgba(48, 220, 255, 0.1);
+  .quick-questions {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 12px;
+    flex-wrap: wrap;
+    .quick-label {
+      font-size: 12px;
+      color: rgba(255, 255, 255, 0.5);
+    }
+    .quick-btn {
+      padding: 6px 14px;
+      background: rgba(48, 220, 255, 0.1);
+      border: 1px solid rgba(48, 220, 255, 0.2);
+      border-radius: 15px;
+      color: rgba(48, 220, 255, 0.8);
+      font-size: 12px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      &:hover {
+        background: rgba(48, 220, 255, 0.2);
+        border-color: rgba(48, 220, 255, 0.4);
+      }
+    }
+  }
+  .input-wrapper {
+    display: flex;
+    gap: 10px;
+    .chat-input {
+      flex: 1;
+      padding: 12px 16px;
+      background: rgba(0, 20, 40, 0.6);
+      border: 1px solid rgba(48, 220, 255, 0.3);
+      border-radius: 8px;
+      color: #fff;
+      font-size: 14px;
+      outline: none;
+      transition: all 0.3s ease;
+      &::placeholder {
+        color: rgba(255, 255, 255, 0.4);
+      }
+      &:focus {
+        border-color: rgba(48, 220, 255, 0.6);
+        box-shadow: 0 0 10px rgba(48, 220, 255, 0.2);
+      }
+    }
+    .send-btn {
+      padding: 12px 24px;
+      background: linear-gradient(135deg, rgba(48, 220, 255, 0.3) 0%, rgba(0, 100, 150, 0.3) 100%);
+      border: 1px solid rgba(48, 220, 255, 0.4);
+      border-radius: 8px;
+      color: #fff;
+      font-size: 14px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      &:hover:not(:disabled) {
+        background: linear-gradient(135deg, rgba(48, 220, 255, 0.5) 0%, rgba(0, 100, 150, 0.5) 100%);
+        box-shadow: 0 0 15px rgba(48, 220, 255, 0.3);
+      }
+      &:disabled {
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
+    }
+  }
+}
+</style>

+ 510 - 0
src/views/science/QuizPanel.vue

@@ -0,0 +1,510 @@
+<template>
+  <div class="quiz-panel">
+    <div class="quiz-container">
+      <div class="quiz-header">
+        <div class="quiz-title">
+          <span class="title-icon">📚</span>
+          <span class="title-text">水文知识问答</span>
+        </div>
+        <div class="quiz-close" @click="$emit('close')">×</div>
+      </div>
+
+      <div class="quiz-progress">
+        <div class="progress-bar">
+          <div class="progress-fill" :style="{ width: progressPercent + '%' }"></div>
+        </div>
+        <div class="progress-text">第 {{ currentIndex + 1 }} 题 / 共 {{ questions.length }} 题</div>
+      </div>
+
+      <div class="quiz-content" v-if="!showResult">
+        <div class="question-area">
+          <div class="question-number">Q{{ currentIndex + 1 }}</div>
+          <div class="question-text">{{ currentQuestion.question }}</div>
+        </div>
+
+        <div class="options-area">
+          <div 
+            v-for="(option, index) in currentQuestion.options" 
+            :key="index"
+            class="option-item"
+            :class="{
+              'selected': selectedAnswer === index,
+              'correct': answered && index === currentQuestion.answer,
+              'wrong': answered && selectedAnswer === index && index !== currentQuestion.answer
+            }"
+            @click="selectAnswer(index)"
+          >
+            <span class="option-letter">{{ optionLetters[index] }}</span>
+            <span class="option-text">{{ option }}</span>
+            <span class="option-icon" v-if="answered && index === currentQuestion.answer">✓</span>
+            <span class="option-icon wrong" v-if="answered && selectedAnswer === index && index !== currentQuestion.answer">✗</span>
+          </div>
+        </div>
+
+        <div class="answer-feedback" v-if="answered">
+          <div class="feedback-icon" :class="isCorrect ? 'correct' : 'wrong'">
+            {{ isCorrect ? '回答正确!' : '回答错误' }}
+          </div>
+          <div class="feedback-explanation">
+            <span class="explanation-label">解析:</span>
+            <span class="explanation-text">{{ currentQuestion.explanation }}</span>
+          </div>
+        </div>
+
+        <div class="quiz-actions">
+          <button class="action-btn" v-if="!answered" @click="submitAnswer" :disabled="selectedAnswer === null">
+            确认答案
+          </button>
+          <button class="action-btn next" v-if="answered && !isLastQuestion" @click="nextQuestion">
+            下一题 →
+          </button>
+          <button class="action-btn finish" v-if="answered && isLastQuestion" @click="showResult = true">
+            查看成绩
+          </button>
+        </div>
+      </div>
+
+      <div class="quiz-result" v-else>
+        <div class="result-circle">
+          <svg class="circle-svg" viewBox="0 0 100 100">
+            <circle class="circle-bg" cx="50" cy="50" r="45"></circle>
+            <circle 
+              class="circle-fill" 
+              cx="50" 
+              cy="50" 
+              r="45"
+              :style="{ strokeDasharray: `${scorePercent * 2.83} 283` }"
+              :class="scoreClass"
+            ></circle>
+          </svg>
+          <div class="result-score">{{ correctCount }}/{{ questions.length }}</div>
+          <div class="result-label">答对</div>
+        </div>
+        <div class="result-text" :class="scoreClass">
+          {{ resultText }}
+        </div>
+        <div class="result-actions">
+          <button class="action-btn restart" @click="restartQuiz">
+            重新答题
+          </button>
+          <button class="action-btn close" @click="$emit('close')">
+            关闭
+          </button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from "vue"
+
+defineEmits(["close"])
+
+const optionLetters = ["A", "B", "C", "D"]
+
+const questions = ref([
+  {
+    question: "水文监测中,常用的水位测量单位是什么?",
+    options: ["厘米(cm)", "米(m)", "毫米(mm)", "分米(dm)"],
+    answer: 1,
+    explanation: "水位测量通常使用米(m)作为单位,精确到厘米或毫米。水位是指水体表面相对于某一基准面的高度。"
+  },
+  {
+    question: "我国的母亲河是指哪两条河流?",
+    options: ["珠江和淮河", "长江和黄河", "松花江和辽河", "海河和钱塘江"],
+    answer: 1,
+    explanation: "长江和黄河被称为中华民族的母亲河。长江是我国第一长河,黄河是我国第二长河,两河流域孕育了灿烂的中华文明。"
+  },
+  {
+    question: "降雨量通常用什么仪器测量?",
+    options: ["风速仪", "温度计", "雨量筒", "水位计"],
+    answer: 2,
+    explanation: "降雨量使用雨量筒(雨量计)测量,单位为毫米(mm)。雨量筒收集雨水后,通过测量雨水的深度或重量来计算降雨量。"
+  },
+  {
+    question: "水文站的主要功能不包括以下哪项?",
+    options: ["水位监测", "流量测验", "水质监测", "天气预报"],
+    answer: 3,
+    explanation: "水文站主要负责水位监测、流量测验、水质监测、泥沙监测等水文要素的观测。天气预报属于气象部门的职责范围。"
+  },
+  {
+    question: "地下水和地表水的关系是?",
+    options: ["互不相关", "可以相互转化", "地下水只能补给地表水", "地表水只能补给地下水"],
+    answer: 1,
+    explanation: "地下水和地表水之间可以相互转化。枯水期地下水补给地表水,洪水期地表水补给地下水,形成一个动态平衡系统。"
+  }
+])
+
+const currentIndex = ref(0)
+const selectedAnswer = ref(null)
+const answered = ref(false)
+const showResult = ref(false)
+const correctCount = ref(0)
+
+const currentQuestion = computed(() => questions.value[currentIndex.value])
+const isCorrect = computed(() => selectedAnswer.value === currentQuestion.value.answer)
+const isLastQuestion = computed(() => currentIndex.value === questions.value.length - 1)
+const progressPercent = computed(() => ((currentIndex.value + 1) / questions.value.length) * 100)
+const scorePercent = computed(() => (correctCount.value / questions.value.length) * 100)
+
+const scoreClass = computed(() => {
+  const percent = scorePercent.value
+  if (percent >= 80) return "excellent"
+  if (percent >= 60) return "good"
+  return "poor"
+})
+
+const resultText = computed(() => {
+  const percent = scorePercent.value
+  if (percent >= 80) return "🎉 太棒了!水文知识掌握得很好!"
+  if (percent >= 60) return "👍 不错!继续加油学习水文知识!"
+  return "💪 需要加强学习哦,再接再厉!"
+})
+
+function selectAnswer(index) {
+  if (answered.value) return
+  selectedAnswer.value = index
+}
+
+function submitAnswer() {
+  if (selectedAnswer.value === null) return
+  answered.value = true
+  if (isCorrect.value) {
+    correctCount.value++
+  }
+}
+
+function nextQuestion() {
+  currentIndex.value++
+  selectedAnswer.value = null
+  answered.value = false
+}
+
+function restartQuiz() {
+  currentIndex.value = 0
+  selectedAnswer.value = null
+  answered.value = false
+  showResult.value = false
+  correctCount.value = 0
+}
+</script>
+
+<style lang="scss">
+.quiz-panel {
+  position: absolute;
+  top: 80px;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 100;
+  background: rgba(0, 0, 0, 0.9);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  pointer-events: auto;
+}
+
+.quiz-container {
+  width: 800px;
+  max-width: 90%;
+  background: linear-gradient(135deg, rgba(0, 30, 60, 0.95) 0%, rgba(0, 50, 80, 0.95) 100%);
+  border: 1px solid rgba(48, 220, 255, 0.4);
+  border-radius: 16px;
+  box-shadow: 0 0 40px rgba(48, 220, 255, 0.2);
+  overflow: hidden;
+}
+
+.quiz-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 20px 30px;
+  background: linear-gradient(90deg, rgba(48, 220, 255, 0.1) 0%, transparent 100%);
+  border-bottom: 1px solid rgba(48, 220, 255, 0.2);
+}
+
+.quiz-title {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  .title-icon {
+    font-size: 28px;
+  }
+  .title-text {
+    font-size: 22px;
+    font-weight: bold;
+    color: #fff;
+    letter-spacing: 2px;
+  }
+}
+
+.quiz-close {
+  width: 36px;
+  height: 36px;
+  background: rgba(0, 20, 40, 0.8);
+  border: 1px solid rgba(48, 220, 255, 0.5);
+  border-radius: 50%;
+  color: #30dcff;
+  font-size: 24px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: all 0.3s ease;
+  &:hover {
+    background: rgba(48, 220, 255, 0.3);
+    transform: scale(1.1);
+  }
+}
+
+.quiz-progress {
+  padding: 15px 30px;
+  .progress-bar {
+    height: 6px;
+    background: rgba(48, 220, 255, 0.1);
+    border-radius: 3px;
+    overflow: hidden;
+    .progress-fill {
+      height: 100%;
+      background: linear-gradient(90deg, #30dcff, #00ffff);
+      border-radius: 3px;
+      transition: width 0.3s ease;
+    }
+  }
+  .progress-text {
+    margin-top: 8px;
+    text-align: right;
+    font-size: 12px;
+    color: rgba(48, 220, 255, 0.7);
+  }
+}
+
+.quiz-content {
+  padding: 30px;
+}
+
+.question-area {
+  margin-bottom: 30px;
+  .question-number {
+    display: inline-block;
+    padding: 6px 16px;
+    background: linear-gradient(90deg, rgba(48, 220, 255, 0.2), transparent);
+    border-left: 3px solid #30dcff;
+    color: #30dcff;
+    font-size: 14px;
+    font-weight: bold;
+    margin-bottom: 15px;
+  }
+  .question-text {
+    font-size: 20px;
+    color: #fff;
+    line-height: 1.6;
+  }
+}
+
+.options-area {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  margin-bottom: 25px;
+}
+
+.option-item {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  padding: 15px 20px;
+  background: rgba(0, 20, 40, 0.5);
+  border: 1px solid rgba(48, 220, 255, 0.2);
+  border-radius: 10px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  &:hover:not(.correct):not(.wrong) {
+    border-color: rgba(48, 220, 255, 0.5);
+    background: rgba(48, 220, 255, 0.1);
+  }
+  &.selected:not(.correct):not(.wrong) {
+    border-color: #30dcff;
+    background: rgba(48, 220, 255, 0.15);
+  }
+  &.correct {
+    border-color: #00ff88;
+    background: rgba(0, 255, 136, 0.15);
+  }
+  &.wrong {
+    border-color: #ff4444;
+    background: rgba(255, 68, 68, 0.15);
+  }
+  .option-letter {
+    width: 32px;
+    height: 32px;
+    background: rgba(48, 220, 255, 0.2);
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #30dcff;
+    font-weight: bold;
+    font-size: 14px;
+  }
+  .option-text {
+    flex: 1;
+    color: rgba(255, 255, 255, 0.9);
+    font-size: 16px;
+  }
+  .option-icon {
+    font-size: 18px;
+    &.correct {
+      color: #00ff88;
+    }
+    &.wrong {
+      color: #ff4444;
+    }
+  }
+}
+
+.answer-feedback {
+  padding: 20px;
+  background: rgba(0, 20, 40, 0.6);
+  border-radius: 10px;
+  margin-bottom: 25px;
+  .feedback-icon {
+    font-size: 18px;
+    font-weight: bold;
+    margin-bottom: 10px;
+    &.correct {
+      color: #00ff88;
+    }
+    &.wrong {
+      color: #ff4444;
+    }
+  }
+  .feedback-explanation {
+    .explanation-label {
+      color: #30dcff;
+      font-weight: bold;
+    }
+    .explanation-text {
+      color: rgba(255, 255, 255, 0.7);
+      line-height: 1.6;
+    }
+  }
+}
+
+.quiz-actions {
+  display: flex;
+  justify-content: center;
+  gap: 15px;
+}
+
+.action-btn {
+  padding: 12px 35px;
+  background: linear-gradient(135deg, rgba(48, 220, 255, 0.3) 0%, rgba(0, 100, 150, 0.3) 100%);
+  border: 1px solid rgba(48, 220, 255, 0.5);
+  border-radius: 8px;
+  color: #fff;
+  font-size: 16px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  &:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+  &:not(:disabled):hover {
+    background: linear-gradient(135deg, rgba(48, 220, 255, 0.5) 0%, rgba(0, 100, 150, 0.5) 100%);
+    transform: translateY(-2px);
+    box-shadow: 0 5px 20px rgba(48, 220, 255, 0.3);
+  }
+  &.next {
+    background: linear-gradient(135deg, rgba(0, 255, 136, 0.3) 0%, rgba(0, 150, 100, 0.3) 100%);
+    border-color: rgba(0, 255, 136, 0.5);
+  }
+  &.finish {
+    background: linear-gradient(135deg, rgba(255, 165, 0, 0.3) 0%, rgba(200, 100, 0, 0.3) 100%);
+    border-color: rgba(255, 165, 0, 0.5);
+  }
+}
+
+.quiz-result {
+  padding: 50px 30px;
+  text-align: center;
+}
+
+.result-circle {
+  position: relative;
+  width: 180px;
+  height: 180px;
+  margin: 0 auto 30px;
+  .circle-svg {
+    width: 100%;
+    height: 100%;
+    transform: rotate(-90deg);
+    .circle-bg {
+      fill: none;
+      stroke: rgba(48, 220, 255, 0.1);
+      stroke-width: 8;
+    }
+    .circle-fill {
+      fill: none;
+      stroke-width: 8;
+      stroke-linecap: round;
+      transition: stroke-dasharray 1s ease;
+      &.excellent {
+        stroke: #00ff88;
+      }
+      &.good {
+        stroke: #ffaa00;
+      }
+      &.poor {
+        stroke: #ff6666;
+      }
+    }
+  }
+  .result-score {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -60%);
+    font-size: 36px;
+    font-weight: bold;
+    color: #fff;
+  }
+  .result-label {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, 40%);
+    font-size: 14px;
+    color: rgba(255, 255, 255, 0.6);
+  }
+}
+
+.result-text {
+  font-size: 24px;
+  margin-bottom: 40px;
+  &.excellent {
+    color: #00ff88;
+  }
+  &.good {
+    color: #ffaa00;
+  }
+  &.poor {
+    color: #ff6666;
+  }
+}
+
+.result-actions {
+  display: flex;
+  justify-content: center;
+  gap: 20px;
+  .action-btn.restart {
+    background: linear-gradient(135deg, rgba(48, 220, 255, 0.3) 0%, rgba(0, 100, 150, 0.3) 100%);
+    border-color: rgba(48, 220, 255, 0.5);
+  }
+  .action-btn.close {
+    background: rgba(100, 100, 100, 0.3);
+    border-color: rgba(255, 255, 255, 0.3);
+  }
+}
+</style>

+ 374 - 0
src/views/science/index.vue

@@ -0,0 +1,374 @@
+<template>
+  <div class="science-content">
+    <div class="video-overlay" v-if="showVideo" @click.self="closeVideo">
+      <div class="video-container">
+        <video ref="videoRef" class="overlay-video" loop autoplay muted>
+          <source :src="videoSrc" type="video/mp4">
+        </video>
+        <div class="video-close" @click="closeVideo">×</div>
+      </div>
+    </div>
+    <QuizPanel v-if="showQuiz" @close="showQuiz = false" />
+    <GuidePanel v-if="showGuide" @close="showGuide = false" />
+    <div class="cards-container">
+      <div 
+        v-for="(card, index) in cards" 
+        :key="index" 
+        class="science-card"
+        :style="{ animationDelay: `${index * 0.15}s` }"
+        @click="handleCardClick(index)"
+      >
+        <div class="card-border">
+          <div class="corner corner-tl"></div>
+          <div class="corner corner-tr"></div>
+          <div class="corner corner-bl"></div>
+          <div class="corner corner-br"></div>
+        </div>
+        <div class="card-icon">{{ card.icon }}</div>
+        <div class="card-title">{{ card.title }}</div>
+        <div class="card-desc">{{ card.desc }}</div>
+        <div class="card-line"></div>
+        <div class="card-glow"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from "vue"
+import QuizPanel from "./QuizPanel.vue"
+import GuidePanel from "./GuidePanel.vue"
+
+const emit = defineEmits(["navigate"])
+
+const videoRef = ref(null)
+const showVideo = ref(false)
+const showQuiz = ref(false)
+const showGuide = ref(false)
+const selectedCardIndex = ref(-1)
+
+const videoSrc = computed(() => {
+  if (selectedCardIndex.value === 0) {
+    return new URL("@/assets/video/水文化.mp4", import.meta.url).href
+  }
+  return ""
+})
+
+const cards = ref([
+  {
+    icon: "📢",
+    title: "水文宣传",
+    desc: "了解水文知识,传播水文化"
+  },
+  {
+    icon: "❓",
+    title: "知识问答",
+    desc: "互动问答,学习水文知识"
+  },
+  {
+    icon: "🤖",
+    title: "数字人导览",
+    desc: "智能导览,沉浸式体验"
+  }
+])
+
+function handleCardClick(index) {
+  if (index === 0) {
+    selectedCardIndex.value = index
+    showVideo.value = true
+  } else if (index === 1) {
+    showQuiz.value = true
+  } else if (index === 2) {
+    showGuide.value = true
+  } else {
+    emit("navigate", index)
+  }
+}
+
+function closeVideo() {
+  if (videoRef.value) {
+    videoRef.value.pause()
+  }
+  showVideo.value = false
+}
+</script>
+
+<style lang="scss">
+.science-content {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 2;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  pointer-events: none;
+}
+
+.video-overlay {
+  position: absolute;
+  top: 80px;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 100;
+  background: rgba(0, 0, 0, 0.85);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  pointer-events: auto;
+}
+
+.video-container {
+  position: relative;
+  width: auto;
+  height: 90%;
+  border: 2px solid rgba(48, 220, 255, 0.5);
+  border-radius: 10px;
+  box-shadow: 0 0 30px rgba(48, 220, 255, 0.3);
+  overflow: hidden;
+}
+
+.overlay-video {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+  display: block;
+}
+
+.video-close {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  width: 36px;
+  height: 36px;
+  background: rgba(0, 20, 40, 0.8);
+  border: 1px solid rgba(48, 220, 255, 0.5);
+  border-radius: 50%;
+  color: #30dcff;
+  font-size: 24px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: all 0.3s ease;
+  z-index: 10;
+  &:hover {
+    background: rgba(48, 220, 255, 0.3);
+    transform: scale(1.1);
+  }
+}
+
+.cards-container {
+  display: flex;
+  gap: 60px;
+  perspective: 1000px;
+}
+
+.science-card {
+  position: relative;
+  width: 320px;
+  height: 420px;
+  background: linear-gradient(135deg, rgba(0, 30, 60, 0.9) 0%, rgba(0, 50, 80, 0.8) 100%);
+  border: 1px solid rgba(48, 220, 255, 0.3);
+  border-radius: 16px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 20px;
+  cursor: pointer;
+  pointer-events: auto;
+  overflow: hidden;
+  transition: all 0.4s ease;
+  transform: translateY(100px);
+  opacity: 0;
+  animation: cardEnter 0.6s ease forwards;
+  
+  &:hover {
+    transform: translateY(-10px) scale(1.02);
+    border-color: rgba(48, 220, 255, 0.8);
+    box-shadow: 
+      0 0 30px rgba(48, 220, 255, 0.3),
+      0 0 60px rgba(48, 220, 255, 0.1),
+      inset 0 0 30px rgba(48, 220, 255, 0.1);
+    
+    .card-glow {
+      opacity: 1;
+    }
+    
+    .card-line {
+      width: 80%;
+    }
+    
+    .corner {
+      opacity: 1;
+      &::before, &::after {
+        background: #30dcff;
+      }
+    }
+  }
+  
+  &:active {
+    transform: scale(0.98);
+  }
+}
+
+@keyframes cardEnter {
+  to {
+    transform: translateY(0);
+    opacity: 1;
+  }
+}
+
+.card-border {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  pointer-events: none;
+}
+
+.corner {
+  position: absolute;
+  width: 20px;
+  height: 20px;
+  opacity: 0.6;
+  transition: all 0.3s ease;
+  
+  &::before, &::after {
+    content: "";
+    position: absolute;
+    background: rgba(48, 220, 255, 0.5);
+    transition: all 0.3s ease;
+  }
+  
+  &.corner-tl {
+    top: 10px;
+    left: 10px;
+    &::before {
+      top: 0;
+      left: 0;
+      width: 30px;
+      height: 2px;
+    }
+    &::after {
+      top: 0;
+      left: 0;
+      width: 2px;
+      height: 30px;
+    }
+  }
+  
+  &.corner-tr {
+    top: 10px;
+    right: 10px;
+    &::before {
+      top: 0;
+      right: 0;
+      width: 30px;
+      height: 2px;
+    }
+    &::after {
+      top: 0;
+      right: 0;
+      width: 2px;
+      height: 30px;
+    }
+  }
+  
+  &.corner-bl {
+    bottom: 10px;
+    left: 10px;
+    &::before {
+      bottom: 0;
+      left: 0;
+      width: 30px;
+      height: 2px;
+    }
+    &::after {
+      bottom: 0;
+      left: 0;
+      width: 2px;
+      height: 30px;
+    }
+  }
+  
+  &.corner-br {
+    bottom: 10px;
+    right: 10px;
+    &::before {
+      bottom: 0;
+      right: 0;
+      width: 30px;
+      height: 2px;
+    }
+    &::after {
+      bottom: 0;
+      right: 0;
+      width: 2px;
+      height: 30px;
+    }
+  }
+}
+
+.card-icon {
+  font-size: 72px;
+  filter: drop-shadow(0 0 20px rgba(48, 220, 255, 0.5));
+  transition: all 0.3s ease;
+  
+  .science-card:hover & {
+    transform: scale(1.1);
+    filter: drop-shadow(0 0 30px rgba(48, 220, 255, 0.8));
+  }
+}
+
+.card-title {
+  font-size: 28px;
+  font-weight: bold;
+  color: #fff;
+  letter-spacing: 4px;
+  text-shadow: 0 0 20px rgba(48, 220, 255, 0.5);
+  transition: all 0.3s ease;
+  
+  .science-card:hover & {
+    color: #30dcff;
+    text-shadow: 0 0 30px rgba(48, 220, 255, 0.8);
+  }
+}
+
+.card-desc {
+  font-size: 14px;
+  color: rgba(163, 220, 222, 0.8);
+  letter-spacing: 2px;
+  text-align: center;
+  transition: all 0.3s ease;
+  
+  .science-card:hover & {
+    color: #c4f3fe;
+  }
+}
+
+.card-line {
+  width: 60%;
+  height: 2px;
+  background: linear-gradient(90deg, transparent, rgba(48, 220, 255, 0.8), transparent);
+  transition: all 0.3s ease;
+}
+
+.card-glow {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  width: 200%;
+  height: 200%;
+  background: radial-gradient(circle, rgba(48, 220, 255, 0.1) 0%, transparent 60%);
+  transform: translate(-50%, -50%);
+  opacity: 0;
+  transition: opacity 0.3s ease;
+  pointer-events: none;
+}
+</style>