Browse Source

样式修改+土壤Cd优化+土壤酸化数据统计的绘制

yes-yes-yes-k 3 tháng trước cách đây
mục cha
commit
e29301a497

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

+ 1 - 6
src/components/detectionStatistics/atmcompanyStatics.vue

@@ -209,7 +209,7 @@ export default {
           left: 0,
           right: "15%",
           top: 20,
-          bottom: "45%",
+          bottom: "3%",
           containLabel: true
         },
         xAxis: {
@@ -274,11 +274,6 @@ export default {
   margin: 0 auto;
 }
 
-header {
-  padding: 20px 0;
-  margin-bottom: 20px;
-}
-
 h1 {
   font-size: 24px;
   color: #2980b9;

+ 115 - 190
src/components/detectionStatistics/atmsampleStatistics.vue

@@ -2,7 +2,7 @@
   <div class="boxplot-container">
     <div class="chart-container">
       <div class="header">
-        <div class="chart-title">大气重金属浓度统计箱线图</div>
+        <div class="chart-title">灌溉水重金属浓度统计箱线图</div>
         <p>展示各重金属浓度的分布特征(最小值、四分位数、中位数、最大值)</p>
         <p class="sample-subtitle">样本来源:{{ totalPoints }}个数据</p>
       </div>
@@ -28,119 +28,77 @@
 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/water_sampling_data')
+    const apiTimestamp = ref(null)
+    const statsData = ref({}); // 新增:存储接口返回的预统计数据
+    const chartOption = ref({})
+    const isLoading = ref(true)
+    const error = ref(null)
+    
+    // 样本数统计(从预统计数据中获取)
+    const totalPoints = computed(() => {
+      const firstMetalKey = heavyMetals[0]?.key;
+      return statsData.value[firstMetalKey]?.count || 0;
+    })
+
+    // 缓存每个品类的统计量(与 x 轴顺序一致)
+    const statsByIndex = ref([])
+
+    // -------- 配置:金属字段 --------
     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:'大气污染物重量' }
+      { key: 'cr_concentration', name: '铬 (Cr)', color: '#FF9800' },
+      { key: 'as_concentration', name: '砷 (As)', color: '#4CAF50' },
+      { key: 'cd_concentration', name: '镉 (Cd)', color: '#9C27B0' },
+      { key: 'hg_concentration', name: '汞 (Hg)', color: '#2196F3' },
+      { key: 'pb_concentration', name: '铅 (Pb)', color: '#F44336' }
     ]
 
-    // -------- 状态 --------
-    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 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])
+      console.log(`%c[${metalName || '全局'}] %c${message}`,
+        'color:#4CAF50;font-weight:bold', 'color:#333')
     }
 
-    // -------- 数据统计 --------
-    /** 计算每个重金属的箱线图统计量(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
+    // -------- 构建箱线数据 --------
+    const buildBoxplotData = () => {
+      const xAxisData = heavyMetals.map(m => m.name);
+
+      // 缓存每个重金属的统计量(用于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箱线图数据
+      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];
+      });
 
-        // 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]
-      )
-      return { xAxisData, data }
-    }
+      return { xAxisData, data };
+    };
 
-    // -------- 图表初始化 --------
+    // -------- 初始化图表 --------
     const initChart = () => {
-      //log('开始初始化图表')
-      const stats = calculateBoxplotStats()
-      const { xAxisData, data } = buildBoxplotData(stats)
+      const { xAxisData, data } = buildBoxplotData(); // 修复:直接调用构建函数
 
-      // ECharts 配置(重点检查 series 数据格式)
       chartOption.value = {
         tooltip: {
           trigger: 'item',
@@ -149,7 +107,6 @@ export default {
             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>
@@ -158,28 +115,29 @@ export default {
                 <div>上四分位:<span style="color:#d87a80;">${s.q3.toFixed(4)}</span></div>
                 <div>最大值:<span style="color:#5a5;">${s.max.toFixed(4)}</span></div>
               </div>`
-          }
+          },
         },
         xAxis: {
           type: 'category',
           data: xAxisData,
           name: '重金属类型',
           nameLocation: 'middle',
-          nameGap: 45,
-          axisLabel: { color: '#555', rotate: 30, fontWeight: 'bold',fontSize:11 }
+          nameGap: 30,
+          axisLabel: { color: '#555', rotate: 0, fontWeight: 'bold' ,fontSize :11}
         },
         yAxis: {
           type: 'value',
-          name: 'mg/kg',
+          name: 'ug/L',
+          nameTextStyle: { fontSize: 12 }, 
           nameLocation: 'end',
-          nameGap: 5,
-          axisLabel: { color: '#555', fontWeight: 'bold' ,fontSize:11},
+          nameGap: 8,
+          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,67 +146,29 @@ export default {
             itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.2)', borderWidth: 3 }
           }
         }],
-        grid: { top: '8%', right: '5%', left: '5%', bottom: '20%' }
+        grid: { top: '10%', right: '3%', left: '6%', bottom: '10%' }
       }
+
       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 解析失败')
-      }
-    }
-
-    // 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 不是数组')
+        const response = await axios.get(apiUrl.value);
+        statsData.value = response.data; // 现在statsData已声明,可以正常赋值
+        apiTimestamp.value = new Date().toLocaleString();
+        initChart();
+      } catch (err) {
+        error.value = err;
+        isLoading.value = false;
+        console.error('接口请求失败:', err);
       }
-    } else if (Array.isArray(data)) {
-      // 情况2:直接返回 features 数组
-      features = data
-    } else {
-      // 情况3:其他非法结构
-      throw new Error(`接口结构异常,响应:${JSON.stringify(data)}`)
-    }
-
-    // 2. 提取 properties 数据(确保非空)
-    sampleData.value = features.map(f => f.properties)
-    if (sampleData.value.length === 0) {
-      throw new Error('接口返回数据为空(properties 为空)')
-    }
-    //log(`成功提取 ${sampleData.value.length} 条数据`, '接口')
-
-    // 3. 初始化图表
-    initChart()
-
-  } catch (err) {
-    error.value = err
-    isLoading.value = false
-    console.error('接口请求失败:', err)
-  }
-})
-
+    })
 
     return {
+      apiUrl,
+      apiTimestamp,
       chartOption,
       isLoading,
       error,
@@ -267,13 +187,14 @@ export default {
 }
 .header { 
   text-align: left;
-  margin-bottom: 20px; 
-}
-.chart-title {
-  font-size: 14px;
-  color: #2980b9;
-  font-weight: 600;
+   margin-bottom: 10px; 
 }
+
+.header h2 { 
+  font-size: 0.6rem; 
+  color: #333; 
+  margin-bottom: 4px;
+ }
 .header p { 
   font-size: 0.6rem; 
   color: #666;
@@ -281,37 +202,42 @@ export default {
 }
 .loading-state { 
   text-align: center;
-  padding: 40px 0;
-  color: #666; 
+   padding: 40px 0;
+   color: #666; 
 }
 .loading-state .spinner {
-  display: inline-block; 
-  width: 24px; 
-  height: 24px; 
-  margin-right: 8px;
-  border: 3px solid #ccc; 
-  border-top-color: #1890ff; 
-  border-radius: 50%;
+  display: inline-block; width: 24px; height: 24px; margin-right: 8px;
+  border: 3px solid #ccc; border-top-color: #1890ff; border-radius: 50%;
   animation: spin 1s linear infinite;
 }
-@keyframes spin { 
-  to { transform: rotate(360deg); } 
-}
-.error-state { 
-  text-align: center; 
-  padding: 40px 0; 
-  color: #f56c6c; 
-}
-.chart-wrapper { 
-  width: 100%; 
-  height: 220px; /* 确保高度有效 */
-}
+@keyframes spin { to { transform: rotate(360deg); } }
+.error-state { text-align: center; padding: 40px 0; color: #f56c6c; }
+.chart-wrapper { width: 100%; height: 220px; }
 .chart-container {
   background: white;
   border-radius: 12px;
   box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
-  padding: 20px;
   margin-bottom: 25px;
+  height: 100%;
+  box-sizing: border-box;
+}
+
+.chart-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 15px;
+}
+
+.chart-title {
+  font-size: 14px;
+  color: #2980b9;
+  font-weight: 600;
+}
+
+.title-group {
+    display: flex;
+    align-items: left;
 }
 
 .sample-subtitle {
@@ -319,5 +245,4 @@ export default {
   color: #888;
   margin-top: 4px;
 }
-
-</style>
+</style>

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

@@ -204,7 +204,7 @@ export default {
           left: 0,
           right: '15%',
           top: 20, // 缩减顶部边距
-          bottom: "46%", // 缩减底部边距
+          bottom: "3%", // 缩减底部边距
           containLabel: true
         },
         xAxis: {

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

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

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

@@ -60,8 +60,8 @@ onMounted(() => {
     return '#cccccc';
   }
 
-  // 加载区县边界(保持不变)
-  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')
+  // 加载区县边界(保持不变)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}`);
       return res.json();
@@ -189,7 +189,7 @@ onMounted(() => {
 /* 原有样式保持不变 */
 .map-wrapper {
   width: 100%;
-  height: 80%;
+  height: 100%;
   position: relative;
 }
 .map-container {

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

@@ -47,8 +47,8 @@ onMounted(() => {
     "南雄市": "#06D6A0",
   };
 
-  // 加载区县边界(带完整错误处理)
-  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')
+  // 加载区县边界(带完整错误处理)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}`);
       return res.json();
@@ -195,7 +195,7 @@ onMounted(() => {
 }
 .map-container {
   width: 100% !important;
-  height: 500px !important;
+  height: 100% !important;
 }
 
 /*  标题和分隔线 */

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

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

+ 37 - 69
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,7 +107,7 @@ 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");
+     const res = await axios.get("http://localhost:8000/api/vector/stats/EffCd_input_data");
      return res.data;
 
   } catch (err) {
@@ -114,66 +115,33 @@ const fetchData = async () => {
   }
 };
 
-// 核心:分位数计算(参考灌溉水的精准计算逻辑)
-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",

+ 22 - 40
src/components/soilcdStatistics/fluxcdStatictics.vue

@@ -99,7 +99,7 @@ const log = (message, field = '') => {
 // 数据请求
 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) 
@@ -127,40 +127,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 +155,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;
 };
 
 // 构建箱线图数据(通用函数)

+ 3 - 14
src/views/User/HmOutFlux/atmosDeposition/airSampleData.vue

@@ -3,7 +3,7 @@
     <!-- 系统标题区域 -->
     <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>
           <select 
@@ -18,7 +18,7 @@
     </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>
@@ -58,7 +58,6 @@
         <div class="dashboard-card chart-card">
           <div class="chart-header">
             <h3>各区县平均大气重金属污染柱状图</h3>
-            <p>大气污染物区域分布情况 ({{ calculationMethod === 'weight' ? 'mg/kg' : 'μg/m³' }})</p>
           </div>
           <div class="chart-container">
             <AirsampleChart :calculation-method="calculationMethod"/>
@@ -93,7 +92,7 @@ const toggleMapType = () => {
   min-height: 100vh;
   background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
   padding: 25px;
-  gap: 30px;
+  gap: 20px;
   position: relative;
   overflow-x: hidden;
 }
@@ -101,7 +100,6 @@ const toggleMapType = () => {
 /* 页眉区域 */
 .header-section {
    text-align: center;
-  padding: 30px 0 20px;
   position: relative;
 }
 
@@ -209,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;
@@ -404,8 +395,6 @@ const toggleMapType = () => {
 }
 
 .chart-header {
-  margin-bottom: 15px;
-  padding-bottom: 10px;
   border-bottom: 1px solid #e2e8f0;
 }
 

+ 6 - 20
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>
@@ -82,7 +76,7 @@ import HeavyMetalEnterprisechart from '@/components/atmpollution/heavyMetalEnter
   min-height: 100vh;
   background: linear-gradient(135deg, #f0f9ff 0%, #e9ecef 100%);
   padding: 25px;
-  gap: 30px;
+  gap: 20px;
   position: relative;
   overflow-x: hidden;
 }
@@ -90,7 +84,6 @@ import HeavyMetalEnterprisechart from '@/components/atmpollution/heavyMetalEnter
 /* 页眉区域 */
 .header-section {
  text-align: center;
-  padding: 30px 0 20px;
   position: relative;
 }
 
@@ -188,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;
@@ -317,7 +303,7 @@ import HeavyMetalEnterprisechart from '@/components/atmpollution/heavyMetalEnter
 
 /* 图表区域 - 固定高度 */
 .chart-card {
-  height: 750px;
+  height: 600px;
   padding: 20px;
   background: white;
   border-radius: 16px;

+ 3 - 5
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>
@@ -173,7 +173,6 @@ import crosssectionmap from '@/components/irrpollution/crosssectionmap.vue';
 /* 数据卡片 - 添加滚动条 */
 .data-card {
   height: 550px;
-  padding: 0; /* 移除内边距 */
   display: flex;
   flex-direction: column;
 }
@@ -187,7 +186,6 @@ import crosssectionmap from '@/components/irrpollution/crosssectionmap.vue';
 /* 图表卡片 */
 .chart-card {
   height: 600px;
-  padding: 0; /* 移除内边距 */
   display: flex;
   flex-direction: column;
 }
@@ -195,7 +193,7 @@ import crosssectionmap from '@/components/irrpollution/crosssectionmap.vue';
 .chart-container {
   flex: 1;
   overflow: hidden; /* 确保图表不会溢出 */
-  padding: 20px;
+  padding: 25px;
 }
 
 .chart-header {

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

@@ -81,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 {
@@ -452,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;
   }

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