BAI 2 тижнів тому
батько
коміт
8245261e91

+ 1 - 1
package.json

@@ -4,7 +4,7 @@
   "version": "1.0.0",
   "type": "module",
   "scripts": {
-    "dev": "vite --host",
+    "dev": "vite",
     "build": "vite build",
     "preview": "vite preview"
   },

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

@@ -19,6 +19,9 @@
     <div class="science-bg" v-show="state.activeIndex === '5'">
       <img src="@/assets/images/背景图.png" alt="背景图" />
     </div>
+    <div class="study-bg" v-show="state.activeIndex === '6'">
+      <img src="@/assets/images/昆山水文站.jpg" alt="昆山水文站" />
+    </div>
     <!-- 视频背景 -->
     <div class="video-background" v-if="state.showVideoPlayer">
       <video
@@ -137,6 +140,9 @@
 
       <!-- 水文科普内容 -->
       <ScienceContent v-if="state.activeIndex === '5'" />
+
+      <!-- 研学联建内容 -->
+      <StudyContent v-if="state.activeIndex === '6'" />
       <!-- 左右装饰线 -->
       <div class="large-screen-left-zsline"></div>
       <div class="large-screen-right-zsline"></div>
@@ -181,6 +187,7 @@ 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 StudyContent from "@/views/study/index.vue"
 
 import { Assets } from "./assets.js"
 import emitter from "@/utils/emitter"
@@ -389,6 +396,8 @@ function handleMenuSelect(index) {
       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 })
+    } else if (index === "6") {
+      gsap.to(".study-content .module-card", { x: 0, opacity: 1, duration: 0.5, stagger: 0.1 })
     }
   })
 }
@@ -649,7 +658,7 @@ function handleMapPlayComplete() {
   }
 }
 
-.fusion-bg, .history-bg, .culture-bg, .science-bg {
+.fusion-bg, .history-bg, .culture-bg, .science-bg, .study-bg {
   position: absolute;
   z-index: 1;
   left: 0;

+ 259 - 0
src/views/study/components/StudyBase.vue

@@ -0,0 +1,259 @@
+<template>
+  <m-card title="示范基地全景" :width="398" :height="450">
+    <div class="study-base">
+      <div class="base-tabs">
+        <div
+          class="tab-item"
+          v-for="(tab, index) in baseTabs"
+          :key="index"
+          :class="{ active: activeTab === index }"
+          @click="activeTab = index"
+        >
+          {{ tab.name }}
+        </div>
+      </div>
+      <div class="base-detail">
+        <div class="detail-thumbnail">
+          <div class="thumbnail-icon">{{ currentTabData.icon }}</div>
+          <div class="thumbnail-overlay"></div>
+        </div>
+        <div class="detail-info">
+          <div class="info-title">{{ currentTabData.title }}</div>
+          <div class="info-desc">{{ currentTabData.desc }}</div>
+          <div class="info-partner">
+            <span class="partner-label">合作单位:</span>
+            <span class="partner-value">{{ currentTabData.partner }}</span>
+          </div>
+        </div>
+      </div>
+      <div class="base-cycle">
+        <div class="cycle-title">产学研闭环流程</div>
+        <div class="cycle-container">
+          <div class="cycle-item" v-for="(item, index) in cycleData" :key="index">
+            <div class="cycle-icon">{{ item.icon }}</div>
+            <div class="cycle-label">{{ item.label }}</div>
+            <div class="cycle-desc">{{ item.desc }}</div>
+            <div class="cycle-arrow" v-if="index < cycleData.length - 1">→</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </m-card>
+</template>
+
+<script setup>
+import { ref, computed } from "vue"
+import mCard from "@/components/mCard/index.vue"
+
+const activeTab = ref(0)
+
+const baseTabs = ref([
+  { name: "实习实训区" },
+  { name: "技术研发区" },
+  { name: "决策实验室" },
+])
+
+const tabData = ref([
+  {
+    icon: "🎓",
+    title: "实习实训区",
+    desc: "为高校师生提供实践实训平台,开展水文监测、水质分析等专业技能培训,培养专业技术人才。",
+    partner: "河海大学、南京大学",
+  },
+  {
+    icon: "🔬",
+    title: "技术研发区",
+    desc: "联合高校研发面源污染预警模型,开展水文监测新技术、新设备的研发与测试。",
+    partner: "河海大学、清华大学",
+  },
+  {
+    icon: "🧠",
+    title: "决策实验室",
+    desc: "基于数字孪生技术的仿真决策平台,开展水旱灾害防御、水资源调度模拟推演。",
+    partner: "中科院地理所",
+  },
+])
+
+const currentTabData = computed(() => tabData.value[activeTab.value] || tabData.value[0])
+
+const cycleData = ref([
+  { icon: "📚", label: "学", desc: "高校师生实训" },
+  { icon: "🔬", label: "研", desc: "联合攻关模型" },
+  { icon: "🏭", label: "产", desc: "技术产品化" },
+  { icon: "💡", label: "用", desc: "业务落地应用" },
+])
+</script>
+
+<style lang="scss" scoped>
+.study-base {
+  padding: 12px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  box-sizing: border-box;
+}
+
+.base-tabs {
+  display: flex;
+  gap: 8px;
+  background: rgba(0, 180, 255, 0.05);
+  padding: 6px;
+  border-radius: 6px;
+}
+
+.tab-item {
+  flex: 1;
+  padding: 8px 10px;
+  text-align: center;
+  border-radius: 4px;
+  font-size: 10px;
+  color: rgba(255, 255, 255, 0.6);
+  cursor: pointer;
+  transition: all 0.3s ease;
+  &:hover {
+    color: rgba(255, 255, 255, 0.9);
+    background: rgba(48, 220, 255, 0.1);
+  }
+  &.active {
+    background: linear-gradient(135deg, #30DCFF, #00BFFF);
+    color: #fff;
+    box-shadow: 0 0 15px rgba(48, 220, 255, 0.4);
+  }
+}
+
+.base-detail {
+  flex: 1;
+  background: rgba(0, 180, 255, 0.05);
+  border-radius: 6px;
+  padding: 12px;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.detail-thumbnail {
+  height: 65px;
+  border-radius: 6px;
+  background: linear-gradient(135deg, rgba(48, 220, 255, 0.1) 0%, rgba(67, 233, 123, 0.1) 100%);
+  border: 1px solid rgba(48, 220, 255, 0.2);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  overflow: hidden;
+  .thumbnail-icon {
+    font-size: 36px;
+    z-index: 1;
+  }
+  .thumbnail-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: 
+      radial-gradient(circle at 30% 30%, rgba(48, 220, 255, 0.2) 0%, transparent 50%),
+      radial-gradient(circle at 70% 70%, rgba(67, 233, 123, 0.15) 0%, transparent 50%);
+  }
+}
+
+.detail-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.info-title {
+  font-size: 12px;
+  font-weight: 600;
+  color: #30DCFF;
+}
+
+.info-desc {
+  font-size: 10px;
+  color: rgba(255, 255, 255, 0.7);
+  line-height: 1.4;
+  flex: 1;
+}
+
+.info-partner {
+  background: rgba(48, 220, 255, 0.1);
+  border-radius: 4px;
+  padding: 6px 8px;
+  font-size: 10px;
+  .partner-label {
+    color: rgba(255, 255, 255, 0.5);
+  }
+  .partner-value {
+    color: #43e97b;
+    font-weight: 500;
+  }
+}
+
+.base-cycle {
+  background: rgba(0, 180, 255, 0.05);
+  border-radius: 8px;
+  padding: 10px;
+}
+
+.cycle-title {
+  font-size: 11px;
+  color: #30DCFF;
+  font-weight: 600;
+  text-align: center;
+  margin-bottom: 10px;
+}
+
+.cycle-container {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  gap: 5px;
+}
+
+.cycle-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  text-align: center;
+  position: relative;
+}
+
+.cycle-icon {
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, rgba(48, 220, 255, 0.2), rgba(67, 233, 123, 0.2));
+  border: 2px solid rgba(48, 220, 255, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 16px;
+  margin-bottom: 4px;
+}
+
+.cycle-label {
+  font-size: 12px;
+  font-weight: 600;
+  color: #30DCFF;
+  margin-bottom: 2px;
+}
+
+.cycle-desc {
+  font-size: 9px;
+  color: rgba(255, 255, 255, 0.5);
+  line-height: 1.2;
+}
+
+.cycle-arrow {
+  position: absolute;
+  right: -10px;
+  top: 12px;
+  color: rgba(48, 220, 255, 0.6);
+  font-size: 12px;
+  font-weight: bold;
+}
+</style>

+ 401 - 0
src/views/study/components/StudyEffect.vue

@@ -0,0 +1,401 @@
+<template>
+  <m-card title="研学成效数据" :width="398" :height="450">
+    <div class="study-effect">
+      <div class="effect-tabs">
+        <div
+          class="tab-item"
+          v-for="(tab, index) in effectTabs"
+          :key="index"
+          :class="{ active: activeTab === index }"
+          @click="activeTab = index"
+        >
+          {{ tab.name }}
+        </div>
+      </div>
+      <div class="effect-chart">
+        <div v-if="activeTab === 0" class="chart-line">
+          <div class="chart-title">学生能力提升 40%</div>
+          <div class="line-container">
+            <div class="line-item">
+              <div class="line-label">研学前</div>
+              <div class="line-bar-wrapper">
+                <div class="line-bar before" style="width: 50%"></div>
+              </div>
+              <div class="line-value">基础水平</div>
+            </div>
+            <div class="line-item">
+              <div class="line-label">研学后</div>
+              <div class="line-bar-wrapper">
+                <div class="line-bar after" style="width: 90%"></div>
+              </div>
+              <div class="line-value">专业水平</div>
+            </div>
+          </div>
+        </div>
+        <div v-if="activeTab === 1" class="chart-bar">
+          <div class="chart-title">项目成果成效</div>
+          <div class="bar-container">
+            <div class="bar-item">
+              <div class="bar-value">+80%</div>
+              <div class="bar-fill" style="height: 80%"></div>
+              <div class="bar-label">学生满意度</div>
+            </div>
+            <div class="bar-item">
+              <div class="bar-value">+65%</div>
+              <div class="bar-fill" style="height: 65%"></div>
+              <div class="bar-label">就业率提升</div>
+            </div>
+          </div>
+        </div>
+        <div v-if="activeTab === 2" class="chart-ring">
+          <div class="chart-title">社会影响提升</div>
+          <div class="ring-container">
+            <div class="ring-item">
+              <div class="ring-wrapper">
+                <svg width="80" height="80" viewBox="0 0 80 80">
+                  <circle cx="40" cy="40" r="35" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="6" />
+                  <circle cx="40" cy="40" r="35" fill="none" stroke="url(#gradient1)" stroke-width="6" stroke-linecap="round"
+                    stroke-dasharray="220" stroke-dashoffset="44" transform="rotate(-90 40 40)" />
+                  <defs>
+                    <linearGradient id="gradient1" x1="0%" y1="0%" x2="100%" y2="0%">
+                      <stop offset="0%" stop-color="#30DCFF" />
+                      <stop offset="100%" stop-color="#43e97b" />
+                    </linearGradient>
+                  </defs>
+                </svg>
+                <div class="ring-text">
+                  <span class="percent">+80%</span>
+                  <span class="label">社会认可度</span>
+                </div>
+              </div>
+            </div>
+            <div class="ring-item">
+              <div class="ring-wrapper">
+                <svg width="80" height="80" viewBox="0 0 80 80">
+                  <circle cx="40" cy="40" r="35" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="6" />
+                  <circle cx="40" cy="40" r="35" fill="none" stroke="url(#gradient2)" stroke-width="6" stroke-linecap="round"
+                    stroke-dasharray="220" stroke-dashoffset="33" transform="rotate(-90 40 40)" />
+                  <defs>
+                    <linearGradient id="gradient2" x1="0%" y1="0%" x2="100%" y2="0%">
+                      <stop offset="0%" stop-color="#ff6b6b" />
+                      <stop offset="100%" stop-color="#ffd93d" />
+                    </linearGradient>
+                  </defs>
+                </svg>
+                <div class="ring-text">
+                  <span class="percent">+75%</span>
+                  <span class="label">媒体报道率</span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="effect-compare">
+        <div class="compare-title">研学前后对比</div>
+        <div class="compare-container">
+          <div class="compare-card before">
+            <div class="card-label">研学前</div>
+            <div class="card-items">
+              <div class="card-item">
+                <span class="item-label">专业技能</span>
+                <span class="item-value">基础</span>
+              </div>
+              <div class="card-item">
+                <span class="item-label">实践经验</span>
+                <span class="item-value">缺乏</span>
+              </div>
+              <div class="card-item">
+                <span class="item-label">就业竞争力</span>
+                <span class="item-value">一般</span>
+              </div>
+            </div>
+          </div>
+          <div class="compare-arrow">→</div>
+          <div class="compare-card after">
+            <div class="card-label">研学后</div>
+            <div class="card-items">
+              <div class="card-item">
+                <span class="item-label">专业技能</span>
+                <span class="item-value highlight">专业</span>
+              </div>
+              <div class="card-item">
+                <span class="item-label">实践经验</span>
+                <span class="item-value highlight">丰富</span>
+              </div>
+              <div class="card-item">
+                <span class="item-label">就业竞争力</span>
+                <span class="item-value highlight">优秀</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </m-card>
+</template>
+
+<script setup>
+import { ref } from "vue"
+import mCard from "@/components/mCard/index.vue"
+
+const activeTab = ref(0)
+
+const effectTabs = ref([
+  { name: "能力提升" },
+  { name: "成果成效" },
+  { name: "社会影响" },
+])
+</script>
+
+<style lang="scss" scoped>
+.study-effect {
+  padding: 12px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  box-sizing: border-box;
+}
+
+.effect-tabs {
+  display: flex;
+  gap: 8px;
+  background: rgba(0, 180, 255, 0.05);
+  padding: 6px;
+  border-radius: 6px;
+}
+
+.tab-item {
+  flex: 1;
+  padding: 8px 10px;
+  text-align: center;
+  border-radius: 4px;
+  font-size: 10px;
+  color: rgba(255, 255, 255, 0.6);
+  cursor: pointer;
+  transition: all 0.3s ease;
+  &:hover {
+    color: rgba(255, 255, 255, 0.9);
+    background: rgba(48, 220, 255, 0.1);
+  }
+  &.active {
+    background: linear-gradient(135deg, #30DCFF, #00BFFF);
+    color: #fff;
+    box-shadow: 0 0 15px rgba(48, 220, 255, 0.4);
+  }
+}
+
+.effect-chart {
+  flex: 1;
+  background: rgba(0, 180, 255, 0.05);
+  border-radius: 6px;
+  padding: 12px;
+  display: flex;
+  flex-direction: column;
+}
+
+.chart-title {
+  font-size: 11px;
+  color: #30DCFF;
+  font-weight: 600;
+  text-align: center;
+  margin-bottom: 12px;
+}
+
+.chart-line .line-container {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.line-item {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.line-label {
+  width: 50px;
+  font-size: 10px;
+  color: rgba(255, 255, 255, 0.7);
+}
+
+.line-bar-wrapper {
+  flex: 1;
+  height: 18px;
+  background: rgba(255, 255, 255, 0.1);
+  border-radius: 9px;
+  overflow: hidden;
+}
+
+.line-bar {
+  height: 100%;
+  border-radius: 10px;
+  transition: width 1.5s ease;
+  &.before {
+    background: linear-gradient(90deg, rgba(255, 107, 107, 0.8), rgba(255, 217, 61, 0.8));
+  }
+  &.after {
+    background: linear-gradient(90deg, #30DCFF, #43e97b);
+    box-shadow: 0 0 10px rgba(48, 220, 255, 0.4);
+  }
+}
+
+.line-value {
+  width: 60px;
+  font-size: 12px;
+  font-weight: 600;
+  color: #30DCFF;
+  text-align: right;
+}
+
+.chart-bar .bar-container {
+  display: flex;
+  justify-content: space-around;
+  align-items: flex-end;
+  flex: 1;
+  padding: 0 20px;
+}
+
+.bar-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+}
+
+.bar-value {
+  font-size: 14px;
+  font-weight: 700;
+  color: #43e97b;
+}
+
+.bar-fill {
+  width: 40px;
+  background: linear-gradient(to top, #30DCFF, #43e97b);
+  border-radius: 5px 5px 0 0;
+  transition: height 1.5s ease;
+}
+
+.bar-label {
+  font-size: 10px;
+  color: rgba(255, 255, 255, 0.6);
+}
+
+.chart-ring .ring-container {
+  display: flex;
+  justify-content: space-around;
+  flex: 1;
+}
+
+.ring-item {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.ring-wrapper {
+  position: relative;
+  width: 80px;
+  height: 80px;
+}
+
+.ring-text {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  text-align: center;
+  .percent {
+    display: block;
+    font-size: 14px;
+    font-weight: 700;
+    color: #30DCFF;
+    &.down {
+      color: #ff6b6b;
+    }
+  }
+  .label {
+    display: block;
+    font-size: 9px;
+    color: rgba(255, 255, 255, 0.6);
+    margin-top: 2px;
+  }
+}
+
+.effect-compare {
+  background: rgba(0, 180, 255, 0.05);
+  border-radius: 8px;
+  padding: 10px;
+}
+
+.compare-title {
+  font-size: 11px;
+  color: #30DCFF;
+  font-weight: 600;
+  text-align: center;
+  margin-bottom: 10px;
+}
+
+.compare-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 10px;
+}
+
+.compare-card {
+  flex: 1;
+  background: rgba(255, 255, 255, 0.05);
+  border-radius: 6px;
+  padding: 8px;
+  &.before {
+    border: 1px solid rgba(255, 107, 107, 0.3);
+    .card-label {
+      color: #ff6b6b;
+    }
+  }
+  &.after {
+    border: 1px solid rgba(48, 220, 255, 0.3);
+    .card-label {
+      color: #30DCFF;
+    }
+  }
+}
+
+.card-label {
+  font-size: 10px;
+  font-weight: 600;
+  text-align: center;
+  margin-bottom: 6px;
+}
+
+.card-items {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.card-item {
+  display: flex;
+  justify-content: space-between;
+  font-size: 10px;
+  .item-label {
+    color: rgba(255, 255, 255, 0.5);
+  }
+  .item-value {
+    color: rgba(255, 255, 255, 0.8);
+    &.highlight {
+      color: #30DCFF;
+      font-weight: 600;
+    }
+  }
+}
+
+.compare-arrow {
+  font-size: 18px;
+  color: #30DCFF;
+  font-weight: bold;
+}
+</style>

+ 304 - 0
src/views/study/components/StudyIntegration.vue

@@ -0,0 +1,304 @@
+<template>
+  <m-card title="研学机制融合" :width="398" :height="450">
+    <div class="study-integration">
+      <div class="core-indicator">
+        <div class="indicator-main">
+          <span class="main-value">95</span>
+          <span class="main-unit">%</span>
+        </div>
+        <div class="indicator-label">研学项目优秀率</div>
+      </div>
+      <div class="stats-grid">
+        <div class="stat-item" v-for="(item, index) in statsData" :key="index">
+          <div class="stat-value">{{ item.value }}</div>
+          <div class="stat-label">{{ item.label }}</div>
+        </div>
+      </div>
+      <div class="architecture-flow">
+        <div class="architecture-title">架构体系</div>
+        <div class="architecture-chain">
+          <div class="arch-node">
+            <div class="node-icon">🎯</div>
+            <div class="node-label">研学发展委员会</div>
+          </div>
+          <div class="arch-arrow">→</div>
+          <div class="arch-node">
+            <div class="node-icon">👥</div>
+            <div class="node-label">研学导师团队</div>
+          </div>
+          <div class="arch-arrow">→</div>
+          <div class="arch-node">
+            <div class="node-icon">🏢</div>
+            <div class="node-label">学生实践组</div>
+          </div>
+        </div>
+        <div class="flow-title">运作流程</div>
+        <div class="flow-chain">
+          <div class="flow-item" v-for="(item, index) in flowData" :key="index" :class="{ active: index === currentStep }">
+            <div class="flow-number">{{ index + 1 }}</div>
+            <div class="flow-label">{{ item.label }}</div>
+          </div>
+        </div>
+      </div>
+      <div class="action-buttons">
+        <button class="action-btn primary" @click="showTeamDetail">
+          <span>📋</span> 查看导师团队详情
+        </button>
+        <button class="action-btn secondary" @click="showOfficeScene">
+          <span>🏢</span> 查看实践基地
+        </button>
+      </div>
+    </div>
+  </m-card>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted } from "vue"
+import mCard from "@/components/mCard/index.vue"
+
+const currentStep = ref(1)
+
+const statsData = ref([
+  { value: "56", label: "研学课程数量" },
+  { value: "12", label: "合作高校" },
+  { value: "8", label: "实践基地" },
+  { value: "3200", label: "参与学生数" },
+])
+
+const flowData = ref([
+  { label: "课程设计" },
+  { label: "导师培训" },
+  { label: "学生实践" },
+])
+
+let stepInterval = null
+
+onMounted(() => {
+  startStepAnimation()
+})
+
+onUnmounted(() => {
+  if (stepInterval) clearInterval(stepInterval)
+})
+
+function startStepAnimation() {
+  stepInterval = setInterval(() => {
+    currentStep.value = (currentStep.value + 1) % flowData.value.length
+  }, 3000)
+}
+
+function showTeamDetail() {
+  console.log("查看导师团队详情")
+}
+
+function showOfficeScene() {
+  console.log("查看实践基地")
+}
+</script>
+
+<style lang="scss" scoped>
+.study-integration {
+  padding: 12px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  box-sizing: border-box;
+}
+
+.core-indicator {
+  text-align: center;
+  padding: 12px;
+  background: linear-gradient(135deg, rgba(48, 220, 255, 0.1) 0%, rgba(0, 191, 255, 0.05) 100%);
+  border-radius: 8px;
+  border: 1px solid rgba(48, 220, 255, 0.3);
+}
+
+.indicator-main {
+  margin-bottom: 5px;
+}
+
+.main-value {
+  font-size: 40px;
+  font-weight: 700;
+  color: #30DCFF;
+  font-family: "D-DIN";
+  text-shadow: 0 0 20px rgba(48, 220, 255, 0.5);
+}
+
+.main-unit {
+  font-size: 20px;
+  color: rgba(48, 220, 255, 0.8);
+  font-weight: 600;
+}
+
+.indicator-label {
+  color: rgba(255, 255, 255, 0.8);
+  font-size: 12px;
+  letter-spacing: 1px;
+}
+
+.stats-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 6px;
+}
+
+.stat-item {
+  background: rgba(0, 180, 255, 0.08);
+  border: 1px solid rgba(0, 180, 255, 0.2);
+  border-radius: 4px;
+  padding: 6px 4px;
+  text-align: center;
+  transition: all 0.3s ease;
+  &:hover {
+    background: rgba(0, 180, 255, 0.15);
+    border-color: rgba(48, 220, 255, 0.5);
+  }
+}
+
+.stat-value {
+  font-size: 16px;
+  font-weight: 700;
+  color: #30DCFF;
+  font-family: "D-DIN";
+  margin-bottom: 2px;
+}
+
+.stat-label {
+  font-size: 9px;
+  color: rgba(255, 255, 255, 0.6);
+  line-height: 1.2;
+}
+
+.architecture-flow {
+  flex: 1;
+  background: rgba(0, 180, 255, 0.05);
+  border-radius: 6px;
+  padding: 8px;
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.architecture-title,
+.flow-title {
+  font-size: 11px;
+  color: #30DCFF;
+  font-weight: 600;
+  padding-bottom: 4px;
+  border-bottom: 1px solid rgba(48, 220, 255, 0.2);
+}
+
+.architecture-chain {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 4px;
+  padding: 6px 0;
+}
+
+.arch-node {
+  background: rgba(48, 220, 255, 0.15);
+  border: 1px solid rgba(48, 220, 255, 0.4);
+  border-radius: 6px;
+  padding: 6px 8px;
+  text-align: center;
+  min-width: 80px;
+}
+
+.node-icon {
+  font-size: 16px;
+  margin-bottom: 2px;
+}
+
+.node-label {
+  font-size: 9px;
+  color: rgba(255, 255, 255, 0.9);
+}
+
+.arch-arrow {
+  color: #30DCFF;
+  font-size: 14px;
+  font-weight: bold;
+}
+
+.flow-chain {
+  display: flex;
+  justify-content: space-around;
+  padding: 6px 0;
+}
+
+.flow-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+  opacity: 0.5;
+  transition: all 0.3s ease;
+  &.active {
+    opacity: 1;
+    .flow-number {
+      background: linear-gradient(135deg, #30DCFF, #00BFFF);
+      box-shadow: 0 0 15px rgba(48, 220, 255, 0.6);
+    }
+  }
+}
+
+.flow-number {
+  width: 20px;
+  height: 20px;
+  border-radius: 50%;
+  background: rgba(255, 255, 255, 0.2);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 11px;
+  font-weight: 700;
+  color: #fff;
+  transition: all 0.3s ease;
+}
+
+.flow-label {
+  font-size: 11px;
+  color: rgba(255, 255, 255, 0.8);
+}
+
+.action-buttons {
+  display: flex;
+  gap: 10px;
+}
+
+.action-btn {
+  flex: 1;
+  padding: 10px 12px;
+  border-radius: 6px;
+  border: none;
+  cursor: pointer;
+  font-size: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 5px;
+  transition: all 0.3s ease;
+  &.primary {
+    background: linear-gradient(135deg, #30DCFF, #00BFFF);
+    color: #fff;
+    &:hover {
+      box-shadow: 0 0 15px rgba(48, 220, 255, 0.5);
+      transform: translateY(-2px);
+    }
+  }
+  &.secondary {
+    background: rgba(48, 220, 255, 0.15);
+    border: 1px solid rgba(48, 220, 255, 0.4);
+    color: #30DCFF;
+    &:hover {
+      background: rgba(48, 220, 255, 0.25);
+    }
+  }
+  span {
+    font-size: 14px;
+  }
+}
+</style>

+ 335 - 0
src/views/study/components/StudyProcess.vue

@@ -0,0 +1,335 @@
+<template>
+  <m-card title="研学协同流程" :width="398">
+    <div class="study-process">
+      <div class="process-tabs">
+        <div
+          class="tab-item"
+          v-for="(tab, index) in processTabs"
+          :key="index"
+          :class="{ active: activeTab === index }"
+          @click="activeTab = index"
+        >
+          {{ tab.name }}
+        </div>
+      </div>
+      <div class="process-flow">
+        <div class="flow-container">
+          <div
+            class="flow-node"
+            v-for="(node, index) in currentProcessData"
+            :key="index"
+            :class="{ active: index === currentStep }"
+          >
+            <div class="node-thumbnail">
+              <div class="thumbnail-icon">{{ node.icon }}</div>
+            </div>
+            <div class="node-content">
+              <div class="node-title">{{ node.title }}</div>
+              <div class="node-desc">{{ node.desc }}</div>
+            </div>
+            <div class="node-connector" v-if="index < currentProcessData.length - 1">
+              <div class="connector-line"></div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="process-actions">
+        <button class="action-btn primary" @click="showAnimation">
+          <span>🎬</span> 查看完整流程动画
+        </button>
+        <button class="action-btn secondary" @click="jumpToScene">
+          <span>🔗</span> 跳转到实践场景
+        </button>
+      </div>
+    </div>
+  </m-card>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted } from "vue"
+import mCard from "@/components/mCard/index.vue"
+
+const activeTab = ref(0)
+const currentStep = ref(0)
+
+const processTabs = ref([
+  { name: "研学课程" },
+  { name: "实践活动" },
+  { name: "成果展示" },
+])
+
+const processData = ref({
+  0: [
+    {
+      icon: "📚",
+      title: "课程设计",
+      desc: "根据水文站实际情况,设计专业研学课程",
+    },
+    {
+      icon: "👨‍🏫",
+      title: "导师培训",
+      desc: "对研学导师进行专业知识和教学方法培训",
+    },
+    {
+      icon: "🎯",
+      title: "课程实施",
+      desc: "在水文站现场开展理论与实践相结合的教学",
+    },
+    {
+      icon: "📝",
+      title: "学习评估",
+      desc: "对学生学习成果进行评估和反馈",
+    },
+    {
+      icon: "📄",
+      title: "证书颁发",
+      desc: "为完成研学课程的学生颁发结业证书",
+    },
+  ],
+  1: [
+    {
+      icon: "🔬",
+      title: "现场参观",
+      desc: "参观水文站设施,了解水文监测设备",
+    },
+    {
+      icon: "📊",
+      title: "数据采集",
+      desc: "在导师指导下进行实际水文数据采集",
+    },
+    {
+      icon: "💻",
+      title: "数据分析",
+      desc: "学习使用专业软件分析水文数据",
+    },
+    {
+      icon: "🌊",
+      title: "水文实验",
+      desc: "开展水文相关的小型实验活动",
+    },
+    {
+      icon: "🤝",
+      title: "交流分享",
+      desc: "与水文站工作人员交流学习心得",
+    },
+  ],
+  2: [
+    {
+      icon: "📋",
+      title: "成果整理",
+      desc: "整理研学过程中的学习笔记和实验数据",
+    },
+    {
+      icon: "📊",
+      title: "报告撰写",
+      desc: "撰写研学报告,总结学习成果和收获",
+    },
+    {
+      icon: "🎨",
+      title: "成果展示",
+      desc: "制作展板或PPT,展示研学成果",
+    },
+    {
+      icon: "🏆",
+      title: "成果评选",
+      desc: "组织研学成果评选活动,表彰优秀学生",
+    },
+    {
+      icon: "📚",
+      title: "成果存档",
+      desc: "将优秀研学成果存档,作为教学资源",
+    },
+  ],
+})
+
+const currentProcessData = computed(() => processData.value[activeTab.value] || [])
+
+let stepInterval = null
+
+onMounted(() => {
+  startStepAnimation()
+})
+
+onUnmounted(() => {
+  if (stepInterval) clearInterval(stepInterval)
+})
+
+function startStepAnimation() {
+  stepInterval = setInterval(() => {
+    currentStep.value = (currentStep.value + 1) % currentProcessData.value.length
+  }, 4000)
+}
+
+function showAnimation() {
+  console.log("查看完整流程动画")
+}
+
+function jumpToScene() {
+  console.log("跳转到实践场景")
+}
+</script>
+
+<style lang="scss" scoped>
+.study-process {
+  padding: 12px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  box-sizing: border-box;
+}
+
+.process-tabs {
+  display: flex;
+  gap: 8px;
+  background: rgba(0, 180, 255, 0.05);
+  padding: 6px;
+  border-radius: 6px;
+}
+
+.tab-item {
+  flex: 1;
+  padding: 8px 10px;
+  text-align: center;
+  border-radius: 4px;
+  font-size: 11px;
+  color: rgba(255, 255, 255, 0.6);
+  cursor: pointer;
+  transition: all 0.3s ease;
+  &:hover {
+    color: rgba(255, 255, 255, 0.9);
+    background: rgba(48, 220, 255, 0.1);
+  }
+  &.active {
+    background: linear-gradient(135deg, #30DCFF, #00BFFF);
+    color: #fff;
+    box-shadow: 0 0 15px rgba(48, 220, 255, 0.4);
+  }
+}
+
+.process-flow {
+  flex: 1;
+  background: rgba(0, 180, 255, 0.05);
+  border-radius: 6px;
+  padding: 12px;
+  overflow-y: auto;
+}
+
+.flow-container {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.flow-node {
+  display: flex;
+  align-items: flex-start;
+  gap: 10px;
+  padding: 10px;
+  background: rgba(48, 220, 255, 0.05);
+  border-radius: 6px;
+  border: 1px solid rgba(48, 220, 255, 0.1);
+  transition: all 0.3s ease;
+  position: relative;
+  &.active {
+    background: rgba(48, 220, 255, 0.15);
+    border-color: rgba(48, 220, 255, 0.5);
+    box-shadow: 0 0 10px rgba(48, 220, 255, 0.2);
+    .node-thumbnail {
+      background: linear-gradient(135deg, #30DCFF, #00BFFF);
+      box-shadow: 0 0 15px rgba(48, 220, 255, 0.5);
+    }
+    .node-title {
+      color: #30DCFF;
+    }
+  }
+}
+
+.node-thumbnail {
+  width: 40px;
+  height: 40px;
+  border-radius: 8px;
+  background: rgba(255, 255, 255, 0.1);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  transition: all 0.3s ease;
+  .thumbnail-icon {
+    font-size: 20px;
+  }
+}
+
+.node-content {
+  flex: 1;
+  min-width: 0;
+}
+
+.node-title {
+  font-size: 13px;
+  font-weight: 600;
+  color: rgba(255, 255, 255, 0.9);
+  margin-bottom: 4px;
+  transition: all 0.3s ease;
+}
+
+.node-desc {
+  font-size: 11px;
+  color: rgba(255, 255, 255, 0.5);
+  line-height: 1.4;
+}
+
+.node-connector {
+  position: absolute;
+  left: 21px;
+  bottom: -12px;
+  width: 2px;
+  height: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  .connector-line {
+    width: 2px;
+    height: 100%;
+    background: linear-gradient(to bottom, rgba(48, 220, 255, 0.5), rgba(48, 220, 255, 0.1));
+  }
+}
+
+.process-actions {
+  display: flex;
+  gap: 10px;
+}
+
+.action-btn {
+  flex: 1;
+  padding: 10px 12px;
+  border-radius: 6px;
+  border: none;
+  cursor: pointer;
+  font-size: 11px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 5px;
+  transition: all 0.3s ease;
+  &.primary {
+    background: linear-gradient(135deg, #30DCFF, #00BFFF);
+    color: #fff;
+    &:hover {
+      box-shadow: 0 0 15px rgba(48, 220, 255, 0.5);
+      transform: translateY(-2px);
+    }
+  }
+  &.secondary {
+    background: rgba(48, 220, 255, 0.15);
+    border: 1px solid rgba(48, 220, 255, 0.4);
+    color: #30DCFF;
+    &:hover {
+      background: rgba(48, 220, 255, 0.25);
+    }
+  }
+  span {
+    font-size: 14px;
+  }
+}
+</style>

+ 124 - 0
src/views/study/index.vue

@@ -0,0 +1,124 @@
+<template>
+  <div class="study-content">
+    <div class="left-column">
+      <div class="left-column-3d">
+        <div class="module-card">
+          <StudyIntegration />
+        </div>
+        <div class="module-card">
+          <StudyEffect />
+        </div>
+      </div>
+    </div>
+    <div class="right-column">
+      <div class="right-column-3d">
+        <div class="module-card full-height">
+          <StudyProcess />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import StudyIntegration from "./components/StudyIntegration.vue"
+import StudyProcess from "./components/StudyProcess.vue"
+import StudyEffect from "./components/StudyEffect.vue"
+import StudyBase from "./components/StudyBase.vue"
+</script>
+
+<style lang="scss">
+.study-content {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 2;
+  pointer-events: none;
+}
+
+.left-column {
+  position: absolute;
+  z-index: 4;
+  width: 398px;
+  left: 32px;
+  top: 150px;
+  bottom: 50px;
+  perspective: 500px;
+  perspective-origin: 50% 50%;
+  .left-column-3d {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    gap: 20px;
+    transform: translate3d(0px, 0px, 0px) scaleX(1) scaleY(1) rotateX(0deg) rotateY(6deg) rotateZ(0deg) skewX(0deg) skewY(0deg);
+    z-index: 4;
+  }
+  .module-card {
+    height: 450px;
+    background: rgba(0, 20, 40, 0.8);
+    border: 1px solid rgba(48, 220, 255, 0.3);
+    border-radius: 10px;
+    box-shadow: 0 0 20px rgba(48, 220, 255, 0.1);
+    transform: translateX(-150%);
+    opacity: 0;
+    &:first-child {
+      margin-top: -50px;
+    }
+  }
+}
+
+.right-column {
+  position: absolute;
+  z-index: 4;
+  width: 398px;
+  right: 32px;
+  top: 150px;
+  bottom: 50px;
+  perspective: 800px;
+  perspective-origin: 50% 50%;
+  .right-column-3d {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    transform: translate3d(0px, 0px, 0px) scaleX(1) scaleY(1) rotateX(0deg) rotateY(-6deg) rotateZ(0deg) skewX(0deg) skewY(0deg);
+  }
+  .module-card {
+    height: 450px;
+    background: rgba(0, 20, 40, 0.8);
+    border: 1px solid rgba(48, 220, 255, 0.3);
+    border-radius: 10px;
+    box-shadow: 0 0 20px rgba(48, 220, 255, 0.1);
+    transform: translateX(150%);
+    opacity: 0;
+    &:first-child {
+      margin-top: -30px;
+    }
+    &.full-height {
+      height: 100%;
+      margin-top: -30px;
+      display: flex;
+      flex-direction: column;
+      .m-card {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        .m-card-bd {
+          flex: 1;
+        }
+      }
+    }
+  }
+}
+</style>

+ 3 - 0
vite.config.js

@@ -22,6 +22,9 @@ export default ({ mode }) => {
   const env = loadEnv(mode, process.cwd());
   return defineConfig({
     base: './', //env.VITE_APP_BASE_URL, // 动态改变base值
+    server: {
+      port: 8080,
+    },
     assetsInclude: [
       "**/*.glb",
       "**/*.gltf",