Browse Source

修改导航和cd模型页面

yangtaodemon 1 month ago
parent
commit
29b96360e3

+ 1 - 1
auto-imports.d.ts

@@ -68,6 +68,6 @@ declare global {
 // for type re-export
 // for type re-export
 declare global {
 declare global {
   // @ts-ignore
   // @ts-ignore
-  export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
+  export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
   import('vue')
   import('vue')
 }
 }

+ 2 - 12
components.d.ts

@@ -2,6 +2,7 @@
 // @ts-nocheck
 // @ts-nocheck
 // Generated by unplugin-vue-components
 // Generated by unplugin-vue-components
 // Read more: https://github.com/vuejs/core/pull/3399
 // Read more: https://github.com/vuejs/core/pull/3399
+// biome-ignore lint: disable
 export {}
 export {}
 
 
 /* prettier-ignore */
 /* prettier-ignore */
@@ -11,29 +12,22 @@ declare module 'vue' {
     AppAsideForTab2: typeof import('./src/components/layout/AppAsideForTab2.vue')['default']
     AppAsideForTab2: typeof import('./src/components/layout/AppAsideForTab2.vue')['default']
     AppHeader: typeof import('./src/components/layout/AppHeader.vue')['default']
     AppHeader: typeof import('./src/components/layout/AppHeader.vue')['default']
     AppLayout: typeof import('./src/components/layout/AppLayout.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']
     ElAside: typeof import('element-plus/es')['ElAside']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElAvatar: typeof import('element-plus/es')['ElAvatar']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElButton: typeof import('element-plus/es')['ElButton']
-    ElCard: typeof import('element-plus/es')['ElCard']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
     ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElContainer: typeof import('element-plus/es')['ElContainer']
     ElContainer: typeof import('element-plus/es')['ElContainer']
-    ElDialog: typeof import('element-plus/es')['ElDialog']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
-    ElForm: typeof import('element-plus/es')['ElForm']
-    ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElHeader: typeof import('element-plus/es')['ElHeader']
     ElHeader: typeof import('element-plus/es')['ElHeader']
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElIcon: typeof import('element-plus/es')['ElIcon']
-    ElInput: typeof import('element-plus/es')['ElInput']
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMain: typeof import('element-plus/es')['ElMain']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElOption: typeof import('element-plus/es')['ElOption']
-    ElPagination: typeof import('element-plus/es')['ElPagination']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSelect: typeof import('element-plus/es')['ElSelect']
     ElSelect: typeof import('element-plus/es')['ElSelect']
@@ -42,8 +36,7 @@ declare module 'vue' {
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabPane: typeof import('element-plus/es')['ElTabPane']
     ElTabs: typeof import('element-plus/es')['ElTabs']
     ElTabs: typeof import('element-plus/es')['ElTabs']
-    ElTooltip: typeof import('element-plus/es')['ElTooltip']
-    ElUpload: typeof import('element-plus/es')['ElUpload']
+    ElTag: typeof import('element-plus/es')['ElTag']
     HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
     HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
     IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
     IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
     IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
     IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
@@ -56,7 +49,4 @@ declare module 'vue' {
     TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
     TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
     WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default']
     WelcomeItem: typeof import('./src/components/WelcomeItem.vue')['default']
   }
   }
-  export interface ComponentCustomProperties {
-    vLoading: typeof import('element-plus/es')['ElLoadingDirective']
-  }
 }
 }

+ 1 - 1
src/components/layout/AppLayout.vue

@@ -27,7 +27,7 @@ const tabs = computed(() => {
       { name: "introduction", label: "软件简介", icon: "el-icon-info-filled", routes: ["/SoilPro", "/Overview", "/ResearchFindings", "/Unit"] },
       { name: "introduction", label: "软件简介", icon: "el-icon-info-filled", routes: ["/SoilPro", "/Overview", "/ResearchFindings", "/Unit"] },
       { name: "heavyMetalFluxCalculation", label: "重金属输入输出通量", icon: "el-icon-refresh", routes: ["/irrigationWater", "/agriculturalProductInput", "/atmosphericDryWetDeposition", "/surfaceRunoff", "/cropRemoval", "/subsurfaceFlow"] },
       { name: "heavyMetalFluxCalculation", label: "重金属输入输出通量", icon: "el-icon-refresh", routes: ["/irrigationWater", "/agriculturalProductInput", "/atmosphericDryWetDeposition", "/surfaceRunoff", "/cropRemoval", "/subsurfaceFlow"] },
       { name: "mapView", label: "地图展示", icon: "el-icon-map-location", routes: ["/mapView"] },
       { name: "mapView", label: "地图展示", icon: "el-icon-map-location", routes: ["/mapView"] },
-      { name: "cadmiumPrediction", label: "土壤镉预测", icon: "el-icon-c-scale-to-original", routes: ["/TotalCadmiumPrediction", "/EffectiveCadmiumPrediction"] },
+      { name: "cadmiumPrediction", label: "土壤镉预测", icon: "el-icon-c-scale-to-original", routes: ["/EffectiveCadmiumPrediction","/CropCadmiumPrediction"] },
       { name: "cropRiskAssessment", label: "作物风险评估", icon: "el-icon-warning", routes: ["/cropRiskAssessment"] },
       { name: "cropRiskAssessment", label: "作物风险评估", icon: "el-icon-warning", routes: ["/cropRiskAssessment"] },
       { name: "farmlandQualityAssessment", label: "耕地质量评估", icon: "el-icon-rank", routes: ["/farmlandQualityAssessment"] },
       { name: "farmlandQualityAssessment", label: "耕地质量评估", icon: "el-icon-rank", routes: ["/farmlandQualityAssessment"] },
       { name: "soilAcidificationPrediction", label: "土壤酸化预测", icon: "el-icon-magic-stick", routes: ["/Calculation", "/AcidNeutralizationModel"] },
       { name: "soilAcidificationPrediction", label: "土壤酸化预测", icon: "el-icon-magic-stick", routes: ["/Calculation", "/AcidNeutralizationModel"] },

+ 0 - 6
src/components/layout/menuItems.ts

@@ -117,12 +117,6 @@ import {
       icon: Location,
       icon: Location,
       tab: 'mapView'
       tab: 'mapView'
     },
     },
-    {
-      index: '/TotalCadmiumPrediction',
-      label: '土壤镉的总含量预测',
-      icon: PieChart,
-      tab: 'cadmiumPrediction'
-    },
     {
     {
       index: '/EffectiveCadmiumPrediction',
       index: '/EffectiveCadmiumPrediction',
       label: '土壤镉有效态含量预测',
       label: '土壤镉有效态含量预测',

+ 0 - 7
src/router/index.ts

@@ -103,13 +103,6 @@ const routes = [
         component: () => import("@/views/User/mapView/tencentMapView.vue"), // 修复路径
         component: () => import("@/views/User/mapView/tencentMapView.vue"), // 修复路径
         meta: { title: "地图展示" },
         meta: { title: "地图展示" },
       },
       },
-      {
-        path: "TotalCadmiumPrediction",
-        name: "TotalCadmiumPrediction",
-        component: () =>
-          import("@/views/User/cadmiumPrediction/TotalCadmiumPrediction.vue"), // 修复路径
-        meta: { title: "土壤镉的总含量预测" },
-      },
       {
       {
         path: "EffectiveCadmiumPrediction",
         path: "EffectiveCadmiumPrediction",
         name: "EffectiveCadmiumPrediction",
         name: "EffectiveCadmiumPrediction",

+ 336 - 41
src/views/User/cadmiumPrediction/CropCadmiumPrediction.vue

@@ -28,7 +28,7 @@
         <el-button class="custom-button" :disabled="!histogramBlob" @click="exportHistogram">
         <el-button class="custom-button" :disabled="!histogramBlob" @click="exportHistogram">
           <el-icon class="upload-icon"><Download /></el-icon>
           <el-icon class="upload-icon"><Download /></el-icon>
           导出直方图</el-button>
           导出直方图</el-button>
-        <el-button class="custom-button" :disabled="!tableData.length" @click="exportData">
+        <el-button class="custom-button" :disabled="!statisticsData.length" @click="exportData">
           <el-icon class="upload-icon"><Download /></el-icon>
           <el-icon class="upload-icon"><Download /></el-icon>
           导出数据</el-button>
           导出数据</el-button>
       </div>
       </div>
@@ -67,15 +67,50 @@
         </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 class="stats-area">
+        <h3>{{countyName}} - 作物Cd预测统计信息</h3>
+        <div class="model-info">
+          <el-tag type="info">{{currentStats?.['模型类型'] || '作物Cd模型'}}</el-tag>
+          <span class="update-time">
+            最后更新: {{currentStats?.['数据更新时间'] ? new Date(currentStats['数据更新时间']).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 class="charts-container">
+            <div class="chart-item">
+              <div ref="distributionChart" style="width: 100%; height: 400px;"></div>
+            </div>
+            <div class="chart-item">
+              <div ref="exceedanceChart" style="width: 100%; height: 400px;"></div>
+            </div>
+          </div>
+        </div>
+        
+        <div v-if="!loadingStats && !statisticsData.length" class="no-data">
+          <el-icon><DataAnalysis /></el-icon>
+          <p>暂无统计数据</p>
+        </div>
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
@@ -85,28 +120,45 @@
 import * as XLSX from 'xlsx';
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
 import { saveAs } from 'file-saver';
 import axios from 'axios';
 import axios from 'axios';
-import { Loading, Upload, Picture, Histogram, Download, Document } from '@element-plus/icons-vue';
+import * as echarts from 'echarts';
+import { 
+  Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+} from '@element-plus/icons-vue';
 
 
 export default {
 export default {
   name: 'CropCadmiumPrediction',
   name: 'CropCadmiumPrediction',
-  components: { Loading, Upload, Picture, Histogram, Download, Document },
+  components: { 
+    Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+  },
   data() {
   data() {
     return {
     return {
       isCalculating: false,
       isCalculating: false,
       loadingMap: false,
       loadingMap: false,
       loadingHistogram: false,
       loadingHistogram: false,
-      tableData: [],
+      loadingStats: false,
+      statisticsData: [],
       mapImageUrl: null,
       mapImageUrl: null,
       histogramImageUrl: null,
       histogramImageUrl: null,
       mapBlob: null,
       mapBlob: null,
       histogramBlob: null,
       histogramBlob: null,
       selectedFile: null,
       selectedFile: null,
-      countyName: '乐昌市' // 默认县市名称
+      countyName: '乐昌市', // 默认县市名称
+      distributionChart: null,
+      exceedanceChart: null
     };
     };
   },
   },
+
   mounted() {
   mounted() {
     // 组件挂载时获取最新数据
     // 组件挂载时获取最新数据
     this.fetchLatestResults();
     this.fetchLatestResults();
+    this.fetchStatistics();
+  },
+
+  beforeDestroy() {
+    if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
+    if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
+    if (this.distributionChart) this.distributionChart.dispose();
+    if (this.exceedanceChart) this.exceedanceChart.dispose();
   },
   },
   methods: {
   methods: {
     // 触发文件选择
     // 触发文件选择
@@ -177,6 +229,197 @@ export default {
       }
       }
     },
     },
     
     
+    // 格式化统计数据
+    formatStatisticsData(stats) {
+      return [
+        { name: '数据点总数', value: stats['基础统计']['数据点总数'], unit: '个', description: '总样本数量' },
+        { name: '平均值', value: stats['基础统计']['均值'].toFixed(4), unit: '(mg/kg)', description: '所有样本的平均Cd含量' },
+        { name: '中位数', value: stats['基础统计']['中位数'].toFixed(4), unit: '(mg/kg)', description: '样本的中位Cd含量' },
+        { name: '标准差', value: stats['基础统计']['标准差'].toFixed(4), unit: '(mg/kg)', description: 'Cd含量的标准差' },
+        { name: '最小值', value: stats['基础统计']['最小值'].toFixed(4), unit: '(mg/kg)', description: '样本中的最小Cd含量' },
+        { name: '最大值', value: stats['基础统计']['最大值'].toFixed(4), unit: '(mg/kg)', description: '样本中的最大Cd含量' },
+        { name: '偏度', value: stats['基础统计']['偏度'].toFixed(4), unit: '', description: '数据分布偏斜程度' },
+        { name: '峰度', value: stats['基础统计']['峰度'].toFixed(4), unit: '', description: '数据分布峰态' },
+        { 
+          name: '经度范围', 
+          value: `${stats['空间统计']['经度范围']['最小值'].toFixed(6)} - ${stats['空间统计']['经度范围']['最大值'].toFixed(6)}`, 
+          unit: '度', 
+          description: `跨度: ${stats['空间统计']['经度范围']['跨度'].toFixed(6)}度` 
+        },
+        { 
+          name: '纬度范围', 
+          value: `${stats['空间统计']['纬度范围']['最小值'].toFixed(6)} - ${stats['空间统计']['纬度范围']['最大值'].toFixed(6)}`, 
+          unit: '度', 
+          description: `跨度: ${stats['空间统计']['纬度范围']['跨度'].toFixed(6)}度` 
+        }
+      ];
+    },
+
+    // 初始化图表 - 根据实际数据更新
+    initCharts() {
+      if (!this.statisticsData.length || !this.currentStats) return;
+      
+      // 销毁旧图表
+      if (this.distributionChart) this.distributionChart.dispose();
+      if (this.exceedanceChart) this.exceedanceChart.dispose();
+      
+      const histData = this.currentStats['分布直方图'];
+      
+      // 1. 分布直方图
+      this.distributionChart = echarts.init(this.$refs.distributionChart);
+      this.distributionChart.setOption({
+        title: {
+          text: 'Cd含量分布直方图',
+          left: 'center'
+        },
+        tooltip: {
+          trigger: 'item',
+          formatter: params => {
+            const index = params.dataIndex;
+            const lowerBound = histData['区间边界'][index].toFixed(4);
+            const upperBound = histData['区间边界'][index + 1].toFixed(4);
+            return `区间: ${lowerBound} ~ ${upperBound}<br/>频次: ${params.value}`;
+          }
+        },
+        xAxis: {
+          type: 'category',
+          data: histData['区间中心'].map(v => v.toFixed(4)),
+          name: 'Cd含量',
+          axisLabel: {
+            rotate: 45
+          }
+        },
+        yAxis: {
+          type: 'value',
+          name: '频次'
+        },
+        series: [{
+          name: '样本分布',
+          type: 'bar',
+          data: histData['频次'],
+          itemStyle: {
+            color: '#47C3B9'
+          },
+          barWidth: '80%'
+        }],
+        grid: {
+          bottom: '20%'
+        }
+      });
+      
+      // 2. 箱线图/统计图表
+      this.exceedanceChart = echarts.init(this.$refs.exceedanceChart);
+      
+      // 准备箱线图数据
+      const boxData = [
+        [
+          this.currentStats['基础统计']['最小值'],
+          this.currentStats['基础统计']['25%分位数'],
+          this.currentStats['基础统计']['中位数'],
+          this.currentStats['基础统计']['75%分位数'],
+          this.currentStats['基础统计']['最大值'],
+          // 还可以添加离群点数据(如果有)
+        ]
+      ];
+      
+      this.exceedanceChart.setOption({
+        title: {
+          text: 'Cd含量统计指标',
+          left: 'center'
+        },
+        tooltip: {
+          trigger: 'item',
+          axisPointer: {
+            type: 'shadow'
+          },
+          formatter: params => {
+            const data = boxData[0];
+            return [
+              '最大值: ' + data[4].toFixed(4),
+              '75%分位数: ' + data[3].toFixed(4),
+              '中位数: ' + data[2].toFixed(4),
+              '25%分位数: ' + data[1].toFixed(4),
+              '最小值: ' + data[0].toFixed(4)
+            ].join('<br/>');
+          }
+        },
+        xAxis: {
+          type: 'category',
+          data: ['Cd含量统计'],
+          axisLabel: {
+            rotate: 45
+          }
+        },
+        yAxis: {
+          type: 'value',
+          name: '(Cd含量)'
+        },
+        series: [{
+          name: '统计值',
+          type: 'boxplot',
+          data: boxData,
+          itemStyle: {
+            color: '#47C3B9',
+            borderColor: '#2F4554'
+          },
+          emphasis: {
+            itemStyle: {
+              color: '#FF6B6B',
+              borderColor: '#C23531'
+            }
+          },
+          tooltip: {
+            formatter: param => {
+              const data = boxData[0];
+              return [
+                '最大值: ' + data[4].toFixed(4),
+                '75%分位数: ' + data[3].toFixed(4),
+                '中位数: ' + data[2].toFixed(4),
+                '25%分位数: ' + data[1].toFixed(4),
+                '最小值: ' + data[0].toFixed(4)
+              ].join('<br/>');
+            }
+          }
+        }],
+        grid: {
+          bottom: '15%'
+        }
+      });
+      
+      // 响应式调整
+      window.addEventListener('resize', this.handleResize);
+    },
+
+    // 修改fetchStatistics方法
+    async fetchStatistics() {
+      try {
+        this.loadingStats = true;
+        
+        const response = await axios.get(
+          `https://soilgd.com:8000/api/cd-prediction/crop-cd/statistics/${this.countyName}`
+        );
+        
+        if (response.data.success && response.data.data) {
+          this.currentStats = response.data.data; // 保存原始统计数据
+          this.statisticsData = this.formatStatisticsData(response.data.data);
+          this.$nextTick(() => {
+            this.initCharts();
+          });
+        }
+      } catch (error) {
+        console.error('获取统计信息失败:', error);
+        this.$message.warning('获取统计信息失败');
+      } finally {
+        this.loadingStats = false;
+      }
+    },
+    
+    // 处理窗口大小变化
+    handleResize() {
+      if (this.distributionChart) this.distributionChart.resize();
+      if (this.exceedanceChart) this.exceedanceChart.resize();
+    },
+    
     // 上传并计算
     // 上传并计算
     async calculate() {
     async calculate() {
       if (!this.selectedFile) {
       if (!this.selectedFile) {
@@ -188,13 +431,14 @@ export default {
         this.isCalculating = true;
         this.isCalculating = true;
         this.loadingMap = true;
         this.loadingMap = true;
         this.loadingHistogram = true;
         this.loadingHistogram = true;
+        this.loadingStats = true;
         
         
         // 创建FormData
         // 创建FormData
         const formData = new FormData();
         const formData = new FormData();
         formData.append('county_name', this.countyName);
         formData.append('county_name', this.countyName);
         formData.append('data_file', this.selectedFile);
         formData.append('data_file', this.selectedFile);
         
         
-        // 调用有效态Cd地图接口
+        // 调用作物Cd地图接口
         const mapResponse = await axios.post(
         const mapResponse = await axios.post(
           'https://soilgd.com:8000/api/cd-prediction/crop-cd/generate-and-get-map',
           'https://soilgd.com:8000/api/cd-prediction/crop-cd/generate-and-get-map',
           formData,
           formData,
@@ -210,14 +454,9 @@ export default {
         this.mapBlob = mapResponse.data;
         this.mapBlob = mapResponse.data;
         this.mapImageUrl = URL.createObjectURL(this.mapBlob);
         this.mapImageUrl = URL.createObjectURL(this.mapBlob);
         
         
-        // 更新后重新获取直方图(因为生成新数据后直方图也会更新)
+        // 更新后重新获取直方图和统计数据
         await this.fetchLatestHistogram();
         await this.fetchLatestHistogram();
-        
-        // 更新表格数据(示例)
-        this.tableData = [
-          { name: '样本1', value: 10, unit: 'mg/L', description: '描述1' },
-          { name: '样本2', value: 20, unit: 'mg/L', description: '描述2' }
-        ];
+        await this.fetchStatistics();
         
         
         this.$message.success('计算完成!');
         this.$message.success('计算完成!');
         
         
@@ -226,7 +465,6 @@ export default {
         let errorMessage = '计算失败,请重试';
         let errorMessage = '计算失败,请重试';
         
         
         if (error.response) {
         if (error.response) {
-          // 处理不同错误状态码
           if (error.response.status === 400) {
           if (error.response.status === 400) {
             errorMessage = '文件格式错误:' + (error.response.data.detail || '请上传正确的CSV文件');
             errorMessage = '文件格式错误:' + (error.response.data.detail || '请上传正确的CSV文件');
           } else if (error.response.status === 404) {
           } else if (error.response.status === 404) {
@@ -241,6 +479,7 @@ export default {
         this.isCalculating = false;
         this.isCalculating = false;
         this.loadingMap = false;
         this.loadingMap = false;
         this.loadingHistogram = false;
         this.loadingHistogram = false;
+        this.loadingStats = false;
       }
       }
     },
     },
     
     
@@ -272,29 +511,41 @@ export default {
       URL.revokeObjectURL(link.href);
       URL.revokeObjectURL(link.href);
     },
     },
     
     
-    // 导出数据
-    exportData() {
-      if (!this.tableData.length) {
-        this.$message.warning('暂无数据可导出');
-        return;
+    // 导出数据 - 修改为获取作物Cd的CSV文件
+    async exportData() {
+      try {
+        this.$message.info('正在获取作物Cd预测数据...');
+        
+        const response = await axios.get(
+          `https://soilgd.com:8000/api/cd-prediction/download-final-crop-cd-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.click();
+        URL.revokeObjectURL(link.href);
+        
+        this.$message.success('数据导出成功');
+      } catch (error) {
+        console.error('导出数据失败:', error);
+        this.$message.error('导出数据失败: ' + (error.response?.data?.detail || '请稍后重试'));
       }
       }
-      
-      const workbook = XLSX.utils.book_new();
-      const worksheet = XLSX.utils.json_to_sheet(this.tableData);
-      XLSX.utils.book_append_sheet(workbook, worksheet, '作物态Cd数据');
-      const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
-      const excelData = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
-      saveAs(excelData, `${this.countyName}_作物态Cd数据.xlsx`);
     }
     }
-  },
-  beforeDestroy() {
-    if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
-    if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
   }
   }
 };
 };
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
+::v-deep .el-table th.el-table__cell {
+  text-align: center;
+  background-color: #f5f7fa !important;
+}
+::v-deep .el-table td.el-table__cell {
+  text-align: center;
+}
 .container {
 .container {
   padding: 20px;
   padding: 20px;
   background-color: #f5f7fa;
   background-color: #f5f7fa;
@@ -367,7 +618,7 @@ export default {
 
 
 .map-section, .histogram-section {
 .map-section, .histogram-section {
   flex: 1;
   flex: 1;
-  min-width: 300px; /* 最小宽度,确保在小屏幕上也能正常显示 */
+  min-width: 300px;
   background-color: white;
   background-color: white;
   border-radius: 8px;
   border-radius: 8px;
   padding: 15px;
   padding: 15px;
@@ -384,15 +635,34 @@ export default {
   border-radius: 4px;
   border-radius: 4px;
 }
 }
 
 
-.table-area {
+.stats-area {
   width: 100%;
   width: 100%;
   background-color: white;
   background-color: white;
   border-radius: 8px;
   border-radius: 8px;
   padding: 15px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.stats-container {
   margin-top: 20px;
   margin-top: 20px;
 }
 }
 
 
+.charts-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
+  margin-top: 30px;
+}
+
+.chart-item {
+  flex: 1;
+  min-width: 300px;
+  background: #f9f9f9;
+  padding: 15px;
+  border-radius: 8px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+}
+
 .loading-container {
 .loading-container {
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
@@ -443,4 +713,29 @@ export default {
     flex: none;
     flex: none;
   }
   }
 }
 }
+.model-info {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin: 10px 0;
+  color: #666;
+}
+
+.update-time {
+  font-size: 14px;
+}
+
+.chart-item {
+  flex: 1;
+  min-width: 400px;
+  background: white;
+  padding: 15px;
+  border-radius: 8px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+  margin-bottom: 20px;
+}
+
+.el-table {
+  margin-top: 20px;
+}
 </style>
 </style>

+ 335 - 40
src/views/User/cadmiumPrediction/EffectiveCadmiumPrediction.vue

@@ -28,7 +28,7 @@
         <el-button class="custom-button" :disabled="!histogramBlob" @click="exportHistogram">
         <el-button class="custom-button" :disabled="!histogramBlob" @click="exportHistogram">
           <el-icon class="upload-icon"><Download /></el-icon>
           <el-icon class="upload-icon"><Download /></el-icon>
           导出直方图</el-button>
           导出直方图</el-button>
-        <el-button class="custom-button" :disabled="!tableData.length" @click="exportData">
+        <el-button class="custom-button" :disabled="!statisticsData.length" @click="exportData">
           <el-icon class="upload-icon"><Download /></el-icon>
           <el-icon class="upload-icon"><Download /></el-icon>
           导出数据</el-button>
           导出数据</el-button>
       </div>
       </div>
@@ -67,15 +67,50 @@
         </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 class="stats-area">
+        <h3>{{countyName}} - 有效Cd预测统计信息</h3>
+        <div class="model-info">
+          <el-tag type="info">{{currentStats?.['模型类型'] || '有效Cd模型'}}</el-tag>
+          <span class="update-time">
+            最后更新: {{currentStats?.['数据更新时间'] ? new Date(currentStats['数据更新时间']).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 class="charts-container">
+            <div class="chart-item">
+              <div ref="distributionChart" style="width: 100%; height: 400px;"></div>
+            </div>
+            <div class="chart-item">
+              <div ref="exceedanceChart" style="width: 100%; height: 400px;"></div>
+            </div>
+          </div>
+        </div>
+        
+        <div v-if="!loadingStats && !statisticsData.length" class="no-data">
+          <el-icon><DataAnalysis /></el-icon>
+          <p>暂无统计数据</p>
+        </div>
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
@@ -85,28 +120,45 @@
 import * as XLSX from 'xlsx';
 import * as XLSX from 'xlsx';
 import { saveAs } from 'file-saver';
 import { saveAs } from 'file-saver';
 import axios from 'axios';
 import axios from 'axios';
-import { Loading, Upload, Picture, Histogram, Download, Document } from '@element-plus/icons-vue';
+import * as echarts from 'echarts';
+import { 
+  Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+} from '@element-plus/icons-vue';
 
 
 export default {
 export default {
-  name: 'EffectiveCadmiumPrediction',
-  components: { Loading, Upload, Picture, Histogram, Download, Document },
+  name: 'CropCadmiumPrediction',
+  components: { 
+    Loading, Upload, Picture, Histogram, Download, Document, DataAnalysis 
+  },
   data() {
   data() {
     return {
     return {
       isCalculating: false,
       isCalculating: false,
       loadingMap: false,
       loadingMap: false,
       loadingHistogram: false,
       loadingHistogram: false,
-      tableData: [],
+      loadingStats: false,
+      statisticsData: [],
       mapImageUrl: null,
       mapImageUrl: null,
       histogramImageUrl: null,
       histogramImageUrl: null,
       mapBlob: null,
       mapBlob: null,
       histogramBlob: null,
       histogramBlob: null,
       selectedFile: null,
       selectedFile: null,
-      countyName: '乐昌市' // 默认县市名称
+      countyName: '乐昌市', // 默认县市名称
+      distributionChart: null,
+      exceedanceChart: null
     };
     };
   },
   },
+
   mounted() {
   mounted() {
     // 组件挂载时获取最新数据
     // 组件挂载时获取最新数据
     this.fetchLatestResults();
     this.fetchLatestResults();
+    this.fetchStatistics();
+  },
+
+  beforeDestroy() {
+    if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
+    if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
+    if (this.distributionChart) this.distributionChart.dispose();
+    if (this.exceedanceChart) this.exceedanceChart.dispose();
   },
   },
   methods: {
   methods: {
     // 触发文件选择
     // 触发文件选择
@@ -177,6 +229,197 @@ export default {
       }
       }
     },
     },
     
     
+    // 格式化统计数据
+    formatStatisticsData(stats) {
+      return [
+        { name: '数据点总数', value: stats['基础统计']['数据点总数'], unit: '个', description: '总样本数量' },
+        { name: '平均值', value: stats['基础统计']['均值'].toFixed(4), unit: '(mg/kg)', description: '所有样本的平均Cd含量' },
+        { name: '中位数', value: stats['基础统计']['中位数'].toFixed(4), unit: '(mg/kg)', description: '样本的中位Cd含量' },
+        { name: '标准差', value: stats['基础统计']['标准差'].toFixed(4), unit: '(mg/kg)', description: 'Cd含量的标准差' },
+        { name: '最小值', value: stats['基础统计']['最小值'].toFixed(4), unit: '(mg/kg)', description: '样本中的最小Cd含量' },
+        { name: '最大值', value: stats['基础统计']['最大值'].toFixed(4), unit: '(mg/kg)', description: '样本中的最大Cd含量' },
+        { name: '偏度', value: stats['基础统计']['偏度'].toFixed(4), unit: '', description: '数据分布偏斜程度' },
+        { name: '峰度', value: stats['基础统计']['峰度'].toFixed(4), unit: '', description: '数据分布峰态' },
+        { 
+          name: '经度范围', 
+          value: `${stats['空间统计']['经度范围']['最小值'].toFixed(6)} - ${stats['空间统计']['经度范围']['最大值'].toFixed(6)}`, 
+          unit: '度', 
+          description: `跨度: ${stats['空间统计']['经度范围']['跨度'].toFixed(6)}度` 
+        },
+        { 
+          name: '纬度范围', 
+          value: `${stats['空间统计']['纬度范围']['最小值'].toFixed(6)} - ${stats['空间统计']['纬度范围']['最大值'].toFixed(6)}`, 
+          unit: '度', 
+          description: `跨度: ${stats['空间统计']['纬度范围']['跨度'].toFixed(6)}度` 
+        }
+      ];
+    },
+
+    // 初始化图表 - 根据实际数据更新
+    initCharts() {
+      if (!this.statisticsData.length || !this.currentStats) return;
+      
+      // 销毁旧图表
+      if (this.distributionChart) this.distributionChart.dispose();
+      if (this.exceedanceChart) this.exceedanceChart.dispose();
+      
+      const histData = this.currentStats['分布直方图'];
+      
+      // 1. 分布直方图
+      this.distributionChart = echarts.init(this.$refs.distributionChart);
+      this.distributionChart.setOption({
+        title: {
+          text: 'Cd含量分布直方图',
+          left: 'center'
+        },
+        tooltip: {
+          trigger: 'item',
+          formatter: params => {
+            const index = params.dataIndex;
+            const lowerBound = histData['区间边界'][index].toFixed(4);
+            const upperBound = histData['区间边界'][index + 1].toFixed(4);
+            return `区间: ${lowerBound} ~ ${upperBound}<br/>频次: ${params.value}`;
+          }
+        },
+        xAxis: {
+          type: 'category',
+          data: histData['区间中心'].map(v => v.toFixed(4)),
+          name: 'Cd含量',
+          axisLabel: {
+            rotate: 45
+          }
+        },
+        yAxis: {
+          type: 'value',
+          name: '频次'
+        },
+        series: [{
+          name: '样本分布',
+          type: 'bar',
+          data: histData['频次'],
+          itemStyle: {
+            color: '#47C3B9'
+          },
+          barWidth: '80%'
+        }],
+        grid: {
+          bottom: '20%'
+        }
+      });
+      
+      // 2. 箱线图/统计图表
+      this.exceedanceChart = echarts.init(this.$refs.exceedanceChart);
+      
+      // 准备箱线图数据
+      const boxData = [
+        [
+          this.currentStats['基础统计']['最小值'],
+          this.currentStats['基础统计']['25%分位数'],
+          this.currentStats['基础统计']['中位数'],
+          this.currentStats['基础统计']['75%分位数'],
+          this.currentStats['基础统计']['最大值'],
+          // 还可以添加离群点数据(如果有)
+        ]
+      ];
+      
+      this.exceedanceChart.setOption({
+        title: {
+          text: 'Cd含量统计指标',
+          left: 'center'
+        },
+        tooltip: {
+          trigger: 'item',
+          axisPointer: {
+            type: 'shadow'
+          },
+          formatter: params => {
+            const data = boxData[0];
+            return [
+              '最大值: ' + data[4].toFixed(4),
+              '75%分位数: ' + data[3].toFixed(4),
+              '中位数: ' + data[2].toFixed(4),
+              '25%分位数: ' + data[1].toFixed(4),
+              '最小值: ' + data[0].toFixed(4)
+            ].join('<br/>');
+          }
+        },
+        xAxis: {
+          type: 'category',
+          data: ['Cd含量统计'],
+          axisLabel: {
+            rotate: 45
+          }
+        },
+        yAxis: {
+          type: 'value',
+          name: 'Cd含量'
+        },
+        series: [{
+          name: '统计值',
+          type: 'boxplot',
+          data: boxData,
+          itemStyle: {
+            color: '#47C3B9',
+            borderColor: '#2F4554'
+          },
+          emphasis: {
+            itemStyle: {
+              color: '#FF6B6B',
+              borderColor: '#C23531'
+            }
+          },
+          tooltip: {
+            formatter: param => {
+              const data = boxData[0];
+              return [
+                '最大值: ' + data[4].toFixed(4),
+                '75%分位数: ' + data[3].toFixed(4),
+                '中位数: ' + data[2].toFixed(4),
+                '25%分位数: ' + data[1].toFixed(4),
+                '最小值: ' + data[0].toFixed(4)
+              ].join('<br/>');
+            }
+          }
+        }],
+        grid: {
+          bottom: '15%'
+        }
+      });
+      
+      // 响应式调整
+      window.addEventListener('resize', this.handleResize);
+    },
+
+    // 修改fetchStatistics方法
+    async fetchStatistics() {
+      try {
+        this.loadingStats = true;
+        
+        const response = await axios.get(
+          `https://soilgd.com:8000/api/cd-prediction/effective-cd/statistics/${this.countyName}`
+        );
+        
+        if (response.data.success && response.data.data) {
+          this.currentStats = response.data.data; // 保存原始统计数据
+          this.statisticsData = this.formatStatisticsData(response.data.data);
+          this.$nextTick(() => {
+            this.initCharts();
+          });
+        }
+      } catch (error) {
+        console.error('获取统计信息失败:', error);
+        this.$message.warning('获取统计信息失败');
+      } finally {
+        this.loadingStats = false;
+      }
+    },
+    
+    // 处理窗口大小变化
+    handleResize() {
+      if (this.distributionChart) this.distributionChart.resize();
+      if (this.exceedanceChart) this.exceedanceChart.resize();
+    },
+    
     // 上传并计算
     // 上传并计算
     async calculate() {
     async calculate() {
       if (!this.selectedFile) {
       if (!this.selectedFile) {
@@ -188,6 +431,7 @@ export default {
         this.isCalculating = true;
         this.isCalculating = true;
         this.loadingMap = true;
         this.loadingMap = true;
         this.loadingHistogram = true;
         this.loadingHistogram = true;
+        this.loadingStats = true;
         
         
         // 创建FormData
         // 创建FormData
         const formData = new FormData();
         const formData = new FormData();
@@ -210,14 +454,9 @@ export default {
         this.mapBlob = mapResponse.data;
         this.mapBlob = mapResponse.data;
         this.mapImageUrl = URL.createObjectURL(this.mapBlob);
         this.mapImageUrl = URL.createObjectURL(this.mapBlob);
         
         
-        // 更新后重新获取直方图(因为生成新数据后直方图也会更新)
+        // 更新后重新获取直方图和统计数据
         await this.fetchLatestHistogram();
         await this.fetchLatestHistogram();
-        
-        // 更新表格数据(示例)
-        this.tableData = [
-          { name: '样本1', value: 10, unit: 'mg/L', description: '描述1' },
-          { name: '样本2', value: 20, unit: 'mg/L', description: '描述2' }
-        ];
+        await this.fetchStatistics();
         
         
         this.$message.success('计算完成!');
         this.$message.success('计算完成!');
         
         
@@ -226,7 +465,6 @@ export default {
         let errorMessage = '计算失败,请重试';
         let errorMessage = '计算失败,请重试';
         
         
         if (error.response) {
         if (error.response) {
-          // 处理不同错误状态码
           if (error.response.status === 400) {
           if (error.response.status === 400) {
             errorMessage = '文件格式错误:' + (error.response.data.detail || '请上传正确的CSV文件');
             errorMessage = '文件格式错误:' + (error.response.data.detail || '请上传正确的CSV文件');
           } else if (error.response.status === 404) {
           } else if (error.response.status === 404) {
@@ -241,6 +479,7 @@ export default {
         this.isCalculating = false;
         this.isCalculating = false;
         this.loadingMap = false;
         this.loadingMap = false;
         this.loadingHistogram = false;
         this.loadingHistogram = false;
+        this.loadingStats = false;
       }
       }
     },
     },
     
     
@@ -273,28 +512,40 @@ export default {
     },
     },
     
     
     // 导出数据
     // 导出数据
-    exportData() {
-      if (!this.tableData.length) {
-        this.$message.warning('暂无数据可导出');
-        return;
+    async exportData() {
+      try {
+        this.$message.info('正在获取有效Cd预测数据...');
+        
+        const response = await axios.get(
+          `https://soilgd.com:8000/api/cd-prediction/download-final-effective-cd-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.click();
+        URL.revokeObjectURL(link.href);
+        
+        this.$message.success('数据导出成功');
+      } catch (error) {
+        console.error('导出数据失败:', error);
+        this.$message.error('导出数据失败: ' + (error.response?.data?.detail || '请稍后重试'));
       }
       }
-      
-      const workbook = XLSX.utils.book_new();
-      const worksheet = XLSX.utils.json_to_sheet(this.tableData);
-      XLSX.utils.book_append_sheet(workbook, worksheet, '有效态Cd数据');
-      const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
-      const excelData = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
-      saveAs(excelData, `${this.countyName}_有效态Cd数据.xlsx`);
     }
     }
-  },
-  beforeDestroy() {
-    if (this.mapImageUrl) URL.revokeObjectURL(this.mapImageUrl);
-    if (this.histogramImageUrl) URL.revokeObjectURL(this.histogramImageUrl);
   }
   }
 };
 };
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>
+::v-deep .el-table th.el-table__cell {
+  text-align: center;
+  background-color: #f5f7fa !important;
+}
+::v-deep .el-table td.el-table__cell {
+  text-align: center;
+}
 .container {
 .container {
   padding: 20px;
   padding: 20px;
   background-color: #f5f7fa;
   background-color: #f5f7fa;
@@ -367,7 +618,7 @@ export default {
 
 
 .map-section, .histogram-section {
 .map-section, .histogram-section {
   flex: 1;
   flex: 1;
-  min-width: 300px; /* 最小宽度,确保在小屏幕上也能正常显示 */
+  min-width: 300px;
   background-color: white;
   background-color: white;
   border-radius: 8px;
   border-radius: 8px;
   padding: 15px;
   padding: 15px;
@@ -384,15 +635,34 @@ export default {
   border-radius: 4px;
   border-radius: 4px;
 }
 }
 
 
-.table-area {
+.stats-area {
   width: 100%;
   width: 100%;
   background-color: white;
   background-color: white;
   border-radius: 8px;
   border-radius: 8px;
   padding: 15px;
   padding: 15px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.stats-container {
   margin-top: 20px;
   margin-top: 20px;
 }
 }
 
 
+.charts-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
+  margin-top: 30px;
+}
+
+.chart-item {
+  flex: 1;
+  min-width: 300px;
+  background: #f9f9f9;
+  padding: 15px;
+  border-radius: 8px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+}
+
 .loading-container {
 .loading-container {
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
@@ -443,4 +713,29 @@ export default {
     flex: none;
     flex: none;
   }
   }
 }
 }
+.model-info {
+  display: flex;
+  align-items: center;
+  gap: 15px;
+  margin: 10px 0;
+  color: #666;
+}
+
+.update-time {
+  font-size: 14px;
+}
+
+.chart-item {
+  flex: 1;
+  min-width: 400px;
+  background: white;
+  padding: 15px;
+  border-radius: 8px;
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+  margin-bottom: 20px;
+}
+
+.el-table {
+  margin-top: 20px;
+}
 </style>
 </style>