Bladeren bron

补充Cd模型上传并计算

yangtaodemon 2 dagen geleden
bovenliggende
commit
3651feac03

+ 1 - 1
.env

@@ -1,2 +1,2 @@
-VITE_API_URL= 'https://soilgd.com:5000'
+VITE_API_URL= 'https://www.soilgd.com:5000'
 VITE_TMAP_KEY='2R4BZ-FF4RM-Q6C6U-6TCJL-O2EN5-DVFH5'

+ 7 - 0
components.d.ts

@@ -12,6 +12,7 @@ declare module 'vue' {
     AppAsideForTab2: typeof import('./src/components/layout/AppAsideForTab2.vue')['default']
     AppHeader: typeof import('./src/components/layout/AppHeader.vue')['default']
     AppLayout: typeof import('./src/components/layout/AppLayout.vue')['default']
+    ElAlert: typeof import('element-plus/es')['ElAlert']
     ElAside: typeof import('element-plus/es')['ElAside']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElButton: typeof import('element-plus/es')['ElButton']
@@ -33,6 +34,7 @@ declare module 'vue' {
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
@@ -41,6 +43,8 @@ declare module 'vue' {
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabs: typeof import('element-plus/es')['ElTabs']
+    ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElUpload: typeof import('element-plus/es')['ElUpload']
     HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
     IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
     IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
@@ -53,4 +57,7 @@ declare module 'vue' {
     TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
     WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default']
   }
+  export interface ComponentCustomProperties {
+    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
+  }
 }

+ 267 - 119
src/views/User/cadmiumPrediction/CropCadmiumPrediction.vue

@@ -2,35 +2,67 @@
   <div class="container">
     <!-- 顶部操作栏 -->
     <div class="toolbar">
-      <el-button class="custom-button" :loading="isCalculating" @click="calculate">计算</el-button>
-      <el-button class="custom-button" :disabled="isCalculating || !mapBlob" @click="exportMap">导出地图</el-button>
-      <el-button class="custom-button" :disabled="isCalculating || !histogramBlob" @click="exportHistogram">导出直方图</el-button>
-      <el-button class="custom-button" :disabled="isCalculating || !tableData.length" @click="exportData">导出数据</el-button>
+      <!-- 文件上传区域 -->
+      <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">
+          <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="!tableData.length" @click="exportData">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出数据</el-button>
+      </div>
     </div>
 
-    <!-- 主体内容区,计算后显示 -->
-    <div v-if="result" class="content-area">
-      <!-- 地图区域 - 现在包含两个图片展示区 -->
-      <div class="map-area">
-        <div class="map-container">
-          <!-- 地图展示 -->
-          <div class="map-section">
-            <h3>作物态Cd预测地图</h3>
-            <img v-if="mapImageUrl" :src="mapImageUrl" alt="作物态Cd预测地图" class="map-image">
-             <div v-if="loadingMap" class="loading-container">
-              <el-icon class="loading-icon"><Loading /></el-icon>
-              <span>地图加载中...</span>
-            </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>
-          
-          <!-- 直方图展示 -->
-          <div class="histogram-section">
-            <h3>作物态Cd预测直方图</h3>
-            <img v-if="histogramImageUrl" :src="histogramImageUrl" alt="作物态Cd预测直方图" class="histogram-image">
-             <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>
@@ -53,143 +85,209 @@
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
 import axios from 'axios';
-import { Loading } from '@element-plus/icons-vue';
+import { Loading, Upload, Picture, Histogram, Download, Document } from '@element-plus/icons-vue';
 
 export default {
   name: 'CropCadmiumPrediction',
-  components: { Loading },
+  components: { Loading, Upload, Picture, Histogram, Download, Document },
   data() {
     return {
       isCalculating: false,
       loadingMap: false,
       loadingHistogram: false,
-      result: false,
       tableData: [],
-      mapImageUrl: null,       // 存储地图图片 URL
-      histogramImageUrl: null, // 存储直方图图片 URL
-      mapBlob: null,           // 存储地图原始Blob数据
-      histogramBlob: null      // 存储直方图原始Blob数据
+      mapImageUrl: null,
+      histogramImageUrl: null,
+      mapBlob: null,
+      histogramBlob: null,
+      selectedFile: null,
+      countyName: '乐昌市' // 默认县市名称
     };
   },
+  mounted() {
+    // 组件挂载时获取最新数据
+    this.fetchLatestResults();
+  },
   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(
+          `https://soilgd.com:8000/api/cd-prediction/crop-cd/latest-map/${this.countyName}`,
+          { 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(
+          `https://soilgd.com:8000/api/cd-prediction/crop-cd/latest-histogram/${this.countyName}`,
+          { responseType: 'blob' }
+        );
+        
+        this.histogramBlob = response.data;
+        this.histogramImageUrl = URL.createObjectURL(this.histogramBlob);
+      } catch (error) {
+        console.error('获取最新直方图失败:', error);
+        this.$message.warning('获取最新直方图失败,请先执行预测');
+      }
+    },
+    
+    // 上传并计算
     async calculate() {
+      if (!this.selectedFile) {
+        this.$message.warning('请先选择CSV文件');
+        return;
+      }
+      
       try {
-        // 重置状态
         this.isCalculating = true;
         this.loadingMap = true;
         this.loadingHistogram = true;
-        this.result = false;
-        this.mapImageUrl = null;
-        this.histogramImageUrl = null;
-        this.mapBlob = null;
-        this.histogramBlob = null;
-        this.tableData = [];
         
-        // 调用有效态Cd地图接口
-        const mapResponse = await axios.post('https://soilgd.com:8000/api/cd-prediction/crop-cd/generate-and-get-map', {}, {
-          responseType: 'blob'
-        });
+        // 创建FormData
+        const formData = new FormData();
+        formData.append('county_name', this.countyName);
+        formData.append('data_file', this.selectedFile);
         
-        // 调用有效态Cd直方图接口
-        const histogramResponse = await axios.post('https://soilgd.com:8000/api/cd-prediction/crop-cd/generate-and-get-histogram', {}, {
-          responseType: 'blob'
-        });
+        // 调用有效态Cd地图接口
+        const mapResponse = await axios.post(
+          'https://soilgd.com:8000/api/cd-prediction/crop-cd/generate-and-get-map',
+          formData,
+          {
+            headers: {
+              'Content-Type': 'multipart/form-data'
+            },
+            responseType: 'blob'
+          }
+        );
         
-        // 保存原始Blob数据用于导出
+        // 保存地图数据
         this.mapBlob = mapResponse.data;
-        this.histogramBlob = histogramResponse.data;
-        
-        // 为图片数据创建 URL
         this.mapImageUrl = URL.createObjectURL(this.mapBlob);
-        this.histogramImageUrl = URL.createObjectURL(this.histogramBlob);
         
-        // 更新表格数据
-        this.result = true;
+        // 更新后重新获取直方图(因为生成新数据后直方图也会更新)
+        await this.fetchLatestHistogram();
+        
+        // 更新表格数据(示例)
         this.tableData = [
           { name: '样本1', value: 10, unit: 'mg/L', description: '描述1' },
           { name: '样本2', value: 20, unit: 'mg/L', description: '描述2' }
         ];
         
+        this.$message.success('计算完成!');
+        
       } catch (error) {
-        console.error('获取地图或直方图失败:', error);
-        this.$message.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 === 404) {
+            errorMessage = '不支持的县市:' + this.countyName;
+          } 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;
       }
     },
     
-    // 导出地图方法
+    // 导出地图
     exportMap() {
       if (!this.mapBlob) {
         this.$message.warning('请先计算生成地图');
         return;
       }
       
-      // 创建下载链接并添加到文档中
       const link = document.createElement('a');
       link.href = URL.createObjectURL(this.mapBlob);
-      link.download = '作物态Cd预测地图.jpg';
-      link.style.display = 'none';
-      document.body.appendChild(link);
-      
-      // 触发点击事件
+      link.download = `${this.countyName}_作物态Cd预测地图.jpg`;
       link.click();
-      
-      // 清理临时URL和链接元素
-      setTimeout(() => {
-        document.body.removeChild(link);
-        URL.revokeObjectURL(link.href);
-      }, 100);
+      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.style.display = 'none';
-      document.body.appendChild(link);
-      
-      // 触发点击事件
+      link.download = `${this.countyName}_作物态Cd预测直方图.jpg`;
       link.click();
-      
-      // 清理临时URL和链接元素
-      setTimeout(() => {
-        document.body.removeChild(link);
-        URL.revokeObjectURL(link.href);
-      }, 100);
+      URL.revokeObjectURL(link.href);
     },
     
-    // 导出数据方法
+    // 导出数据
     exportData() {
-      // 创建工作簿
-      const workbook = XLSX.utils.book_new();
+      if (!this.tableData.length) {
+        this.$message.warning('暂无数据可导出');
+        return;
+      }
       
-      // 创建数据表
+      const workbook = XLSX.utils.book_new();
       const worksheet = XLSX.utils.json_to_sheet(this.tableData);
-      
-      // 将工作表添加到工作簿
       XLSX.utils.book_append_sheet(workbook, worksheet, '作物态Cd数据');
-      
-      // 生成Excel文件
       const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
       const excelData = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
-      
-      // 保存文件
-      saveAs(excelData, 'crop-cd-data.xlsx');
+      saveAs(excelData, `${this.countyName}_作物态Cd数据.xlsx`);
     }
   },
   beforeDestroy() {
-    // 组件销毁时释放图片 URL 防止内存泄漏
     if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
     if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
   }
@@ -197,7 +295,6 @@ export default {
 </script>
 
 <style scoped>
-/* 样式保持不变 */
 .container {
   padding: 20px;
   background-color: #f5f7fa;
@@ -206,10 +303,37 @@ export default {
 }
 
 .toolbar {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  margin-bottom: 20px;
+  padding: 15px;
+  background-color: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.upload-section {
   display: flex;
   align-items: center;
+  gap: 15px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid #eee;
+}
+
+.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;
-  margin-bottom: 20px;
 }
 
 .custom-button {
@@ -219,57 +343,54 @@ export default {
   border-radius: 155px;
   padding: 10px 20px;
   font-weight: bold;
+  display: flex;
+  align-items: center;
+}
+
+.upload-icon {
+  margin-right: 5px;
 }
 
 .content-area {
   display: flex;
+  flex-direction: column;
   gap: 20px;
 }
 
-.map-area {
-  flex: 1;
-  min-width: 300px;
-}
-
-.map-container {
+/* 横向布局容器 */
+.horizontal-container {
   display: flex;
-  flex-direction: column;
+  flex-wrap: wrap;
   gap: 20px;
+  width: 100%;
 }
 
 .map-section, .histogram-section {
+  flex: 1;
+  min-width: 300px; /* 最小宽度,确保在小屏幕上也能正常显示 */
   background-color: white;
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
+  min-height: 400px;
 }
 
 .map-image, .histogram-image {
   width: 100%;
+  height: 100%;
   max-height: 600px;
   object-fit: contain;
   border-radius: 4px;
 }
 
-.map-placeholder {
-  background-color: #cce5ff;
-  height: 200px;
-  border-radius: 8px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-weight: bold;
-  font-size: 16px;
-  color: #003366;
-}
-
 .table-area {
-  flex: 1;
+  width: 100%;
   background-color: white;
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  margin-top: 20px;
 }
 
 .loading-container {
@@ -277,10 +398,25 @@ export default {
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  height: 200px;
+  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;
+}
+
 .loading-icon {
   font-size: 36px;
   margin-bottom: 10px;
@@ -295,4 +431,16 @@ export default {
     transform: rotate(360deg);
   }
 }
+
+/* 响应式布局调整 */
+@media (max-width: 992px) {
+  .horizontal-container {
+    flex-direction: column;
+  }
+  
+  .map-section, .histogram-section {
+    width: 100%;
+    flex: none;
+  }
+}
 </style>

+ 267 - 119
src/views/User/cadmiumPrediction/EffectiveCadmiumPrediction.vue

@@ -2,35 +2,67 @@
   <div class="container">
     <!-- 顶部操作栏 -->
     <div class="toolbar">
-      <el-button class="custom-button" :loading="isCalculating" @click="calculate">计算</el-button>
-      <el-button class="custom-button" :disabled="isCalculating || !mapBlob" @click="exportMap">导出地图</el-button>
-      <el-button class="custom-button" :disabled="isCalculating || !histogramBlob" @click="exportHistogram">导出直方图</el-button>
-      <el-button class="custom-button" :disabled="isCalculating || !tableData.length" @click="exportData">导出数据</el-button>
+      <!-- 文件上传区域 -->
+      <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">
+          <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="!tableData.length" @click="exportData">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出数据</el-button>
+      </div>
     </div>
 
-    <!-- 主体内容区,计算后显示 -->
-    <div v-if="result" class="content-area">
-      <!-- 地图区域 - 现在包含两个图片展示区 -->
-      <div class="map-area">
-        <div class="map-container">
-          <!-- 地图展示 -->
-          <div class="map-section">
-            <h3>有效态Cd预测地图</h3>
-            <img v-if="mapImageUrl" :src="mapImageUrl" alt="有效态Cd预测地图" class="map-image">
-             <div v-if="loadingMap" class="loading-container">
-              <el-icon class="loading-icon"><Loading /></el-icon>
-              <span>地图加载中...</span>
-            </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>
-          
-          <!-- 直方图展示 -->
-          <div class="histogram-section">
-            <h3>有效态Cd预测直方图</h3>
-            <img v-if="histogramImageUrl" :src="histogramImageUrl" alt="有效态Cd预测直方图" class="histogram-image">
-             <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>
@@ -53,143 +85,209 @@
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
 import axios from 'axios';
-import { Loading } from '@element-plus/icons-vue';
+import { Loading, Upload, Picture, Histogram, Download, Document } from '@element-plus/icons-vue';
 
 export default {
   name: 'EffectiveCadmiumPrediction',
-  components: { Loading },
+  components: { Loading, Upload, Picture, Histogram, Download, Document },
   data() {
     return {
       isCalculating: false,
       loadingMap: false,
       loadingHistogram: false,
-      result: false,
       tableData: [],
-      mapImageUrl: null,       // 存储地图图片 URL
-      histogramImageUrl: null, // 存储直方图图片 URL
-      mapBlob: null,           // 存储地图原始Blob数据
-      histogramBlob: null      // 存储直方图原始Blob数据
+      mapImageUrl: null,
+      histogramImageUrl: null,
+      mapBlob: null,
+      histogramBlob: null,
+      selectedFile: null,
+      countyName: '乐昌市' // 默认县市名称
     };
   },
+  mounted() {
+    // 组件挂载时获取最新数据
+    this.fetchLatestResults();
+  },
   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(
+          `https://soilgd.com:8000/api/cd-prediction/effective-cd/latest-map/${this.countyName}`,
+          { 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(
+          `https://soilgd.com:8000/api/cd-prediction/effective-cd/latest-histogram/${this.countyName}`,
+          { responseType: 'blob' }
+        );
+        
+        this.histogramBlob = response.data;
+        this.histogramImageUrl = URL.createObjectURL(this.histogramBlob);
+      } catch (error) {
+        console.error('获取最新直方图失败:', error);
+        this.$message.warning('获取最新直方图失败,请先执行预测');
+      }
+    },
+    
+    // 上传并计算
     async calculate() {
+      if (!this.selectedFile) {
+        this.$message.warning('请先选择CSV文件');
+        return;
+      }
+      
       try {
-        // 重置状态
         this.isCalculating = true;
         this.loadingMap = true;
         this.loadingHistogram = true;
-        this.result = false;
-        this.mapImageUrl = null;
-        this.histogramImageUrl = null;
-        this.mapBlob = null;
-        this.histogramBlob = null;
-        this.tableData = [];
         
-        // 调用有效态Cd地图接口
-        const mapResponse = await axios.post('https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-map', {}, {
-          responseType: 'blob'
-        });
+        // 创建FormData
+        const formData = new FormData();
+        formData.append('county_name', this.countyName);
+        formData.append('data_file', this.selectedFile);
         
-        // 调用有效态Cd直方图接口
-        const histogramResponse = await axios.post('https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-histogram', {}, {
-          responseType: 'blob'
-        });
+        // 调用有效态Cd地图接口
+        const mapResponse = await axios.post(
+          'https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-map',
+          formData,
+          {
+            headers: {
+              'Content-Type': 'multipart/form-data'
+            },
+            responseType: 'blob'
+          }
+        );
         
-        // 保存原始Blob数据用于导出
+        // 保存地图数据
         this.mapBlob = mapResponse.data;
-        this.histogramBlob = histogramResponse.data;
-        
-        // 为图片数据创建 URL
         this.mapImageUrl = URL.createObjectURL(this.mapBlob);
-        this.histogramImageUrl = URL.createObjectURL(this.histogramBlob);
         
-        // 更新表格数据
-        this.result = true;
+        // 更新后重新获取直方图(因为生成新数据后直方图也会更新)
+        await this.fetchLatestHistogram();
+        
+        // 更新表格数据(示例)
         this.tableData = [
           { name: '样本1', value: 10, unit: 'mg/L', description: '描述1' },
           { name: '样本2', value: 20, unit: 'mg/L', description: '描述2' }
         ];
         
+        this.$message.success('计算完成!');
+        
       } catch (error) {
-        console.error('获取地图或直方图失败:', error);
-        this.$message.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 === 404) {
+            errorMessage = '不支持的县市:' + this.countyName;
+          } 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;
       }
     },
     
-    // 导出地图方法
+    // 导出地图
     exportMap() {
       if (!this.mapBlob) {
         this.$message.warning('请先计算生成地图');
         return;
       }
       
-      // 创建下载链接并添加到文档中
       const link = document.createElement('a');
       link.href = URL.createObjectURL(this.mapBlob);
-      link.download = '有效态Cd预测地图.jpg';
-      link.style.display = 'none';
-      document.body.appendChild(link);
-      
-      // 触发点击事件
+      link.download = `${this.countyName}_有效态Cd预测地图.jpg`;
       link.click();
-      
-      // 清理临时URL和链接元素
-      setTimeout(() => {
-        document.body.removeChild(link);
-        URL.revokeObjectURL(link.href);
-      }, 100);
+      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.style.display = 'none';
-      document.body.appendChild(link);
-      
-      // 触发点击事件
+      link.download = `${this.countyName}_有效态Cd预测直方图.jpg`;
       link.click();
-      
-      // 清理临时URL和链接元素
-      setTimeout(() => {
-        document.body.removeChild(link);
-        URL.revokeObjectURL(link.href);
-      }, 100);
+      URL.revokeObjectURL(link.href);
     },
     
-    // 导出数据方法
+    // 导出数据
     exportData() {
-      // 创建工作簿
-      const workbook = XLSX.utils.book_new();
+      if (!this.tableData.length) {
+        this.$message.warning('暂无数据可导出');
+        return;
+      }
       
-      // 创建数据表
+      const workbook = XLSX.utils.book_new();
       const worksheet = XLSX.utils.json_to_sheet(this.tableData);
-      
-      // 将工作表添加到工作簿
       XLSX.utils.book_append_sheet(workbook, worksheet, '有效态Cd数据');
-      
-      // 生成Excel文件
       const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
       const excelData = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
-      
-      // 保存文件
-      saveAs(excelData, 'effective-cd-data.xlsx');
+      saveAs(excelData, `${this.countyName}_有效态Cd数据.xlsx`);
     }
   },
   beforeDestroy() {
-    // 组件销毁时释放图片 URL 防止内存泄漏
     if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
     if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
   }
@@ -197,7 +295,6 @@ export default {
 </script>
 
 <style scoped>
-/* 样式保持不变 */
 .container {
   padding: 20px;
   background-color: #f5f7fa;
@@ -206,10 +303,37 @@ export default {
 }
 
 .toolbar {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  margin-bottom: 20px;
+  padding: 15px;
+  background-color: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.upload-section {
   display: flex;
   align-items: center;
+  gap: 15px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid #eee;
+}
+
+.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;
-  margin-bottom: 20px;
 }
 
 .custom-button {
@@ -219,57 +343,54 @@ export default {
   border-radius: 155px;
   padding: 10px 20px;
   font-weight: bold;
+  display: flex;
+  align-items: center;
+}
+
+.upload-icon {
+  margin-right: 5px;
 }
 
 .content-area {
   display: flex;
+  flex-direction: column;
   gap: 20px;
 }
 
-.map-area {
-  flex: 1;
-  min-width: 300px;
-}
-
-.map-container {
+/* 横向布局容器 */
+.horizontal-container {
   display: flex;
-  flex-direction: column;
+  flex-wrap: wrap;
   gap: 20px;
+  width: 100%;
 }
 
 .map-section, .histogram-section {
+  flex: 1;
+  min-width: 300px; /* 最小宽度,确保在小屏幕上也能正常显示 */
   background-color: white;
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
+  min-height: 400px;
 }
 
 .map-image, .histogram-image {
   width: 100%;
+  height: 100%;
   max-height: 600px;
   object-fit: contain;
   border-radius: 4px;
 }
 
-.map-placeholder {
-  background-color: #cce5ff;
-  height: 200px;
-  border-radius: 8px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-weight: bold;
-  font-size: 16px;
-  color: #003366;
-}
-
 .table-area {
-  flex: 1;
+  width: 100%;
   background-color: white;
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  margin-top: 20px;
 }
 
 .loading-container {
@@ -277,10 +398,25 @@ export default {
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  height: 200px;
+  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;
+}
+
 .loading-icon {
   font-size: 36px;
   margin-bottom: 10px;
@@ -295,4 +431,16 @@ export default {
     transform: rotate(360deg);
   }
 }
+
+/* 响应式布局调整 */
+@media (max-width: 992px) {
+  .horizontal-container {
+    flex-direction: column;
+  }
+  
+  .map-section, .histogram-section {
+    width: 100%;
+    flex: none;
+  }
+}
 </style>

+ 267 - 119
src/views/User/cadmiumPrediction/TotalCadmiumPrediction.vue

@@ -2,35 +2,67 @@
   <div class="container">
     <!-- 顶部操作栏 -->
     <div class="toolbar">
-      <el-button class="custom-button" :loading="isCalculating" @click="calculate">计算</el-button>
-      <el-button class="custom-button" :disabled="isCalculating || !mapBlob" @click="exportMap">导出地图</el-button>
-      <el-button class="custom-button" :disabled="isCalculating || !histogramBlob" @click="exportHistogram">导出直方图</el-button>
-      <el-button class="custom-button" :disabled="isCalculating || !tableData.length" @click="exportData">导出数据</el-button>
+      <!-- 文件上传区域 -->
+      <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">
+          <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="!tableData.length" @click="exportData">
+          <el-icon class="upload-icon"><Download /></el-icon>
+          导出数据</el-button>
+      </div>
     </div>
 
-    <!-- 主体内容区,计算后显示 -->
-    <div v-if="result" class="content-area">
-      <!-- 地图区域 - 现在包含两个图片展示区 -->
-      <div class="map-area">
-        <div class="map-container">
-          <!-- 地图展示 -->
-          <div class="map-section">
-            <h3>有效态Cd预测地图</h3>
-            <img v-if="mapImageUrl" :src="mapImageUrl" alt="有效态Cd预测地图" class="map-image">
-             <div v-if="loadingMap" class="loading-container">
-              <el-icon class="loading-icon"><Loading /></el-icon>
-              <span>地图加载中...</span>
-            </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>
-          
-          <!-- 直方图展示 -->
-          <div class="histogram-section">
-            <h3>有效态Cd预测直方图</h3>
-            <img v-if="histogramImageUrl" :src="histogramImageUrl" alt="有效态Cd预测直方图" class="histogram-image">
-             <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>
@@ -53,143 +85,209 @@
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
 import axios from 'axios';
-import { Loading } from '@element-plus/icons-vue';
+import { Loading, Upload, Picture, Histogram, Download, Document } from '@element-plus/icons-vue';
 
 export default {
   name: 'EffectiveCadmiumPrediction',
-  components: { Loading },
+  components: { Loading, Upload, Picture, Histogram, Download, Document },
   data() {
     return {
       isCalculating: false,
       loadingMap: false,
       loadingHistogram: false,
-      result: false,
       tableData: [],
-      mapImageUrl: null,       // 存储地图图片 URL
-      histogramImageUrl: null, // 存储直方图图片 URL
-      mapBlob: null,           // 存储地图原始Blob数据
-      histogramBlob: null      // 存储直方图原始Blob数据
+      mapImageUrl: null,
+      histogramImageUrl: null,
+      mapBlob: null,
+      histogramBlob: null,
+      selectedFile: null,
+      countyName: '乐昌市' // 默认县市名称
     };
   },
+  mounted() {
+    // 组件挂载时获取最新数据
+    this.fetchLatestResults();
+  },
   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(
+          `https://soilgd.com:8000/api/cd-prediction/effective-cd/latest-map/${this.countyName}`,
+          { 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(
+          `https://soilgd.com:8000/api/cd-prediction/effective-cd/latest-histogram/${this.countyName}`,
+          { responseType: 'blob' }
+        );
+        
+        this.histogramBlob = response.data;
+        this.histogramImageUrl = URL.createObjectURL(this.histogramBlob);
+      } catch (error) {
+        console.error('获取最新直方图失败:', error);
+        this.$message.warning('获取最新直方图失败,请先执行预测');
+      }
+    },
+    
+    // 上传并计算
     async calculate() {
+      if (!this.selectedFile) {
+        this.$message.warning('请先选择CSV文件');
+        return;
+      }
+      
       try {
-        // 重置状态
         this.isCalculating = true;
         this.loadingMap = true;
         this.loadingHistogram = true;
-        this.result = false;
-        this.mapImageUrl = null;
-        this.histogramImageUrl = null;
-        this.mapBlob = null;
-        this.histogramBlob = null;
-        this.tableData = [];
         
-        // 调用有效态Cd地图接口
-        const mapResponse = await axios.post('https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-map', {}, {
-          responseType: 'blob'
-        });
+        // 创建FormData
+        const formData = new FormData();
+        formData.append('county_name', this.countyName);
+        formData.append('data_file', this.selectedFile);
         
-        // 调用有效态Cd直方图接口
-        const histogramResponse = await axios.post('https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-histogram', {}, {
-          responseType: 'blob'
-        });
+        // 调用有效态Cd地图接口
+        const mapResponse = await axios.post(
+          'https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-map',
+          formData,
+          {
+            headers: {
+              'Content-Type': 'multipart/form-data'
+            },
+            responseType: 'blob'
+          }
+        );
         
-        // 保存原始Blob数据用于导出
+        // 保存地图数据
         this.mapBlob = mapResponse.data;
-        this.histogramBlob = histogramResponse.data;
-        
-        // 为图片数据创建 URL
         this.mapImageUrl = URL.createObjectURL(this.mapBlob);
-        this.histogramImageUrl = URL.createObjectURL(this.histogramBlob);
         
-        // 更新表格数据
-        this.result = true;
+        // 更新后重新获取直方图(因为生成新数据后直方图也会更新)
+        await this.fetchLatestHistogram();
+        
+        // 更新表格数据(示例)
         this.tableData = [
           { name: '样本1', value: 10, unit: 'mg/L', description: '描述1' },
           { name: '样本2', value: 20, unit: 'mg/L', description: '描述2' }
         ];
         
+        this.$message.success('计算完成!');
+        
       } catch (error) {
-        console.error('获取地图或直方图失败:', error);
-        this.$message.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 === 404) {
+            errorMessage = '不支持的县市:' + this.countyName;
+          } 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;
       }
     },
     
-    // 导出地图方法
+    // 导出地图
     exportMap() {
       if (!this.mapBlob) {
         this.$message.warning('请先计算生成地图');
         return;
       }
       
-      // 创建下载链接并添加到文档中
       const link = document.createElement('a');
       link.href = URL.createObjectURL(this.mapBlob);
-      link.download = '有效态Cd预测地图.jpg';
-      link.style.display = 'none';
-      document.body.appendChild(link);
-      
-      // 触发点击事件
+      link.download = `${this.countyName}_有效态Cd预测地图.jpg`;
       link.click();
-      
-      // 清理临时URL和链接元素
-      setTimeout(() => {
-        document.body.removeChild(link);
-        URL.revokeObjectURL(link.href);
-      }, 100);
+      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.style.display = 'none';
-      document.body.appendChild(link);
-      
-      // 触发点击事件
+      link.download = `${this.countyName}_有效态Cd预测直方图.jpg`;
       link.click();
-      
-      // 清理临时URL和链接元素
-      setTimeout(() => {
-        document.body.removeChild(link);
-        URL.revokeObjectURL(link.href);
-      }, 100);
+      URL.revokeObjectURL(link.href);
     },
     
-    // 导出数据方法
+    // 导出数据
     exportData() {
-      // 创建工作簿
-      const workbook = XLSX.utils.book_new();
+      if (!this.tableData.length) {
+        this.$message.warning('暂无数据可导出');
+        return;
+      }
       
-      // 创建数据表
+      const workbook = XLSX.utils.book_new();
       const worksheet = XLSX.utils.json_to_sheet(this.tableData);
-      
-      // 将工作表添加到工作簿
       XLSX.utils.book_append_sheet(workbook, worksheet, '有效态Cd数据');
-      
-      // 生成Excel文件
       const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
       const excelData = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
-      
-      // 保存文件
-      saveAs(excelData, 'effective-cd-data.xlsx');
+      saveAs(excelData, `${this.countyName}_有效态Cd数据.xlsx`);
     }
   },
   beforeDestroy() {
-    // 组件销毁时释放图片 URL 防止内存泄漏
     if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
     if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
   }
@@ -197,7 +295,6 @@ export default {
 </script>
 
 <style scoped>
-/* 样式保持不变 */
 .container {
   padding: 20px;
   background-color: #f5f7fa;
@@ -206,10 +303,37 @@ export default {
 }
 
 .toolbar {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+  margin-bottom: 20px;
+  padding: 15px;
+  background-color: white;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.upload-section {
   display: flex;
   align-items: center;
+  gap: 15px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid #eee;
+}
+
+.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;
-  margin-bottom: 20px;
 }
 
 .custom-button {
@@ -219,57 +343,54 @@ export default {
   border-radius: 155px;
   padding: 10px 20px;
   font-weight: bold;
+  display: flex;
+  align-items: center;
+}
+
+.upload-icon {
+  margin-right: 5px;
 }
 
 .content-area {
   display: flex;
+  flex-direction: column;
   gap: 20px;
 }
 
-.map-area {
-  flex: 1;
-  min-width: 300px;
-}
-
-.map-container {
+/* 横向布局容器 */
+.horizontal-container {
   display: flex;
-  flex-direction: column;
+  flex-wrap: wrap;
   gap: 20px;
+  width: 100%;
 }
 
 .map-section, .histogram-section {
+  flex: 1;
+  min-width: 300px; /* 最小宽度,确保在小屏幕上也能正常显示 */
   background-color: white;
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   position: relative;
+  min-height: 400px;
 }
 
 .map-image, .histogram-image {
   width: 100%;
+  height: 100%;
   max-height: 600px;
   object-fit: contain;
   border-radius: 4px;
 }
 
-.map-placeholder {
-  background-color: #cce5ff;
-  height: 200px;
-  border-radius: 8px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-weight: bold;
-  font-size: 16px;
-  color: #003366;
-}
-
 .table-area {
-  flex: 1;
+  width: 100%;
   background-color: white;
   border-radius: 8px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  margin-top: 20px;
 }
 
 .loading-container {
@@ -277,10 +398,25 @@ export default {
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  height: 200px;
+  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;
+}
+
 .loading-icon {
   font-size: 36px;
   margin-bottom: 10px;
@@ -295,4 +431,16 @@ export default {
     transform: rotate(360deg);
   }
 }
+
+/* 响应式布局调整 */
+@media (max-width: 992px) {
+  .horizontal-container {
+    flex-direction: column;
+  }
+  
+  .map-section, .histogram-section {
+    width: 100%;
+    flex: none;
+  }
+}
 </style>

+ 9 - 2
src/views/User/mapView/tencentMapView.vue

@@ -202,7 +202,7 @@ const initMap = async () => {
     //   map: map,
     //   styles: { default: defaultStyle }
     // })
-    const geojsonData = await loadGeoJSON('/data/单元格.geojson');
+    const geojsonData = await loadGeoJSON('https://soilgd.com/data/output-cd.geojson');
     initMapWithGeoJSON(geojsonData, map);
     await initSurveyDataLayer(map);
     // 绑定点击事件
@@ -493,10 +493,17 @@ function initMapWithGeoJSON(geojsonData, map) {
     geoJSONLayer.setMap(null);
     geoJSONLayer = null;
   }
+  const filteredData = {
+    ...geojsonData,
+    features: geojsonData.features.filter(feature => 
+      feature.properties['raster_value'] !== null && 
+      feature.properties['raster_value'] !== undefined
+    )
+  };
   // 创建 GeoJSONLayer
   geoJSONLayer = new TMap.value.vector.GeoJSONLayer({
     map: map,
-    data: geojsonData,
+    data: filteredData,
     zIndex: 1,
     polygonStyle: new TMap.value.PolygonStyle({ // 必须用 PolygonStyle 类实例
       color: 'rgba(255, 0, 0, 0.25)', 

+ 2 - 2
src/views/api.vue

@@ -3,12 +3,12 @@
 import axios from 'axios';
 
 export const fetchData = async () => {
-  const response = await axios.post('https://soilgd.com:5000/table', { table: 'Datasets' });
+  const response = await axios.post('https://www.soilgd.com:5000/table', { table: 'Datasets' });
   return response.data.rows;
 };
 
 export const trainAndSaveModel = async (trainData) => {
-  const response = await axios.post('https://soilgd.com:5000/train-and-save-model', trainData);
+  const response = await axios.post('https://www.soilgd.com:5000/train-and-save-model', trainData);
   return response;
 };
 </script>

+ 0 - 1
tsconfig.app.json

@@ -1,4 +1,3 @@
-// filepath: c:\Users\lhf\OneDrive\Desktop\AcidWeb\tsconfig.app.json
 {
   "extends": "@vue/tsconfig/tsconfig.dom.json",
   "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],

+ 0 - 5
vite.config.ts

@@ -14,11 +14,6 @@ import IconsResolver from 'unplugin-icons/resolver'
 // https://vite.dev/config/
 export default defineConfig({
   plugins: [
-    {
-      "compilerOptions": {
-        "types": ["node"]
-      }
-    },
     vue(),
     vueDevTools(),
     AutoImport({