ソースを参照

Merge branch 'ding' of qw/12 into master

Ding 3 ヶ月 前
コミット
b663f2ef3b

+ 234 - 26
src/views/User/cadmiumPrediction/CropCadmiumPrediction.vue

@@ -37,6 +37,9 @@
         <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>
 
@@ -55,8 +58,45 @@
           <p>暂无地图数据</p>
         </div>
       </div>
-      
-      <!-- 直方图区域 - 单独一行 -->
+
+       <!-- 统计图表区域 -->
+      <div class="stats-area">
+        <h3>作物态Cd预测统计信息</h3>
+        <div class="model-info">
+          <el-tag type="info">{{ modelInfo.modelType || 'Cd预测模型' }}</el-tag>
+          <span class="update-time">
+            最后更新: {{ modelInfo.updateTime ? new Date(modelInfo.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>
+        
+        <div v-if="!loadingStats && !statisticsData.length" class="no-data">
+          <el-icon><DataAnalysis /></el-icon>
+          <p>暂无统计数据</p>
+        </div>
+      </div>
+    </div>
+
+     <!-- 直方图区域 - 单独一行 -->
       <div class="histogram-section">
         <h3>作物态Cd预测直方图</h3>
         <div v-if="loadingHistogram" class="loading-container">
@@ -69,7 +109,6 @@
           <p>暂无直方图数据</p>
         </div>
       </div>
-    </div>
   </div>
 </template>
 
@@ -78,13 +117,13 @@ import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
 import { api8000 } from '@/utils/request';
 import { 
-  Loading, Upload, Picture, Histogram, Download, Document, Box 
+  Loading, Upload, Picture, Histogram, Download, Document, Box, DataAnalysis
 } from '@element-plus/icons-vue';
 
 export default {
   name: 'CropCadmiumPrediction',
   components: { 
-    Loading, Upload, Picture, Histogram, Download, Document, Box 
+    Loading, Upload, Picture, Histogram, Download, Document, Box, DataAnalysis
   },
   data() {
     return {
@@ -92,18 +131,27 @@ export default {
       isCalculatingFromDB: false,
       loadingMap: false,
       loadingHistogram: false,
+      loadingStats: false,
+      statisticsData: [],
+      modelInfo: {
+        modelType: '',
+        unit: '',
+        updateTime: null,
+        dataSource: ''
+      },
       mapImageUrl: null,
       histogramImageUrl: null,
       mapBlob: null,
       histogramBlob: null,
       selectedFile: null,
-      countyName: '乐昌市' // 默认县市名称
+      countyName: '乐昌市', // 默认县市名称
     };
   },
 
   mounted() {
     // 组件挂载时获取最新数据
     this.fetchLatestResults();
+    this.fetchStatistics();
   },
 
   beforeDestroy() {
@@ -179,6 +227,93 @@ export default {
       }
     },
     
+    // 格式化统计数据
+    formatStatisticsData(statsData) {
+      if (!statsData || !statsData.data) return [];
+      
+      const { 基础统计, 数据单位 } = statsData.data;
+      
+      return [
+        { 
+          name: '数据点总数', 
+          value: 基础统计.数据点总数, 
+          unit: '-', 
+          description: '样本总数' 
+        },
+        { 
+          name: '均值', 
+          value: 基础统计.均值.toFixed(4), 
+          unit: 数据单位, 
+          description: '所有样本的平均值' 
+        },
+        { 
+          name: '中位数', 
+          value: 基础统计.中位数.toFixed(4), 
+          unit: 数据单位, 
+          description: '样本的中位数值' 
+        },
+        { 
+          name: '标准差', 
+          value: 基础统计.标准差.toFixed(4), 
+          unit: 数据单位, 
+          description: '数据的离散程度' 
+        },
+        { 
+          name: '最小值', 
+          value: 基础统计.最小值.toFixed(4), 
+          unit: 数据单位, 
+          description: '样本中的最小值' 
+        },
+        { 
+          name: '最大值', 
+          value: 基础统计.最大值.toFixed(4), 
+          unit: 数据单位, 
+          description: '样本中的最大值' 
+        },
+        { 
+          name: '25%分位数', 
+          value: 基础统计['25%分位数'].toFixed(4), 
+          unit: 数据单位, 
+          description: '第一四分位数' 
+        },
+        { 
+          name: '75%分位数', 
+          value: 基础统计['75%分位数'].toFixed(4), 
+          unit: 数据单位, 
+          description: '第三四分位数' 
+        }
+      ];
+    },
+
+    // 获取统计信息
+    async fetchStatistics() {
+      try {
+        this.loadingStats = true;
+        
+        const response = await api8000.get(
+          `/api/cd-prediction/crop-cd/statistics`
+        );
+        
+        if (response.data && response.data.success) {
+          const statsData = response.data;
+          this.statisticsData = this.formatStatisticsData(statsData);
+          
+          // 设置模型信息
+          this.modelInfo = {
+            modelType: statsData.data.模型类型,
+            unit: statsData.data.数据单位,
+            updateTime: statsData.data.数据更新时间,
+            dataSource: statsData.data.数据来源
+          };
+        }
+      } catch (error) {
+        console.error('获取统计信息失败:', error);
+        this.$message.warning('获取统计信息失败');
+      } finally {
+        this.loadingStats = false;
+      }
+    },
+    
     // 上传并计算
     async calculate() {
       if (!this.selectedFile) {
@@ -213,8 +348,9 @@ export default {
         this.mapBlob = mapResponse.data;
         this.mapImageUrl = URL.createObjectURL(this.mapBlob);
         
-        // 更新后重新获取直方图
+        // 更新后重新获取直方图和统计数据
         await this.fetchLatestHistogram();
+        await this.fetchStatistics();
         
         this.$message.success('计算完成!');
         
@@ -268,8 +404,9 @@ export default {
         this.mapBlob = mapResponse.data;
         this.mapImageUrl = URL.createObjectURL(this.mapBlob);
         
-        // 更新后重新获取直方图
+        // 更新后重新获取直方图和统计数据
         await this.fetchLatestHistogram();
+        await this.fetchStatistics();
         
         this.$message.success('数据库计算完成!');
         
@@ -322,6 +459,35 @@ export default {
       link.click();
       URL.revokeObjectURL(link.href);
     },
+    
+    // 导出数据
+    async exportData() {
+      try {
+        this.$message.info('正在获取作物态Cd预测数据...');
+        
+        const response = await api8000.get(
+          `/api/cd-prediction/crop-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 || '请稍后重试'));
+      }
+    },
+    
+    // 处理窗口大小变化
+    handleResize() {
+      if (this.distributionChart) this.distributionChart.resize();
+    },
   }
 };
 </script>
@@ -329,7 +495,6 @@ export default {
 <style scoped>
 .container {
   padding: 20px;
-  /* 添加70%透明度的渐变背景 */
   background: linear-gradient(
     135deg, 
     rgba(230, 247, 255, 0.7) 0%, 
@@ -345,10 +510,10 @@ export default {
   gap: 15px;
   margin-bottom: 20px;
   padding: 15px;
-  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  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); /* 添加模糊效果增强半透明感 */
+  backdrop-filter: blur(5px);
 }
 
 .upload-section {
@@ -356,7 +521,7 @@ export default {
   align-items: center;
   gap: 15px;
   padding-bottom: 15px;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.1); /* 调整边框透明度 */
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
 }
 
 .file-name {
@@ -372,6 +537,7 @@ export default {
 .action-buttons {
   display: flex;
   gap: 10px;
+  flex-wrap: wrap;
 }
 
 .custom-button {
@@ -397,13 +563,13 @@ export default {
 
 /* 地图区域 */
 .map-section {
-  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  background-color: rgba(255, 255, 255, 0.8);
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
   min-height: 500px;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
+  backdrop-filter: blur(5px);
 }
 
 .map-image {
@@ -414,25 +580,15 @@ export default {
   border-radius: 4px;
 }
 
-/* 统计图表区域 */
-.stats-area {
-  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
-  border-radius: 8px;
-  padding: 15px;
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-  position: relative;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
-}
-
 /* 直方图区域 */
 .histogram-section {
-  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  background-color: rgba(255, 255, 255, 0.8);
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
   min-height: 500px;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
+  backdrop-filter: blur(5px);
 }
 
 .histogram-image {
@@ -443,6 +599,40 @@ export default {
   border-radius: 4px;
 }
 
+/* 统计区域 */
+.stats-area {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  position: relative;
+  backdrop-filter: blur(5px);
+}
+
+.model-info {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin-bottom: 15px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.update-time {
+  color: #666;
+  font-size: 14px;
+}
+
+.data-source {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-top: 15px;
+  padding-top: 15px;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+  color: #666;
+}
+
 .loading-container {
   display: flex;
   flex-direction: column;
@@ -487,5 +677,23 @@ export default {
   .content-area {
     flex-direction: column;
   }
+  
+  .action-buttons {
+    flex-direction: column;
+    align-items: stretch;
+  }
+  
+  .custom-button {
+    justify-content: center;
+  }
+  
+  .upload-section {
+    flex-direction: column;
+    align-items: stretch;
+  }
+  
+  .file-name {
+    text-align: center;
+  }
 }
 </style>

+ 212 - 17
src/views/User/cadmiumPrediction/EffectiveCadmiumPrediction.vue

@@ -37,6 +37,9 @@
         <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>
 
@@ -56,6 +59,43 @@
         </div>
       </div>
       
+      <!-- 统计图表区域 -->
+      <div class="stats-area">
+        <h3>有效态Cd预测统计信息</h3>
+        <div class="model-info">
+          <el-tag type="info">{{ modelInfo.modelType || '有效Cd预测模型' }}</el-tag>
+          <span class="update-time">
+            最后更新: {{ modelInfo.updateTime ? new Date(modelInfo.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>
+        
+        <div v-if="!loadingStats && !statisticsData.length" class="no-data">
+          <el-icon><DataAnalysis /></el-icon>
+          <p>暂无统计数据</p>
+        </div>
+      </div>
+    </div>
+
       <!-- 直方图区域 - 单独一行,放在统计信息下面 -->
       <div class="histogram-section">
         <h3>有效态Cd预测直方图</h3>
@@ -69,7 +109,6 @@
           <p>暂无直方图数据</p>
         </div>
       </div>
-    </div>
   </div>
 </template>
 
@@ -79,13 +118,13 @@ import { saveAs } from 'file-saver';
 import { api8000 } from '@/utils/request';
 import * as echarts from 'echarts';
 import { 
-  Loading, Upload, Picture, Histogram, Download, Document, Box 
+  Loading, Upload, Picture, Histogram, Download, Document, Box, DataAnalysis
 } from '@element-plus/icons-vue';
 
 export default {
   name: 'EffectiveCadmiumPrediction',
   components: { 
-    Loading, Upload, Picture, Histogram, Download, Document, Box 
+    Loading, Upload, Picture, Histogram, Download, Document, Box, DataAnalysis
   },
   data() {
     return {
@@ -93,6 +132,14 @@ export default {
       isCalculatingFromDB: false,
       loadingMap: false,
       loadingHistogram: false,
+      loadingStats: false,
+      statisticsData: [],
+      modelInfo: {
+        modelType: '',
+        unit: '',
+        updateTime: null,
+        dataSource: ''
+      },
       mapImageUrl: null,
       histogramImageUrl: null,
       mapBlob: null,
@@ -105,6 +152,7 @@ export default {
   mounted() {
     // 组件挂载时获取最新数据
     this.fetchLatestResults();
+    this.fetchStatistics();
   },
 
   beforeDestroy() {
@@ -126,7 +174,94 @@ export default {
         this.selectedFile = null;
       }
     },
-    
+     
+    // 格式化统计数据
+    formatStatisticsData(statsData) {
+      if (!statsData || !statsData.data) return [];
+      
+      const { 基础统计, 数据单位 } = statsData.data;
+      
+      return [
+        { 
+          name: '数据点总数', 
+          value: 基础统计.数据点总数, 
+          unit: '-', 
+          description: '样本总数' 
+        },
+        { 
+          name: '均值', 
+          value: 基础统计.均值.toFixed(4), 
+          unit: 数据单位, 
+          description: '所有样本的平均值' 
+        },
+        { 
+          name: '中位数', 
+          value: 基础统计.中位数.toFixed(4), 
+          unit: 数据单位, 
+          description: '样本的中位数值' 
+        },
+        { 
+          name: '标准差', 
+          value: 基础统计.标准差.toFixed(4), 
+          unit: 数据单位, 
+          description: '数据的离散程度' 
+        },
+        { 
+          name: '最小值', 
+          value: 基础统计.最小值.toFixed(4), 
+          unit: 数据单位, 
+          description: '样本中的最小值' 
+        },
+        { 
+          name: '最大值', 
+          value: 基础统计.最大值.toFixed(4), 
+          unit: 数据单位, 
+          description: '样本中的最大值' 
+        },
+        { 
+          name: '25%分位数', 
+          value: 基础统计['25%分位数'].toFixed(4), 
+          unit: 数据单位, 
+          description: '第一四分位数' 
+        },
+        { 
+          name: '75%分位数', 
+          value: 基础统计['75%分位数'].toFixed(4), 
+          unit: 数据单位, 
+          description: '第三四分位数' 
+        }
+      ];
+    },
+
+    // 获取统计信息
+    async fetchStatistics() {
+      try {
+        this.loadingStats = true;
+        
+        const response = await api8000.get(
+          `/api/cd-prediction/effective-cd/statistics`
+        );
+        
+        if (response.data && response.data.success) {
+          const statsData = response.data;
+          this.statisticsData = this.formatStatisticsData(statsData);
+          
+          // 设置模型信息
+          this.modelInfo = {
+            modelType: statsData.data.模型类型,
+            unit: statsData.data.数据单位,
+            updateTime: statsData.data.数据更新时间,
+            dataSource: statsData.data.数据来源
+          };
+        }
+      } catch (error) {
+        console.error('获取统计信息失败:', error);
+        this.$message.warning('获取统计信息失败');
+      } finally {
+        this.loadingStats = false;
+      }
+    },
+
     // 获取最新结果
     async fetchLatestResults() {
       try {
@@ -139,6 +274,7 @@ export default {
         // 获取最新直方图
         await this.fetchLatestHistogram();
         
+        
       } catch (error) {
         console.error('获取最新结果失败:', error);
         this.$message.error('获取最新结果失败');
@@ -216,6 +352,8 @@ export default {
         
         // 更新后重新获取直方图
         await this.fetchLatestHistogram();
+        await this.fetchStatistics();
+
         
         this.$message.success('计算完成!');
         
@@ -324,20 +462,20 @@ export default {
       URL.revokeObjectURL(link.href);
     },
     
-    // 导出数据
+     // 导出数据
     async exportData() {
       try {
-        this.$message.info('正在获取有效Cd预测数据...');
+        this.$message.info('正在获取有效Cd预测数据...');
         
         const response = await api8000.get(
-          `/api/cd-prediction/download-final-effective-cd-csv`,
+          `/api/cd-prediction/effective-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 = `${this.countyName}_有效Cd预测数据.csv`;
+        link.download = `有效Cd预测数据.csv`;
         link.click();
         URL.revokeObjectURL(link.href);
         
@@ -346,7 +484,12 @@ export default {
         console.error('导出数据失败:', error);
         this.$message.error('导出数据失败: ' + (error.response?.data?.detail || '请稍后重试'));
       }
-    }
+    },
+    
+    // 处理窗口大小变化
+    handleResize() {
+      if (this.distributionChart) this.distributionChart.resize();
+    },
   }
 };
 </script>
@@ -354,7 +497,6 @@ export default {
 <style scoped>
 .container {
   padding: 20px;
-  /* 添加70%透明度的渐变背景 */
   background: linear-gradient(
     135deg, 
     rgba(230, 247, 255, 0.7) 0%, 
@@ -370,10 +512,10 @@ export default {
   gap: 15px;
   margin-bottom: 20px;
   padding: 15px;
-  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  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); /* 添加模糊效果增强半透明感 */
+  backdrop-filter: blur(5px);
 }
 
 .upload-section {
@@ -381,7 +523,7 @@ export default {
   align-items: center;
   gap: 15px;
   padding-bottom: 15px;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.1); /* 调整边框透明度 */
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
 }
 
 .file-name {
@@ -397,6 +539,7 @@ export default {
 .action-buttons {
   display: flex;
   gap: 10px;
+  flex-wrap: wrap;
 }
 
 .custom-button {
@@ -422,13 +565,13 @@ export default {
 
 /* 地图区域 */
 .map-section {
-  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  background-color: rgba(255, 255, 255, 0.8);
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
   min-height: 500px;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
+  backdrop-filter: blur(5px);
 }
 
 .map-image {
@@ -441,13 +584,13 @@ export default {
 
 /* 直方图区域 */
 .histogram-section {
-  background-color: rgba(255, 255, 255, 0.8); /* 调整为半透明白色 */
+  background-color: rgba(255, 255, 255, 0.8);
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
   min-height: 500px;
-  backdrop-filter: blur(5px); /* 添加模糊效果增强半透明感 */
+  backdrop-filter: blur(5px);
 }
 
 .histogram-image {
@@ -458,6 +601,40 @@ export default {
   border-radius: 4px;
 }
 
+/* 统计区域 */
+.stats-area {
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  position: relative;
+  backdrop-filter: blur(5px);
+}
+
+.model-info {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin-bottom: 15px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.update-time {
+  color: #666;
+  font-size: 14px;
+}
+
+.data-source {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-top: 15px;
+  padding-top: 15px;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+  color: #666;
+}
+
 .loading-container {
   display: flex;
   flex-direction: column;
@@ -502,5 +679,23 @@ export default {
   .content-area {
     flex-direction: column;
   }
+  
+  .action-buttons {
+    flex-direction: column;
+    align-items: stretch;
+  }
+  
+  .custom-button {
+    justify-content: center;
+  }
+  
+  .upload-section {
+    flex-direction: column;
+    align-items: stretch;
+  }
+  
+  .file-name {
+    text-align: center;
+  }
 }
 </style>

+ 195 - 258
src/views/User/farmlandQualityAssessment/farmlandQualityAssessment.vue

@@ -1,80 +1,97 @@
 <template>
-  <div>
-    <!-- 顶部信息卡片区域 -->
-    <div class="dashboard">
-      <!-- 合并的统计与分布卡片 -->
-      <div class="dashboard-card combined-card">
-        <div class="card-title">统计与分布</div>
-        <div class="combined-content">
-          <!-- 左侧:单元统计 -->
-          <div class="statistics-section">
-            <h3>单元统计</h3>
-            <div class="statistics">
-              <div class="stat-item">
-                <div class="stat-value">{{ statistics.total_units }}</div>
-                <div class="stat-label">总单元数</div>
-              </div>
-              <div class="stat-item">
-                <div class="stat-value">{{ statistics.units_with_data }}</div>
-                <div class="stat-label">有数据单元</div>
-              </div>
-              <!-- 仅当无数据单元数不为0时显示 -->
-              <div class="stat-item" v-if="statistics.units_without_data !== 0">
-                <div class="stat-value">{{ statistics.units_without_data }}</div>
-                <div class="stat-label">无数据单元</div>
+  <div class="agricultural-input-management">
+    <div class="page-container">
+      <!-- 顶部信息卡片区域 -->
+      <div class="dashboard">
+        <!-- 合并的统计与分布卡片 -->
+        <div class="dashboard-card combined-card">
+          <div class="card-title">统计与分布</div>
+          <div class="combined-content">
+            <!-- 左侧:单元统计 -->
+            <div class="statistics-section">
+              <h3>单元统计</h3>
+              <div class="statistics">
+                <div class="stat-item">
+                  <div class="stat-value">{{ statistics.total_units }}</div>
+                  <div class="stat-label">总单元数</div>
+                </div>
+                <div class="stat-item">
+                  <div class="stat-value">{{ statistics.units_with_data }}</div>
+                  <div class="stat-label">有数据单元</div>
+                </div>
+                <!-- 仅当无数据单元数不为0时显示 -->
+                <div class="stat-item" v-if="statistics.units_without_data !== 0">
+                  <div class="stat-value">{{ statistics.units_without_data }}</div>
+                  <div class="stat-label">无数据单元</div>
+                </div>
               </div>
             </div>
-          </div>
-          
-          <!-- 右侧:分类分布 -->
-          <div class="distribution-section">
-            <h3>分类分布</h3>
-            <div class="distribution">
-              <div v-for="(count, category) in statistics.category_distribution" :key="category" 
-                  class="category-dist" :style="{ backgroundColor: categoryColors[category] }">
-                <div class="dist-category">{{ category }}</div>
-                <div class="dist-count">{{ count }}</div>
+            
+            <!-- 右侧:分类分布 -->
+            <div class="distribution-section">
+              <h3>分类分布</h3>
+              <div class="distribution">
+                <div v-for="(count, category) in statistics.category_distribution" :key="category" 
+                    class="category-dist" :style="{ backgroundColor: categoryColors[category] }">
+                  <div class="dist-category">{{ category }}</div>
+                  <div class="dist-count">{{ count }}</div>
+                </div>
               </div>
             </div>
           </div>
         </div>
+        
+        <!-- 饼图卡片 -->
+        <div class="dashboard-card chart-card">
+          <div class="card-title">点位分类分布</div>
+          <div ref="pointPieChart" class="chart"></div>
+        </div>
       </div>
       
-      <!-- 饼图卡片 -->
-      <div class="dashboard-card chart-card">
-        <div class="card-title">点位分类分布</div>
-        <div ref="pointPieChart" class="chart"></div>
+      <!-- 地图区域 -->
+      <div class="map-area">
+        <div class="map-title">土壤分类分布地图</div>
+        <div class="map-container">
+          <div v-if="loadingMap" class="loading-container">
+            <el-icon class="loading-icon"><Loading /></el-icon>
+            <span>地图加载中...</span>
+            
+          </div>
+          <div v-else-if="mapError" class="error-container">
+            <el-icon class="error-icon"><Warning /></el-icon>
+            <span>{{ mapError }}</span>
+          </div>
+          
+           <div v-else class="image-container">
+              <img :src="mapImageUrl" class="map-image"></img>
+            </div>
+        </div>
       </div>
     </div>
-    
-    <!-- 地图区域 -->
-    <div class="map-area">
-      <div class="map-title">土壤分类分布地图</div>
-      <div ref="mapContainer" class="map-container"></div>
-    </div>
   </div>
 </template>
 
 <script>
 import * as echarts from 'echarts';
-import { api8000 } from '@/utils/request';  // 导入api8000
+import { api8000 } from '@/utils/request';
+import { 
+  Loading, Picture, Warning 
+} from '@element-plus/icons-vue';
 
 // 分类颜色配置
 const categoryColors = {
-  '优先保护类': 'rgba(255, 214, 0, 0.7)', // #FFD600 转换为RGBA
-  '安全利用类': 'rgba(0, 200, 83, 0.7)', // #00C853 转换为RGBA
-  '严格管控类': 'rgba(213, 0, 0, 0.7)'   // #D50000 转换为RGBA
+  '优先保护类': 'rgba(255, 214, 0, 0.7)',
+  '安全利用类': 'rgba(0, 200, 83, 0.7)',
+  '严格管控类': 'rgba(213, 0, 0, 0.7)'
 };
 
 export default {
-  name: 'CategoryMap',
+  name: 'SoilCategoryMap',
+  components: { 
+    Loading, Picture, Warning 
+  },
   data() {
     return {
-      map: null,
-      geoJSONLayer: null,
-      shaoguanBoundaryLayer:null,
-      multiPolygon: null, 
-      categoryColors,
       statistics: {
         total_units: 0,
         units_with_data: 0,
@@ -90,225 +107,68 @@ export default {
           '严格管控类': 0
         }
       },
-      groupingData: [] // 存储接口数据
+      mapImageUrl: '',
+      loadingMap: true,
+      mapError: null,
+      pieChart: null,
+      categoryColors
     };
   },
+  
   async mounted() {
-    // 获取分类数据
-    await this.fetchGroupingData();
-    
-    // 初始化地图
-    await this.initMap();
-    
-    // 初始化点位分布饼图
+    await this.fetchStatistics();
+    await this.fetchMapImage();
     this.initPointPieChart();
   },
+  
+  beforeUnmount() {
+    if (this.mapImageUrl) {
+      URL.revokeObjectURL(this.mapImageUrl);
+    }
+    if (this.pieChart) {
+      this.pieChart.dispose();
+    }
+  },
+  
   methods: {
-    // 获取分类数据
-    async fetchGroupingData() {
+    // 从API获取统计数据
+    async fetchStatistics() {
       try {
-        const response = await api8000.get(`/api/unit-grouping/h_xtfx`);
+        const response = await api8000.get('/api/unit-grouping/statistics');
         
         if (response.data.success) {
-          this.groupingData = response.data.data;
           this.statistics = response.data.statistics;
         }
       } catch (error) {
-        console.error('获取分类数据失败:', error);
-      }
-    },
-    
-    // 初始化地图
-    async initMap() {
-      // 加载TMap SDK
-      const TMap = await this.loadSDK();
-      
-      // 创建地图实例
-      this.map = new TMap.Map(this.$refs.mapContainer, {
-        center: new TMap.LatLng(24.81088, 113.59762),
-        zoom: 12,
-        mapStyleId: 'style1'
-      });
-      
-      // 加载GeoJSON数据
-      const geojsonData = await this.loadGeoJSON('/data/单元格.geojson');
-      
-      // 初始化GeoJSON图层 - 传递TMap对象
-      this.initMapWithGeoJSON(geojsonData, TMap);
-
-      //加载并初始化韶关边界图层
-      const shaoguanBoundaryGeojson = await this.fetchShaoguanBoundary();
-      this.initShaoguanBoundaryLayer(shaoguanBoundaryGeojson,TMap);
-    },
-    
-    // 加载SDK
-    loadSDK() {
-      return new Promise((resolve, reject) => {
-        if (window.TMap) return resolve(window.TMap);
-        
-        const script = document.createElement('script');
-        script.src = `https://map.qq.com/api/gljs?v=2.exp&libraries=basic,service,vector&key=${import.meta.env.VITE_TMAP_KEY}&callback=initTMap`;
-        
-        window.initTMap = () => {
-          if (!window.TMap) {
-            reject(new Error('TMap SDK 加载失败'));
-            return;
-          }
-          resolve(window.TMap);
-        };
-        
-        script.onerror = (err) => {
-          reject(new Error('加载地图SDK失败'));
-          document.head.removeChild(script);
-        };
-        
-        document.head.appendChild(script);
-      });
-    },
-
-    // 新增:获取韶关市边界GeoJSON数据
-  async fetchShaoguanBoundary() {
-    try {
-      // 替换为用户实际的韶关市边界接口地址
-      const boundaryUrl = "http://localhost:8000/api/vector/boundary?table_name=counties&field_name=city_name&field_value=%E9%9F%B6%E5%85%B3%E5%B8%82"; 
-      const response = await fetch(boundaryUrl);
-      
-      if (!response.ok) {
-        throw new Error(`获取韶关市边界失败: ${response.statusText}`);
+        console.error('获取统计信息失败:', error);
       }
-      
-      const boundaryGeoJSON = await response.json();
-      // 验证GeoJSON格式(必须是FeatureCollection,geometry为Polygon/MultiPolygon)
-      if (boundaryGeoJSON.type !== "FeatureCollection") {
-        throw new Error("韶关市边界数据不是有效的FeatureCollection");
-      }
-      return boundaryGeoJSON;
-    } catch (error) {
-      console.error("韶关市边界数据加载失败:", error);
-      return { type: "FeatureCollection", features: [] }; // 返回空数据避免地图崩溃
-    }
-  },
-  
-  // 新增:初始化韶关市边界图层
-  initShaoguanBoundaryLayer(boundaryGeoJSON, TMap) {
-    try {
-      if (!boundaryGeoJSON.features.length) {
-        console.warn("韶关市边界数据为空,不渲染边界");
-        return;
-      }
-
-       const lightEarthYellow = "rgba(245, 222, 179, 0.4)";
-
-      // 创建边界图层(独立于单元格图层)
-      this.shaoguanBoundaryLayer = new TMap.vector.GeoJSONLayer({
-        map: this.map, // 绑定到现有地图实例
-        data: boundaryGeoJSON, // 边界GeoJSON数据
-        // 边界样式配置:突出边框,透明填充(不遮挡下方单元格)
-        polygonStyle: new TMap.PolygonStyle({
-          color: lightEarthYellow, // 填充色:透明
-          showBorder: true, // 显示边框
-          borderColor: '#000000', // 边框颜色:蓝色(醒目)
-          borderWidth: 3 // 边框宽度:3px(确保清晰)
-        })
-      });
-
-      // 确保边界图层在最上层(覆盖单元格,不遮挡交互)
-      this.shaoguanBoundaryLayer.setZIndex(1); 
-    } catch (error) {
-      console.error("初始化韶关市边界图层失败:", error);
-    }
     },
     
-    // 加载GeoJSON数据
-    async loadGeoJSON(url) {
+    // 从API获取地图图片
+    async fetchMapImage() {
       try {
-        const response = await fetch(url);
-        if (!response.ok) {
-          throw new Error(`加载GeoJSON失败: ${response.statusText}`);
-        }
-        return await response.json();
-      } catch (error) {
-        console.error('加载GeoJSON数据失败:', error);
-        return { type: 'FeatureCollection', features: [] };
-      }
-    },
-    
-    // 初始化GeoJSON图层 - 使用MultiPolygon的setStyles方法
-    initMapWithGeoJSON(geojsonData, TMap) {
-      try {
-        // 创建分类映射表
-        const categoryMap = {};
-        this.groupingData.forEach(item => {
-          categoryMap[item.OBJECTID] = item.h_xtfx;
-        });
-        
-        // 处理GeoJSON特征
-        geojsonData.features.forEach(feature => {
-          const objectId = feature.properties.OBJECTID;
-          const category = categoryMap[objectId];
-          
-          // 添加分类属性
-          feature.properties.category = category;
-        });
-        
-        // 检查TMap对象是否有效
-        if (!TMap || !TMap.PolygonStyle) {
-          throw new Error('TMap对象无效,缺少PolygonStyle');
-        }
-        
-        // 创建GeoJSON图层
-        this.geoJSONLayer = new TMap.vector.GeoJSONLayer({
-          map: this.map,
-          data: geojsonData,
-          polygonStyle: new TMap.PolygonStyle({
-            color: 'rgba(0,0,0,0)',
-            showBorder: false
-          })
-        });
-
+        this.loadingMap = true;
+        this.mapError = null;
         
-        // 获取多边形覆盖层
-        this.multiPolygon = this.geoJSONLayer.getGeometryOverlay('polygon');
-        this.multiPolygon.setMap(this.map);
-        const polygons = this.multiPolygon.getGeometries();
-        
-        // 创建样式映射
-        const styles = {};
-        
-        // 遍历所有多边形,为每个多边形设置样式和唯一ID
-        // 遍历所有多边形,为每个多边形设置样式
-        polygons.forEach((polygon) => {
-          // 直接访问properties属性
-          const properties = polygon.properties;
-          const category = properties.category;
-          
-          // 使用多边形的id作为样式ID的键
-          const styleId = `style_${polygon.id}`;
-          
-          // 根据分类设置颜色
-          const color = category ? this.categoryColors[category] : '#CCCCCC';
-          
-          // 添加样式到映射表
-          styles[styleId] = new TMap.PolygonStyle({
-            color: color,
-            showBorder: true,
-            borderColor: '#000000',
-            borderWidth: 2
-          });
-          
-          // 关键修复:为每个多边形设置样式ID(正确方式)
-          polygon.styleId = styleId; // 直接设置属性
+        // 调用visualize接口获取地图图片
+        const response = await api8000.get('/api/unit-grouping/visualize', {
+          params: {
+            area: '乐昌市',
+            level: 'county',
+            colormap: 'viridis'
+          },
+          responseType: 'blob'
         });
-        // 使用setStyles方法一次性设置所有样式
-        this.multiPolygon.setStyles(styles);
         
-        // 更新几何体以应用新样式
-        this.multiPolygon.updateGeometries(polygons);
+        // 创建图片Blob URL
+        const blob = new Blob([response.data], { type: 'image/jpeg' });
+        this.mapImageUrl = URL.createObjectURL(blob);
         
-        this.geoJSONLayer.setZIndex(100);
-      
       } catch (error) {
-        console.error('初始化GeoJSON图层失败:', error);
+        console.error('获取地图图片失败:', error);
+        this.mapError = error.response?.data?.detail || '获取地图失败';
+      } finally {
+        this.loadingMap = false;
       }
     },
     
@@ -317,10 +177,10 @@ export default {
       const chartDom = this.$refs.pointPieChart;
       if (!chartDom) return;
       
-      const chart = echarts.init(chartDom);
+      this.pieChart = echarts.init(chartDom);
       
       // 准备饼图数据
-      const pieData = Object.entries(this.statistics.category_distribution).map(([name, value]) => ({
+      const pieData = Object.entries(this.statistics.point_distribution).map(([name, value]) => ({
         name,
         value,
         itemStyle: { color: this.categoryColors[name] || '#CCCCCC' }
@@ -378,11 +238,11 @@ export default {
         ]
       };
       
-      chart.setOption(option);
+      this.pieChart.setOption(option);
       
       // 响应式调整
       window.addEventListener('resize', () => {
-        chart.resize();
+        this.pieChart.resize();
       });
     }
   }
@@ -390,6 +250,23 @@ export default {
 </script>
 
 <style scoped>
+/* 整体布局优化 */
+.agricultural-input-management {
+  padding: 20px;
+  background: linear-gradient(
+    135deg, 
+    rgba(230, 247, 255, 0.7) 0%, 
+    rgba(240, 248, 255, 0.7) 100%
+  );
+  min-height: 100vh;
+  box-sizing: border-box;
+}
+
+.page-container {
+  width: 100%;
+  height: 100%;
+}
+
 /* 顶部信息卡片区域 */
 .dashboard {
   display: flex;
@@ -540,9 +417,69 @@ export default {
 
 .map-container {
   width: 100%;
-  height: 60vh; /* 调整为60vh,更紧凑 */
-  min-height: 400px; /* 降低最小高度 */
-  max-height: 700px; /* 添加最大高度限制 */
+  height: 100%;
+  min-height: 400px;
+  max-height: 700px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: #f9f9f9;
+  border-radius: 0 0 12px 12px;
+  overflow: hidden;
+}
+
+.map-image {
+  max-width: 100%;
+  max-height: 500px;
+  object-fit: contain;
+}
+
+.loading-container, .error-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 300px;
+  gap: 15px;
+}
+
+.loading-container {
+  color: #47C3B9;
+}
+
+.error-container {
+  color: #F56C6C;
+}
+
+.loading-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+  animation: rotate 2s linear infinite;
+}
+
+.error-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+}
+
+.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;
+}
+
+@keyframes rotate {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
 }
 
 /* 响应式调整 */

+ 1 - 1
src/views/User/hmInFlux/grainRemoval/grainRemovalInputFlux.vue

@@ -169,7 +169,7 @@ export default {
         // 获取可视化图片
         try {
           const imageResponse = await api8000.get(
-            `/api/cd-flux-removal/grain-removal/visualize?area=${encodeURIComponent(props.area)}&level=county`,
+            `/api/cd-flux-removal/grain-removal/latest-map/${encodeURIComponent(props.area)}`,
             { responseType: 'blob' }
           );
           

+ 1 - 1
src/views/User/hmInFlux/strawRemoval/strawRemovalInputFlux.vue

@@ -165,7 +165,7 @@ export default {
         // 获取可视化图片
         try {
           const imageResponse = await api8000.get(
-            `/api/cd-flux-removal/straw-removal/visualize?area=${encodeURIComponent(props.area)}&level=county`,
+            `/api/cd-flux-removal/straw-removal/latest-map/${encodeURIComponent(props.area)}`,
             { responseType: 'blob' }
           );
           

+ 1 - 1
src/views/User/hmInFlux/subsurfaceLeakage/subsurfaceLeakageInputFlux.vue

@@ -64,7 +64,7 @@ export default {
       
       try {
         const response = await api8000.get(
-          `/api/cd-flux-removal/groundwater_leaching/visualize`,
+          `/api/cd-flux-removal/groundwater_leaching/latest-map/乐昌市`,
           {
             params: {
               area: this.area,

+ 1 - 1
src/views/User/hmInFlux/surfaceRunoff/surfaceRunoffInputFlux.vue

@@ -64,7 +64,7 @@ export default {
       
       try {
         const response = await api8000.get(
-          `/api/cd-flux-removal/surface_runoff/visualize`,
+          `/api/cd-flux-removal/surface_runoff/latest-map/乐昌市`,
           {
             params: {
               area: this.area,