Quellcode durchsuchen

Merge branch 'lili' of qw/12 into master

qw vor 7 Monaten
Ursprung
Commit
bb15f1fcaf
34 geänderte Dateien mit 1074 neuen und 1956 gelöschten Zeilen
  1. 3 2
      components.d.ts
  2. 4 5
      src/components/atmpollution/airsampleChart.vue
  3. 3 3
      src/components/atmpollution/atmsamplemap.vue
  4. 3 5
      src/components/atmpollution/heavyMetalEnterprisechart.vue
  5. 0 232
      src/components/cdStatictics/reducedataStatistics.vue
  6. 0 259
      src/components/cdStatictics/refluxcdStatictics.vue
  7. 2 22
      src/components/detectionStatistics/atmcompanyStatics.vue
  8. 70 136
      src/components/detectionStatistics/atmsampleStatistics.vue
  9. 2 2
      src/components/detectionStatistics/crosscetionStatistics.vue
  10. 36 94
      src/components/detectionStatistics/irrigationstatistics.vue
  11. 3 3
      src/components/irrpollution/crossSetionData1.vue
  12. 2 2
      src/components/irrpollution/crosssectionmap.vue
  13. 2 2
      src/components/irrpollution/irrwatermap.vue
  14. 0 222
      src/components/irrpollution/waterassaydata1.vue
  15. 1 1
      src/components/irrpollution/waterassaydata2.vue
  16. 0 197
      src/components/irrpollution/waterassaydata3.vue
  17. 0 340
      src/components/irrpollution/waterassaydata4.vue
  18. 349 0
      src/components/soilStatictics/reducedataStatistics.vue
  19. 214 0
      src/components/soilStatictics/refluxcedataStatictics.vue
  20. 208 179
      src/components/soilcdStatistics/cropcdStatictics.vue
  21. 38 70
      src/components/soilcdStatistics/effcdStatistics.vue
  22. 23 48
      src/components/soilcdStatistics/fluxcdStatictics.vue
  23. 7 25
      src/views/User/HmOutFlux/atmosDeposition/airSampleData.vue
  24. 6 27
      src/views/User/HmOutFlux/atmosDeposition/heavyMetalEnterprise.vue
  25. 3 12
      src/views/User/HmOutFlux/irrigationWater/crossSection.vue
  26. 5 13
      src/views/User/HmOutFlux/irrigationWater/irriWaterSampleData.vue
  27. 5 17
      src/views/User/HmOutFlux/irrigationWater/samplingMethodDevice1.vue
  28. 3 2
      src/views/User/dataStatistics/DetectionStatistics.vue
  29. 8 0
      src/views/User/dataStatistics/LandCultivatedStatistics.vue
  30. 3 0
      src/views/User/dataStatistics/SoilCdStatistics.vue
  31. 12 10
      src/views/User/dataStatistics/SoilacidificationStatistics.vue
  32. 59 0
      src/views/User/farmlandQualityAssessment/farmlandQualityAssessment.vue
  33. 0 14
      src/views/User/heavyMetalFluxCalculation/inputFluxCalculation/waterdata/rivermessage.vue
  34. 0 12
      src/views/User/introduction/Introduce.vue

+ 3 - 2
components.d.ts

@@ -78,8 +78,9 @@ declare module 'vue' {
     Irrigationstatistics: typeof import('./src/components/detectionStatistics/irrigationstatistics.vue')['default']
     Irrwatermap: typeof import('./src/components/irrpollution/irrwatermap.vue')['default']
     PaginationComponent: typeof import('./src/components/PaginationComponent.vue')['default']
-    ReducedataStatistics: typeof import('./src/components/cdStatictics/reducedataStatistics.vue')['default']
-    RefluxcdStatictics: typeof import('./src/components/cdStatictics/refluxcdStatictics.vue')['default']
+    ReducedataStatistics: typeof import('./src/components/soilStatictics/reducedataStatistics.vue')['default']
+    RefluxcdStatictics: typeof import('./src/components/soilStatictics/refluxcdStatictics.vue')['default']
+    RefluxcedataStatictics: typeof import('./src/components/soilStatictics/refluxcedataStatictics.vue')['default']
     Riverwaterassay: typeof import('./src/components/irrpollution/riverwaterassay.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

+ 4 - 5
src/components/atmpollution/airsampleChart.vue

@@ -292,11 +292,11 @@ async function initChart() {
     const { unit, titleText } = props.calculationMethod === 'weight' 
       ? { 
           unit: 'mg/kg', 
-          titleText: '各区域空气颗粒物重量指标平均值' ,
+          titleText: '各区域重金属含量平均值' ,
         } 
       : { 
           unit: 'ug/m³',  // 体积单位为ug/m³,可根据实际需求修改
-          titleText: '各区域空气颗粒物体积指标平均值' ,
+          titleText: '各区域重金属含量平均值' ,
         };
 
     // 销毁旧图表
@@ -327,7 +327,7 @@ async function initChart() {
       xAxis: {
         type: 'category',
         data: chartData.regions,
-        axisLabel: { rotate: 45 ,fontSize:18 }
+        axisLabel: { fontSize:16 }
       },
       yAxis: { 
         type: 'value',
@@ -352,7 +352,7 @@ async function initChart() {
         textStyle:{fontSize:18}
       },
       grid: {
-        left: '5%', right: '5%', bottom: '5%', top: '25%',
+        left: '1%', right: '2%', bottom: '2%', top: '20%',
         containLabel: true,
         axisLabel:{fontSize:18}
       }
@@ -389,7 +389,6 @@ onUnmounted(() => {
   width: 100%;
   max-width: 1400px;
   margin: 0 auto;
-  padding: 20px;
   box-sizing: border-box;
   position: relative;
 }

+ 3 - 3
src/components/atmpollution/atmsamplemap.vue

@@ -28,7 +28,7 @@ function calculateConcentration(heavyMetalWeight, volume) {
   }
   const ug = heavyMetalWeight * 1000; 
   const concentration = ug / volume;
-  return concentration.toFixed(2); 
+  return concentration; 
 }
 
 function calculateParticleConcentration(particleWeight, volume) {
@@ -37,7 +37,7 @@ function calculateParticleConcentration(particleWeight, volume) {
   }
   const ug = particleWeight * 1000; 
   const concentration = ug / volume;
-  return concentration.toFixed(2);
+  return concentration;
 }
 
 // ====================== 指标映射(重量直接取数,体积动态计算) ======================
@@ -87,7 +87,7 @@ function generatePopupContent(item, method) {
     return `
       <div class="data-item">
         <span class="item-label">${metric.label}:</span>
-        <span class="item-value">${value}</span>
+        <span class="item-value">${value.toFixed(6)}</span>
       </div>
     `;
   }).join('');

+ 3 - 5
src/components/atmpollution/heavyMetalEnterprisechart.vue

@@ -206,7 +206,7 @@ const initChart = (data) => {
     myChart = echarts.init(chartRef.value);
     myChart.setOption({
       title: {
-        text: '韶关市各区县企业污染物平均值',
+        text: '韶关市各区县企业排放大气颗粒物浓度平均值',
         left: 'center',
         subtext: `基于 ${data.totalSamples} 个有效样本`,
         subtextStyle: { fontSize: 15 }
@@ -229,7 +229,7 @@ const initChart = (data) => {
       },
       yAxis: {
         type: 'value',
-        name: '浓度 (t/a)',
+        name: '颗粒物排放量 (t/a)',
         nameTextStyle: { fontSize: 15 },
         axisLabel:{fontSize:15},
       },
@@ -310,10 +310,8 @@ onUnmounted(() => window.removeEventListener('resize', handleResize));
 
 <style scoped>
 .heavy-metal-chart {
-  width: 90%;
+  width: 100%;
   max-width: 1200px;
-  margin: 20px auto;
-  padding: 20px;
   background: #fff;
   border-radius: 12px;
   box-shadow: 0 2px 8px rgba(0,0,0,0.1);

+ 0 - 232
src/components/cdStatictics/reducedataStatistics.vue

@@ -1,232 +0,0 @@
-<template>
-  <div class="chart-container">
-
-    <div v-if="loading" class="loading">
-      <p>数据加载中...</p>
-    </div>
-    <div ref="chartRef" class="chart"></div>
-  </div>
-</template>
-
-<script>
-import { ref, onMounted, onUnmounted, watch ,nextTick} from 'vue';
-import * as echarts from 'echarts';
-
-export default {
-  name: 'HeavyMetalChart',
-  setup() {
-    // 接口数据
-    const interfaces = [
-      { time: '20241229_201018', url: 'http://localhost:5000/api/table-averages?table_name=dataset_5' },
-      { time: '20250104_171827', url: 'http://localhost:5000/api/table-averages?table_name=dataset_35' },
-      { time: '20250104_171959', url: 'http://localhost:5000/api/table-averages?table_name=dataset_36' },
-      { time: '20250104_214026', url: 'http://localhost:5000/api/table-averages?table_name=dataset_37' },
-      { time: '20250308_161945', url: 'http://localhost:5000/api/table-averages?table_name=dataset_65' },
-      { time: '20250308_163248', url: 'http://localhost:5000/api/table-averages?table_name=dataset_66' },
-    ];
-    
-    const chartRef = ref(null);
-    const loading = ref(true);
-    const error = ref(null);
-    const selectedData = ref('Al');
-    const displayOption = ref('all');
-    const lastUpdate = ref(new Date().toLocaleString());
-    
-    const chartData = ref({
-      timestamps: [],
-      series: {
-        Al: [],
-        CL: [],
-        H: [],
-        OM: [],
-        Q_over_b: [],
-        pH: []
-      }
-    });
-    
-    let chartInstance = null;
-    
-    // 获取数据
-    async function fetchData() {
-      try {
-        loading.value = true;
-        error.value = null;
-        
-        // 尝试获取真实数据,失败则使用模拟数据
-        try {
-          const responses = await Promise.all(
-            interfaces.map(intf => 
-              fetch(intf.url)
-                .then(response => {
-                  if (!response.ok) throw new Error(`网络响应错误: ${response.status}`);
-                  return response.json();
-                })
-                .then(data => {
-                  if (data.success !== "true" && data.success !== true) {
-                    throw new Error('接口返回失败状态');
-                  }
-                  return { time: intf.time, data: data.averages };
-                })
-            )
-          );
-          
-          // 处理数据 - 直接使用原始时间字符串
-          chartData.value.timestamps = responses.map(item => item.time);
-          
-          // 初始化所有数据系列
-          Object.keys(chartData.value.series).forEach(key => {
-            chartData.value.series[key] = responses.map(item => parseFloat(item.data[key]));
-          });
-        } catch (err) {
-          error.value = '获取真实数据失败: ' + err.message;
-          chartData.value = generateMockData();
-        }
-        
-        renderChart();
-        lastUpdate.value = new Date().toLocaleString();
-      } catch (err) {
-        error.value = err.message || '获取数据失败';
-        console.error('获取数据错误:', err);
-      } finally {
-        loading.value = false;
-      }
-    }
-    
-    // 渲染图表
-    function renderChart() {
-      if (!chartRef.value) return;
-      
-      // 初始化或更新ECharts实例
-      if (!chartInstance) {
-        chartInstance = echarts.init(chartRef.value);
-      }
-      
-      // 准备系列数据
-      const series = [];
-      
-      if (displayOption.value === 'all') {
-        // 显示所有数据
-        Object.keys(chartData.value.series).forEach(key => {
-          series.push({
-            name: key,
-            type: 'line',
-            symbol: 'circle',
-            symbolSize: 6,
-            data: chartData.value.series[key],
-            lineStyle: { width: 2 },
-            emphasis: {
-              focus: 'series'
-            }
-          });
-        });
-      } else {
-   
-      }
-      
-      const option = {
-        title: {
-          text: displayOption.value === 'all' ? '酸化缓解数据趋势图' : `${selectedData.value} 数据趋势`,
-          left: 'center',
-          top: 10
-        },
-        tooltip: {
-          trigger: 'axis',
-          axisPointer: {
-            type: 'cross',
-            label: {
-              backgroundColor: '#6a7985'
-            }
-          }
-        },
-        legend: {
-          data: displayOption.value === 'all' ? Object.keys(chartData.value.series) : [selectedData.value],
-          top: 40,
-          type: 'scroll'
-        },
-        grid: {
-          left: '15%',
-          right: '4%',
-          bottom: '3%',
-          top: '80px',
-          containLabel: true
-        },
-        xAxis: {
-          type: 'category',
-          boundaryGap: false,
-          data: chartData.value.timestamps, // 直接使用原始时间字符串
-          axisLabel:{rotate:30}
-        },
-        yAxis: {
-          type: 'value',
-          scale: true,
-          
-        },
-        series: series
-      };
-      
-      chartInstance.setOption(option);
-      chartInstance.resize();
-    }
-    
-    // 刷新数据
-    function refreshData() {
-      fetchData();
-    }
-    
-onMounted(() => {
-  fetchData().then(() => {
-    // 等待 Vue 完成 DOM 更新(如父元素布局、样式生效)
-    nextTick(() => {
-      if (chartInstance) {
-        chartInstance.resize(); // 确保图表适配最终尺寸
-      }
-    });
-  });
-
-  // 保留原有窗口 resize 监听
-  window.addEventListener('resize', () => {
-    if (chartInstance) {
-      chartInstance.resize();
-    }
-  });
-});
-    
-    onUnmounted(() => {
-      if (chartInstance) {
-        chartInstance.dispose();
-      }
-    });
-    
-    // 监听显示选项变化
-    watch([selectedData, displayOption], () => {
-      renderChart();
-    });
-    
-    return {
-      chartRef,
-      loading,
-      error,
-      chartData,
-      selectedData,
-      displayOption,
-      lastUpdate,
-      refreshData
-    };
-  }
-}
-</script>
-
-<style scoped>
-.chart-container {
-  width: 100%;
-  padding: 20px;
-  height: 470px;
-  box-sizing: border-box;
-}
-
-.chart {
-  width: 100%;
-  height: 100%;
-  margin-bottom: 20px;
-}
-</style>

+ 0 - 259
src/components/cdStatictics/refluxcdStatictics.vue

@@ -1,259 +0,0 @@
-<template>
-  <div class="container">    
-    <div class="chart-container">
-      <div ref="chartRef" class="chart-wrapper"></div>
-    </div>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted, onUnmounted, nextTick } from 'vue' 
-import * as echarts from 'echarts' 
-
-const indicators = [
-  //{ key: 'Al3_plus', name: 'Al3_plus', color: '#3498db' },
-  //{ key: 'CEC', name: 'CEC', color: '#2ecc71' },
-//  { key: 'CL', name: 'CL', color: '#e74c3c' },
-  { key: 'Delta_pH', name: 'Delta_pH', color: '#f39c12' },
-  //{ key: 'H_plus', name: 'H_plus', color: '#9b59b6' },
-  //{ key: 'N', name: 'N', color: '#1abc9c' },
-  //{ key: 'OM', name: 'OM', color: '#d35400' }
-];
-
-let chart = ref(null) // 存储 ECharts 实例
-let chartData = ref([]) // 存储图表数据
-const chartRef = ref(null) // 图表容器
-const statsByIndex = ref([]);//存储每个指标的统计量
-const isLoading = ref(true);
-const error = ref(false);
-const errorMessage = ref('');
-
-function initChart() {
-  // 确保图表容器已渲染
-  if (!chartRef.value) return;
-  
- 
-  //console.log('图表容器宽高:', chartRef.value.clientWidth, chartRef.value.clientHeight);
-  
-  // 初始化 ECharts 实例
-  chart.value = echarts.init(chartRef.value);
-  
-
-  const option = {
-    tooltip: {
-      trigger: 'item',
-      axisPointer: { type: 'shadow' },
-      formatter: function(params) {
-        const stat = statsByIndex.value[params.dataIndex];
-        if (!stat || stat.min === null) {
-          return `<div style="font-weight:bold;margin-bottom:8px;color:#2c3e50;">${params.name}</div>
-                  <div>无有效数据</div>`;
-        }  
-        return `
-          <div style="font-weight:bold;margin-bottom:8px;color:#2c3e50;">${stat.name}</div>
-          <div style="display:grid;grid-template-columns:1fr 1fr;gap:5px;">
-            <span style="color:#7f8c8d;">最小值:</span> <span style="text-align:right;font-weight:bold;">${stat.min}</span>
-            <span style="color:#7f8c8d;">下四分位:</span> <span style="text-align:right;font-weight:bold;color:#e74c3c;">${stat.q1}</span>
-            <span style="color:#7f8c8d;">中位数:</span> <span style="text-align:right;font-weight:bold;color:#3498db;">${stat.median}</span>
-            <span style="color:#7f8c8d;">上四分位:</span> <span style="text-align:right;font-weight:bold;color:#e74c3c;">${stat.q3}</span>
-            <span style="color:#7f8c8d;">最大值:</span> <span style="text-align:right;font-weight:bold;">${stat.max}</span>
-          </div>
-        `;
-      }
-    },
-    title: {
-      text: '酸化加剧指标箱线图展示',
-      left: 'center',
-      textStyle: {
-        fontSize: 18,
-        fontWeight: 'normal'
-      },
-      top: 10
-    },
-    grid: {
-      left: '0px',
-      right: '0px',
-      bottom: '0px',
-      top: '70px',
-      containLabel: true
-    },
-    xAxis: {
-      type: 'category',
-      data: indicators.map(i => i.name),
-      axisLabel: {
-        rotate: 30,
-        fontSize: 12,
-        margin: 15
-      },
-      axisTick: {
-        alignWithLabel: true
-      },
-    },
-    yAxis: {
-      type: 'value',
-      name: '数值',
-      nameTextStyle: {
-        fontSize: 14
-      },
-      nameGap: 25,
-      splitLine: {
-        lineStyle: {
-          type: 'dashed',
-          color: '#ddd'
-        }
-      }
-    },
-    series: [{
-      name: '指标分布',
-      type: 'boxplot',
-      itemStyle: {
-        color: function(params) {
-          return indicators[params.dataIndex].color;
-        },
-        borderWidth: 2
-      },
-      emphasis: {
-        itemStyle: {
-          shadowBlur: 10,
-          shadowColor: 'rgba(0, 0, 0, 0.3)'
-        }
-      }
-    }]
-  };
-  
-  chart.value.setOption(option);
-}
-
-
-function calculatePercentile(sortedArray, percentile) {
-  const n = sortedArray.length;
-  if (n === 0) return null;
-  if (percentile <= 0) return sortedArray[0];
-  if (percentile >= 100) return sortedArray[n - 1];
-  
-  const index = (n - 1) * (percentile / 100);
-  const lowerIndex = Math.floor(index);
-  const upperIndex = lowerIndex + 1;
-  const fraction = index - lowerIndex;
-  
-  if (upperIndex >= n) return sortedArray[lowerIndex];
-  return sortedArray[lowerIndex] + fraction * (sortedArray[upperIndex] - sortedArray[lowerIndex]);
-}
-
-
-function calculateBoxplotStats(data, indicators) {
-  const boxplotData = [];
-  const statsArray = [];
-  
-  indicators.forEach(indicator => {
-    const values = data
-      .map(item => Number(item[indicator.key]))
-      .filter(val => !isNaN(val))
-      .sort((a, b) => a - b);
-    
-    if (values.length === 0) {
-      boxplotData.push([null, null, null, null, null]);
-      statsArray.push({
-        min: null, q1: null, median: null, q3: null, max: null,
-        name: indicator.name,
-        color: indicator.color
-      });
-    } else {
-      const min = Math.min(...values);
-      const max = Math.max(...values);
-      const q1 = calculatePercentile(values, 25);
-      const median = calculatePercentile(values, 50);
-      const q3 = calculatePercentile(values, 75);
-      boxplotData.push([min, q1, median, q3, max]);
-      
-      statsArray.push({
-        min, q1, median, q3, max,
-        name: indicator.name,
-        color: indicator.color
-      });
-    }
-  });
-  
-  return { boxplotData, statsArray };
-}
-
-
-async function loadData() {
-  isLoading.value = true;
-  error.value = false;
-  
-  try {
-    const response = await fetch('http://localhost:5000/api/table-data?table_name=dataset_60');
-    const result = await response.json();
-    chartData.value = result.data;
-
-    const { boxplotData, statsArray } = calculateBoxplotStats(chartData.value, indicators);
-    statsByIndex.value = statsArray;
-    
-    chart.value.setOption({
-      series: [{
-        data: boxplotData
-      }]
-    });
-    
-  } catch (err) {
-    error.value = true;
-    errorMessage.value = `加载失败: ${err.message || '网络错误'}`;
-    console.error('数据加载失败:', err);
-  } finally {
-    isLoading.value = false;
-  }
-}
-
-const handleResize = () => {
-  if (chart.value) {
-    chart.value.resize();
-  }
-};
-
-onMounted(() => {
-  nextTick(() => {
-    initChart();
-    loadData();
-  });
-  window.addEventListener('resize', handleResize);
-});
-
-onUnmounted(() => {
-  if (chart.value) {
-    chart.value.dispose(); // 销毁 ECharts 实例
-    chart.value = null;
-  }
-  window.removeEventListener('resize', handleResize);
-});
-</script>
-
-<style scoped>
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
-}
-
-.container {
-  width: 100%;
-  height: 100%;
-  margin: 0 auto;
-  background: white;
-  border-radius: 12px;
-  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
-  overflow: hidden;
-}
-.chart-container {
-    width: 100%;
-    height: 100%;
-  padding: 20px;
-  position: relative;
-}
-.chart-wrapper {
-  width: 100%;
-  height: 100%; 
-  min-height: 200px;
-}
-</style>

+ 2 - 22
src/components/detectionStatistics/atmcompanyStatics.vue

@@ -209,7 +209,7 @@ export default {
           left: 0,
           right: "15%",
           top: 20,
-          bottom: 10,
+          bottom: "3%",
           containLabel: true
         },
         xAxis: {
@@ -267,33 +267,13 @@ export default {
 </script>
 
 <style>
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
-}
-
-body {
-  background: #f8fafc;
-  color: #34495e;
-  min-height: 100vh;
-  padding: 20px;
-}
-
 .dashboard {
-  height: 350px;
+  height: 100%;
   width: 100%;
   max-width: 1000px;
   margin: 0 auto;
 }
 
-header {
-  text-align: center;
-  padding: 20px 0;
-  margin-bottom: 20px;
-}
-
 h1 {
   font-size: 24px;
   color: #2980b9;

+ 70 - 136
src/components/detectionStatistics/atmsampleStatistics.vue

@@ -28,29 +28,33 @@
 import * as echarts from 'echarts'
 import VChart from 'vue-echarts'
 import axios from 'axios'
-import { ref, onMounted ,computed} from 'vue'
+import { ref, onMounted, computed } from 'vue'
 
 export default {
   components: { VChart },
   setup() {
     // -------- 核心配置 --------
-    const apiUrl = ref('http://localhost:8000/api/vector/export/all?table_name=Atmo_sample_data')
+    // 新接口地址(直接返回箱线图所需统计数据)
+    const apiUrl = ref('http://localhost:8000/api/vector/stats/Atmo_sample_data')
     const heavyMetals = [
       { key: 'Cr_particulate', name: '铬 (Cr)', color: '#FF9800' },
       { key: 'As_particulate', name: '砷 (As)', color: '#4CAF50' },
       { key: 'Cd_particulate', name: '镉 (Cd)', color: '#9C27B0' },
       { key: 'Hg_particulate', name: '汞 (Hg)', color: '#2196F3' },
       { key: 'Pb_particulate', name: '铅 (Pb)', color: '#F44336' },
-     // { key: 'particle_weight', name:'大气污染物重量' }
     ]
 
-    // -------- 状态 --------
-    const sampleData = ref([])    // 存储 properties 数据
+    // -------- 状态管理 --------
     const chartOption = ref({})   // ECharts 配置
     const isLoading = ref(true)   // 加载状态
     const error = ref(null)       // 错误信息
-    const statsByIndex = ref([])  // 缓存统计结果(与 x 轴对齐)
-    const totalPoints = computed(() => sampleData.value.length)
+    const statsByIndex = ref([])  // 与x轴对齐的统计结果(用于tooltip)
+    const totalPoints = computed(() => {
+      // 从统计数据中获取样本总数(假设所有金属样本数相同)
+      return statsByIndex.value.length > 0 
+        ? statsByIndex.value[0].count || 0 
+        : 0;
+    })
 
 
     // -------- 工具函数 --------
@@ -63,84 +67,22 @@ export default {
       )
     }
 
-    /** 计算百分位数(线性插值法) */
-    const calculatePercentile = (sortedArray, percentile) => {
-      const n = sortedArray.length
-      if (n === 0) return null
-      if (percentile <= 0) return sortedArray[0]
-      if (percentile >= 100) return sortedArray[n - 1]
-      const index = (n - 1) * (percentile / 100)
-      const lowerIndex = Math.floor(index)
-      const upperIndex = lowerIndex + 1
-      const fraction = index - lowerIndex
-      if (upperIndex >= n) return sortedArray[lowerIndex]
-      return sortedArray[lowerIndex] + fraction * (sortedArray[upperIndex] - sortedArray[lowerIndex])
-    }
-
-    // -------- 数据统计 --------
-    /** 计算每个重金属的箱线图统计量(min/q1/median/q3/max) */
-    const calculateBoxplotStats = () => {
-      const stats = []
-      heavyMetals.forEach((metal) => {
-        //log(`开始处理 ${metal.name}`, metal.name)
-        // 1. 提取原始值
-        const rawValues = sampleData.value.map(item => item[metal.key])
-        //log(`原始值:[${rawValues.slice(0, 5)}${rawValues.length > 5 ? ', ...' : ''}]`, metal.name)
-
-        // 2. 过滤无效值(NaN、非数字)
-        const values = rawValues
-          .map((val, idx) => {
-            const num = Number(val)
-            if (isNaN(num)) {
-              log(`⚠️ 第${idx+1}条数据无效: ${val}`, metal.name)
-              return null
-            }
-            return num
-          })
-          .filter(v => v !== null)
-        //log(`有效数据量:${values.length} 条`, metal.name)
-
-        // 3. 无有效数据时,记录空统计
-        if (values.length === 0) {
-          stats.push({ ...metal, min: null, q1: null, median: null, q3: null, max: null })
-          return
-        }
-
-        // 4. 排序并计算统计量
-        const sorted = [...values].sort((a, b) => a - b)
-        const min = sorted[0]
-        const max = sorted[sorted.length - 1]
-        const q1 = calculatePercentile(sorted, 25)
-        const median = calculatePercentile(sorted, 50)
-        const q3 = calculatePercentile(sorted, 75)
-
-        //log(`统计结果:min=${min}, q1=${q1}, median=${median}, q3=${q3}, max=${max}`, metal.name)
-        stats.push({ ...metal, min, q1, median, q3, max })
-      })
-      return stats
-    }
-
-    /** 构建 ECharts 箱线图数据 */
+    /** 构建箱线图数据 */
     const buildBoxplotData = (stats) => {
       const xAxisData = heavyMetals.map(m => m.name)
-      // 与 x 轴顺序对齐(确保 tooltip 能正确匹配)
-      statsByIndex.value = heavyMetals.map(m => 
-        stats.find(s => s.key === m.key) || { ...m, min: null, q1: null, median: null, q3: null, max: null }
-      )
-      // 生成箱线图数据:[min, q1, median, q3, max]
-      const data = statsByIndex.value.map(s => 
-        s.min === null ? [null, null, null, null, null] : [s.min, s.q1, s.median, s.q3, s.max]
+      // 生成箱线图所需格式:[[min, q1, median, q3, max], ...]
+      const data = stats.map(s => 
+        s.min === null 
+          ? [null, null, null, null, null] 
+          : [s.min, s.q1, s.median, s.q3, s.max]
       )
       return { xAxisData, data }
     }
 
-    // -------- 图表初始化 --------
-    const initChart = () => {
-      //log('开始初始化图表')
-      const stats = calculateBoxplotStats()
-      const { xAxisData, data } = buildBoxplotData(stats)
-
-      // ECharts 配置(重点检查 series 数据格式)
+    /** 初始化图表 */
+    const initChart = (xAxisData, data, stats) => {
+      statsByIndex.value = stats // 缓存统计数据用于tooltip
+      
       chartOption.value = {
         tooltip: {
           trigger: 'item',
@@ -148,8 +90,7 @@ export default {
             const s = statsByIndex.value[params.dataIndex]
             if (!s || s.min === null) {
               return `<div style="font-weight:bold;color:#f56c6c">${xAxisData[params.dataIndex]}</div><div>无有效数据</div>`
-            }
-            
+            }          
             return `<div style="font-weight:bold">${xAxisData[params.dataIndex]}</div>
               <div style="margin-top:8px">
                 <div>最小值:<span style="color:#5a5;">${s.min.toFixed(4)}</span></div>
@@ -166,20 +107,20 @@ export default {
           name: '重金属类型',
           nameLocation: 'middle',
           nameGap: 45,
-          axisLabel: { color: '#555', rotate: 30, fontWeight: 'bold',fontSize:11 }
+          axisLabel: { color: '#555', rotate: 30, fontWeight: 'bold', fontSize: 11 }
         },
         yAxis: {
           type: 'value',
           name: 'mg/kg',
           nameLocation: 'end',
           nameGap: 5,
-          axisLabel: { color: '#555', fontWeight: 'bold' ,fontSize:11},
+          axisLabel: { color: '#555', fontWeight: 'bold', fontSize: 11 },
           splitLine: { lineStyle: { color: '#f0f0f0' } }
         },
         series: [{
           name: '重金属浓度分布',
           type: 'boxplot',
-          data, // 必须是 [[min,q1,median,q3,max], ...] 格式
+          data, // 直接使用接口返回的统计数据
           itemStyle: {
             color: (p) => (heavyMetals[p.dataIndex]?.color || '#1890ff'),
             borderWidth: 2
@@ -188,65 +129,58 @@ export default {
             itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.2)', borderWidth: 3 }
           }
         }],
-        grid: { top: '8%', right: '5%', left: '12%', bottom: '20%' }
+        grid: { top: '8%', right: '5%', left: '8%', bottom: '20%' }
       }
-      isLoading.value = false
-      //log('图表初始化完成')
     }
 
     // -------- 接口请求 --------
-   onMounted(async () => {
-  try {
-    //log('发起API请求...')
-    const response = await axios.get(apiUrl.value)
-    //console.log('接口原始响应:', response.data) // 调试必看!
-
-    let data = response.data
-
-    // ✅ 兼容接口返回字符串的情况(比如后端没设置 application/json)
-    if (typeof data === 'string') {
+    onMounted(async () => {
       try {
-        // 兼容 NaN(非标准 JSON 值)→ 替换为 null
-        data = JSON.parse(data.replace(/\bNaN\b/g, 'null'))
-      } catch (parseErr) {
-        throw new Error('接口返回的是字符串,但 JSON 解析失败')
-      }
-    }
+        log('发起新接口请求,获取统计数据...')
+        const response = await axios.get(apiUrl.value)
+        const apiData = response.data.data
 
-    // 1. 分情况提取 features(严格校验结构)
-    let features = []
-    if (data?.type === 'FeatureCollection') {
-      // 情况1:标准 GeoJSON FeatureCollection
-      if (Array.isArray(data.features)) {
-        features = data.features
-      } else {
-        throw new Error('FeatureCollection 中 features 不是数组')
-      }
-    } else if (Array.isArray(data)) {
-      // 情况2:直接返回 features 数组
-      features = data
-    } else {
-      // 情况3:其他非法结构
-      throw new Error(`接口结构异常,响应:${JSON.stringify(data)}`)
-    }
+        // 从接口数据中提取每个重金属的统计量
+        const stats = heavyMetals.map(metal => {
+          const metalStats = apiData[metal.key]
+          if (!metalStats) {
+            log(`警告:接口缺少${metal.name}的统计数据`)
+            return { ...metal, min: null, q1: null, median: null, q3: null, max: null, count: 0 }
+          }
+          
+          // 验证必要的统计字段
+          const requiredFields = ['min', 'q1', 'median', 'q3', 'max']
+          const hasValidData = requiredFields.every(field => 
+            metalStats[field] !== undefined && !isNaN(metalStats[field])
+          )
 
-    // 2. 提取 properties 数据(确保非空)
-    sampleData.value = features.map(f => f.properties)
-    if (sampleData.value.length === 0) {
-      throw new Error('接口返回数据为空(properties 为空)')
-    }
-    //log(`成功提取 ${sampleData.value.length} 条数据`, '接口')
+          if (!hasValidData) {
+            log(`警告:${metal.name}的统计数据不完整`)
+            return { ...metal, min: null, q1: null, median: null, q3: null, max: null, count: 0 }
+          }
 
-    // 3. 初始化图表
-    initChart()
+          return {
+            ...metal,
+            min: Number(metalStats.min),
+            q1: Number(metalStats.q1),
+            median: Number(metalStats.median),
+            q3: Number(metalStats.q3),
+            max: Number(metalStats.max),
+            count: metalStats.count ? Number(metalStats.count) : 0
+          }
+        })
 
-  } catch (err) {
-    error.value = err
-    isLoading.value = false
-    console.error('接口请求失败:', err)
-  }
-})
+        // 构建图表数据并初始化图表
+        const { xAxisData, data } = buildBoxplotData(stats)
+        initChart(xAxisData, data, stats)
+        isLoading.value = false
 
+      } catch (err) {
+        error.value = err
+        isLoading.value = false
+        console.error('接口请求失败:', err)
+      }
+    })
 
     return {
       chartOption,
@@ -304,7 +238,7 @@ export default {
 }
 .chart-wrapper { 
   width: 100%; 
-  height: 220px; /* 确保高度有效 */
+  height: 220px; 
 }
 .chart-container {
   background: white;
@@ -312,6 +246,7 @@ export default {
   box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
   padding: 20px;
   margin-bottom: 25px;
+  height: 100%;
 }
 
 .sample-subtitle {
@@ -319,5 +254,4 @@ export default {
   color: #888;
   margin-top: 4px;
 }
-
-</style>
+</style>

+ 2 - 2
src/components/detectionStatistics/crosscetionStatistics.vue

@@ -204,7 +204,7 @@ export default {
           left: 0,
           right: '15%',
           top: 20, // 缩减顶部边距
-          bottom: 10, // 缩减底部边距
+          bottom: "3%", // 缩减底部边距
           containLabel: true
         },
         xAxis: {
@@ -268,7 +268,7 @@ export default {
 /* 固定组件总高度为350px */
 .dashboard {
   width: 100%;
-  height: 300px;
+  height: 100%;
   max-width: 1000px;
   margin: 0 auto;
 }

+ 36 - 94
src/components/detectionStatistics/irrigationstatistics.vue

@@ -36,15 +36,18 @@ export default {
   components: { VChart },
   setup() {
     // -------- 基本状态 --------
-    const apiUrl = ref('http://localhost:8000/api/vector/export/all?table_name=water_sampling_data')
+    const apiUrl = ref('http://localhost:8000/api/vector/stats/water_sampling_data')
     const apiTimestamp = ref(null)
     const sampleCount = ref(0)
-
-    const sampleData = ref([])
+    const statsData = ref({}); 
     const chartOption = ref({})
     const isLoading = ref(true)
     const error = ref(null)
-    const totalPoints = computed(() => sampleData.value.length)
+    const stats = null;
+    const totalPoints = computed(() => {
+      const firstMetalKey = heavyMetals[0]?.key;
+      return statsData.value[firstMetalKey]?.count || 0;
+    })
 
     // 关键:缓存每个品类的统计量(与 x 轴顺序一致)
     const statsByIndex = ref([])
@@ -57,90 +60,38 @@ export default {
       { key: 'hg_concentration', name: '汞 (Hg)', color: '#2196F3' },
       { key: 'pb_concentration', name: '铅 (Pb)', color: '#F44336' }
     ]
-
-    // -------- 日志 --------
-    const log = (message, metalName = '') => {
-      console.log(`%c[${metalName || '全局'}] %c${message}`,
-        'color:#4CAF50;font-weight:bold', 'color:#333')
-    }
-
-    // -------- 工具:百分位(线性插值) --------
-    const calculatePercentile = (sortedArray, percentile) => {
-      const n = sortedArray.length
-      if (n === 0) return null
-      if (percentile <= 0) return sortedArray[0]
-      if (percentile >= 100) return sortedArray[n - 1]
-      const index = (n - 1) * (percentile / 100)
-      const lowerIndex = Math.floor(index)
-      const upperIndex = lowerIndex + 1
-      const fraction = index - lowerIndex
-      if (upperIndex >= n) return sortedArray[lowerIndex]
-      return sortedArray[lowerIndex] + fraction * (sortedArray[upperIndex] - sortedArray[lowerIndex])
-    }
-
-    // -------- 计算统计量 --------
-    const calculateBoxplotStats = () => {
-      const stats = []
-
-      heavyMetals.forEach((metal) => {
-        //log(`开始处理重金属: ${metal.name}`, metal.name)
-        const rawValues = sampleData.value.map(item => item[metal.key])
-        //log(`原始值(未过滤): [${rawValues.join(', ')}]`, metal.name)
-
-        const values = rawValues
-          .map((val, idx) => {
-            const num = Number(val)
-            if (isNaN(num)) log(`⚠️ 第${idx + 1}条数据无效: ${val}`, metal.name)
-            return isNaN(num) ? null : num
-          })
-          .filter(v => v !== null)
-
-        //log(`有效数值(过滤后): [${values.join(', ')}]`, metal.name)
-
-        const sorted = [...values].sort((a, b) => a - b)
-        //log(`排序后(升序): [${sorted.join(', ')}]`, metal.name)
-
-        let min = null, q1 = null, median = null, q3 = null, max = null
-        if (sorted.length > 0) {
-          min = sorted[0]
-          max = sorted[sorted.length - 1]
-          q1 = calculatePercentile(sorted, 25)
-          median = calculatePercentile(sorted, 50)
-          q3 = calculatePercentile(sorted, 75)
-          const ok = min <= q1 && q1 <= median && median <= q3 && q3 <= max
-          //log(`统计量: min=${min}, q1=${q1}, median=${median}, q3=${q3}, max=${max} → 顺序OK: ${ok}`, metal.name)
-        } else {
-          log(`⚠️ 无有效数据,跳过统计`, metal.name)
-        }
-
-        stats.push({ key: metal.key, name: metal.name, min, q1, median, q3, max, color: metal.color })
-      })
-
-      return stats
-    }
-
     // -------- 构建箱线数据(保留你自己的顺序) --------
-    const buildBoxplotData = (stats) => {
-      const xAxisData = heavyMetals.map(m => m.name)
-
-      // 与 x 轴顺序对齐的统计量缓存(**tooltip 用它,不用 params.data**)
-      statsByIndex.value = heavyMetals.map(m =>
-        stats.find(s => s.key === m.key) || { min: null, q1: null, median: null, q3: null, max: null }
-      )
-
-      const data = statsByIndex.value.map((s, i) => {
-        if (s.min === null) return [null, null, null, null, null]
-        const box = [s.min, s.q1, s.median, s.q3, s.max]
-        //log(`箱线数据(${xAxisData[i]}): [${box.join(', ')}]`, '箱线构建')
-        return box
-      })
+    const buildBoxplotData = () => {
+      const xAxisData = heavyMetals.map(m => m.name); // 保持x轴顺序与配置一致
+
+      // 缓存每个重金属的统计量(用于tooltip)
+      statsByIndex.value = heavyMetals.map(metal => {
+       const stat = statsData.value[metal.key] || {}; // 从接口数据中取当前金属的统计
+        return {
+          key: metal.key,
+          name: metal.name,
+          min: stat.min,
+          q1: stat.q1,
+          median: stat.median,
+          q3: stat.q3,
+          max: stat.max,
+          color: metal.color
+        };
+      });
+
+      // 构建ECharts箱线图所需的二维数组格式 [[min, q1, median, q3, max], ...]
+      const data = statsByIndex.value.map(s => {
+        if (s.min === undefined || s.min === null) {
+          return [null, null, null, null, null]; // 无数据时返回空数组
+        }
+        return [s.min, s.q1, s.median, s.q3, s.max];
+      });
 
-      return { xAxisData, data }
-    }
+       return { xAxisData, data };
+    };
 
     // -------- 初始化图表 --------
     const initChart = () => {
-      const stats = calculateBoxplotStats()
       const { xAxisData, data } = buildBoxplotData(stats)
 
       chartOption.value = {
@@ -201,18 +152,10 @@ export default {
     onMounted(async () => {
       try {
         //log('发起API请求...')
-        const response = await axios.get(apiUrl.value)
+        const response = await axios.get(apiUrl.value);
+        statsData.value = response.data.data; 
         apiTimestamp.value = new Date().toLocaleString()
-
-        if (response.data && response.data.features) {
-          // 你的接口:GeoJSON -> features[].properties
-          sampleData.value = response.data.features.map(f => f.properties)
-          sampleCount.value = sampleData.value.length
-          //log(`接口返回数据量: ${sampleCount.value} 条`, '接口')
           initChart()
-        } else {
-          throw new Error('接口返回数据格式不正确(无features字段)')
-        }
       } catch (err) {
         error.value = err
         isLoading.value = false
@@ -272,7 +215,6 @@ export default {
   background: white;
   border-radius: 12px;
   box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
-  margin-bottom: 25px;
   height: 100%;
   box-sizing: border-box;
 }

+ 3 - 3
src/components/irrpollution/crossSetionData1.vue

@@ -150,7 +150,8 @@ const updateChart = () => {
       formatter: '{a} <br/>{b}: {c} ug/L'
     },
     grid: {
-      right: '4%',
+      left: '5%',
+      right: '5%',
       bottom: '3%',
       containLabel: true
     },
@@ -207,7 +208,6 @@ onBeforeUnmount(() => {
 .chart-page {
   width: 100%;
   margin: 0 auto 24px;
-  background-color: white;
   border-radius: 12px;
   padding: 20px;
   box-sizing: border-box;
@@ -216,7 +216,7 @@ onBeforeUnmount(() => {
 
 .chart-container {
   width: 100%; 
-  height: 400px;
+  height:400px;
   margin: 0 auto;
   border-radius: 12px;
 }

+ 2 - 2
src/components/irrpollution/crosssectionmap.vue

@@ -60,7 +60,7 @@ onMounted(() => {
     return '#cccccc';
   }
 
-  // 加载区县边界(保持不变)
+  // 加载区县边界(保持不变)http://localhost:8000/api/vector/boundary?table_name=counties&field_name=city_name&field_value=%E9%9F%B6%E5%85%B3%E5%B8%82
   fetch('/data/韶关市各区县边界图.geojson')
     .then(res => {
       if (!res.ok) throw new Error(`区县边界加载失败:${res.status}`);
@@ -189,7 +189,7 @@ onMounted(() => {
 /* 原有样式保持不变 */
 .map-wrapper {
   width: 100%;
-  height: 80%;
+  height: 100%;
   position: relative;
 }
 .map-container {

+ 2 - 2
src/components/irrpollution/irrwatermap.vue

@@ -47,7 +47,7 @@ onMounted(() => {
     "南雄市": "#06D6A0",
   };
 
-  // 加载区县边界(带完整错误处理)
+  // 加载区县边界(带完整错误处理)http://localhost:8000/api/vector/boundary?table_name=counties&field_name=city_name&field_value=%E9%9F%B6%E5%85%B3%E5%B8%82
   fetch('/data/韶关市各区县边界图.geojson')
     .then(res => {
       if (!res.ok) throw new Error(`区县边界加载失败:${res.status}`);
@@ -195,7 +195,7 @@ onMounted(() => {
 }
 .map-container {
   width: 100% !important;
-  height: 500px !important;
+  height: 100% !important;
 }
 
 /*  标题和分隔线 */

+ 0 - 222
src/components/irrpollution/waterassaydata1.vue

@@ -1,222 +0,0 @@
-<template>
-  <div class="boxplot-container">
-    <div ref="chartRef" style="width: 100%; height: 500px;"></div>
-  </div>
-</template>
-<!--各种重金属的箱图-->
-<script setup lang="ts">
-import * as echarts from 'echarts';
-import { ref, onMounted, onUnmounted } from 'vue';
-import axios from 'axios';
-
-// 明确定义数据类型
-interface HeavyMetalData {
-  sampleId: string;
-  Cr: number | null;
-  As: number | null;
-  Cd: number | null;
-  Hg: number | null;
-  Pb: number | null;
-}
-
-const METALS = ['Cr', 'As', 'Cd', 'Hg', 'Pb'] as const;
-type MetalType = typeof METALS[number];
-
-const METAL_LABELS: Record<MetalType, string> = {
-  Cr: '铬(Cr)',
-  As: '砷(As)',
-  Cd: '镉(Cd)',
-  Hg: '汞(Hg)',
-  Pb: '铅(Pb)'
-};
-
-// 图表变量
-const chartRef = ref<HTMLElement | null>(null);
-const chartInstance = ref<echarts.ECharts | null>(null);
-const metalData = ref<HeavyMetalData[]>([]);
-let resizeHandler: (() => void) | null = null; // 用于存储resize处理函数
-
-// 数据清洗函数
-const cleanData = (rawValue: any): number | null => {
-  if (typeof rawValue === 'string') {
-    const num = parseFloat(rawValue);
-    return isNaN(num) || num < 0 ? null : num;
-  }
-  return typeof rawValue === 'number' && rawValue >= 0 ? rawValue : null;
-};
-
-// 修复后的四分位数计算算法
-const calculateBoxplotStats = (values: number[]): [number, number, number, number, number] | null => {
-  if (values.length < 5) return null; // 至少需要5个数据点才能生成有效的箱线图
-  
-  // 升序排序
-  const sorted = [...values].sort((a, b) => a - b);
-  const n = sorted.length;
-
-  // 正确的分位位置计算
-  const quantile = (p: number) => {
-    const pos = (n + 1) * p;
-    const lowerIndex = Math.max(0, Math.min(n - 1, Math.floor(pos) - 1));
-    const fraction = pos - Math.floor(pos);
-    
-    if (lowerIndex >= n - 1) return sorted[n - 1];
-    return sorted[lowerIndex] + fraction * (sorted[lowerIndex + 1] - sorted[lowerIndex]);
-  };
-
-  return [
-    sorted[0],               // 最小值
-    quantile(0.25),          // Q1
-    quantile(0.5),           // 中位数
-    quantile(0.75),          // Q3
-    sorted[n - 1]            // 最大值
-  ];
-};
-
-// 渲染图表
-const renderBoxplot = () => {
-  if (!chartRef.value || metalData.value.length === 0) return;
-  
-  // 移除旧的resize监听器
-  if (resizeHandler) {
-    window.removeEventListener('resize', resizeHandler);
-  }
-
-  // 分组收集每种金属的有效数值
-  const metalValues = Object.fromEntries(
-    METALS.map(metal => [
-      metal, 
-      metalData.value
-        .map(item => item[metal])
-        .filter((val): val is number => val !== null)
-    ])
-  ) as Record<MetalType, number[]>;
-
-  // 准备箱线图数据
-  const validBoxplotData: ([number, number, number, number, number] | null)[] = 
-    METALS.map(metal => calculateBoxplotStats(metalValues[metal]));
-
-  // ECharts配置
-  const option: echarts.EChartsOption = {
-    backgroundColor: '#FFFFFF',
-    title: {
-      text: '重金属浓度分布箱线图',
-      left: 'center',
-      textStyle: { color: '#333', fontSize: 16 }
-    },
-    tooltip: {
-      trigger: 'item',
-      formatter: (params: any) => {
-        const metalIndex = params.dataIndex;
-        const metal = METALS[metalIndex];
-        const stats = validBoxplotData[metalIndex];
-        
-        // 处理空数据情况(修复图片中的null错误)
-        if (stats === null || stats[0] === null) {
-          return `<span style="color:#ff0000">${METAL_LABELS[metal]}数据不足,无法生成统计值</span>`;
-        }
-        
-        // 类型安全解构(确保所有值都是number类型)
-        const [min, q1, median, q3, max] = stats;
-        
-        return `
-          <b>${METAL_LABELS[metal]}</b><br/>
-          最小值: ${min.toFixed(4)} mg/L<br/>
-          下四分位: ${q1.toFixed(4)} mg/L<br/>
-          中位数: ${median.toFixed(4)} mg/L<br/>
-          上四分位: ${q3.toFixed(4)} mg/L<br/>
-          最大值: ${max.toFixed(4)} mg/L
-        `;
-      }
-    },
-    xAxis: {
-      type: 'category',
-      data: METALS.map(metal => METAL_LABELS[metal]),
-      axisLabel: { color: '#333', interval: 0 }
-    },
-    yAxis: {
-      type: 'value',
-      name: '浓度(mg/L)',
-      nameTextStyle: { color: '#333' },
-      axisLabel: { 
-        color: '#333',
-        formatter: (value: number) => value.toFixed(4)
-      }
-    },
-    series: [{
-      type: 'boxplot',
-      // 过滤无效数据(解决ts 2322错误)
-      data: validBoxplotData.filter(arr => arr !== null) as [number, number, number, number, number][],
-      itemStyle: {
-        color: '#4285F4',
-        borderWidth: 1.5
-      },
-      emphasis: {
-        itemStyle: {
-          borderColor: '#333',
-          borderWidth: 2
-        }
-      }
-    }]
-  };
-
-  // 初始化图表
-  if (chartInstance.value) {
-    chartInstance.value.dispose();
-  }
-  chartInstance.value = echarts.init(chartRef.value);
-  chartInstance.value.setOption(option);
-  
-  // 响应式处理
-  resizeHandler = () => chartInstance.value?.resize();
-  window.addEventListener('resize', resizeHandler);
-};
-
-// 数据加载
-const loadData = async () => {
-  try {
-    const response = await axios.get<any[]>(
-      'http://localhost:3000/table/Water_assay_data',
-      { timeout: 5000 }
-    );
-    
-    // 数据转换与过滤
-    metalData.value = response.data
-      .map(item => ({
-        sampleId: String(item.sampleId),
-        Cr: cleanData(item.Cr),
-        As: cleanData(item.As),
-        Cd: cleanData(item.Cd),
-        Hg: cleanData(item.Hg),
-        Pb: cleanData(item.Pb)
-      }))
-      // 修复:允许部分有效数据
-      .filter(item => METALS.some(metal => item[metal] !== null));
-    
-    renderBoxplot();
-  } catch (error) {
-    console.error('数据加载失败:', error);
-    alert('数据加载错误,请查看控制台日志');
-  }
-};
-
-onMounted(() => loadData());
-onUnmounted(() => {
-  // 清理资源
-  if (resizeHandler) {
-    window.removeEventListener('resize', resizeHandler);
-  }
-  chartInstance.value?.dispose();
-});
-</script>
-
-<style scoped>
-.boxplot-container {
-  width: 100%;
-  max-width: 1000px;
-  margin: 20px auto;
-  padding: 20px;
-  background: white;
-  border-radius: 8px;
-  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
-}
-</style>

+ 1 - 1
src/components/irrpollution/waterassaydata2.vue

@@ -244,7 +244,7 @@ const initChart = ({ regions, series, totalSamples }) => {
     grid: { //整个图的位置
       left: '3%', 
       right: '3%', 
-      bottom: '5%', 
+      bottom: 0, 
       containLabel: true 
     },
   };

+ 0 - 197
src/components/irrpollution/waterassaydata3.vue

@@ -1,197 +0,0 @@
-<template>
-  <div class="heavy-metal-radar">
-    <h2 class="chart-title">重金属指标雷达图分析</h2>
-    <canvas ref="chartRef" class="chart-box"></canvas>
-    <div v-if="loading" class="status">数据加载中...</div>
-    <div v-else-if="error" class="status error">{{ error }}</div>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted, onUnmounted } from 'vue';
-import Chart from 'chart.js/auto';
-import axios from 'axios';
-
-// ========== 接口配置(和柱状图对齐) ==========
-const ASSAY_API = 'http://localhost:3000/table/Water_assay_data'; // 复用柱状图的接口
-
-// ========== 配置项(模仿柱状图) ==========
-const EXCLUDE_FIELDS = [
-  'water_assay_ID', 'sample_code', 'assayer_ID', 'assay_time', 
-  'assay_instrument_model', 'water_sample_ID', 'pH'
-];
-const COLORS = ['#165DFF', '#36CFC9', '#722ED1']; // 雷达图三色
-
-// ========== 响应式数据 ==========
-const chartRef = ref(null);
-const loading = ref(true);
-const error = ref('');
-let radarChart = null;
-
-// ========== 数据处理:提取重金属指标 + 统计计算 ==========
-const processRadarData = (assayData) => {
-  // 1. 提取重金属字段(排除指定字段,且为数值类型)
-  const metals = Object.keys(assayData[0] || {})
-    .filter(key => !EXCLUDE_FIELDS.includes(key) && !isNaN(parseFloat(assayData[0][key])));
-
-  // 2. 计算每个重金属的统计值(均值、中位数、标准差)
-  const stats = metals.map(metal => {
-    const values = assayData.map(item => parseFloat(item[metal])).filter(v => !isNaN(v));
-    return {
-      mean: calculateMean(values),
-      median: calculateMedian(values),
-      std: calculateStdDev(values)
-    };
-  });
-
-  return { metals, stats };
-};
-
-// ========== 统计工具函数 ==========
-const calculateMean = (values) => {
-  if (values.length === 0) return 0;
-  return values.reduce((sum, val) => sum + val, 0) / values.length;
-};
-
-const calculateMedian = (values) => {
-  if (values.length === 0) return 0;
-  const sorted = [...values].sort((a, b) => a - b);
-  const mid = Math.floor(sorted.length / 2);
-  return sorted.length % 2 === 0 
-    ? (sorted[mid - 1] + sorted[mid]) / 2 
-    : sorted[mid];
-};
-
-const calculateStdDev = (values) => {
-  if (values.length <= 1) return 0;
-  const mean = calculateMean(values);
-  const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
-  return Math.sqrt(variance);
-};
-
-// ========== 初始化雷达图(Chart.js) ==========
-const initRadarChart = ({ metals, stats }) => {
-  if (!chartRef.value) return;
-  if (radarChart) radarChart.destroy();
-
-  const ctx = chartRef.value.getContext('2d');
-  radarChart = new Chart(ctx, {
-    type: 'radar',
-    data: {
-      labels: metals,
-      datasets: [
-        {
-          label: '均值',
-          data: stats.map(s => s.mean.toFixed(2)),
-          borderColor: COLORS[0],
-          backgroundColor: 'rgba(22, 93, 255, 0.1)',
-          pointRadius: 4,
-          borderWidth: 2
-        },
-        {
-          label: '中位数',
-          data: stats.map(s => s.median.toFixed(2)),
-          borderColor: COLORS[1],
-          backgroundColor: 'rgba(54, 207, 201, 0.1)',
-          pointRadius: 4,
-          borderWidth: 2
-        },
-        {
-          label: '标准差',
-          data: stats.map(s => s.std.toFixed(2)),
-          borderColor: COLORS[2],
-          backgroundColor: 'rgba(114, 46, 209, 0.1)',
-          pointRadius: 4,
-          borderWidth: 2
-        }
-      ]
-    },
-    options: {
-      responsive: true,
-      maintainAspectRatio: false,
-      scales: {
-        r: {
-          beginAtZero: true,
-          ticks: { display: false },
-          pointLabels: { font: { size: 12, weight: 'bold' } },
-          grid: { color: 'rgba(0,0,0,0.05)' },
-          angleLines: { color: 'rgba(0,0,0,0.1)' }
-        }
-      },
-      plugins: {
-        legend: { position: 'bottom' },
-        tooltip: {
-          callbacks: {
-            label: (ctx) => `${ctx.dataset.label}: ${ctx.raw} mg/L`
-          }
-        }
-      }
-    }
-  });
-};
-
-// ========== 生命周期钩子(和柱状图对齐) ==========
-onMounted(async () => {
-  try {
-    // 【关键】和柱状图一样,axios 请求 **不携带凭证**(withCredentials: false,默认就是false)
-    const assayRes = await axios.get(ASSAY_API, { timeout: 10000, withCredentials:false });
-    const processed = processRadarData(assayRes.data);
-    
-    if (processed.metals.length === 0) {
-      throw new Error('未检测到有效重金属指标');
-    }
-    
-    initRadarChart(processed);
-  } catch (err) {
-    error.value = '数据加载失败: ' + (err.message || '未知错误');
-    console.error('接口错误:', err);
-  } finally {
-    loading.value = false;
-  }
-});
-
-// 响应式resize(模仿柱状图)
-const resizeHandler = () => radarChart && radarChart.resize();
-onMounted(() => window.addEventListener('resize', resizeHandler));
-onUnmounted(() => window.removeEventListener('resize', resizeHandler));
-</script>
-
-<style scoped>
-.heavy-metal-radar {
-  width: 100%;
-  max-width: 800px;
-  margin: 20px auto;
-  position: relative;
-  padding-top: 0;
-  background-color: white;
-  border-radius: 8px;
-}
-.chart-box {
-  width: 100%;
-  min-height: 350px;
-  max-height: 600px;
-  height: auto;
-  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
-}
-.chart-title {
-  text-align: center;   /* 水平居中 */
-  font-size: 18px;      /* 字体大小 */
-  font-weight: 600;     /* 加粗 */
-  color: #333;          /* 字体颜色 */
-  margin: 10px 0;     /* 底部间距,避免和图表贴紧 */
-}
-.status {
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  padding: 15px;
-  background: rgba(255,255,255,0.8);
-  border-radius: 4px;
-  z-index: 10;
-}
-.error { 
-  color: #ff4d4f;
-  font-weight: bold;
-}
-</style>

+ 0 - 340
src/components/irrpollution/waterassaydata4.vue

@@ -1,340 +0,0 @@
-<template>
-  <div class="region-average-chart">
-    <!-- 重金属选择器 -->
-    <div class="metal-selector" v-if="!loading && !error && metals.length > 0">
-      <label for="metal-select">选择重金属:</label>
-      <select id="metal-select" v-model="selectedMetal">
-        <option v-for="metal in metals" :key="metal" :value="metal">{{ metal }}</option>
-      </select>
-    </div>
-    
-    <!-- 图表容器 -->
-    <div ref="chartRef" class="chart-box"></div>
-    
-    <!-- 状态信息 -->
-    <div v-if="loading" class="status">数据加载中...</div>
-    <div v-else-if="error" class="status error">{{ error }}</div>
-    <div v-else-if="metals.length === 0" class="status">没有有效的重金属数据</div>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted, onUnmounted, watch } from 'vue';
-import * as echarts from 'echarts';
-import axios from 'axios';
-
-// ========== 接口配置 ==========
-const SAMPLING_API = 'http://localhost:3000/table/Water_sampling_data';
-const ASSAY_API = 'http://localhost:3000/table/Water_assay_data';
-
-// ========== 配置项 ==========
-const EXCLUDE_FIELDS = [
-  'water_assay_ID', 'sample_code', 'assayer_ID', 'assay_time', 
-  'assay_instrument_model', 'water_sample_ID', 'pH'
-];
-const COLORS = [
-  '#FF6B6B', '#4ECDC4', '#FFD166', '#6A4C93', '#1982C4',
-  '#FF9F1C', '#2EC4B6', '#E71D36', '#3A86FF', '#FF006E'
-];
-
-// 韶关市下属行政区划
-const SG_REGIONS = [
-  '浈江区', '武江区', '曲江区', '乐昌市', 
-  '南雄市', '始兴县', '仁化县', '翁源县', 
-  '新丰县', '乳源瑶族自治县'
-];
-
-// ========== 响应式数据 ==========
-const chartRef = ref(null);
-const loading = ref(true);
-const error = ref('');
-// 修复 selectedMetal 未定义问题:提前声明变量
-const selectedMetal = ref(''); 
-const metals = ref([]);
-const chartData = ref(null);
-let myChart = null;
-
-// ========== 地区提取函数 ==========
-const extractRegion = (location) => {
-  if (!location || typeof location !== 'string') return null;
-
-  // 1. 精确匹配官方区县名称
-  const officialMatch = SG_REGIONS.find(region => 
-    location.includes(region)
-  );
-  if (officialMatch) return officialMatch;
-
-  // 2. 处理嵌套格式(如"韶关市-浈江区")
-  const nestedMatch = location.match(/(韶关市)([^市]+?[区市县])/);
-  if (nestedMatch && nestedMatch[2]) {
-    const region = nestedMatch[2].replace("韶关市", "").trim();
-    // 验证是否为合法区县
-    const validRegion = SG_REGIONS.find(r => r.includes(region));
-    if (validRegion) return validRegion;
-  }
-
-  // 3. 特殊格式处理(如"韶关市浈江区")
-  const shortMatch = location.match(/韶关市([区市县][^市]{2,5})/);
-  if (shortMatch && shortMatch[1]) return shortMatch[1];
-
-  // 4. 修正常见拼写错误
-  if (location.includes('乐昌')) return '乐昌市';
-  if (location.includes('乳源')) return '乳源瑶族自治县';
-
-  console.warn(`⚠️ 未识别地区: ${location}`);
-  return '未知区县';
-};
-
-// ========== 数据处理流程 ==========
-const processMergedData = (samplingData, assayData) => {
-  // 1. 构建采样点ID到区县的映射
-  const regionMap = new Map();
-  const uniqueSampleIds = new Set();
-  
-  samplingData.forEach(item => {
-    const region = extractRegion(item.sampling_location || '');
-    if (region && region !== '未知区县') {
-      regionMap.set(item.water_sample_ID, region);
-    }
-  });
-
-  // 2. 关联重金属数据与区县
-  const mergedData = assayData.map(item => ({
-    ...item,
-    region: regionMap.get(item.water_sample_ID) || '未知区县'
-  }));
-
-  // 3. 识别重金属字段
-  const detectedMetals = Object.keys(mergedData[0] || {})
-    .filter(key => !EXCLUDE_FIELDS.includes(key) && !isNaN(parseFloat(mergedData[0][key])));
-  
-  metals.value = detectedMetals;
-  if (detectedMetals.length > 0 && !selectedMetal.value) {
-    selectedMetal.value = detectedMetals[0];
-  }
-
-  // 4. 按区县分组统计
-  const regionGroups = {};
-  
-  mergedData.forEach(item => {
-    const region = item.region;
-    const sampleId = item.water_sample_ID;
-    
-    if (sampleId) uniqueSampleIds.add(sampleId);
-    
-    // 初始化区县数据
-    if (!regionGroups[region]) {
-      regionGroups[region] = {};
-      detectedMetals.forEach(metal => {
-        regionGroups[region][metal] = { sum: 0, count: 0 };
-      });
-    }
-
-    // 统计重金属数据
-    detectedMetals.forEach(metal => {
-      const val = parseFloat(item[metal]);
-      if (!isNaN(val)) {
-        regionGroups[region][metal].sum += val;
-        regionGroups[region][metal].count++;
-      }
-    });
-  });
-
-  // 5. 构建扇形图数据
-  const pieSeriesData = detectedMetals.map(metal => {
-    // 该重金属在各区县的平均浓度总和
-    let totalAverage = 0;
-    
-    // 收集各区县该重金属的平均值
-    const regionAverages = SG_REGIONS.map(region => {
-      if (!regionGroups[region]) return null;
-      
-      const group = regionGroups[region][metal];
-      const avg = group.count ? group.sum / group.count : 0;
-      totalAverage += avg;
-      
-      return { 
-        name: region,
-        value: avg
-      };
-    }).filter(Boolean);
-
-    // 计算占比
-    const seriesData = regionAverages.map(item => ({
-      name: item.name,
-      value: totalAverage > 0 ? (item.value / totalAverage) * 100 : 0,
-      rawValue: item.value // 保留原始浓度值用于显示
-    }));
-
-    return {
-      metal,
-      seriesData
-    };
-  });
-
-  return {
-    regions: SG_REGIONS,
-    pieSeriesData,
-    totalSamples: uniqueSampleIds.size
-  };
-};
-
-// ========== 初始化/更新图表 ==========
-const initChart = () => {
-  if (!chartRef.value || !selectedMetal.value || !chartData.value) return;
-  
-  if (myChart) myChart.dispose();
-  myChart = echarts.init(chartRef.value);
-
-  // 获取当前重金属的数据
-  const currentMetalData = chartData.value.pieSeriesData.find(
-    item => item.metal === selectedMetal.value
-  );
-
-  if (!currentMetalData) return;
-
-  const option = {
-    title: { 
-      text: `韶关市${selectedMetal.value}平均浓度区域占比`, 
-      left: 'center',
-      subtext: `数据来源: ${chartData.value.totalSamples}个有效检测样本`
-    },
-    tooltip: {
-      trigger: 'item',
-      formatter: function(params) {
-        return `${params.name}<br/>
-                ${selectedMetal.value}: ${params.data.rawValue.toFixed(4)} mg/L<br/>
-                占比: ${params.percent}%`;
-      }
-    },
-    legend: {
-      orient: 'vertical',
-      right: 10,
-      top: 'center',
-      data: currentMetalData.seriesData.map(item => item.name)
-    },
-    series: [
-      {
-        name: selectedMetal.value,
-        type: 'pie',
-        radius: ['35%', '65%'],
-        center: ['45%', '50%'],
-        avoidLabelOverlap: false,
-        itemStyle: {
-          borderRadius: 10,
-          borderColor: '#fff',
-          borderWidth: 2
-        },
-        label: {
-          show: false,
-          position: 'center'
-        },
-        emphasis: {
-          label: {
-            show: true,
-            fontSize: '16',
-            fontWeight: 'bold',
-            formatter: '{b}\n{c}%'
-          }
-        },
-        labelLine: {
-          show: false
-        },
-        data: currentMetalData.seriesData
-      }
-    ],
-    color: COLORS
-  };
-
-  myChart.setOption(option);
-};
-
-// ========== 生命周期钩子 ==========
-onMounted(async () => {
-  try {
-    // 修复请求超时问题:将超时时间延长至10秒
-    const [samplingRes, assayRes] = await Promise.all([
-      axios.get(SAMPLING_API, { timeout: 10000 }), // 10秒超时
-      axios.get(ASSAY_API, { timeout: 10000 })
-    ]);
-    
-    chartData.value = processMergedData(samplingRes.data, assayRes.data);
-    initChart();
-  } catch (err) {
-    // 处理超时错误
-    if (err.code === 'ECONNABORTED') {
-      error.value = '请求超时:服务器响应时间超过10秒';
-    } else {
-      error.value = '数据加载失败: ' + (err.message || '未知错误');
-    }
-    console.error('接口错误:', err);
-  } finally {
-    loading.value = false;
-  }
-});
-
-// 监听重金属选择变化
-watch(selectedMetal, (newVal) => {
-  if (newVal && myChart && chartData.value) {
-    initChart();
-  }
-});
-
-// 响应式布局
-const resizeHandler = () => myChart && myChart.resize();
-onMounted(() => window.addEventListener('resize', resizeHandler));
-onUnmounted(() => window.removeEventListener('resize', resizeHandler));
-</script>
-
-<style scoped>
-.region-average-chart {
-  width: 100%;
-  max-width: 1200px;
-  margin: 20px auto;
-  position: relative;
-}
-.chart-box {
-  width: 100%;
-  height: 600px;
-  min-height: 400px;
-  background-color: white;
-  border-radius: 8px;
-  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
-}
-.status {
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  padding: 15px;
-  background: rgba(255,255,255,0.8);
-  border-radius: 4px;
-  text-align: center;
-}
-.error { 
-  color: #ff4d4f;
-  font-weight: bold;
-}
-.metal-selector {
-  margin-bottom: 15px;
-  text-align: center;
-  padding: 10px;
-}
-.metal-selector label {
-  margin-right: 10px;
-  font-weight: bold;
-}
-.metal-selector select {
-  padding: 8px 15px;
-  border-radius: 4px;
-  border: 1px solid #ddd;
-  background-color: #f8f8f8;
-  font-size: 14px;
-  min-width: 150px;
-  cursor: pointer;
-  transition: border 0.3s;
-}
-.metal-selector select:hover {
-  border-color: #1890ff;
-}
-</style>

+ 349 - 0
src/components/soilStatictics/reducedataStatistics.vue

@@ -0,0 +1,349 @@
+<template>
+  <div class="chart-container">
+    <div class="chart-controls">
+      <button @click="refreshData" class="refresh-btn">
+        <i class="fas fa-sync-alt"></i> 刷新数据
+      </button>
+      <div class="selected-ids">
+        <span>选中的ID: </span>
+        <span v-for="id in selectedIds" :key="id" class="id-tag">{{ id }}</span>
+      </div>
+    </div>
+    
+    <div v-if="loading" class="loading">
+      <p>数据加载中...</p>
+    </div>
+    
+    <div v-if="error" class="error">
+      <p>{{ error }}</p>
+    </div>
+    
+    <div ref="chartRef" class="chart" v-show="!loading && !error"></div>
+  </div>
+</template>
+
+<script>
+import { ref, onMounted, onUnmounted, nextTick } from 'vue';
+import * as echarts from 'echarts';
+
+export default {
+  name: 'PhTrendChart',
+  setup() {
+    // 接口数据 - 不同时间点的数据集
+    const interfaces = [
+      { time: '20241229_201018', url: 'http://localhost:5000/api/table-data?table_name=dataset_5' },
+      { time: '20250104_171827', url: 'http://localhost:5000/api/table-data?table_name=dataset_35' },
+      { time: '20250104_171959', url: 'http://localhost:5000/api/table-data?table_name=dataset_36' },
+      { time: '20250104_214026', url: 'http://localhost:5000/api/table-data?table_name=dataset_37' },
+      { time: '20250308_161945', url: 'http://localhost:5000/api/table-data?table_name=dataset_65' },
+      { time: '20250308_163248', url: 'http://localhost:5000/api/table-data?table_name=dataset_66' },
+    ];
+    
+    const chartRef = ref(null);
+    const loading = ref(true);
+    const error = ref(null);
+    const selectedIds = ref([]);
+    const lastUpdate = ref(new Date().toLocaleString());
+    
+    let chartInstance = null;
+    let chartData = {};
+    
+    // 生成1-43之间的6个不重复随机数
+    function generateRandomIds() {
+      const ids = new Set();
+      while (ids.size < 6) {
+        const id = Math.floor(Math.random() * 43) + 1;
+        ids.add(id);
+      }
+      return Array.from(ids).sort((a, b) => a - b);
+    }
+    
+    // 获取数据
+    async function fetchData() {
+      try {
+        loading.value = true;
+        error.value = null;
+        
+        const newSelectedIds = generateRandomIds();
+        selectedIds.value = newSelectedIds;
+        
+        // 初始化图表数据
+        const newChartData = {};
+        newSelectedIds.forEach(id => {
+          newChartData[id] = {
+            timestamps: [],
+            phValues: []
+          };
+        });
+        
+        // 按时间顺序获取每个表的数据
+        for (const intf of interfaces) {
+          const response = await fetch(intf.url);
+          if (!response.ok) throw new Error(`网络响应错误: ${response.status}`);
+          
+          const responseData = await response.json();
+          const dataArray = responseData.data || [];
+          
+          for (const id of newSelectedIds) {
+            // 确保数据存在
+            if (!newChartData[id]) {
+              newChartData[id] = {
+                timestamps: [],
+                phValues: []
+              };
+            }
+            
+            // 查找匹配的项目 ,使用小写id
+            const targetItem = dataArray.find(item => item.id === id);
+            
+            if (targetItem) {
+              // 获取pH值,使用小写pH
+              const phValue = parseFloat(targetItem.pH || 0);
+              newChartData[id].timestamps.push(intf.time);
+              newChartData[id].phValues.push(phValue);
+            } else {
+              // 如果没有找到数据,添加null值保持数据一致性
+              newChartData[id].timestamps.push(intf.time);
+              newChartData[id].phValues.push(null);
+            }
+          }
+        }
+        
+        // 更新图表数据
+        chartData = newChartData;
+        lastUpdate.value = new Date().toLocaleString();
+        
+        // 确保DOM更新后再渲染图表
+        await nextTick();
+        renderChart();
+        
+      } catch (err) {
+        error.value = err.message || '获取数据失败';
+        console.error('获取数据错误:', err);
+      } finally {
+        loading.value = false;
+      }
+    }
+    
+    // 渲染图表
+    function renderChart() {
+      if (!chartRef.value) {
+        console.error('图表容器未找到');
+        return;
+      }
+      
+      // 销毁旧的图表实例(如果存在)
+      if (chartInstance) {
+        try {
+          chartInstance.dispose();
+        } catch (e) {
+          console.warn('销毁图表实例时出错:', e);
+        }
+        chartInstance = null;
+      }
+      
+      try {
+        // 初始化新的ECharts实例
+        chartInstance = echarts.init(chartRef.value);
+        
+        // 准备系列数据
+        const series = [];
+        const xAxisData = interfaces.map(item => item.time);
+        
+        for (const id of selectedIds.value) {
+          const data = chartData[id]?.phValues || [];
+          series.push({
+            name: `ID: ${id}`,
+            type: 'line',
+            symbol: 'circle',
+            symbolSize: 8,
+            data: data,
+            lineStyle: { width: 2 },
+            emphasis: {
+              focus: 'series'
+            }
+          });
+        }
+        
+        const option = {
+          title: {
+            text: '随机样本点的pH值趋势图',
+            left: 'center',
+            top: 10
+          },
+          tooltip: {
+            trigger: 'axis',
+            axisPointer: {
+              type: 'cross',
+              label: {
+                backgroundColor: '#6a7985'
+              }
+            }
+          },
+          legend: {
+            data: selectedIds.value.map(id => `ID: ${id}`),
+            top: 40,
+            type: 'scroll'
+          },
+          grid: {
+            left: '8%',
+            right: '5%',
+            bottom: '10%',
+            top: '100px',
+            containLabel: true
+          },
+          xAxis: {
+            type: 'category',
+            boundaryGap: false,
+            data: xAxisData,
+            axisLabel: {
+              rotate: 30,
+              interval: 0
+            },
+            name: '时间'
+          },
+          yAxis: {
+            type: 'value',
+            scale: true,
+            name: 'pH值'
+          },
+          series: series
+        };
+
+        chartInstance.setOption(option);
+        
+        // 延迟resize调用
+        setTimeout(() => {
+          if (chartInstance) {
+            chartInstance.resize();
+          }
+        }, 100);
+      } catch (e) {
+        console.error('初始化图表时出错:', e);
+        error.value = '图表初始化失败: ' + e.message;
+      }
+    }
+    
+    // 刷新数据
+    function refreshData() {
+      fetchData();
+    }
+    
+    onMounted(() => {
+      // 确保DOM完全加载后再初始化图表
+      const initChart = () => {
+        if (chartRef.value) {
+          fetchData();
+        } else {
+          // 如果DOM还未准备好,延迟执行
+          setTimeout(initChart, 100);
+        }
+      };
+      
+      initChart();
+      
+      // 窗口大小变化时重绘图表
+      const resizeHandler = () => {
+        if (chartInstance) {
+          setTimeout(() => {
+            chartInstance.resize();
+          }, 100);
+        }
+      };
+      
+      window.addEventListener('resize', resizeHandler);
+      
+      // 在卸载时移除事件监听器
+      onUnmounted(() => {
+        window.removeEventListener('resize', resizeHandler);
+        if (chartInstance) {
+          try {
+            chartInstance.dispose();
+          } catch (e) {
+            console.warn('卸载时销毁图表实例出错:', e);
+          }
+        }
+      });
+    });
+    
+    return {
+      chartRef,
+      loading,
+      error,
+      selectedIds,
+      lastUpdate,
+      refreshData
+    };
+  }
+}
+</script>
+
+<style scoped>
+.chart-container {
+  width: 100%;
+  padding: 20px;
+  height: 550px;
+  box-sizing: border-box;
+}
+
+.chart-controls {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 15px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #eee;
+}
+
+.refresh-btn {
+  background-color: #42b983;
+  color: white;
+  border: none;
+  padding: 8px 16px;
+  border-radius: 4px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  transition: background-color 0.3s;
+}
+
+.refresh-btn:hover {
+  background-color: #359e75;
+}
+
+.selected-ids {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  flex-wrap: wrap;
+}
+
+.id-tag {
+  background-color: #e6f7ff;
+  color: #1890ff;
+  padding: 3px 8px;
+  border-radius: 12px;
+  font-size: 12px;
+}
+
+.chart {
+  width: 100%;
+  height: calc(100% - 60px);
+  border: 1px solid #e0e0e0;
+  border-radius: 4px;
+}
+
+.loading, .error {
+  width: 100%;
+  height: calc(100% - 60px);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: #666;
+}
+
+.error {
+  color: #f5222d;
+}
+</style>

+ 214 - 0
src/components/soilStatictics/refluxcedataStatictics.vue

@@ -0,0 +1,214 @@
+<template>
+  <div class="chart-container">
+    <h3 class="title">酸化加剧Delta_pH柱状图</h3>
+    <div class="echarts-box" ref="chartRef"></div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
+import * as echarts from 'echarts';
+import axios from 'axios';
+
+// 图表相关
+const chartRef = ref(null);
+let myChart = null;
+
+// 数据状态
+const chartData = ref([]);
+const isLoading = ref(false);
+const errorMessage = ref('');
+
+// 接口地址(请替换为实际接口地址)
+const API_URL = 'http://localhost:5000/api/table-data?table_name=dataset_60'; // 替换为实际接口
+
+// 获取数据函数
+const fetchData = async () => {
+  //console.log('开始获取数据...');
+  // 重置状态
+  errorMessage.value = '';
+  isLoading.value = true;
+  
+  try {
+    // 发起请求
+    const response = await axios.get(API_URL);
+    //console.log('API响应:', response);
+    
+    // 检查响应数据是否符合预期格式
+    if (response.data && response.data.success) {
+  if (Array.isArray(response.data.data) && response.data.data.length > 0) {
+    if (response.data.data[0].id !== undefined && response.data.data[0].Delta_pH !== undefined) {
+      chartData.value = response.data.data;
+    } else {
+      errorMessage.value = '数据字段缺失:缺少 id 或 Delta_pH';
+    }
+  } else {
+    errorMessage.value = '未获取到有效数据,请稍后重试';
+  }
+} else {
+  errorMessage.value = 'API返回失败状态';
+}
+  } catch (error) {
+    console.error('数据获取失败:', error);
+    // 根据错误类型显示不同信息
+    if (error.response) {
+      errorMessage.value = `请求失败 (${error.response.status}): ${error.response.statusText}`;
+    } else if (error.request) {
+      errorMessage.value = '未收到响应,请检查网络连接';
+    } else {
+      errorMessage.value = '请求发生错误,请稍后重试';
+    }
+  } finally {
+    isLoading.value = false;
+  }
+};
+
+// 初始化图表
+const initChart = () => {
+  //console.log('初始化图表,数据长度:', chartData.value.length);
+  // 确保DOM已挂载且有数据
+  if (!chartRef.value) {
+    console.error('图表容器未找到');
+    return;
+  }
+
+  if (chartData.value.length === 0) {
+    console.error('没有数据可用于渲染图表');
+    return;
+  }
+  
+  // 销毁已有实例
+  if (myChart) {
+    myChart.dispose();
+    //console.log('已销毁旧图表实例');
+  }
+  
+  // 初始化图表
+  myChart = echarts.init(chartRef.value);
+  //console.log('ECharts实例已创建');
+
+   myChart.on('rendered', () => {
+    //console.log('图表已渲染');
+  });
+  
+  // 提取数据
+  const xAxisData = chartData.value.map(item => item.id);
+  const seriesData = chartData.value.map(item => ({
+    value: item.Delta_pH,
+    itemStyle: {
+      // 根据值的正负设置不同颜色
+      color: item.Delta_pH >= 0 ? '#0F52BA' : '#F44336'
+    }
+  }));
+
+  //console.log('X轴数据:', xAxisData);
+  //console.log('系列数据:', seriesData);
+  
+  // 图表配置
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      },
+      formatter: function(params) {
+        const item = chartData.value.find(d => d.id === params[0].name);
+        return `ID: ${item.id}<br/>Delta_pH: ${item.Delta_pH.toFixed(4)}`;
+      }
+    },
+    grid: {
+      left: '5%',
+      right: '1%',
+      bottom: '5%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: xAxisData,
+      name: 'ID',
+      nameLocation: 'middle',
+      nameGap: 30,
+    },
+    yAxis: {
+      type: 'value',
+      name: 'Delta_pH',
+      nameLocation: 'middle',
+      nameGap: 50,
+      axisLabel: {
+        formatter: '{value}'
+      }
+    },
+    series: [
+      {
+        name: 'Delta_pH',
+        type: 'bar',
+        data: seriesData,
+        barWidth: '60%',
+        label: {
+          show: false,
+          position: 'top',
+          formatter: function(params) {
+            return params.value.toFixed(4);
+          }
+        }
+      }
+    ]
+  };
+  
+  // 设置图表配置
+  myChart.setOption(option);
+};
+
+// 监听数据变化,重新渲染图表
+watch(chartData, () => {
+  nextTick(()=>{
+    initChart();  
+  })
+
+});
+
+// 窗口大小变化时重绘图表
+const handleResize = () => {
+  if (myChart) {
+    myChart.resize();
+  }
+};
+
+// 组件挂载时初始化
+onMounted(async() => {
+  // 首次加载数据
+  await fetchData();
+  initChart();
+  // 监听窗口大小变化
+  window.addEventListener('resize', handleResize);
+});
+
+// 组件卸载时清理
+onUnmounted(() => {
+  if (myChart) {
+    myChart.dispose();
+  }
+  window.removeEventListener('resize', handleResize);
+});
+</script>
+
+<style scoped>
+.chart-container {
+  width: 100%;
+  height: 470px;
+  padding: 20px;
+  box-sizing: border-box;
+}
+
+.echarts-box {
+  width: 100%;
+  height: 100%;
+  border: 1px solid #e0e0e0;
+  border-radius: 6px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.title {
+  text-align: center;
+}
+</style>

+ 208 - 179
src/components/soilcdStatistics/cropcdStatictics.vue

@@ -25,13 +25,7 @@
     <!-- 1️⃣ 作物态Cd指标 -->
     <section class="mb-4 chart-container">
       <h3 class="section-title text-base font-semibold">作物态Cd主要指标</h3>
-      <div class="flex flex-wrap mb-3">
-        <div class="legend-item" v-for="(item, index) in fieldConfig.pollution" :key="index">
-          <div class="legend-color" :style="{ backgroundColor: item.color }"></div>
-          <span>{{ item.legendName }}</span>
-        </div>
-      </div>
-      <div ref="cdBarChart" style="width: 100%; height: 320px;"></div>
+      <div ref="cdBarChart" style="width: 100%; height: 415px;"></div>
     </section>
 
     <!-- 2️⃣ 养分元素 -->
@@ -93,24 +87,21 @@ const fieldConfig = {
  pollution: [
     { 
       key: '002_0002IDW', 
-      name: '002_0002IDW',  // 横坐标保持原标识
-      legendName: '002_0002IDW:粒径在0.002~0.02mm的粉粒组分质量占比,%',  // 图例显示中文
+      name: '粉粒组分质量占比',  // 横坐标保持原标识
       color: '#5470c6' ,
       unit:"%",
       convert: false
     },
     { 
       key: '02_002IDW', 
-      name: '02_002IDW',  // 横坐标保持原标识
-      legendName: '02_002IDW:粒径在0.02~0.2mm的砂粒组分质量占比,%',  // 图例显示中文
+      name: '砂粒组分质量占比',  // 横坐标保持原标识
       color: '#91cc75' ,
       unit:"%",
       convert: false
     },
     { 
       key: '2_02IDW', 
-      name: '2_02IDW',  // 横坐标保持原标识
-      legendName: '2_02IDW:粒径大于2mm的石砾组分质量占比,%',  // 图例显示中文
+      name: '石砾组分质量占比',  // 横坐标保持原标识
       color: '#fac858' ,
       unit:"%",
       convert: false
@@ -136,116 +127,15 @@ const fieldConfig = {
 // 数据请求(作物态Cd接口)
 const fetchData = async () => {
   try {
-    const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=CropCd_input_data&format=json';
+    const apiUrl = 'http://localhost:8000/api/vector/stats/CropCd_input_data';
     const response = await axios.get(apiUrl);
     
-    // 接口返回格式判断(GeoJSON或直接数组)
-    const rawData = response.data.features 
-      ? response.data.features.map(f => f.properties) 
-      : response.data;
-    return rawData;
+    return response.data.data;
   } catch (err) {
     throw new Error('数据加载失败: ' + err.message);
   }
 };
 
-// 分位数计算(采用QUARTILE.INC算法)
-const calculatePercentile = (sortedArray, percentile) => {
-  const n = sortedArray.length;
-  if (n === 0) return null;
-  if (percentile <= 0) return sortedArray[0];
-  if (percentile >= 100) return sortedArray[n - 1];
-  
-  const index = (n - 1) * (percentile / 100);
-  const lowerIndex = Math.floor(index);
-  const upperIndex = lowerIndex + 1;
-  const fraction = index - lowerIndex;
-  
-  if (upperIndex >= n) return sortedArray[lowerIndex];
-  return sortedArray[lowerIndex] + fraction * (sortedArray[upperIndex] - sortedArray[lowerIndex]);
-};
-
-// 计算单个字段的统计量
-const calculateFieldStats = (data, fieldKey, fieldName) => {
-  // 获取字段配置
-  const fieldConfigItem = [
-    ...fieldConfig.pollution,
-    ...fieldConfig.nutrient,
-    ...fieldConfig.extra
-  ].find(f => f.key === fieldKey);
-  // 提取并清洗数据
-  const rawValues = data.map(item => item[fieldKey]);
-  const values = rawValues
-    .map((val, idx) => {
-      let num = Number(val);
-      // 应用单位转换
-      if (fieldConfigItem?.convert && fieldConfigItem.conversionFactor) {
-        num = num * fieldConfigItem.conversionFactor;
-      }
-      //if (isNaN(num)) log(`无效数据: 第${idx+1}条 → ${val}`, fieldName);
-      return isNaN(num) ? null : num;
-    })
-    .filter(v => v !== null);
-  
-  if (values.length === 0) {
-    //log(`无有效数据`, fieldName);
-    return { key: fieldKey, name: fieldName, min: null, q1: null, median: null, q3: null, max: null };
-  }
-  
-  // 排序并计算统计量
-  const sorted = [...values].sort((a, b) => a - b);
-  const min = sorted[0];
-  const max = sorted[sorted.length - 1];
-  const q1 = calculatePercentile(sorted, 25);
-  const median = calculatePercentile(sorted, 50);
-  const q3 = calculatePercentile(sorted, 75);
-  
-  // 强制校正顺序
-  const sortedStats = [min, q1, median, q3, max].sort((a, b) => a - b);
-  return {
-    key: fieldKey,
-    name: fieldName,
-    min: sortedStats[0],
-    q1: sortedStats[1],
-    median: sortedStats[2],
-    q3: sortedStats[3],
-    max: sortedStats[4],
-    avg: values.reduce((sum, val) => sum + val, 0) / values.length // 计算平均值
-  };
-};
-
-// 批量计算所有统计量
-const calculateAllStats = (data) => {
-  // 1. 作物态Cd指标统计
-  pollutionStats.value = fieldConfig.pollution.map(field => {
-     const stats =calculateFieldStats(data, field.key, field.name);
-    //console.log(`${field.name}统计结果:`,stats);
-    return stats;
-  });
-  
-  // 2. 养分元素统计
-  nutrientStats.value = fieldConfig.nutrient.map(field => 
-    calculateFieldStats(data, field.key, field.name)
-  );
-  
-  // 3. 其他理化性质统计
-  extraStats.value = fieldConfig.extra.map(field => 
-    calculateFieldStats(data, field.key, field.name)
-  );
-  
-  // 更新平均值统计
-  const cd002Stats = pollutionStats.value.find(s => s.key === '002_0002IDW');
-  const cd02Stats = pollutionStats.value.find(s => s.key === '02_002IDW');
-  const cd2Stats = pollutionStats.value.find(s => s.key === '2_02IDW');
-  
-  stats.value = {
-    cd002Avg: cd002Stats?.avg || 0,
-    cd02Avg: cd02Stats?.avg || 0,
-    cd2Avg: cd2Stats?.avg || 0,
-    samples: data.length
-  };
-};
-
 // 构建箱线图数据
 const buildBoxplotData = (statsArray) => {
   return statsArray.map(stat => {
@@ -256,69 +146,74 @@ const buildBoxplotData = (statsArray) => {
 
 // 初始化作物态Cd指标图表
 const initPollutionChart = () => {
-  if (chartInstanceCd) chartInstanceCd.dispose();
-  chartInstanceCd = echarts.init(cdBarChart.value);
-  
-  const xAxisData = fieldConfig.pollution.map(f => f.name);
-  const barData = pollutionStats.value.map(stat => stat.avg || 0);
-  
-  chartInstanceCd.setOption({
-    title: { },
-    tooltip: {
-      trigger: 'axis',
-      formatter: (params) => `${params[0].name}<br/>平均值: ${params[0].value.toFixed(4)}`
-    },
-    grid: { top: 40, right: 15, bottom: 50, left: 40 },
-    xAxis: { type: "category", data: xAxisData, axisLabel: { fontSize: 12,rotate:30 } },
-    yAxis: { type: "value", name: '%', nameTextStyle: { fontSize: 12 }, axisLabel: { fontSize: 11 } },
-    series: [{
-      name: '平均值', type: "bar",
-      itemStyle: { color: (p) => fieldConfig.pollution[p.dataIndex].color },
-      data: barData
-    }]
+  nextTick(() => {
+    // 若图表实例已存在,先销毁避免内存泄漏
+    if (chartInstanceCd) chartInstanceCd.dispose();
+    // 校验 DOM 存在性(防止 ref 未关联到有效 DOM)
+    if (!cdBarChart.value) return;
+    // 初始化 ECharts 实例
+    chartInstanceCd = echarts.init(cdBarChart.value);
+    
+    const xAxisData = fieldConfig.pollution.map(f => f.name);
+    const barData = pollutionStats.value.map(stat => stat.avg || 0);
+    
+    chartInstanceCd.setOption({
+      title: {},
+      tooltip: {
+        trigger: 'axis',
+        formatter: (params) => `${params[0].name}<br/>平均值: ${params[0].value.toFixed(4)}`
+      },
+      grid: { top: 40, right: 15, bottom: '18%', left: '10%' },
+      xAxis: { type: "category", data: xAxisData, axisLabel: { fontSize: 12, rotate: 30 } },
+      yAxis: { type: "value", name: '%', nameTextStyle: { fontSize: 12 }, axisLabel: { fontSize: 11 } },
+      series: [{
+        name: '平均值', type: "bar",
+        itemStyle: { color: (p) => fieldConfig.pollution[p.dataIndex].color },
+        data: barData
+      }]
+    });
   });
-  
 };
 
 // 初始化养分元素图表
 const initNutrientChart = () => {
-  if (chartInstanceNutrient) chartInstanceNutrient.dispose();
-  chartInstanceNutrient = echarts.init(nutrientBoxChart.value);
-  
-  const xAxisData = fieldConfig.nutrient.map(f => f.name);
-  const boxData = buildBoxplotData(nutrientStats.value);
-  
-  chartInstanceNutrient.setOption({
-    title: { text: "主要养分元素分布", left: 'center', textStyle: { fontSize: 14 } },
-    tooltip: {
-      trigger: "item",
-      formatter: (params) => {
-        const stat = nutrientStats.value[params.dataIndex];
-        const fieldConfigItem = fieldConfig.nutrient.find(f => f.key === stat.key);
-        return formatTooltip(stat, fieldConfigItem?.unit);
-      }
-    },
-    grid: { top: 40, right: 15, bottom: 45, left: 40 },
-    xAxis: { 
-      type: "category", 
-      data: xAxisData, 
-      axisLabel: { 
-        fontSize: 11, 
-        rotate: 30,
-      } 
-    },
-    yAxis: { 
-      type: "value", 
-      name: 'mg/kg', 
-      nameTextStyle: { fontSize: 12 }, 
-      axisLabel: { fontSize: 11 } 
-    },
-    series: [{
-      name: '养分元素', 
-      type: "boxplot",
-      itemStyle: { color: '#ee6666', borderColor: '#fac858' },
-      data: boxData
-    }]
+  nextTick(() => {
+    if (chartInstanceNutrient) chartInstanceNutrient.dispose();
+    if (!nutrientBoxChart.value) return;
+    chartInstanceNutrient = echarts.init(nutrientBoxChart.value);
+    
+    const xAxisData = fieldConfig.nutrient.map(f => f.name);
+    const boxData = buildBoxplotData(nutrientStats.value);
+    
+    chartInstanceNutrient.setOption({
+      title: { text: "主要养分元素分布", left: 'center', textStyle: { fontSize: 14 } },
+      tooltip: {
+        trigger: "item",
+        formatter: (params) => {
+          const stat = nutrientStats.value[params.dataIndex];
+          const fieldConfigItem = fieldConfig.nutrient.find(f => f.key === stat.key);
+          return formatTooltip(stat, fieldConfigItem?.unit);
+        }
+      },
+      grid: { top: 40, right: 15, bottom: 45, left: 40 },
+      xAxis: { 
+        type: "category", 
+        data: xAxisData, 
+        axisLabel: { fontSize: 11, rotate: 30 } 
+      },
+      yAxis: { 
+        type: "value", 
+        name: 'mg/kg', 
+        nameTextStyle: { fontSize: 12 }, 
+        axisLabel: { fontSize: 11 } 
+      },
+      series: [{
+        name: '养分元素', 
+        type: "boxplot",
+        itemStyle: { color: '#ee6666', borderColor: '#fac858' },
+        data: boxData
+      }]
+    });
   });
 };
 
@@ -337,7 +232,7 @@ const initExtraChart = () => {
            formatter: (params) => formatTooltip(extraStats.value[params.dataIndex])
         },
          grid: { top: 40, right: 15, bottom: 40, left: 40 },
-         xAxis: { type: "category", data: xAxisData, axisLabel: { fontSize: 11,rotate:30} },
+         xAxis: { type: "category", data: xAxisData, axisLabel: { fontSize: 11} },
         yAxis: { type: "value", name: '%', nameTextStyle: { fontSize: 12 }, axisLabel: { fontSize: 11 } },
         series: [{
           name: '理化性质', type: "boxplot",
@@ -370,9 +265,143 @@ const initCharts = async () => {
     isLoading.value = true;
     error.value = null;
     
-    const data = await fetchData();
-    calculateAllStats(data);
+    const statsData = await fetchData(); // 新接口返回的统计数据
     
+    // -------- 1. 处理「作物态Cd指标」统计 --------
+    pollutionStats.value = fieldConfig.pollution.map(field => {
+      const fieldStats = statsData[field.key]; // 从接口数据中取对应字段的统计
+      if (!fieldStats) {
+        return { 
+          key: field.key, 
+          name: field.name, 
+          min: null, 
+          q1: null, 
+          median: null, 
+          q3: null, 
+          max: null, 
+          avg: null 
+        };
+      }
+      // (可选)单位转换:若接口返回原始值,需按fieldConfig的convert规则转换
+      let min = fieldStats.min;
+      let q1 = fieldStats.q1;
+      let median = fieldStats.median;
+      let q3 = fieldStats.q3;
+      let max = fieldStats.max;
+      let avg = fieldStats.mean;
+      if (field.convert && field.conversionFactor) {
+        min *= field.conversionFactor;
+        q1 *= field.conversionFactor;
+        median *= field.conversionFactor;
+        q3 *= field.conversionFactor;
+        max *= field.conversionFactor;
+        avg *= field.conversionFactor;
+      }
+      return {
+        key: field.key,
+        name: field.name,
+        min,
+        q1,
+        median,
+        q3,
+        max,
+        avg
+      };
+    });
+
+    // -------- 2. 处理「主要养分元素」统计 --------
+    nutrientStats.value = fieldConfig.nutrient.map(field => {
+      const fieldStats = statsData[field.key];
+      if (!fieldStats) {
+        return { 
+          key: field.key, 
+          name: field.name, 
+          min: null, 
+          q1: null, 
+          median: null, 
+          q3: null, 
+          max: null, 
+          avg: null 
+        };
+      }
+      // (可选)单位转换
+      let min = fieldStats.min;
+      let q1 = fieldStats.q1;
+      let median = fieldStats.median;
+      let q3 = fieldStats.q3;
+      let max = fieldStats.max;
+      let avg = fieldStats.mean;
+      if (field.convert && field.conversionFactor) {
+        min *= field.conversionFactor;
+        q1 *= field.conversionFactor;
+        median *= field.conversionFactor;
+        q3 *= field.conversionFactor;
+        max *= field.conversionFactor;
+        avg *= field.conversionFactor;
+      }
+      return {
+        key: field.key,
+        name: field.name,
+        min,
+        q1,
+        median,
+        q3,
+        max,
+        avg
+      };
+    });
+
+    // -------- 3. 处理「其他理化性质」统计 --------
+    extraStats.value = fieldConfig.extra.map(field => {
+      const fieldStats = statsData[field.key];
+      if (!fieldStats) {
+        return { 
+          key: field.key, 
+          name: field.name, 
+          min: null, 
+          q1: null, 
+          median: null, 
+          q3: null, 
+          max: null, 
+          avg: null 
+        };
+      }
+      // (可选)单位转换
+      let min = fieldStats.min;
+      let q1 = fieldStats.q1;
+      let median = fieldStats.median;
+      let q3 = fieldStats.q3;
+      let max = fieldStats.max;
+      let avg = fieldStats.mean;
+      if (field.convert && field.conversionFactor) {
+        min *= field.conversionFactor;
+        q1 *= field.conversionFactor;
+        median *= field.conversionFactor;
+        q3 *= field.conversionFactor;
+        max *= field.conversionFactor;
+        avg *= field.conversionFactor;
+      }
+      return {
+        key: field.key,
+        name: field.name,
+        min,
+        q1,
+        median,
+        q3,
+        max,
+        avg
+      };
+    });
+
+    // -------- 更新「样本数量」等汇总统计 --------
+    const firstFieldKey = fieldConfig.pollution[0]?.key;
+    stats.value = {
+      cd002Avg: pollutionStats.value.find(s => s.key === '002_0002IDW')?.avg || 0,
+      cd02Avg: pollutionStats.value.find(s => s.key === '02_002IDW')?.avg || 0,
+      cd2Avg: pollutionStats.value.find(s => s.key === '2_02IDW')?.avg || 0,
+      samples: statsData[firstFieldKey]?.count || 0 // 从接口的count字段取样本数
+    };
+
     // 初始化图表
     initPollutionChart();
     initNutrientChart();
@@ -392,7 +421,7 @@ onMounted(() => {
   
   // 窗口resize处理
   const handleResize = () => {
-    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra, chartInstancePopup]
+    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra]
       .forEach(inst => inst && inst.resize());
   };
   window.addEventListener('resize', handleResize);
@@ -400,7 +429,7 @@ onMounted(() => {
   // 组件卸载清理
   return () => {
     window.removeEventListener('resize', handleResize);
-    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra, chartInstancePopup]
+    [chartInstanceCd, chartInstanceNutrient, chartInstanceExtra]
       .forEach(inst => inst && inst.dispose());
   };
 });

+ 38 - 70
src/components/soilcdStatistics/effcdStatistics.vue

@@ -57,6 +57,7 @@ const extraBoxChart = ref(null);
 let chartInstanceCd = null;
 let chartInstanceNutrient = null;
 let chartInstanceExtra = null;
+const chartInstancePopup =null;
 
 
 // 响应式状态
@@ -106,74 +107,41 @@ const fieldConfig = {
 const fetchData = async () => {
   try {
     // 实际项目中替换为真实API
-     const res = await axios.get("http://localhost:8000/api/vector/export/all?table_name=EffCd_input_data&format=json");
-     return res.data;
+     const res = await axios.get("http://localhost:8000/api/vector/stats/EffCd_input_data");
+     return res.data.data;
 
   } catch (err) {
     throw new Error('数据加载失败: ' + err.message);
   }
 };
 
-// 核心:分位数计算(参考灌溉水的精准计算逻辑)
-const calculatePercentile = (sortedArray, percentile) => {
-  const n = sortedArray.length;
-  if (n === 0) return null;
-  if (percentile <= 0) return sortedArray[0];
-  if (percentile >= 100) return sortedArray[n - 1];
-  
-  // 采用与Excel QUARTILE.INC一致的算法
-  const index = (n - 1) * (percentile / 100);
-  const lowerIndex = Math.floor(index);
-  const upperIndex = lowerIndex + 1;
-  const fraction = index - lowerIndex;
-  
-  if (upperIndex >= n) return sortedArray[lowerIndex];
-  return sortedArray[lowerIndex] + fraction * (sortedArray[upperIndex] - sortedArray[lowerIndex]);
-};
-
 // 计算单个字段的箱线图统计量(带顺序校正)
-const calculateFieldStats = (data, fieldKey, fieldName) => {
-  // 获取字段配置
-  const fieldConfigItem = [
-    ...fieldConfig.pollution,
-    ...fieldConfig.nutrient,
-    ...fieldConfig.extra
-  ].find(f => f.key === fieldKey);
-  // 提取并清洗数据
-  const rawValues = data.map(item => item[fieldKey]);
-  const values = rawValues
-    .map((val, idx) => {
-      let num = Number(val);
-      if(fieldConfigItem?.convert && fieldConfigItem.conversionFactor){
-        num = num*fieldConfigItem.conversionFactor;
-      }
-      //if (isNaN(num)) log(`无效数据: 第${idx+1}条 → ${val}`, fieldName);
-      return isNaN(num) ? null : num;
-    })
-    .filter(v => v !== null);
-  
-  if (values.length === 0) {
-    //log(`无有效数据`, fieldName);
-    return { key: fieldKey, name: fieldName, min: null, q1: null, median: null, q3: null, max: null };
+const calculateFieldStats = (statsData, fieldKey, fieldName, fieldConfigItem) => {
+  const fieldStats = statsData[fieldKey]; // 从接口数据中取当前字段的统计结果
+  if (!fieldStats) {
+    return { key: fieldKey, name: fieldName, min: null, q1: null, median: null, q3: null, max: null, mean: null };
   }
-  
-  // 排序并计算统计量
-  const sorted = [...values].sort((a, b) => a - b);
-  const min = sorted[0];
-  const max = sorted[sorted.length - 1];
-  const q1 = calculatePercentile(sorted, 25);
-  const median = calculatePercentile(sorted, 50);
-  const q3 = calculatePercentile(sorted, 75);
 
-  const mean = values.reduce((sum,val)=>sum+val,0)/values.length;
-  
-  // 强制校正顺序(核心修复)
+  // 提取原始统计值
+  let min = fieldStats.min;
+  let q1 = fieldStats.q1;
+  let median = fieldStats.median;
+  let q3 = fieldStats.q3;
+  let max = fieldStats.max;
+  let mean = fieldStats.mean;
+
+  // 处理「单位转换」(与原代码逻辑一致,若配置了convert则乘以系数)
+  if (fieldConfigItem?.convert && fieldConfigItem.conversionFactor) {
+    min *= fieldConfigItem.conversionFactor;
+    q1 *= fieldConfigItem.conversionFactor;
+    median *= fieldConfigItem.conversionFactor;
+    q3 *= fieldConfigItem.conversionFactor;
+    max *= fieldConfigItem.conversionFactor;
+    mean *= fieldConfigItem.conversionFactor;
+  }
+
+  // 强制校正统计量顺序(确保 min ≤ q1 ≤ median ≤ q3 ≤ max)
   const sortedStats = [min, q1, median, q3, max].sort((a, b) => a - b);
-  
-  //log(`统计量: min=${finalStats.min.toFixed(4)}, q1=${finalStats.q1.toFixed(4)}, 
-    //median=${finalStats.median.toFixed(4)}, q3=${finalStats.q3.toFixed(4)}, max=${finalStats.max.toFixed(4)}`, 
-    //fieldName);
-  
   return {
     key: fieldKey,
     name: fieldName,
@@ -182,34 +150,34 @@ const calculateFieldStats = (data, fieldKey, fieldName) => {
     median: sortedStats[2],
     q3: sortedStats[3],
     max: sortedStats[4],
-    mean:mean
+    mean: mean
   };
 };
 
 // 批量计算所有字段的统计量(按图表类型缓存)
-const calculateAllStats = (data) => {
+const calculateAllStats = (statsData) => {
   // 1. 污染指标统计(与x轴顺序一致)
   pollutionStats.value = fieldConfig.pollution.map(field => 
-    calculateFieldStats(data, field.key, field.name)
+    calculateFieldStats(statsData, field.key, field.name, field)
   );
   
   // 2. 养分元素统计(与x轴顺序一致)
   nutrientStats.value = fieldConfig.nutrient.map(field => 
-    calculateFieldStats(data, field.key, field.name)
+    calculateFieldStats(statsData, field.key, field.name, field)
   );
   
   // 3. 其他理化性质统计(与x轴顺序一致)
   extraStats.value = fieldConfig.extra.map(field => 
-    calculateFieldStats(data, field.key, field.name)
+    calculateFieldStats(statsData, field.key, field.name, field)
   );
   
-  // 更新平均值统计
-  const totalCdStats = pollutionStats.value.find(s => s.key === 'TCd_IDW');
-  const effCdStats = pollutionStats.value.find(s => s.key === 'Cdsolution');
+  // 更新「样本数量、平均值统计(从接口数据中取mean更准确)
+  const totalCdStats = pollutionStats.value.find(s => s.key === 'TCd_IDW'); // 替换为实际「总Cd」字段名
+  const effCdStats = pollutionStats.value.find(s => s.key === 'Cdsolution'); // 替换为实际「有效态Cd」字段名
   stats.value = {
-    totalCdAvg: totalCdStats ? (totalCdStats.min + totalCdStats.max) / 2 : 0, // 示例:用范围中点模拟平均值
-    effCdAvg: effCdStats ? (effCdStats.min + effCdStats.max) / 2 : 0,
-    samples: data.length
+    totalCdAvg: totalCdStats ? totalCdStats.mean : 0,
+    effCdAvg: effCdStats ? effCdStats.mean : 0,
+    samples: statsData[Object.keys(statsData)[0]]?.count || 0 // 取第一个字段的count作为样本数
   };
 };
 
@@ -287,7 +255,7 @@ const initExtraChart = () => {
             formatter: (params) => formatTooltip(extraStats.value[params.dataIndex])
           },
           grid: { top: 40, right: 15, bottom: 40, left: 40 },
-          xAxis: { type: "category", data: xAxisData, axisLabel: { fontSize: 11, rotate: 30 } },
+          xAxis: { type: "category", data: xAxisData, axisLabel: { fontSize: 11 } },
           yAxis: { type: "value", name: '%', nameTextStyle: { fontSize: 12 }, axisLabel: { fontSize: 11 } },
           series: [{
             name: '理化性质', type: "boxplot",

+ 23 - 48
src/components/soilcdStatistics/fluxcdStatictics.vue

@@ -89,21 +89,14 @@ const fieldConfig = {
   ]
 };
 
-// 日志工具
-const log = (message, field = '') => {
-  console.log(`%c[${field || '全局'}] %c${message}`,
-    'color:#2196F3;font-weight:bold', 'color:#333');
-};
-
-
 // 数据请求
 const fetchData = async () => {
   try {
-    const apiUrl = 'http://localhost:8000/api/vector/export/all?table_name=FluxCd_input_data&format=json';
+    const apiUrl = 'http://localhost:8000/api/vector/stats/FluxCd_input_data';
     const response = await axios.get(apiUrl);
     const rawData = response.data.features 
       ? response.data.features.map(f => f.properties) 
-      : response.data;
+      : response.data.data;
     return rawData;
   } catch (err) {
     throw new Error('数据加载失败: ' + err.message);
@@ -127,40 +120,21 @@ const calculatePercentile = (sortedArray, percentile) => {
 };
 
 // 计算单个字段的统计量
-const calculateFieldStats = (data, fieldKey, fieldName) => {
-  const rawValues = data.map(item => item[fieldKey]);
-  const values = rawValues
-    .map((val, idx) => {
-      // 更严格的数值校验
-      if (val === null || val === undefined || val === '' || isNaN(Number(val))) {
-        log(`无效数据: ${fieldName} 第${idx+1}条 → ${val}`, fieldName);
-        return null;
-      }
-      const num = Number(val);
-      // 过滤极端值(根据业务需求调整阈值)
-      if (num < -1000000 || num > 1000000) {
-        log(`极端值过滤: ${fieldName} 第${idx+1}条 → ${num}`, fieldName);
-        return null;
-      }
-      return num;
-    })
-    .filter(v => v !== null);
-  
-  // 处理无有效数据的情况
-  if (values.length === 0) {
-    log(`无有效数据: ${fieldName}`, fieldName);
+const calculateFieldStats = (statsData, fieldKey, fieldName) => {
+  // 从接口数据中获取当前字段的统计结果
+  const fieldStats = statsData[fieldKey]; 
+  if (!fieldStats) {
     return { key: fieldKey, name: fieldName, min: null, q1: null, median: null, q3: null, max: null };
   }
-  
-  // 排序并计算分位数(保持原逻辑)
-  const sorted = [...values].sort((a, b) => a - b);
-  const min = sorted[0];
-  const max = sorted[sorted.length - 1];
-  const q1 = calculatePercentile(sorted, 25);
-  const median = calculatePercentile(sorted, 50);
-  const q3 = calculatePercentile(sorted, 75);
-  
-  // 强制校正顺序
+
+  // 提取预统计值
+  let min = fieldStats.min;
+  let q1 = fieldStats.q1;
+  let median = fieldStats.median;
+  let q3 = fieldStats.q3;
+  let max = fieldStats.max;
+
+  // 强制校正统计量顺序(确保 min ≤ q1 ≤ median ≤ q3 ≤ max)
   const sortedStats = [min, q1, median, q3, max].sort((a, b) => a - b);
   return {
     key: fieldKey,
@@ -174,19 +148,20 @@ const calculateFieldStats = (data, fieldKey, fieldName) => {
 };
 
 // 计算所有统计数据(拆分两组)
-const calculateAllStats = (data) => {
-  // 初始Cd统计
+const calculateAllStats = (statsData) => {
+  // 1. 初始Cd统计(与配置顺序一致)
   initialCdStats.value = fieldConfig.initialCd.map(indicator => 
-    calculateFieldStats(data, indicator.key, indicator.name)
+    calculateFieldStats(statsData, indicator.key, indicator.name)
   );
   
-  // 其他指标统计
+  // 2. 其他指标统计(与配置顺序一致)
   otherIndicatorsStats.value = fieldConfig.otherIndicators.map(indicator => 
-    calculateFieldStats(data, indicator.key, indicator.name)
+    calculateFieldStats(statsData, indicator.key, indicator.name)
   );
   
-  // 更新样本数
-  stats.value.samples = data.length;
+  // 3. 更新样本数(从预统计数据中取第一个字段的count)
+  const firstFieldKey = fieldConfig.initialCd[0]?.key || fieldConfig.otherIndicators[0]?.key;
+  stats.value.samples = statsData[firstFieldKey]?.count || 0;
 };
 
 // 构建箱线图数据(通用函数)

+ 7 - 25
src/views/User/HmOutFlux/atmosDeposition/airSampleData.vue

@@ -3,22 +3,22 @@
     <!-- 系统标题区域 -->
     <div class="header-section">
       <div class="header-content">
-        <h1 class="system-title">韶关市大气污染监测系统</h1>
+        <h1 class="system-title">韶关市大气重金属污染采样</h1>
         <div class="calculation-selector">
-          <span class="selector-title">计算方式:</span>
+          <span class="selector-title">采样方式:</span>
           <select 
             v-model="calculationMethod" 
             class="calculation-select"
           >
-            <option value="weight">按重量计算</option>
-            <option value="volume">按体积计算</option>
+            <option value="weight">按重量采样</option>
+            <option value="volume">按体积采样</option>
           </select>
         </div>
       </div>
     </div>
     
     <!-- 地图展示区域 -->
-    <div class="dashboard-section map-section">
+    <div class="dashboard-section ">
       <div class="section-header">
         <div class="section-icon">🗺️</div>
         <h2 class="section-title">采样点地理分布</h2>
@@ -38,7 +38,7 @@
           <div class="section-icon">📋</div>
           <h2 class="section-title">采样点数据详情</h2>
           <div class="data-info">
-            <span class="info-item">当前计算方式: {{ calculationMethod === 'weight' ? '按重量' : '按体积' }}</span>
+            <span class="info-item">当前采样方式: {{ calculationMethod === 'weight' ? '按重量' : '按体积' }}</span>
             <span class="info-item">最后更新: 2025-08-16</span>
           </div>
         </div>
@@ -58,7 +58,6 @@
         <div class="dashboard-card chart-card">
           <div class="chart-header">
             <h3>各区县平均大气重金属污染柱状图</h3>
-            <p>大气污染物区域分布情况 ({{ calculationMethod === 'weight' ? 'mg/m³' : 'μg/m³' }})</p>
           </div>
           <div class="chart-container">
             <AirsampleChart :calculation-method="calculationMethod"/>
@@ -87,20 +86,13 @@ const toggleMapType = () => {
 
 <style scoped>
 /* 基础样式重置 */
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border极;
-  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
-}
-
 .page-container {
   display: flex;
   flex-direction: column;
   min-height: 100vh;
   background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
   padding: 25px;
-  gap: 30px;
+  gap: 20px;
   position: relative;
   overflow-x: hidden;
 }
@@ -108,7 +100,6 @@ const toggleMapType = () => {
 /* 页眉区域 */
 .header-section {
    text-align: center;
-  padding: 30px 0 20px;
   position: relative;
 }
 
@@ -216,13 +207,6 @@ const toggleMapType = () => {
   margin-bottom: 20px;
 }
 
-.map-section {
-  background: white;
-  border-radius: 16px;
-  padding: 25px;
-  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
-}
-
 .section-header {
   display: flex;
   align-items: center;
@@ -411,8 +395,6 @@ const toggleMapType = () => {
 }
 
 .chart-header {
-  margin-bottom: 15px;
-  padding-bottom: 10px;
   border-bottom: 1px solid #e2e8f0;
 }
 

+ 6 - 27
src/views/User/HmOutFlux/atmosDeposition/heavyMetalEnterprise.vue

@@ -3,12 +3,12 @@
     <!-- 页面标题区域 -->
     <div class="header-section">
       <div class="header-content">
-        <h1 class="system-title">韶关市涉重企业环境数据监测系统</h1>
+        <h1 class="system-title">韶关市涉重企业重金属排放调查</h1>
       </div>
     </div>
     
     <!-- 地图展示区域 -->
-    <div class="dashboard-section map-section">
+    <div class="dashboard-section ">
       <div class="section-header">
         <div class="section-icon">🗺️</div>
         <h2 class="section-title">涉重企业地理分布</h2>
@@ -42,7 +42,7 @@
       <div class="dashboard-section chart-section">
         <div class="section-header">
           <div class="section-icon">📊</div>
-          <h2 class="section-title">大气颗粒物排放分析</h2>
+          <h2 class="section-title">大气颗粒物排放统计</h2>
           <div class="emission-info">
             <span class="info-item">单位: 吨/年 (t/a)</span>
             <span class="info-item">统计周期: 2025年</span>
@@ -51,17 +51,11 @@
         <div class="chart-card">
           <div class="chart-header">
             <h3>各区县企业平均大气颗粒物排放量</h3>
-            <p>重金属企业大气污染物排放分布情况</p>
           </div>
           <div class="chart-container">
             <HeavyMetalEnterprisechart/>
           </div>
-          <div class="chart-legend">
-            <div class="legend-item">
-              <div class="legend-color primary"></div>
-              <span>颗粒物排放量</span>
-            </div>
-          </div>
+          
         </div>
       </div>
     </div>
@@ -76,20 +70,13 @@ import HeavyMetalEnterprisechart from '@/components/atmpollution/heavyMetalEnter
 
 <style scoped>
 /* 基础样式重置 */
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
-}
-
 .page-container {
   display: flex;
   flex-direction: column;
   min-height: 100vh;
   background: linear-gradient(135deg, #f0f9ff 0%, #e9ecef 100%);
   padding: 25px;
-  gap: 30px;
+  gap: 20px;
   position: relative;
   overflow-x: hidden;
 }
@@ -97,7 +84,6 @@ import HeavyMetalEnterprisechart from '@/components/atmpollution/heavyMetalEnter
 /* 页眉区域 */
 .header-section {
  text-align: center;
-  padding: 30px 0 20px;
   position: relative;
 }
 
@@ -195,13 +181,6 @@ import HeavyMetalEnterprisechart from '@/components/atmpollution/heavyMetalEnter
   margin-bottom: 20px;
 }
 
-.map-section {
-  background: white;
-  border-radius: 16px;
-  padding: 25px;
-  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
-}
-
 .section-header {
   display: flex;
   align-items: center;
@@ -324,7 +303,7 @@ import HeavyMetalEnterprisechart from '@/components/atmpollution/heavyMetalEnter
 
 /* 图表区域 - 固定高度 */
 .chart-card {
-  height: 750px;
+  height: 600px;
   padding: 20px;
   background: white;
   border-radius: 16px;

+ 3 - 12
src/views/User/HmOutFlux/irrigationWater/crossSection.vue

@@ -3,7 +3,7 @@
     <!-- 页面标题 -->
     <div class="section-header">
       <div class="section-icon">📊</div>
-      <h1 class="page-title">韶关市河流断面水质监测分析系统</h1>
+      <h1 class="page-title">韶关市河流断面采样</h1>
     </div>
     
     <!-- 地图展示区域 -->
@@ -25,7 +25,7 @@
       <div class="dashboard-section table-section">
         <div class="section-header">
           <div class="section-icon">📋</div>
-          <h2 class="component-title">断面水质数据详情</h2>
+          <h2 class="component-title">断面采样数据详情</h2>
           <div class="table-info">
             <span class="info-item">数据更新时间: 2025-08-16</span>
           </div>
@@ -83,13 +83,6 @@ import crosssectionmap from '@/components/irrpollution/crosssectionmap.vue';
 
 <style scoped>
 /* 基础样式重置 */
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
-}
-
 .cross-container {
   display: flex;
   flex-direction: column;
@@ -180,7 +173,6 @@ import crosssectionmap from '@/components/irrpollution/crosssectionmap.vue';
 /* 数据卡片 - 添加滚动条 */
 .data-card {
   height: 550px;
-  padding: 0; /* 移除内边距 */
   display: flex;
   flex-direction: column;
 }
@@ -194,7 +186,6 @@ import crosssectionmap from '@/components/irrpollution/crosssectionmap.vue';
 /* 图表卡片 */
 .chart-card {
   height: 600px;
-  padding: 0; /* 移除内边距 */
   display: flex;
   flex-direction: column;
 }
@@ -202,7 +193,7 @@ import crosssectionmap from '@/components/irrpollution/crosssectionmap.vue';
 .chart-container {
   flex: 1;
   overflow: hidden; /* 确保图表不会溢出 */
-  padding: 20px;
+  padding: 25px;
 }
 
 .chart-header {

+ 5 - 13
src/views/User/HmOutFlux/irrigationWater/irriWaterSampleData.vue

@@ -67,13 +67,6 @@ import irrwatermap from '@/components/irrpollution/irrwatermap.vue';
 
 <style scoped>
 /* 基础样式重置 */
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
-}
-
 .page-container {
   display: flex;
   flex-direction: column;
@@ -88,14 +81,13 @@ import irrwatermap from '@/components/irrpollution/irrwatermap.vue';
 /* 页眉样式 */
 .header {
   border-radius: 16px;
-  padding: 25px;
+  padding: 0 0 25px ; 
 }
 
 .header-content {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 20px;
 }
 
 .header-left h1 {
@@ -459,13 +451,13 @@ import irrwatermap from '@/components/irrpollution/irrwatermap.vue';
     height: 400px;
   }
 }
-
-@media (max-width: 768px) {
-  .page-container {
-    padding: 15px;
+.page-container {
+    padding: 25px;
     gap: 20px;
   }
   
+@media (max-width: 768px) {
+  
   .header {
     padding: 20px;
   }

+ 5 - 17
src/views/User/HmOutFlux/irrigationWater/samplingMethodDevice1.vue

@@ -38,7 +38,7 @@
           <div class="image-card">
             <el-image :src="image3" alt="采样容器" class="sampling-image"></el-image>
             <div class="image-info">
-              <h3>样容器</h3>
+              <h3>样容器</h3>
               <p>500mL白色聚乙烯瓶,符合水质采样标准</p>
             </div>
           </div>
@@ -83,26 +83,14 @@
         <div class="field-gallery">
           <div class="field-card">
             <el-image :src="fieldImage1" alt="工作人员采样现场" class="sampling-image"></el-image>
-            <div class="field-info">
-              <!--<h3>河流采样现场</h3>
-              <p>多云天气下的河流采样工作</p>-->
-            </div>
           </div>
           
           <div class="field-card">
             <el-image :src="fieldImage2" alt="工作人员采样现场" class="sampling-image"></el-image>
-            <div class="field-info">
-              <!--<h3>水渠采样现场</h3>
-              <p>小雨天气下的水渠采样工作</p>-->
-            </div>
           </div>
           
           <div class="field-card">
             <el-image :src="fieldImage3" alt="工作人员采样现场" class="sampling-image"></el-image>
-            <div class="field-info">
-              <!--<h3>瀑布区域采样</h3>
-              <p>特殊地形条件下的采样工作</p>-->
-            </div>
           </div>
         </div>
       </div>
@@ -244,7 +232,7 @@ h2 {
 
 .sampling-image {
   width: 100%;
-  height: 230px;
+  height: 250px;
   display: block;
   object-fit: cover;
   transition: transform 0.4s ease;
@@ -255,18 +243,18 @@ h2 {
   transform: scale(1.05);
 }
 
-.image-info, .field-info {
+.image-info {
   padding: 20px;
 }
 
-.image-info h3, .field-info h3 {
+.image-info h3,  h3 {
   color: #1a365d;
   font-size: 1.25rem;
   margin-top: 0;
   margin-bottom: 10px;
 }
 
-.image-info p, .field-info p {
+.image-info p p {
   font-size: 0.95rem;
   color: #4a5568;
   margin: 0;

+ 3 - 2
src/views/User/dataStatistics/DetectionStatistics.vue

@@ -53,8 +53,9 @@ export default {
 .layout-container {
   display: grid;
   grid-template-columns: 1fr 1fr; /* 三列等宽 */
-  gap: 4px; /* 列之间的间距 */
-  padding: 0 0px;
+  gap: 20px; /* 列之间的间距 */
+  padding: 20px;
+  background: linear-gradient(135deg, rgba(230, 247, 255, 0.7) 0%, rgba(240, 248, 255, 0.7) 100%);
   box-sizing: border-box;
   justify-items: start;
   align-items: start;    /* 垂直对齐顶部,避免拉伸变形 */

+ 8 - 0
src/views/User/dataStatistics/LandCultivatedStatistics.vue

@@ -1,5 +1,8 @@
 <template>
+  <div class="container">
     <div class="chart-wrapper" ref="chartRef"></div>  
+  </div>
+    
 </template>
 
 <script setup>
@@ -109,6 +112,11 @@ import { onUnmounted } from 'vue'
 </script>
 
 <style scoped>
+
+.container {
+  padding: 20px;
+  background: linear-gradient(135deg, rgba(230, 247, 255, 0.7) 0%, rgba(240, 248, 255, 0.7) 100%);
+}
 .chart-wrapper {
   width: 100%;
   height: 500px;

+ 3 - 0
src/views/User/dataStatistics/SoilCdStatistics.vue

@@ -42,6 +42,9 @@ export default {
 .scale-wrapper {
   transform: scale(0.9);
   transform-origin: top left;
+  gap: 20px;
+  background: linear-gradient(135deg, rgba(230, 247, 255, 0.7) 0%, rgba(240, 248, 255, 0.7) 100%);
+  padding: 20px;
   width: fit-content;
   margin-left: 0;
   margin-bottom: 0;

+ 12 - 10
src/views/User/dataStatistics/SoilacidificationStatistics.vue

@@ -10,42 +10,44 @@
     <!-- 第二列:酸化加剧数据组件 -->
     <div class="column column-2">
       <div class="component-container">
-        <RefluxcdStatictics />
+        <refluxcedataStatictics />
       </div>
     </div>
   </div>
 </template>
 
 <script>
-import ReducedataStatistics from '@/components/cdStatictics/reducedataStatistics.vue';
-import RefluxcdStatictics from '@/components/cdStatictics/refluxcdStatictics.vue';
+
+import ReducedataStatistics from '@/components/soilStatictics/reducedataStatistics.vue';
+import refluxcedataStatictics from '@/components/soilStatictics/refluxcedataStatictics.vue'
 
 export default {
   components: {
     ReducedataStatistics,
-    RefluxcdStatictics,
+    refluxcedataStatictics
   }
 }
 </script>
 
 <style scoped>
-/* 核心:网格布局统一控制尺寸 */
+/* 核心:网格布局统一控制尺寸 
 .layout-container {
   display: grid;
-  grid-template-columns: 1fr 1fr;  /* 两列等宽 */
+  grid-template-columns: 1fr 1fr;  /* 两列等宽 
   height: 400px;
-  gap: 16px;                      /* 列/行间距 */
-  padding: 16px;                  /* 内边距 */
+  gap: 16px;                      /* 列/行间距 
+  padding: 16px;                  /* 内边距 
   box-sizing: border-box;
   width: 100%;
   min-height: 100vh;
-}
+}*/
 
 /* 统一子组件外层容器:确保填充网格单元格 */
 .component-container {
-  width: 500px;
+  width: 1000px;
   height: 500px;
   padding: 16px;
+  margin: 20px;
   border: 1px solid #e5e7eb;
   border-radius: 8px;
   overflow: hidden;

+ 59 - 0
src/views/User/farmlandQualityAssessment/farmlandQualityAssessment.vue

@@ -71,6 +71,7 @@ export default {
     return {
       map: null,
       geoJSONLayer: null,
+      shaoguanBoundaryLayer:null,
       multiPolygon: null, 
       categoryColors,
       statistics: {
@@ -134,6 +135,10 @@ export default {
       
       // 初始化GeoJSON图层 - 传递TMap对象
       this.initMapWithGeoJSON(geojsonData, TMap);
+
+      //加载并初始化韶关边界图层
+      const shaoguanBoundaryGeojson = await this.fetchShaoguanBoundary();
+      this.initShaoguanBoundaryLayer(shaoguanBoundaryGeojson,TMap);
     },
     
     // 加载SDK
@@ -160,6 +165,59 @@ export default {
         document.head.appendChild(script);
       });
     },
+
+    // 新增:获取韶关市边界GeoJSON数据
+  async fetchShaoguanBoundary() {
+    try {
+      // 替换为用户实际的韶关市边界接口地址
+      const boundaryUrl = "http://localhost:8000/api/vector/boundary?table_name=counties&field_name=city_name&field_value=%E9%9F%B6%E5%85%B3%E5%B8%82"; 
+      const response = await fetch(boundaryUrl);
+      
+      if (!response.ok) {
+        throw new Error(`获取韶关市边界失败: ${response.statusText}`);
+      }
+      
+      const boundaryGeoJSON = await response.json();
+      // 验证GeoJSON格式(必须是FeatureCollection,geometry为Polygon/MultiPolygon)
+      if (boundaryGeoJSON.type !== "FeatureCollection") {
+        throw new Error("韶关市边界数据不是有效的FeatureCollection");
+      }
+      return boundaryGeoJSON;
+    } catch (error) {
+      console.error("韶关市边界数据加载失败:", error);
+      return { type: "FeatureCollection", features: [] }; // 返回空数据避免地图崩溃
+    }
+  },
+  
+  // 新增:初始化韶关市边界图层
+  initShaoguanBoundaryLayer(boundaryGeoJSON, TMap) {
+    try {
+      if (!boundaryGeoJSON.features.length) {
+        console.warn("韶关市边界数据为空,不渲染边界");
+        return;
+      }
+
+       const lightEarthYellow = "rgba(245, 222, 179, 0.4)";
+
+      // 创建边界图层(独立于单元格图层)
+      this.shaoguanBoundaryLayer = new TMap.vector.GeoJSONLayer({
+        map: this.map, // 绑定到现有地图实例
+        data: boundaryGeoJSON, // 边界GeoJSON数据
+        // 边界样式配置:突出边框,透明填充(不遮挡下方单元格)
+        polygonStyle: new TMap.PolygonStyle({
+          color: lightEarthYellow, // 填充色:透明
+          showBorder: true, // 显示边框
+          borderColor: '#000000', // 边框颜色:蓝色(醒目)
+          borderWidth: 3 // 边框宽度:3px(确保清晰)
+        })
+      });
+
+      // 确保边界图层在最上层(覆盖单元格,不遮挡交互)
+      this.shaoguanBoundaryLayer.setZIndex(1); 
+    } catch (error) {
+      console.error("初始化韶关市边界图层失败:", error);
+    }
+    },
     
     // 加载GeoJSON数据
     async loadGeoJSON(url) {
@@ -247,6 +305,7 @@ export default {
         // 更新几何体以应用新样式
         this.multiPolygon.updateGeometries(polygons);
         
+        this.geoJSONLayer.setZIndex(100);
       
       } catch (error) {
         console.error('初始化GeoJSON图层失败:', error);

+ 0 - 14
src/views/User/heavyMetalFluxCalculation/inputFluxCalculation/waterdata/rivermessage.vue

@@ -77,20 +77,6 @@ onMounted(() => {
 </script>
 
 <style scoped>
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-  font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
-}
-
-body {
-  background-color: #f5f9fc;
-  color: #333;
-  line-height: 1.6;
-  padding: 20px;
-}
-
 .container {
   max-width: 1200px;
   margin: 0 auto;

+ 0 - 12
src/views/User/introduction/Introduce.vue

@@ -103,18 +103,6 @@ const setVideoStyle = (video: HTMLVideoElement) => {
 </script>
 
 <style scoped lang="scss">
-* {
-  margin: 0;
-  padding: 0;
-  box-sizing: border-box;
-}
-
-body {
-  background-color: white;
-  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; /* 更美观的字体 */
-  padding: 0;
-}
-
 .software-intro-container {
   width: 95%;
   margin: 0 auto;