Browse Source

添加净通量和当年浓度页面,为采样添加动画说明

yangtaodemon 3 months ago
parent
commit
d38405d3e7

BIN
src/assets/videos/地下渗漏.mp4


BIN
src/assets/videos/地表径流.mp4


BIN
src/assets/videos/干湿沉降.mp4


BIN
src/assets/videos/秸秆移除和籽粒移除.mp4


+ 129 - 2
src/views/User/HmOutFlux/atmosDeposition/AtmosDepositionSamplingDesc.vue

@@ -101,6 +101,23 @@
         </div>
       </div>
     </div>
+      <!-- 新增第四部分:采样视频模块 -->
+    <div class="content-section">
+      <div class="section-header">
+        <div class="step-number">4</div>
+        <h2>采样过程视频演示</h2>
+      </div>
+      
+      <div class="video-section">
+        <div class="video-container">
+          <video controls class="sampling-video">
+            <source src='@/assets/videos/干湿沉降.mp4' type="video/mp4">
+            您的浏览器不支持HTML5视频播放。
+          </video>
+          <p class="video-caption">视频 大气污染物采样过程演示</p>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
@@ -266,9 +283,9 @@ p {
   line-height: 1.6;
 }
 
-/* 紧凑图片容器 */
+/* 紧凑图片容器 - 尺寸调小 */
 .compact-image-container {
-  max-width: 700px;
+  max-width: 600px; /* 从700px调整为600px */
   margin: 0 auto;
   margin-top: 25px;
   border-radius: 12px;
@@ -290,6 +307,7 @@ p {
   transition: transform 0.5s ease;
   background: rgba(245, 249, 255, 0.4);
   object-fit: cover;
+  max-height: 400px; /* 添加最大高度限制 */
 }
 
 .compact-image-container:hover .sampling-image {
@@ -307,6 +325,91 @@ p {
   border-top: 1px dashed #cbd5e0;
 }
 
+/* 新增视频模块样式 */
+.video-section {
+  display: flex;
+  flex-direction: column;
+  gap: 30px;
+  margin-top: 20px;
+}
+
+.video-container {
+  border-radius: 12px;
+  overflow: hidden;
+  position: relative;
+  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+  max-width: 800px;
+  margin: 0 auto;
+  transition: all 0.4s ease;
+}
+
+.video-container:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+}
+
+.sampling-video {
+  width: 100%;
+  height: auto;
+  display: block;
+  background: #f8fafc;
+  min-height: 400px;
+}
+
+.video-caption {
+  text-align: center;
+  font-size: 1.1rem;
+  color: #1a365d;
+  padding: 15px;
+  font-weight: 600;
+  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
+  margin: 0;
+  border-top: 1px dashed #cbd5e0;
+  position: relative;
+}
+
+.video-caption::before {
+  content: "📹";
+  position: absolute;
+  left: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+}
+
+.video-description {
+  background: rgba(245, 249, 255, 0.6);
+  border-radius: 12px;
+  padding: 20px;
+  border-left: 4px solid #3acfd5;
+}
+
+.video-description h3 {
+  color: #1a365d;
+  margin-top: 0;
+  margin-bottom: 15px;
+  font-size: 1.4rem;
+}
+
+.video-description ul {
+  padding-left: 25px;
+  margin: 15px 0;
+}
+
+.video-description li {
+  margin-bottom: 10px;
+  line-height: 1.7;
+  position: relative;
+}
+
+.video-description li::before {
+  content: "•";
+  color: #3acfd5;
+  font-size: 1.2rem;
+  position: absolute;
+  left: -20px;
+  top: 0;
+}
+
 /* 响应式设计 */
 @media (max-width: 900px) {
   .sampling-process {
@@ -320,6 +423,11 @@ p {
   .analysis-methods {
     flex-direction: column;
   }
+  
+  /* 视频模块在平板上的调整 */
+  .video-section {
+    flex-direction: column;
+  }
 }
 
 @media (max-width: 768px) {
@@ -340,6 +448,11 @@ p {
   .compact-image-container {
     max-width: 90%;
   }
+  
+  /* 视频在手机上的调整 */
+  .sampling-video {
+    min-height: 300px;
+  }
 }
 
 @media (max-width: 480px) {
@@ -374,5 +487,19 @@ p {
     margin-bottom: 15px;
     text-align: center;
   }
+  
+  /* 视频在手机上的进一步调整 */
+  .sampling-video {
+    min-height: 250px;
+  }
+  
+  .video-caption {
+    font-size: 1rem;
+    padding: 10px 15px 10px 35px;
+  }
+  
+  .video-caption::before {
+    left: 10px;
+  }
 }
 </style>

+ 0 - 1
src/views/User/HmOutFlux/atmosDeposition/heavyMetalEnterprise.vue

@@ -29,7 +29,6 @@
           <h2 class="section-title">涉重企业数据列表</h2>
           <div class="data-info">
             <span class="info-item">数据更新时间: 2025-08-16</span>
-            <span class="info-item">当前显示: 42家企业</span>
           </div>
         </div>
         <div class="data-card">

+ 478 - 105
src/views/User/cadmiumPrediction/currentYearConcentration.vue

@@ -1,142 +1,515 @@
 <template>
-  <div class="cd-concentration-flux-container">
-    <el-card class="gradient-card" shadow="hover">
-      <h2 class="card-title">标题</h2>
-      <el-row :gutter="20">
-        <el-col :span="12">
-          <p class="label">初始 CD 格浓度 (mg/kg)</p>
-          <el-input
-            v-model="initialCdConcentration"
-            placeholder="请输入内容"
-            class="custom-input"
-          />
-        </el-col>
-        <el-col :span="12">
-          <p class="label">净通量 (g/ha/a)</p>
-          <el-input
-            v-model="netFlux"
-            placeholder="请输入内容"
-            class="custom-input"
-          />
-        </el-col>
-         <el-col :span="12">
-          <p class="label">标题</p>
-          <el-input
-            v-model="netFlux"
-            placeholder="2000"
-            class="custom-input"
-          />
-        </el-col>
-        <el-col :span="24" style="margin-top: 20px;">
-          <el-button class="calculate-btn" @click="onCalculate">计算</el-button>
-        </el-col>
-      </el-row>
-    </el-card>
+  <div class="container">
+    <!-- 顶部操作栏 -->
+    <div class="toolbar">
+      
+      <!-- 操作按钮 -->
+      <div class="action-buttons">
+        <el-button class="custom-button" :disabled="!mapBlob" @click="exportMap">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出地图</el-button>
+        <el-button class="custom-button" :disabled="!histogramBlob" @click="exportHistogram">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出直方图</el-button>
+        <el-button class="custom-button" :disabled="!statisticsData.length" @click="exportData">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出数据</el-button>
+      </div>
+    </div>
+
+    <!-- 主体内容区 -->
+    <div class="content-area">
+      <!-- 地图区域 - 修改为横向布局 -->
+      <div class="horizontal-container">
+        <!-- 地图展示 -->
+        <div class="map-section">
+          <h3>Cd当年浓度空间分布图</h3>
+          <div v-if="loadingMap" class="loading-container">
+            <el-icon class="loading-icon"><Loading /></el-icon>
+            <span>地图加载中...</span>
+          </div>
+          <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="Cd当年浓度空间分布图" class="map-image">
+          <div v-if="!mapImageUrl && !loadingMap" class="no-data">
+            <el-icon><Picture /></el-icon>
+            <p>暂无地图数据</p>
+          </div>
+        </div>
+        
+        <!-- 直方图展示 -->
+        <div class="histogram-section">
+          <h3>Cd当年浓度直方图</h3>
+          <div v-if="loadingHistogram" class="loading-container">
+            <el-icon class="loading-icon"><Loading /></el-icon>
+            <span>直方图加载中...</span>
+          </div>
+          <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="Cd当年浓度直方图" class="histogram-image">
+          <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
+            <el-icon><Histogram /></el-icon>
+            <p>暂无直方图数据</p>
+          </div>
+        </div>
+      </div>
+
+      <!-- 统计图表区域 -->
+      <div class="stats-area">
+        <h3>Cd当年浓度统计信息</h3>
+        <div class="model-info">
+          <el-tag type="info">Cd通量模型</el-tag>
+          <span class="update-time">
+            最后更新: {{ updateTime ? new Date(updateTime).toLocaleString() : '未知' }}
+          </span>
+        </div>
+        
+        <div v-if="loadingStats" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>统计数据加载中...</span>
+        </div>
+        
+        <div v-if="!loadingStats && statisticsData.length" class="stats-container">
+          <!-- 统计表格 -->
+         <el-table 
+            :data="statisticsData" 
+            style="width: 100%; margin-bottom: 20px;"
+            border
+            stripe
+          >
+            <el-table-column prop="name" label="统计项" min-width="180" />
+            <el-table-column prop="value" label="值" min-width="150" />
+            <el-table-column prop="unit" label="单位" min-width="100" />
+            <el-table-column prop="description" label="描述" min-width="200" />
+          </el-table>
+          
+        
+        <div v-if="!loadingStats && !statisticsData.length" class="no-data">
+          <el-icon><DataAnalysis /></el-icon>
+          <p>暂无统计数据</p>
+        </div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
-<script setup>
-import { ref } from 'vue';
-import { ElCard, ElRow, ElCol, ElInput, ElButton } from 'element-plus';
+<script>
+import * as XLSX from 'xlsx';
+import { saveAs } from 'file-saver';
+import axios from 'axios';
+import * as echarts from 'echarts';
+import { 
+  Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+} from '@element-plus/icons-vue';
+
+export default {
+  name: 'CdFluxVisualization',
+  components: { 
+    Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+  },
+  data() {
+    return {
+      isCalculating: false,
+      loadingMap: false,
+      loadingHistogram: false,
+      loadingStats: false,
+      statisticsData: [],
+      mapImageUrl: null,
+      histogramImageUrl: null,
+      mapBlob: null,
+      histogramBlob: null,
+      selectedFile: null,
+      distributionChart: null,
+      updateTime: null
+    };
+  },
+
+  mounted() {
+    // 组件挂载时获取最新数据
+    this.fetchLatestResults();
+    this.fetchStatistics();
+  },
 
-const initialCdConcentration = ref('');
-const netFlux = ref('');
+  beforeDestroy() {
+    if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
+    if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
+    if (this.distributionChart) this.distributionChart.dispose();
+  },
+  methods: {
+    // 触发文件选择
+    triggerFileUpload() {
+      this.$refs.fileInput.click();
+    },
+    
+    // 处理文件上传
+    handleFileUpload(event) {
+      const files = event.target.files;
+      if (files && files.length > 0) {
+        this.selectedFile = files[0];
+      } else {
+        this.selectedFile = null;
+      }
+    },
+    
+    // 获取最新结果
+    async fetchLatestResults() {
+      try {
+        this.loadingMap = true;
+        this.loadingHistogram = true;
+        
+        // 获取最新地图
+        await this.fetchLatestMap();
+        
+        // 获取最新直方图
+        await this.fetchLatestHistogram();
+        
+      } catch (error) {
+        console.error('获取最新结果失败:', error);
+        this.$message.error('获取最新结果失败');
+      } finally {
+        this.loadingMap = false;
+        this.loadingHistogram = false;
+      }
+    },
+    
+    // 获取最新地图
+    async fetchLatestMap() {
+      try {
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/end-cd/map`,
+          { responseType: 'blob' }
+        );
+        
+        this.mapBlob = response.data;
+        this.mapImageUrl = URL.createObjectURL(this.mapBlob);
+      } catch (error) {
+        console.error('获取最新地图失败:', error);
+        this.$message.warning('获取最新地图失败,请先执行预测');
+      }
+    },
+    
+    // 获取最新直方图
+    async fetchLatestHistogram() {
+      try {
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/end-cd/histogram`,
+          { responseType: 'blob' }
+        );
+        
+        this.histogramBlob = response.data;
+        this.histogramImageUrl = URL.createObjectURL(this.histogramBlob);
+      } catch (error) {
+        console.error('获取最新直方图失败:', error);
+        this.$message.warning('获取最新直方图失败,请先执行预测');
+      }
+    },
+    
+    // 格式化统计数据
+    formatStatisticsData(stats) {
+      if (!stats) return [];
+      
+      return [
+        { name: '最小值', value: stats.min.toFixed(4), unit: 'mg/kg', description: '样本中的最小Cd当年浓度' },
+        { name: '最大值', value: stats.max.toFixed(4), unit: 'mg/kg', description: '样本中的最大Cd当年浓度' },
+        { name: '平均值', value: stats.mean.toFixed(4), unit: 'mg/kg', description: '所有样本的平均Cd当年浓度' },
+        { name: '标准差', value: stats.std.toFixed(4), unit: 'mg/kg', description: 'Cd当年浓度的标准差' },
+      ];
+    },
 
-const onCalculate = () => {
-  // 暂无计算逻辑,仅作展示
-  alert('计算按钮已点击');
+    // 修改fetchStatistics方法
+    async fetchStatistics() {
+      try {
+        this.loadingStats = true;
+        
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/end-cd/statistics`
+        );
+        
+        if (response.data) {
+          const stats = response.data;
+          this.statisticsData = this.formatStatisticsData(stats);
+          this.updateTime = new Date().toISOString();
+          
+          this.$nextTick(() => {
+            this.initCharts(stats);
+          });
+        }
+      } catch (error) {
+        console.error('获取统计信息失败:', error);
+        this.$message.warning('获取统计信息失败');
+      } finally {
+        this.loadingStats = false;
+      }
+    },
+    
+    // 处理窗口大小变化
+    handleResize() {
+      if (this.distributionChart) this.distributionChart.resize();
+    },
+    
+    // 上传并计算
+    async calculate() {
+      if (!this.selectedFile) {
+        this.$message.warning('请先选择CSV文件');
+        return;
+      }
+      
+      try {
+        this.isCalculating = true;
+        this.loadingMap = true;
+        this.loadingHistogram = true;
+        this.loadingStats = true;
+        
+        // 创建FormData
+        const formData = new FormData();
+        formData.append('csv_file', this.selectedFile);
+        
+        // 调用Cd通量计算接口
+        await axios.post(
+          'http://localhost:8000/api/cd-flux/end-cd/calculate',
+          formData,
+          {
+            headers: {
+              'Content-Type': 'multipart/form-data'
+            }
+          }
+        );
+        
+        // 更新后重新获取地图、直方图和统计数据
+        await this.fetchLatestResults();
+        await this.fetchStatistics();
+        
+        this.$message.success('计算完成!');
+        
+      } catch (error) {
+        console.error('计算失败:', error);
+        let errorMessage = '计算失败,请重试';
+        
+        if (error.response) {
+          if (error.response.status === 400) {
+            errorMessage = '文件格式错误:' + (error.response.data.detail || '请上传正确的CSV文件');
+          } else if (error.response.status === 500) {
+            errorMessage = '服务器错误:' + (error.response.data.detail || '请稍后重试');
+          }
+        }
+        
+        this.$message.error(errorMessage);
+      } finally {
+        this.isCalculating = false;
+        this.loadingMap = false;
+        this.loadingHistogram = false;
+        this.loadingStats = false;
+      }
+    },
+    
+    // 导出地图
+    exportMap() {
+      if (!this.mapBlob) {
+        this.$message.warning('请先计算生成地图');
+        return;
+      }
+      
+      const link = document.createElement('a');
+      link.href = URL.createObjectURL(this.mapBlob);
+      link.download = `Cd当年浓度空间分布图.jpg`;
+      link.click();
+      URL.revokeObjectURL(link.href);
+    },
+    
+    // 导出直方图
+    exportHistogram() {
+      if (!this.histogramBlob) {
+        this.$message.warning('请先计算生成直方图');
+        return;
+      }
+      
+      const link = document.createElement('a');
+      link.href = URL.createObjectURL(this.histogramBlob);
+      link.download = `Cd当年浓度直方图.jpg`;
+      link.click();
+      URL.revokeObjectURL(link.href);
+    },
+    
+    // 导出数据
+    async exportData() {
+      try {
+        this.$message.info('正在获取Cd当年浓度数据...');
+        
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/end-cd/export-csv`,
+          { responseType: 'blob' }
+        );
+        
+        const blob = new Blob([response.data], { type: 'text/csv' });
+        const link = document.createElement('a');
+        link.href = URL.createObjectURL(blob);
+        link.download = `Cd当年浓度数据.csv`;
+        link.click();
+        URL.revokeObjectURL(link.href);
+        
+        this.$message.success('数据导出成功');
+      } catch (error) {
+        console.error('导出数据失败:', error);
+        this.$message.error('导出数据失败: ' + (error.response?.data?.detail || '请稍后重试'));
+      }
+    }
+  }
 };
 </script>
 
 <style scoped>
-.cd-concentration-flux-container {
-  display: flex;
-  justify-content: center;
-  align-items: center;
+/* 保持原有样式不变 */
+.container {
   padding: 20px;
-}
-
-.gradient-card {
-  /* 半透明渐变背景 */
+  /* 添加70%透明度的渐变背景 */
   background: linear-gradient(
     135deg, 
-    rgba(250, 253, 255, 0.8), 
-    rgba(137, 223, 252, 0.8)
+    rgba(230, 247, 255, 0.7) 0%, 
+    rgba(240, 248, 255, 0.7) 100%
   );
-  border-radius: 12px;
-  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
-  padding: 30px;
-  text-align: left; /* 改为左对齐 */
-  width: 600px;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
-  border: none;
+  min-height: 100vh;
+  box-sizing: border-box;
 }
 
-.card-title {
-  font-weight: bold;
-  font-size: 24px;
+.toolbar {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
   margin-bottom: 20px;
-  color: #333;
-  text-align: center;
+  padding: 15px;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
-.label {
+.upload-section {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1); /* 调整边框透明度 */
+}
+
+.file-name {
+  flex: 1;
+  padding: 0 10px;
+  color: #666;
+  font-size: 14px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 10px;
+}
+
+.custom-button {
+  background-color: #47C3B9 !important;
+  color: #DCFFFA !important;
+  border: none;
+  border-radius: 155px;
+  padding: 10px 20px;
   font-weight: bold;
-  font-size: 18px;
-  margin-bottom: 10px; /* 减少底部外边距 */
-  color: #333;
+  display: flex;
+  align-items: center;
+}
+
+.upload-icon {
+  margin-right: 5px;
 }
 
-.custom-input {
+.content-area {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+/* 横向布局容器 */
+.horizontal-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
   width: 100%;
-  max-width: 200px;
-  margin-left: 0; /* 确保输入框靠左对齐 */
 }
 
-/* 自定义输入框样式 */
-:deep(.custom-input .el-input__inner) {
-  background: rgba(255, 255, 255, 0.7);
+.map-section, .histogram-section {
+  flex: 1;
+  min-width: 300px;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
-  border: 1px solid #dcdfe6;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
-  padding: 10px 15px;
-  font-size: 16px;
-  color: #333;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  position: relative;
+  min-height: 400px;
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
-:deep(.custom-input .el-input__inner:focus) {
-  border-color: #409EFF;
-  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+.map-image, .histogram-image {
+  width: 100%;
+  height: 100%;
+  max-height: 600px;
+  object-fit: contain;
+  border-radius: 4px;
 }
 
-.calculate-btn {
+.table-area {
   width: 100%;
-  max-width: 200px;
-  height: 50px;
-  border: none;
-  border-radius: 25px !important;
-  font-size: 18px;
-  font-weight: bold;
-  transition: all 0.4s ease;
-  
-  /* 渐变背景色 */
-  background: linear-gradient(to right, #8DF9F0, #26B046);
-  color: white !important;
-  /* 按钮整体阴影 */
-  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15),
-              0 2px 6px rgba(38, 176, 70, 0.3) inset;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  margin-top: 20px;
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
+}
+
+.loading-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 300px;
+  color: #47C3B9;
+}
+
+.no-data {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 300px;
+  color: #999;
+  font-size: 16px;
+}
+
+.no-data .el-icon {
+  font-size: 48px;
+  margin-bottom: 10px;
 }
 
-.calculate-btn:hover {
-  transform: scale(1.03);
-  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2),
-              0 2px 8px rgba(38, 176, 70, 0.4) inset;
-  background: linear-gradient(to right, #7de8df, #20a03d);
+.loading-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+  animation: rotate 2s linear infinite;
 }
 
-.calculate-btn:active {
-  transform: scale(0.98);
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1),
-              0 1px 6px rgba(38, 176, 70, 0.4) inset;
+@keyframes rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+/* 响应式布局调整 */
+@media (max-width: 992px) {
+  .horizontal-container {
+    flex-direction: column;
+  }
+  
+  .map-section, .histogram-section {
+    width: 100%;
+    flex: none;
+  }
 }
 </style>

+ 479 - 91
src/views/User/cadmiumPrediction/netFlux.vue

@@ -1,126 +1,514 @@
 <template>
-  <div class="total-input-output-container">
-    <el-card class="gradient-card" shadow="hover">
-      <el-row :gutter="20">
-        <el-col :span="12">
-          <p class="label">输入总量 (g/ha/a)</p>
-          <el-input
-            v-model="inputTotal"
-            placeholder="请输入内容"
-            class="custom-input"
-          />
-        </el-col>
-        <el-col :span="12">
-          <p class="label">输出总量 (g/ha/a)</p>
-          <el-input
-            v-model="outputTotal"
-            placeholder="请输入内容"
-            class="custom-input"
-            readonly
-          />
-        </el-col>
-        <el-col :span="24" style="margin-top: 20px;">
-          <el-button class="calculate-btn" @click="onCalculate">计算</el-button>
-        </el-col>
-      </el-row>
-    </el-card>
+  <div class="container">
+    <!-- 顶部操作栏 -->
+    <div class="toolbar">
+      <!-- 操作按钮 -->
+      <div class="action-buttons">
+        <el-button class="custom-button" :disabled="!mapBlob" @click="exportMap">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出地图</el-button>
+        <el-button class="custom-button" :disabled="!histogramBlob" @click="exportHistogram">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出直方图</el-button>
+        <el-button class="custom-button" :disabled="!statisticsData.length" @click="exportData">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出数据</el-button>
+      </div>
+    </div>
+
+    <!-- 主体内容区 -->
+    <div class="content-area">
+      <!-- 地图区域 - 修改为横向布局 -->
+      <div class="horizontal-container">
+        <!-- 地图展示 -->
+        <div class="map-section">
+          <h3>Cd净通量空间分布图</h3>
+          <div v-if="loadingMap" class="loading-container">
+            <el-icon class="loading-icon"><Loading /></el-icon>
+            <span>地图加载中...</span>
+          </div>
+          <img v-if="mapImageUrl && !loadingMap" :src="mapImageUrl" alt="Cd净通量空间分布图" class="map-image">
+          <div v-if="!mapImageUrl && !loadingMap" class="no-data">
+            <el-icon><Picture /></el-icon>
+            <p>暂无地图数据</p>
+          </div>
+        </div>
+        
+        <!-- 直方图展示 -->
+        <div class="histogram-section">
+          <h3>Cd净通量直方图</h3>
+          <div v-if="loadingHistogram" class="loading-container">
+            <el-icon class="loading-icon"><Loading /></el-icon>
+            <span>直方图加载中...</span>
+          </div>
+          <img v-if="histogramImageUrl && !loadingHistogram" :src="histogramImageUrl" alt="Cd净通量直方图" class="histogram-image">
+          <div v-if="!histogramImageUrl && !loadingHistogram" class="no-data">
+            <el-icon><Histogram /></el-icon>
+            <p>暂无直方图数据</p>
+          </div>
+        </div>
+      </div>
+
+      <!-- 统计图表区域 -->
+      <div class="stats-area">
+        <h3>Cd净通量统计信息</h3>
+        <div class="model-info">
+          <el-tag type="info">Cd通量模型</el-tag>
+          <span class="update-time">
+            最后更新: {{ updateTime ? new Date(updateTime).toLocaleString() : '未知' }}
+          </span>
+        </div>
+        
+        <div v-if="loadingStats" class="loading-container">
+          <el-icon class="loading-icon"><Loading /></el-icon>
+          <span>统计数据加载中...</span>
+        </div>
+        
+        <div v-if="!loadingStats && statisticsData.length" class="stats-container">
+          <!-- 统计表格 -->
+         <el-table 
+            :data="statisticsData" 
+            style="width: 100%; margin-bottom: 20px;"
+            border
+            stripe
+          >
+            <el-table-column prop="name" label="统计项" min-width="180" />
+            <el-table-column prop="value" label="值" min-width="150" />
+            <el-table-column prop="unit" label="单位" min-width="100" />
+            <el-table-column prop="description" label="描述" min-width="200" />
+          </el-table>
+          
+        
+        <div v-if="!loadingStats && !statisticsData.length" class="no-data">
+          <el-icon><DataAnalysis /></el-icon>
+          <p>暂无统计数据</p>
+        </div>
+        </div>
+      </div>
+    </div>
   </div>
 </template>
 
-<script setup>
-import { ref } from 'vue';
-import { ElCard, ElRow, ElCol, ElInput, ElButton } from 'element-plus';
+<script>
+import * as XLSX from 'xlsx';
+import { saveAs } from 'file-saver';
+import axios from 'axios';
+import * as echarts from 'echarts';
+import { 
+  Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+} from '@element-plus/icons-vue';
+
+export default {
+  name: 'CdFluxVisualization',
+  components: { 
+    Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+  },
+  data() {
+    return {
+      isCalculating: false,
+      loadingMap: false,
+      loadingHistogram: false,
+      loadingStats: false,
+      statisticsData: [],
+      mapImageUrl: null,
+      histogramImageUrl: null,
+      mapBlob: null,
+      histogramBlob: null,
+      selectedFile: null,
+      distributionChart: null,
+      updateTime: null
+    };
+  },
+
+  mounted() {
+    // 组件挂载时获取最新数据
+    this.fetchLatestResults();
+    this.fetchStatistics();
+  },
 
-const inputTotal = ref('');
-const outputTotal = ref('');
+  beforeDestroy() {
+    if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
+    if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
+    if (this.distributionChart) this.distributionChart.dispose();
+  },
+  methods: {
+    // 触发文件选择
+    triggerFileUpload() {
+      this.$refs.fileInput.click();
+    },
+    
+    // 处理文件上传
+    handleFileUpload(event) {
+      const files = event.target.files;
+      if (files && files.length > 0) {
+        this.selectedFile = files[0];
+      } else {
+        this.selectedFile = null;
+      }
+    },
+    
+    // 获取最新结果
+    async fetchLatestResults() {
+      try {
+        this.loadingMap = true;
+        this.loadingHistogram = true;
+        
+        // 获取最新地图
+        await this.fetchLatestMap();
+        
+        // 获取最新直方图
+        await this.fetchLatestHistogram();
+        
+      } catch (error) {
+        console.error('获取最新结果失败:', error);
+        this.$message.error('获取最新结果失败');
+      } finally {
+        this.loadingMap = false;
+        this.loadingHistogram = false;
+      }
+    },
+    
+    // 获取最新地图
+    async fetchLatestMap() {
+      try {
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/net/map`,
+          { responseType: 'blob' }
+        );
+        
+        this.mapBlob = response.data;
+        this.mapImageUrl = URL.createObjectURL(this.mapBlob);
+      } catch (error) {
+        console.error('获取最新地图失败:', error);
+        this.$message.warning('获取最新地图失败,请先执行预测');
+      }
+    },
+    
+    // 获取最新直方图
+    async fetchLatestHistogram() {
+      try {
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/net/histogram`,
+          { responseType: 'blob' }
+        );
+        
+        this.histogramBlob = response.data;
+        this.histogramImageUrl = URL.createObjectURL(this.histogramBlob);
+      } catch (error) {
+        console.error('获取最新直方图失败:', error);
+        this.$message.warning('获取最新直方图失败,请先执行预测');
+      }
+    },
+    
+    // 格式化统计数据
+    formatStatisticsData(stats) {
+      if (!stats) return [];
+      
+      return [
+        { name: '最小值', value: stats.min.toFixed(4), unit: 'g/ha/year', description: '样本中的最小Cd通量' },
+        { name: '最大值', value: stats.max.toFixed(4), unit: 'g/ha/year', description: '样本中的最大Cd通量' },
+        { name: '平均值', value: stats.mean.toFixed(4), unit: 'g/ha/year', description: '所有样本的平均Cd通量' },
+        { name: '标准差', value: stats.std.toFixed(4), unit: 'g/ha/year', description: 'Cd通量的标准差' },
+      ];
+    },
 
-const onCalculate = () => {
-  // 暂无计算逻辑,仅作展示
-  alert('计算按钮已点击');
+    // 修改fetchStatistics方法
+    async fetchStatistics() {
+      try {
+        this.loadingStats = true;
+        
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/net/statistics`
+        );
+        
+        if (response.data) {
+          const stats = response.data;
+          this.statisticsData = this.formatStatisticsData(stats);
+          this.updateTime = new Date().toISOString();
+          
+          this.$nextTick(() => {
+            this.initCharts(stats);
+          });
+        }
+      } catch (error) {
+        console.error('获取统计信息失败:', error);
+        this.$message.warning('获取统计信息失败');
+      } finally {
+        this.loadingStats = false;
+      }
+    },
+    
+    // 处理窗口大小变化
+    handleResize() {
+      if (this.distributionChart) this.distributionChart.resize();
+    },
+    
+    // 上传并计算
+    async calculate() {
+      if (!this.selectedFile) {
+        this.$message.warning('请先选择CSV文件');
+        return;
+      }
+      
+      try {
+        this.isCalculating = true;
+        this.loadingMap = true;
+        this.loadingHistogram = true;
+        this.loadingStats = true;
+        
+        // 创建FormData
+        const formData = new FormData();
+        formData.append('csv_file', this.selectedFile);
+        
+        // 调用Cd通量计算接口
+        await axios.post(
+          'http://localhost:8000/api/cd-flux/net/calculate',
+          formData,
+          {
+            headers: {
+              'Content-Type': 'multipart/form-data'
+            }
+          }
+        );
+        
+        // 更新后重新获取地图、直方图和统计数据
+        await this.fetchLatestResults();
+        await this.fetchStatistics();
+        
+        this.$message.success('计算完成!');
+        
+      } catch (error) {
+        console.error('计算失败:', error);
+        let errorMessage = '计算失败,请重试';
+        
+        if (error.response) {
+          if (error.response.status === 400) {
+            errorMessage = '文件格式错误:' + (error.response.data.detail || '请上传正确的CSV文件');
+          } else if (error.response.status === 500) {
+            errorMessage = '服务器错误:' + (error.response.data.detail || '请稍后重试');
+          }
+        }
+        
+        this.$message.error(errorMessage);
+      } finally {
+        this.isCalculating = false;
+        this.loadingMap = false;
+        this.loadingHistogram = false;
+        this.loadingStats = false;
+      }
+    },
+    
+    // 导出地图
+    exportMap() {
+      if (!this.mapBlob) {
+        this.$message.warning('请先计算生成地图');
+        return;
+      }
+      
+      const link = document.createElement('a');
+      link.href = URL.createObjectURL(this.mapBlob);
+      link.download = `Cd净通量空间分布图.jpg`;
+      link.click();
+      URL.revokeObjectURL(link.href);
+    },
+    
+    // 导出直方图
+    exportHistogram() {
+      if (!this.histogramBlob) {
+        this.$message.warning('请先计算生成直方图');
+        return;
+      }
+      
+      const link = document.createElement('a');
+      link.href = URL.createObjectURL(this.histogramBlob);
+      link.download = `Cd净通量直方图.jpg`;
+      link.click();
+      URL.revokeObjectURL(link.href);
+    },
+    
+    // 导出数据
+    async exportData() {
+      try {
+        this.$message.info('正在获取Cd净通量数据...');
+        
+        const response = await axios.get(
+          `http://localhost:8000/api/cd-flux/net/export-csv`,
+          { responseType: 'blob' }
+        );
+        
+        const blob = new Blob([response.data], { type: 'text/csv' });
+        const link = document.createElement('a');
+        link.href = URL.createObjectURL(blob);
+        link.download = `Cd净通量数据.csv`;
+        link.click();
+        URL.revokeObjectURL(link.href);
+        
+        this.$message.success('数据导出成功');
+      } catch (error) {
+        console.error('导出数据失败:', error);
+        this.$message.error('导出数据失败: ' + (error.response?.data?.detail || '请稍后重试'));
+      }
+    }
+  }
 };
 </script>
 
 <style scoped>
-.total-input-output-container {
-  display: flex;
-  justify-content: center;
-  align-items: center;
+/* 保持原有样式不变 */
+.container {
   padding: 20px;
-}
-
-.gradient-card {
-  /* 半透明渐变背景 */
+  /* 添加70%透明度的渐变背景 */
   background: linear-gradient(
     135deg, 
-    rgba(250, 253, 255, 0.8), 
-    rgba(137, 223, 252, 0.8)
+    rgba(230, 247, 255, 0.7) 0%, 
+    rgba(240, 248, 255, 0.7) 100%
   );
-  border-radius: 12px;
-  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
-  padding: 30px;
-  text-align: left; /* 改为左对齐 */
-  width: 600px;
+  min-height: 100vh;
+  box-sizing: border-box;
+}
+
+.toolbar {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  margin-bottom: 20px;
+  padding: 15px;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
-  border: none;
 }
 
-.label {
+.upload-section {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1); /* 调整边框透明度 */
+}
+
+.file-name {
+  flex: 1;
+  padding: 0 10px;
+  color: #666;
+  font-size: 14px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 10px;
+}
+
+.custom-button {
+  background-color: #47C3B9 !important;
+  color: #DCFFFA !important;
+  border: none;
+  border-radius: 155px;
+  padding: 10px 20px;
   font-weight: bold;
-  font-size: 18px;
-  margin-bottom: 10px; /* 减少底部外边距 */
-  color: #333;
+  display: flex;
+  align-items: center;
+}
+
+.upload-icon {
+  margin-right: 5px;
 }
 
-.custom-input {
+.content-area {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+/* 横向布局容器 */
+.horizontal-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
   width: 100%;
-  max-width: 200px;
-  margin-left: 0; /* 确保输入框靠左对齐 */
 }
 
-/* 自定义输入框样式 */
-:deep(.custom-input .el-input__inner) {
-  background: rgba(255, 255, 255, 0.7);
+.map-section, .histogram-section {
+  flex: 1;
+  min-width: 300px;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
   border-radius: 8px;
-  border: 1px solid #dcdfe6;
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
-  padding: 10px 15px;
-  font-size: 16px;
-  color: #333;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  position: relative;
+  min-height: 400px;
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
 }
 
-:deep(.custom-input .el-input__inner:focus) {
-  border-color: #409EFF;
-  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+.map-image, .histogram-image {
+  width: 100%;
+  height: 100%;
+  max-height: 600px;
+  object-fit: contain;
+  border-radius: 4px;
 }
 
-.calculate-btn {
+.table-area {
   width: 100%;
-  max-width: 200px;
-  height: 50px;
-  border: none;
-  border-radius: 25px !important;
-  font-size: 18px;
-  font-weight: bold;
-  transition: all 0.4s ease;
-  
-  /* 渐变背景色 */
-  background: linear-gradient(to right, #8DF9F0, #26B046);
-  color: white !important;
-  /* 按钮整体阴影 */
-  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15),
-              0 2px 6px rgba(38, 176, 70, 0.3) inset;
+  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  margin-top: 20px;
+  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
+}
+
+.loading-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 300px;
+  color: #47C3B9;
+}
+
+.no-data {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 300px;
+  color: #999;
+  font-size: 16px;
+}
+
+.no-data .el-icon {
+  font-size: 48px;
+  margin-bottom: 10px;
 }
 
-.calculate-btn:hover {
-  transform: scale(1.03);
-  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2),
-              0 2px 8px rgba(38, 176, 70, 0.4) inset;
-  background: linear-gradient(to right, #7de8df, #20a03d);
+.loading-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+  animation: rotate 2s linear infinite;
 }
 
-.calculate-btn:active {
-  transform: scale(0.98);
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1),
-              0 1px 6px rgba(38, 176, 70, 0.4) inset;
+@keyframes rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+/* 响应式布局调整 */
+@media (max-width: 992px) {
+  .horizontal-container {
+    flex-direction: column;
+  }
+  
+  .map-section, .histogram-section {
+    width: 100%;
+    flex: none;
+  }
 }
 </style>

+ 1 - 18
src/views/User/cadmiumPrediction/totalInputFlux.vue

@@ -2,24 +2,7 @@
   <div class="container">
     <!-- 顶部操作栏 -->
     <div class="toolbar">
-      <!-- 文件上传区域 -->
-      <div class="upload-section">
-        <input type="file" ref="fileInput" accept=".csv" @change="handleFileUpload" style="display: none">
-        <el-button class="custom-button" @click="triggerFileUpload">
-          <el-icon class="upload-icon"><Upload /></el-icon>
-          选择CSV文件
-        </el-button>
-        <span v-if="selectedFile" class="file-name">{{ selectedFile.name }}</span>
-        <el-button 
-          class="custom-button" 
-          :loading="isCalculating" 
-          :disabled="!selectedFile" 
-          @click="calculate"
-        >
-        <el-icon class="upload-icon"><Document /></el-icon>  
-        上传并计算
-        </el-button>
-      </div>
+     
       <!-- 操作按钮 -->
       <div class="action-buttons">
         <el-button class="custom-button" :disabled="!mapBlob" @click="exportMap">

+ 0 - 18
src/views/User/cadmiumPrediction/totalOutputFlux.vue

@@ -2,24 +2,6 @@
   <div class="container">
     <!-- 顶部操作栏 -->
     <div class="toolbar">
-      <!-- 文件上传区域 -->
-      <div class="upload-section">
-        <input type="file" ref="fileInput" accept=".csv" @change="handleFileUpload" style="display: none">
-        <el-button class="custom-button" @click="triggerFileUpload">
-          <el-icon class="upload-icon"><Upload /></el-icon>
-          选择CSV文件
-        </el-button>
-        <span v-if="selectedFile" class="file-name">{{ selectedFile.name }}</span>
-        <el-button 
-          class="custom-button" 
-          :loading="isCalculating" 
-          :disabled="!selectedFile" 
-          @click="calculate"
-        >
-        <el-icon class="upload-icon"><Document /></el-icon>  
-        上传并计算
-        </el-button>
-      </div>
       <!-- 操作按钮 -->
       <div class="action-buttons">
         <el-button class="custom-button" :disabled="!mapBlob" @click="exportMap">

+ 107 - 6
src/views/User/hmInFlux/grainRemoval/samplingDesc1.vue

@@ -15,6 +15,7 @@
         </p>
       </div>
 
+      <!-- 图片区域 -->
       <div class="image-row">
         <div class="image-container">
           <el-image :src="image1" alt="籽粒移除是重金属输出的重要途径" class="sampling-image"></el-image>
@@ -23,6 +24,20 @@
           </p>
         </div>
       </div>
+
+      <!-- 新增视频区域 -->
+      <div class="video-section">
+        <h3 class="video-title">籽粒移除过程视频演示</h3>
+        <div class="video-container">
+          <video controls class="sampling-video">
+            <source src="@/assets/videos/秸秆移除和籽粒移除.mp4" type="video/mp4">
+            您的浏览器不支持HTML5视频播放。
+          </video>
+          <p class="video-caption">
+            视频展示了重金属在籽粒中的积累和移除过程
+          </p>
+        </div>
+      </div>
     </div>
   </div>
 </template>
@@ -142,6 +157,7 @@ p::before {
 .image-row {
   display: flex;
   justify-content: center;
+  margin-bottom: 30px;
 }
 
 .image-container {
@@ -193,6 +209,77 @@ p::before {
   transform: translateY(-50%);
 }
 
+/* 视频区域 */
+.video-section {
+  margin-top: 40px;
+  padding-top: 30px;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.video-title {
+  text-align: center;
+  color: #1a365d;
+  font-size: 1.5rem;
+  margin-bottom: 20px;
+  position: relative;
+  padding-bottom: 10px;
+}
+
+.video-title::after {
+  content: "";
+  position: absolute;
+  bottom: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 60px;
+  height: 3px;
+  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
+  border-radius: 2px;
+}
+
+.video-container {
+  border-radius: 12px;
+  overflow: hidden;
+  position: relative;
+  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+  max-width: 800px;
+  margin: 0 auto;
+  transition: all 0.4s ease;
+}
+
+.video-container:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+}
+
+.sampling-video {
+  width: 100%;
+  height: auto;
+  display: block;
+  background: #f8fafc;
+  min-height: 350px;
+}
+
+.video-caption {
+  text-align: center;
+  font-size: 1.1rem;
+  color: #1a365d;
+  padding: 15px;
+  font-weight: 600;
+  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
+  margin: 0;
+  border-top: 1px dashed #cbd5e0;
+  position: relative;
+}
+
+.video-caption::before {
+  content: "📹";
+  position: absolute;
+  left: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+}
+
 /* 装饰元素 */
 .decorative-element {
   position: absolute;
@@ -235,7 +322,8 @@ p::before {
     padding: 20px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 300px;
   }
 }
@@ -254,14 +342,20 @@ p::before {
     padding-left: 15px;
   }
   
-  .image-caption {
+  .image-caption,
+  .video-caption {
     font-size: 1rem;
     padding: 12px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 250px;
   }
+  
+  .video-title {
+    font-size: 1.3rem;
+  }
 }
 
 @media (max-width: 480px) {
@@ -286,17 +380,24 @@ p::before {
     padding-left: 10px;
   }
   
-  .image-caption {
+  .image-caption,
+  .video-caption {
     font-size: 0.9rem;
     padding: 10px 15px 10px 35px;
   }
   
-  .image-caption::before {
+  .image-caption::before,
+  .video-caption::before {
     left: 10px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 200px;
   }
+  
+  .video-title {
+    font-size: 1.1rem;
+  }
 }
 </style>

+ 106 - 6
src/views/User/hmInFlux/strawRemoval/samplingDesc2.vue

@@ -23,6 +23,20 @@
           </p>
         </div>
       </div>
+
+       <!-- 新增视频区域 -->
+      <div class="video-section">
+        <h3 class="video-title">秸秆移除过程视频演示</h3>
+        <div class="video-container">
+          <video controls class="sampling-video">
+            <source src="@/assets/videos/秸秆移除和籽粒移除.mp4" type="video/mp4">
+            您的浏览器不支持HTML5视频播放。
+          </video>
+          <p class="video-caption">
+            视频展示了重金属在秸秆中的积累和移除过程
+          </p>
+        </div>
+      </div>
     </div>
   </div>
 </template>
@@ -142,6 +156,7 @@ p::before {
 .image-row {
   display: flex;
   justify-content: center;
+  margin-bottom: 30px;
 }
 
 .image-container {
@@ -193,6 +208,77 @@ p::before {
   transform: translateY(-50%);
 }
 
+/* 视频区域 */
+.video-section {
+  margin-top: 40px;
+  padding-top: 30px;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.video-title {
+  text-align: center;
+  color: #1a365d;
+  font-size: 1.5rem;
+  margin-bottom: 20px;
+  position: relative;
+  padding-bottom: 10px;
+}
+
+.video-title::after {
+  content: "";
+  position: absolute;
+  bottom: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 60px;
+  height: 3px;
+  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
+  border-radius: 2px;
+}
+
+.video-container {
+  border-radius: 12px;
+  overflow: hidden;
+  position: relative;
+  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+  max-width: 800px;
+  margin: 0 auto;
+  transition: all 0.4s ease;
+}
+
+.video-container:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+}
+
+.sampling-video {
+  width: 100%;
+  height: auto;
+  display: block;
+  background: #f8fafc;
+  min-height: 350px;
+}
+
+.video-caption {
+  text-align: center;
+  font-size: 1.1rem;
+  color: #1a365d;
+  padding: 15px;
+  font-weight: 600;
+  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
+  margin: 0;
+  border-top: 1px dashed #cbd5e0;
+  position: relative;
+}
+
+.video-caption::before {
+  content: "📹";
+  position: absolute;
+  left: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+}
+
 /* 装饰元素 */
 .decorative-element {
   position: absolute;
@@ -235,7 +321,8 @@ p::before {
     padding: 20px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 300px;
   }
 }
@@ -254,14 +341,20 @@ p::before {
     padding-left: 15px;
   }
   
-  .image-caption {
+  .image-caption,
+  .video-caption {
     font-size: 1rem;
     padding: 12px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 250px;
   }
+  
+  .video-title {
+    font-size: 1.3rem;
+  }
 }
 
 @media (max-width: 480px) {
@@ -286,17 +379,24 @@ p::before {
     padding-left: 10px;
   }
   
-  .image-caption {
+  .image-caption,
+  .video-caption {
     font-size: 0.9rem;
     padding: 10px 15px 10px 35px;
   }
   
-  .image-caption::before {
+  .image-caption::before,
+  .video-caption::before {
     left: 10px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 200px;
   }
+  
+  .video-title {
+    font-size: 1.1rem;
+  }
 }
 </style>

+ 105 - 6
src/views/User/hmInFlux/subsurfaceLeakage/samplingDesc3.vue

@@ -24,6 +24,19 @@
         </p>
       </div>
     </div>
+     <!-- 新增视频区域 -->
+      <div class="video-section">
+        <h3 class="video-title">土壤渗流过程视频演示</h3>
+        <div class="video-container">
+          <video controls class="sampling-video">
+            <source src="@/assets/videos/地下渗漏.mp4" type="video/mp4">
+            您的浏览器不支持HTML5视频播放。
+          </video>
+          <p class="video-caption">
+            视频展示了重金属在地下中的积累过程
+          </p>
+        </div>
+      </div>
   </div>
 </template>
 
@@ -142,6 +155,7 @@ p::before {
 .image-row {
   display: flex;
   justify-content: center;
+  margin-bottom: 30px;
 }
 
 .image-container {
@@ -193,6 +207,77 @@ p::before {
   transform: translateY(-50%);
 }
 
+/* 视频区域 */
+.video-section {
+  margin-top: 40px;
+  padding-top: 30px;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.video-title {
+  text-align: center;
+  color: #1a365d;
+  font-size: 1.5rem;
+  margin-bottom: 20px;
+  position: relative;
+  padding-bottom: 10px;
+}
+
+.video-title::after {
+  content: "";
+  position: absolute;
+  bottom: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 60px;
+  height: 3px;
+  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
+  border-radius: 2px;
+}
+
+.video-container {
+  border-radius: 12px;
+  overflow: hidden;
+  position: relative;
+  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+  max-width: 800px;
+  margin: 0 auto;
+  transition: all 0.4s ease;
+}
+
+.video-container:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+}
+
+.sampling-video {
+  width: 100%;
+  height: auto;
+  display: block;
+  background: #f8fafc;
+  min-height: 350px;
+}
+
+.video-caption {
+  text-align: center;
+  font-size: 1.1rem;
+  color: #1a365d;
+  padding: 15px;
+  font-weight: 600;
+  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
+  margin: 0;
+  border-top: 1px dashed #cbd5e0;
+  position: relative;
+}
+
+.video-caption::before {
+  content: "📹";
+  position: absolute;
+  left: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+}
+
 /* 装饰元素 */
 .decorative-element {
   position: absolute;
@@ -235,7 +320,8 @@ p::before {
     padding: 20px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 300px;
   }
 }
@@ -254,14 +340,20 @@ p::before {
     padding-left: 15px;
   }
   
-  .image-caption {
+  .image-caption,
+  .video-caption {
     font-size: 1rem;
     padding: 12px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 250px;
   }
+  
+  .video-title {
+    font-size: 1.3rem;
+  }
 }
 
 @media (max-width: 480px) {
@@ -286,17 +378,24 @@ p::before {
     padding-left: 10px;
   }
   
-  .image-caption {
+  .image-caption,
+  .video-caption {
     font-size: 0.9rem;
     padding: 10px 15px 10px 35px;
   }
   
-  .image-caption::before {
+  .image-caption::before,
+  .video-caption::before {
     left: 10px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 200px;
   }
+  
+  .video-title {
+    font-size: 1.1rem;
+  }
 }
 </style>

+ 105 - 6
src/views/User/hmInFlux/surfaceRunoff/samplingDesc4.vue

@@ -22,6 +22,19 @@
         </p>
       </div>
     </div>
+    <!-- 新增视频区域 -->
+      <div class="video-section">
+        <h3 class="video-title">地表径流过程视频演示</h3>
+        <div class="video-container">
+          <video controls class="sampling-video">
+            <source src="@/assets/videos/地表径流.mp4" type="video/mp4">
+            您的浏览器不支持HTML5视频播放。
+          </video>
+          <p class="video-caption">
+            视频展示了重金属在地表中的积累过程
+          </p>
+        </div>
+      </div>
   </div>
 </template>
 
@@ -141,6 +154,7 @@ p::before {
 .image-row {
   display: flex;
   justify-content: center;
+  margin-bottom: 30px;
 }
 
 .image-container {
@@ -192,6 +206,77 @@ p::before {
   transform: translateY(-50%);
 }
 
+/* 视频区域 */
+.video-section {
+  margin-top: 40px;
+  padding-top: 30px;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.video-title {
+  text-align: center;
+  color: #1a365d;
+  font-size: 1.5rem;
+  margin-bottom: 20px;
+  position: relative;
+  padding-bottom: 10px;
+}
+
+.video-title::after {
+  content: "";
+  position: absolute;
+  bottom: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 60px;
+  height: 3px;
+  background: linear-gradient(90deg, #4a9ef7, #3acfd5);
+  border-radius: 2px;
+}
+
+.video-container {
+  border-radius: 12px;
+  overflow: hidden;
+  position: relative;
+  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+  max-width: 800px;
+  margin: 0 auto;
+  transition: all 0.4s ease;
+}
+
+.video-container:hover {
+  transform: translateY(-5px);
+  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
+}
+
+.sampling-video {
+  width: 100%;
+  height: auto;
+  display: block;
+  background: #f8fafc;
+  min-height: 350px;
+}
+
+.video-caption {
+  text-align: center;
+  font-size: 1.1rem;
+  color: #1a365d;
+  padding: 15px;
+  font-weight: 600;
+  background: linear-gradient(to right, rgba(248, 250, 252, 0.9), rgba(240, 248, 255, 0.9));
+  margin: 0;
+  border-top: 1px dashed #cbd5e0;
+  position: relative;
+}
+
+.video-caption::before {
+  content: "📹";
+  position: absolute;
+  left: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+}
+
 /* 装饰元素 */
 .decorative-element {
   position: absolute;
@@ -234,7 +319,8 @@ p::before {
     padding: 20px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 300px;
   }
 }
@@ -253,14 +339,20 @@ p::before {
     padding-left: 15px;
   }
   
-  .image-caption {
+  .image-caption,
+  .video-caption {
     font-size: 1rem;
     padding: 12px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 250px;
   }
+  
+  .video-title {
+    font-size: 1.3rem;
+  }
 }
 
 @media (max-width: 480px) {
@@ -285,17 +377,24 @@ p::before {
     padding-left: 10px;
   }
   
-  .image-caption {
+  .image-caption,
+  .video-caption {
     font-size: 0.9rem;
     padding: 10px 15px 10px 35px;
   }
   
-  .image-caption::before {
+  .image-caption::before,
+  .video-caption::before {
     left: 10px;
   }
   
-  .sampling-image {
+  .sampling-image,
+  .sampling-video {
     min-height: 200px;
   }
+  
+  .video-title {
+    font-size: 1.1rem;
+  }
 }
 </style>