Przeglądaj źródła

修改了土壤酸化数据统计展示,修复了检测信息统计图的问题

yes-yes-yes-k 1 dzień temu
rodzic
commit
65f4a8f406
23 zmienionych plików z 657 dodań i 1363 usunięć
  1. 3 2
      components.d.ts
  2. 3 3
      src/components/atmpollution/atmsamplemap.vue
  3. 0 232
      src/components/cdStatictics/reducedataStatistics.vue
  4. 0 259
      src/components/cdStatictics/refluxcdStatictics.vue
  5. 2 17
      src/components/detectionStatistics/atmcompanyStatics.vue
  6. 1 1
      src/components/detectionStatistics/atmsampleStatistics.vue
  7. 2 2
      src/components/detectionStatistics/crosscetionStatistics.vue
  8. 1 1
      src/components/irrpollution/crosssectionmap.vue
  9. 1 1
      src/components/irrpollution/irrwatermap.vue
  10. 0 222
      src/components/irrpollution/waterassaydata1.vue
  11. 0 197
      src/components/irrpollution/waterassaydata3.vue
  12. 0 340
      src/components/irrpollution/waterassaydata4.vue
  13. 349 0
      src/components/soilStatictics/reducedataStatistics.vue
  14. 214 0
      src/components/soilStatictics/refluxcedataStatictics.vue
  15. 5 12
      src/views/User/HmOutFlux/atmosDeposition/airSampleData.vue
  16. 0 7
      src/views/User/HmOutFlux/atmosDeposition/heavyMetalEnterprise.vue
  17. 0 7
      src/views/User/HmOutFlux/irrigationWater/crossSection.vue
  18. 0 7
      src/views/User/HmOutFlux/irrigationWater/irriWaterSampleData.vue
  19. 5 17
      src/views/User/HmOutFlux/irrigationWater/samplingMethodDevice1.vue
  20. 12 10
      src/views/User/dataStatistics/SoilacidificationStatistics.vue
  21. 59 0
      src/views/User/farmlandQualityAssessment/farmlandQualityAssessment.vue
  22. 0 14
      src/views/User/heavyMetalFluxCalculation/inputFluxCalculation/waterdata/rivermessage.vue
  23. 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']

+ 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('');

+ 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 - 17
src/components/detectionStatistics/atmcompanyStatics.vue

@@ -209,7 +209,7 @@ export default {
           left: 0,
           right: "15%",
           top: 20,
-          bottom: 10,
+          bottom: "45%",
           containLabel: true
         },
         xAxis: {
@@ -267,29 +267,14 @@ 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;
 }

+ 1 - 1
src/components/detectionStatistics/atmsampleStatistics.vue

@@ -188,7 +188,7 @@ 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: '5%', bottom: '20%' }
       }
       isLoading.value = false
       //log('图表初始化完成')

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

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

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

@@ -61,7 +61,7 @@ onMounted(() => {
   }
 
   // 加载区县边界(保持不变)
-  fetch('/data/韶关市各区县边界图.geojson')
+  fetch('http://localhost:8000/api/vector/boundary?table_name=counties&field_name=city_name&field_value=%E9%9F%B6%E5%85%B3%E5%B8%82')
     .then(res => {
       if (!res.ok) throw new Error(`区县边界加载失败:${res.status}`);
       return res.json();

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

@@ -48,7 +48,7 @@ onMounted(() => {
   };
 
   // 加载区县边界(带完整错误处理)
-  fetch('/data/韶关市各区县边界图.geojson')
+  fetch('http://localhost:8000/api/vector/boundary?table_name=counties&field_name=city_name&field_value=%E9%9F%B6%E5%85%B3%E5%B8%82')
     .then(res => {
       if (!res.ok) throw new Error(`区县边界加载失败:${res.status}`);
       return res.json();

+ 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>

+ 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>

+ 5 - 12
src/views/User/HmOutFlux/atmosDeposition/airSampleData.vue

@@ -5,13 +5,13 @@
       <div class="header-content">
         <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>
@@ -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,7 @@
         <div class="dashboard-card chart-card">
           <div class="chart-header">
             <h3>各区县平均大气重金属污染柱状图</h3>
-            <p>大气污染物区域分布情况 ({{ calculationMethod === 'weight' ? 'mg/' : 'μg/m³' }})</p>
+            <p>大气污染物区域分布情况 ({{ calculationMethod === 'weight' ? 'mg/kg' : 'μg/m³' }})</p>
           </div>
           <div class="chart-container">
             <AirsampleChart :calculation-method="calculationMethod"/>
@@ -87,13 +87,6 @@ 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;

+ 0 - 7
src/views/User/HmOutFlux/atmosDeposition/heavyMetalEnterprise.vue

@@ -76,13 +76,6 @@ 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;

+ 0 - 7
src/views/User/HmOutFlux/irrigationWater/crossSection.vue

@@ -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;

+ 0 - 7
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;

+ 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;

+ 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;