Browse Source

添加Cd模型预测

yangtaodemon 3 days ago
parent
commit
5fb71e1d19

+ 1 - 1
AcidWeb/.env

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

+ 0 - 4
AcidWeb/components.d.ts

@@ -33,12 +33,9 @@ declare module 'vue' {
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElPagination: typeof import('element-plus/es')['ElPagination']
-    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
-    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
-    ElSubmenu: typeof import('element-plus/es')['ElSubmenu']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
@@ -54,7 +51,6 @@ declare module 'vue' {
     PaginationComponent: typeof import('./src/components/PaginationComponent.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
-    TabsComponent: typeof import('./src/components/layout/TabsComponent.vue')['default']
     TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
     WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default']
   }

+ 1 - 1
AcidWeb/package-lock.json

@@ -7160,7 +7160,7 @@
     },
     "node_modules/vitest": {
       "version": "2.1.9",
-      "resolved": "https://registry.npmmirror.com/vitest/-/vitest-2.1.9.tgz",
+      "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz",
       "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==",
       "dev": true,
       "license": "MIT",

+ 1 - 0
AcidWeb/src/components/layout/AppAside.vue

@@ -26,6 +26,7 @@
       <template v-else-if="activeTab === 'cadmiumPrediction'">
         <el-menu-item index="/TotalCadmiumPrediction" @click="handleMenuClick('/TotalCadmiumPrediction')">土壤镉的总含量预测</el-menu-item>
         <el-menu-item index="/EffectiveCadmiumPrediction" @click="handleMenuClick('/EffectiveCadmiumPrediction')">土壤镉有效态含量预测</el-menu-item>
+        <el-menu-item index="/EffectiveCadmiumPrediction" @click="handleMenuClick('/CropCadmiumPrediction')">土壤镉作物态含量预测</el-menu-item>
       </template>
       <template v-else-if="activeTab === 'cropRiskAssessment'">
         <el-menu-item index="/RiceCadmiumRisk" @click="handleMenuClick('/RiceCadmiumRisk')">水稻镉污染风险</el-menu-item>

+ 9 - 0
AcidWeb/src/router/index.ts

@@ -119,6 +119,15 @@ const routes = [
           ), // 修复路径
         meta: { title: "土壤镉有效态含量预测" },
       },
+      {
+        path: "CropCadmiumPrediction",
+        name: "CropCadmiumPrediction",
+        component: () =>
+          import(
+            "@/views/User/cadmiumPrediction/CropCadmiumPrediction.vue"
+          ), // 修复路径
+        meta: { title: "土壤镉作物态含量预测" },
+      },
       {
         path: "RiceCadmiumRisk",
         name: "RiceCadmiumRisk",

+ 298 - 0
AcidWeb/src/views/User/cadmiumPrediction/CropCadmiumPrediction.vue

@@ -0,0 +1,298 @@
+<template>
+  <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>
+
+    <!-- 主体内容区,计算后显示 -->
+    <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>
+          
+          <!-- 直方图展示 -->
+          <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>
+          </div>
+        </div>
+      </div>
+
+      <!-- 表格区域 -->
+      <div class="table-area">
+        <h3>表格数据</h3>
+        <el-table :data="tableData" style="width: 100%;">
+          <el-table-column prop="name" label="名称" width="180" />
+          <el-table-column prop="value" label="值" width="100" />
+          <el-table-column prop="unit" label="单位" width="100" />
+          <el-table-column prop="description" label="描述" />
+        </el-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as XLSX from 'xlsx';
+import { saveAs } from 'file-saver';
+import axios from 'axios';
+import { Loading } from '@element-plus/icons-vue';
+
+export default {
+  name: 'CropCadmiumPrediction',
+  components: { Loading },
+  data() {
+    return {
+      isCalculating: false,
+      loadingMap: false,
+      loadingHistogram: false,
+      result: false,
+      tableData: [],
+      mapImageUrl: null,       // 存储地图图片 URL
+      histogramImageUrl: null, // 存储直方图图片 URL
+      mapBlob: null,           // 存储地图原始Blob数据
+      histogramBlob: null      // 存储直方图原始Blob数据
+    };
+  },
+  methods: {
+    async calculate() {
+      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'
+        });
+        
+        // 调用有效态Cd直方图接口
+        const histogramResponse = await axios.post('https://soilgd.com:8000/api/cd-prediction/crop-cd/generate-and-get-histogram', {}, {
+          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;
+        this.tableData = [
+          { name: '样本1', value: 10, unit: 'mg/L', description: '描述1' },
+          { name: '样本2', value: 20, unit: 'mg/L', description: '描述2' }
+        ];
+        
+      } catch (error) {
+        console.error('获取地图或直方图失败:', error);
+        this.$message.error('获取预测结果失败,请重试');
+      } 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.click();
+      
+      // 清理临时URL和链接元素
+      setTimeout(() => {
+        document.body.removeChild(link);
+        URL.revokeObjectURL(link.href);
+      }, 100);
+    },
+    
+    // 导出直方图方法 - 修复:确保链接元素被添加到文档中
+    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.click();
+      
+      // 清理临时URL和链接元素
+      setTimeout(() => {
+        document.body.removeChild(link);
+        URL.revokeObjectURL(link.href);
+      }, 100);
+    },
+    
+    // 导出数据方法
+    exportData() {
+      // 创建工作簿
+      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');
+    }
+  },
+  beforeDestroy() {
+    // 组件销毁时释放图片 URL 防止内存泄漏
+    if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
+    if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
+  }
+};
+</script>
+
+<style scoped>
+/* 样式保持不变 */
+.container {
+  padding: 20px;
+  background-color: #f5f7fa;
+  min-height: 100vh;
+  box-sizing: border-box;
+}
+
+.toolbar {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 20px;
+}
+
+.custom-button {
+  background-color: #47C3B9 !important;
+  color: #DCFFFA !important;
+  border: none;
+  border-radius: 155px;
+  padding: 10px 20px;
+  font-weight: bold;
+}
+
+.content-area {
+  display: flex;
+  gap: 20px;
+}
+
+.map-area {
+  flex: 1;
+  min-width: 300px;
+}
+
+.map-container {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.map-section, .histogram-section {
+  background-color: white;
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  position: relative;
+}
+
+.map-image, .histogram-image {
+  width: 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;
+  background-color: white;
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.loading-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 200px;
+  color: #47C3B9;
+}
+
+.loading-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+  animation: rotate 2s linear infinite;
+}
+
+@keyframes rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+</style>

+ 206 - 36
AcidWeb/src/views/User/cadmiumPrediction/EffectiveCadmiumPrediction.vue

@@ -2,16 +2,37 @@
   <div class="container">
     <!-- 顶部操作栏 -->
     <div class="toolbar">
-      <el-button class="custom-button" @click="calculate">计算</el-button>
-      <el-button class="custom-button" @click="captureScreenshot">截图</el-button>
-      <el-button class="custom-button" @click="exportData">导出</el-button>
+      <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>
 
     <!-- 主体内容区,计算后显示 -->
-    <div v-if="result" class="content-area" ref="captureArea">
-      <!-- 地图区域 -->
+    <div v-if="result" class="content-area">
+      <!-- 地图区域 - 现在包含两个图片展示区 -->
       <div class="map-area">
-        <div class="map-placeholder">地图区域</div>
+        <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>
+          
+          <!-- 直方图展示 -->
+          <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>
+          </div>
+        </div>
       </div>
 
       <!-- 表格区域 -->
@@ -29,54 +50,154 @@
 </template>
 
 <script>
-import html2canvas from 'html2canvas';
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
+import axios from 'axios';
+import { Loading } from '@element-plus/icons-vue';
 
 export default {
-  name: 'TotalCadmiumPrediction',
+  name: 'EffectiveCadmiumPrediction',
+  components: { Loading },
   data() {
     return {
+      isCalculating: false,
+      loadingMap: false,
+      loadingHistogram: false,
       result: false,
-      tableData: []
+      tableData: [],
+      mapImageUrl: null,       // 存储地图图片 URL
+      histogramImageUrl: null, // 存储直方图图片 URL
+      mapBlob: null,           // 存储地图原始Blob数据
+      histogramBlob: null      // 存储直方图原始Blob数据
     };
   },
   methods: {
-    calculate() {
-      this.result = true;
-      this.tableData = [
-        { name: '样本1', value: 10, unit: 'mg/L', description: '描述1' },
-        { name: '样本2', value: 20, unit: 'mg/L', description: '描述2' }
-      ];
-    },
-    async captureScreenshot() {
-      const element = this.$refs.captureArea;
-      if (!element) return;
-
+    async calculate() {
       try {
-        const canvas = await html2canvas(element);
-        const dataUrl = canvas.toDataURL('image/png');
-        const link = document.createElement('a');
-        link.href = dataUrl;
-        link.download = '截图.png';
-        link.click();
-      } catch (err) {
-        console.error('截图失败:', err);
+        // 重置状态
+        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'
+        });
+        
+        // 调用有效态Cd直方图接口
+        const histogramResponse = await axios.post('https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-histogram', {}, {
+          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;
+        this.tableData = [
+          { name: '样本1', value: 10, unit: 'mg/L', description: '描述1' },
+          { name: '样本2', value: 20, unit: 'mg/L', description: '描述2' }
+        ];
+        
+      } catch (error) {
+        console.error('获取地图或直方图失败:', error);
+        this.$message.error('获取预测结果失败,请重试');
+      } 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.click();
+      
+      // 清理临时URL和链接元素
+      setTimeout(() => {
+        document.body.removeChild(link);
+        URL.revokeObjectURL(link.href);
+      }, 100);
     },
+    
+    // 导出直方图方法 - 修复:确保链接元素被添加到文档中
+    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.click();
+      
+      // 清理临时URL和链接元素
+      setTimeout(() => {
+        document.body.removeChild(link);
+        URL.revokeObjectURL(link.href);
+      }, 100);
+    },
+    
+    // 导出数据方法
     exportData() {
-      const worksheet = XLSX.utils.json_to_sheet(this.tableData);
+      // 创建工作簿
       const workbook = XLSX.utils.book_new();
-      XLSX.utils.book_append_sheet(workbook, worksheet, '表格数据');
-      const wbout = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
-      const blob = new Blob([wbout], { type: 'application/octet-stream' });
-      saveAs(blob, '导出数据.xlsx');
+      
+      // 创建数据表
+      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');
     }
+  },
+  beforeDestroy() {
+    // 组件销毁时释放图片 URL 防止内存泄漏
+    if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
+    if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
   }
 };
 </script>
 
 <style scoped>
+/* 样式保持不变 */
 .container {
   padding: 20px;
   background-color: #f5f7fa;
@@ -110,19 +231,68 @@ export default {
   min-width: 300px;
 }
 
+.map-container {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.map-section, .histogram-section {
+  background-color: white;
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  position: relative;
+}
+
+.map-image, .histogram-image {
+  width: 100%;
+  max-height: 600px;
+  object-fit: contain;
+  border-radius: 4px;
+}
+
 .map-placeholder {
   background-color: #cce5ff;
-  height: 400px;
+  height: 200px;
   border-radius: 8px;
   display: flex;
   align-items: center;
   justify-content: center;
   font-weight: bold;
-  font-size: 18px;
+  font-size: 16px;
   color: #003366;
 }
 
 .table-area {
   flex: 1;
+  background-color: white;
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.loading-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 200px;
+  color: #47C3B9;
+}
+
+.loading-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+  animation: rotate 2s linear infinite;
+}
+
+@keyframes rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
 }
-</style>
+</style>

+ 206 - 36
AcidWeb/src/views/User/cadmiumPrediction/TotalCadmiumPrediction.vue

@@ -2,16 +2,37 @@
   <div class="container">
     <!-- 顶部操作栏 -->
     <div class="toolbar">
-      <el-button class="custom-button" @click="calculate">计算</el-button>
-      <el-button class="custom-button" @click="captureScreenshot">截图</el-button>
-      <el-button class="custom-button" @click="exportData">导出</el-button>
+      <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>
 
     <!-- 主体内容区,计算后显示 -->
-    <div v-if="result" class="content-area" ref="captureArea">
-      <!-- 地图区域 -->
+    <div v-if="result" class="content-area">
+      <!-- 地图区域 - 现在包含两个图片展示区 -->
       <div class="map-area">
-        <div class="map-placeholder">地图区域</div>
+        <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>
+          
+          <!-- 直方图展示 -->
+          <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>
+          </div>
+        </div>
       </div>
 
       <!-- 表格区域 -->
@@ -29,54 +50,154 @@
 </template>
 
 <script>
-import html2canvas from 'html2canvas';
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
+import axios from 'axios';
+import { Loading } from '@element-plus/icons-vue';
 
 export default {
-  name: 'TotalCadmiumPrediction',
+  name: 'EffectiveCadmiumPrediction',
+  components: { Loading },
   data() {
     return {
+      isCalculating: false,
+      loadingMap: false,
+      loadingHistogram: false,
       result: false,
-      tableData: []
+      tableData: [],
+      mapImageUrl: null,       // 存储地图图片 URL
+      histogramImageUrl: null, // 存储直方图图片 URL
+      mapBlob: null,           // 存储地图原始Blob数据
+      histogramBlob: null      // 存储直方图原始Blob数据
     };
   },
   methods: {
-    calculate() {
-      this.result = true;
-      this.tableData = [
-        { name: '样本1', value: 10, unit: 'mg/L', description: '描述1' },
-        { name: '样本2', value: 20, unit: 'mg/L', description: '描述2' }
-      ];
-    },
-    async captureScreenshot() {
-      const element = this.$refs.captureArea;
-      if (!element) return;
-
+    async calculate() {
       try {
-        const canvas = await html2canvas(element);
-        const dataUrl = canvas.toDataURL('image/png');
-        const link = document.createElement('a');
-        link.href = dataUrl;
-        link.download = '截图.png';
-        link.click();
-      } catch (err) {
-        console.error('截图失败:', err);
+        // 重置状态
+        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'
+        });
+        
+        // 调用有效态Cd直方图接口
+        const histogramResponse = await axios.post('https://soilgd.com:8000/api/cd-prediction/effective-cd/generate-and-get-histogram', {}, {
+          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;
+        this.tableData = [
+          { name: '样本1', value: 10, unit: 'mg/L', description: '描述1' },
+          { name: '样本2', value: 20, unit: 'mg/L', description: '描述2' }
+        ];
+        
+      } catch (error) {
+        console.error('获取地图或直方图失败:', error);
+        this.$message.error('获取预测结果失败,请重试');
+      } 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.click();
+      
+      // 清理临时URL和链接元素
+      setTimeout(() => {
+        document.body.removeChild(link);
+        URL.revokeObjectURL(link.href);
+      }, 100);
     },
+    
+    // 导出直方图方法 - 修复:确保链接元素被添加到文档中
+    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.click();
+      
+      // 清理临时URL和链接元素
+      setTimeout(() => {
+        document.body.removeChild(link);
+        URL.revokeObjectURL(link.href);
+      }, 100);
+    },
+    
+    // 导出数据方法
     exportData() {
-      const worksheet = XLSX.utils.json_to_sheet(this.tableData);
+      // 创建工作簿
       const workbook = XLSX.utils.book_new();
-      XLSX.utils.book_append_sheet(workbook, worksheet, '表格数据');
-      const wbout = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
-      const blob = new Blob([wbout], { type: 'application/octet-stream' });
-      saveAs(blob, '导出数据.xlsx');
+      
+      // 创建数据表
+      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');
     }
+  },
+  beforeDestroy() {
+    // 组件销毁时释放图片 URL 防止内存泄漏
+    if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
+    if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
   }
 };
 </script>
 
 <style scoped>
+/* 样式保持不变 */
 .container {
   padding: 20px;
   background-color: #f5f7fa;
@@ -110,19 +231,68 @@ export default {
   min-width: 300px;
 }
 
+.map-container {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.map-section, .histogram-section {
+  background-color: white;
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  position: relative;
+}
+
+.map-image, .histogram-image {
+  width: 100%;
+  max-height: 600px;
+  object-fit: contain;
+  border-radius: 4px;
+}
+
 .map-placeholder {
   background-color: #cce5ff;
-  height: 400px;
+  height: 200px;
   border-radius: 8px;
   display: flex;
   align-items: center;
   justify-content: center;
   font-weight: bold;
-  font-size: 18px;
+  font-size: 16px;
   color: #003366;
 }
 
 .table-area {
   flex: 1;
+  background-color: white;
+  border-radius: 8px;
+  padding: 15px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.loading-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 200px;
+  color: #47C3B9;
+}
+
+.loading-icon {
+  font-size: 36px;
+  margin-bottom: 10px;
+  animation: rotate 2s linear infinite;
+}
+
+@keyframes rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
 }
-</style>
+</style>