Эх сурвалжийг харах

配合新修改的后端接口,修改了前端的逻辑,绘图速度更快了

yes-yes-yes-k 3 сар өмнө
parent
commit
9c4e48e66c

+ 123 - 114
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>
@@ -33,72 +33,56 @@ import { ref, onMounted, computed } from 'vue'
 export default {
   components: { VChart },
   setup() {
-    // -------- 基本状态 --------
-    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 apiUrl = ref('http://localhost:8000/api/vector/stats/Atmo_sample_data')
+    const heavyMetals = [
+      { key: 'Cr_particulate', name: '铬 (Cr)', color: '#FF9800' },
+      { key: 'As_particulate', name: '砷 (As)', color: '#4CAF50' },
+      { key: 'Cd_particulate', name: '镉 (Cd)', color: '#9C27B0' },
+      { key: 'Hg_particulate', name: '汞 (Hg)', color: '#2196F3' },
+      { key: 'Pb_particulate', name: '铅 (Pb)', color: '#F44336' },
+    ]
+
+    // -------- 状态管理 --------
+    const chartOption = ref({})   // ECharts 配置
+    const isLoading = ref(true)   // 加载状态
+    const error = ref(null)       // 错误信息
+    const statsByIndex = ref([])  // 与x轴对齐的统计结果(用于tooltip)
     const totalPoints = computed(() => {
-      const firstMetalKey = heavyMetals[0]?.key;
-      return statsData.value[firstMetalKey]?.count || 0;
+      // 从统计数据中获取样本总数(假设所有金属样本数相同)
+      return statsByIndex.value.length > 0 
+        ? statsByIndex.value[0].count || 0 
+        : 0;
     })
 
-    // 缓存每个品类的统计量(与 x 轴顺序一致)
-    const statsByIndex = ref([])
-
-    // -------- 配置:金属字段 --------
-    const heavyMetals = [
-      { 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 log = (message, metalName = '') => {
-      console.log(`%c[${metalName || '全局'}] %c${message}`,
-        'color:#4CAF50;font-weight:bold', 'color:#333')
+      console.log(
+        `%c[${metalName || '全局'}] %c${message}`,
+        'color:#4CAF50;font-weight:bold',
+        'color:#333'
+      )
     }
 
-    // -------- 构建箱线数据 --------
-    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];
-      });
-
-      return { xAxisData, data };
-    };
-
-    // -------- 初始化图表 --------
-    const initChart = () => {
-      const { xAxisData, data } = buildBoxplotData(); // 修复:直接调用构建函数
+    /** 构建箱线图数据 */
+    const buildBoxplotData = (stats) => {
+      const xAxisData = heavyMetals.map(m => m.name)
+      // 生成箱线图所需格式:[[min, q1, median, q3, max], ...]
+      const data = stats.map(s => 
+        s.min === null 
+          ? [null, null, null, null, null] 
+          : [s.min, s.q1, s.median, s.q3, s.max]
+      )
+      return { xAxisData, data }
+    }
 
+    /** 初始化图表 */
+    const initChart = (xAxisData, data, stats) => {
+      statsByIndex.value = stats // 缓存统计数据用于tooltip
+      
       chartOption.value = {
         tooltip: {
           trigger: 'item',
@@ -106,7 +90,7 @@ export default {
             const s = statsByIndex.value[params.dataIndex]
             if (!s || s.min === null) {
               return `<div style="font-weight:bold;color:#f56c6c">${xAxisData[params.dataIndex]}</div><div>无有效数据</div>`
-            }
+            }          
             return `<div style="font-weight:bold">${xAxisData[params.dataIndex]}</div>
               <div style="margin-top:8px">
                 <div>最小值:<span style="color:#5a5;">${s.min.toFixed(4)}</span></div>
@@ -115,29 +99,28 @@ 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: 30,
-          axisLabel: { color: '#555', rotate: 0, fontWeight: 'bold' ,fontSize :11}
+          nameGap: 45,
+          axisLabel: { color: '#555', rotate: 30, fontWeight: 'bold', fontSize: 11 }
         },
         yAxis: {
           type: 'value',
-          name: 'ug/L',
-          nameTextStyle: { fontSize: 12 }, 
+          name: 'mg/kg',
           nameLocation: 'end',
-          nameGap: 8,
-          axisLabel: { color: '#555', fontWeight: 'bold',fontSize:11 },
+          nameGap: 5,
+          axisLabel: { color: '#555', fontWeight: 'bold', fontSize: 11 },
           splitLine: { lineStyle: { color: '#f0f0f0' } }
         },
         series: [{
           name: '重金属浓度分布',
           type: 'boxplot',
-          data,
+          data, // 直接使用接口返回的统计数据
           itemStyle: {
             color: (p) => (heavyMetals[p.dataIndex]?.color || '#1890ff'),
             borderWidth: 2
@@ -146,29 +129,60 @@ export default {
             itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.2)', borderWidth: 3 }
           }
         }],
-        grid: { top: '10%', right: '3%', left: '6%', bottom: '10%' }
+        grid: { top: '8%', right: '5%', left: '8%', bottom: '20%' }
       }
-
-      isLoading.value = false
     }
 
-    // -------- 拉取接口并绘图 --------
+    // -------- 接口请求 --------
     onMounted(async () => {
       try {
-        const response = await axios.get(apiUrl.value);
-        statsData.value = response.data; // 现在statsData已声明,可以正常赋值
-        apiTimestamp.value = new Date().toLocaleString();
-        initChart();
+        log('发起新接口请求,获取统计数据...')
+        const response = await axios.get(apiUrl.value)
+        const apiData = response.data.data
+
+        // 从接口数据中提取每个重金属的统计量
+        const stats = heavyMetals.map(metal => {
+          const metalStats = apiData[metal.key]
+          if (!metalStats) {
+            log(`警告:接口缺少${metal.name}的统计数据`)
+            return { ...metal, min: null, q1: null, median: null, q3: null, max: null, count: 0 }
+          }
+          
+          // 验证必要的统计字段
+          const requiredFields = ['min', 'q1', 'median', 'q3', 'max']
+          const hasValidData = requiredFields.every(field => 
+            metalStats[field] !== undefined && !isNaN(metalStats[field])
+          )
+
+          if (!hasValidData) {
+            log(`警告:${metal.name}的统计数据不完整`)
+            return { ...metal, min: null, q1: null, median: null, q3: null, max: null, count: 0 }
+          }
+
+          return {
+            ...metal,
+            min: Number(metalStats.min),
+            q1: Number(metalStats.q1),
+            median: Number(metalStats.median),
+            q3: Number(metalStats.q3),
+            max: Number(metalStats.max),
+            count: metalStats.count ? Number(metalStats.count) : 0
+          }
+        })
+
+        // 构建图表数据并初始化图表
+        const { xAxisData, data } = buildBoxplotData(stats)
+        initChart(xAxisData, data, stats)
+        isLoading.value = false
+
       } catch (err) {
-        error.value = err;
-        isLoading.value = false;
-        console.error('接口请求失败:', err);
+        error.value = err
+        isLoading.value = false
+        console.error('接口请求失败:', err)
       }
     })
 
     return {
-      apiUrl,
-      apiTimestamp,
       chartOption,
       isLoading,
       error,
@@ -187,14 +201,13 @@ export default {
 }
 .header { 
   text-align: left;
-   margin-bottom: 10px; 
+  margin-bottom: 20px; 
+}
+.chart-title {
+  font-size: 14px;
+  color: #2980b9;
+  font-weight: 600;
 }
-
-.header h2 { 
-  font-size: 0.6rem; 
-  color: #333; 
-  margin-bottom: 4px;
- }
 .header p { 
   font-size: 0.6rem; 
   color: #666;
@@ -202,42 +215,38 @@ 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 {

+ 1 - 2
src/components/detectionStatistics/irrigationstatistics.vue

@@ -153,7 +153,7 @@ export default {
       try {
         //log('发起API请求...')
         const response = await axios.get(apiUrl.value);
-        statsData.value = response.data; 
+        statsData.value = response.data.data; 
         apiTimestamp.value = new Date().toLocaleString()
           initChart()
       } catch (err) {
@@ -215,7 +215,6 @@ export default {
   background: white;
   border-radius: 12px;
   box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
-  margin-bottom: 25px;
   height: 100%;
   box-sizing: border-box;
 }

+ 1 - 1
src/components/soilcdStatistics/cropcdStatictics.vue

@@ -130,7 +130,7 @@ const fetchData = async () => {
     const apiUrl = 'http://localhost:8000/api/vector/stats/CropCd_input_data';
     const response = await axios.get(apiUrl);
     
-    return response.data;
+    return response.data.data;
   } catch (err) {
     throw new Error('数据加载失败: ' + err.message);
   }

+ 1 - 1
src/components/soilcdStatistics/effcdStatistics.vue

@@ -108,7 +108,7 @@ const fetchData = async () => {
   try {
     // 实际项目中替换为真实API
      const res = await axios.get("http://localhost:8000/api/vector/stats/EffCd_input_data");
-     return res.data;
+     return res.data.data;
 
   } catch (err) {
     throw new Error('数据加载失败: ' + err.message);

+ 1 - 8
src/components/soilcdStatistics/fluxcdStatictics.vue

@@ -89,13 +89,6 @@ const fieldConfig = {
   ]
 };
 
-// 日志工具
-const log = (message, field = '') => {
-  console.log(`%c[${field || '全局'}] %c${message}`,
-    'color:#2196F3;font-weight:bold', 'color:#333');
-};
-
-
 // 数据请求
 const fetchData = async () => {
   try {
@@ -103,7 +96,7 @@ const fetchData = async () => {
     const response = await axios.get(apiUrl);
     const rawData = response.data.features 
       ? response.data.features.map(f => f.properties) 
-      : response.data;
+      : response.data.data;
     return rawData;
   } catch (err) {
     throw new Error('数据加载失败: ' + err.message);