Selaa lähdekoodia

新增数据看板页

yes-yes-yes-k 3 viikkoa sitten
vanhempi
commit
50a83ae66d

+ 1 - 0
.eslintrc-auto-import.json

@@ -5,6 +5,7 @@
     "ComputedRef": true,
     "DirectiveBinding": true,
     "EffectScope": true,
+    "ElMessage": true,
     "ExtractDefaultPropTypes": true,
     "ExtractPropTypes": true,
     "ExtractPublicPropTypes": true,

+ 6 - 0
src/components/layout/AppLayout.vue

@@ -218,6 +218,12 @@ const tabs = computed(() => {
     ];
   } else {
     return [
+      {
+        name:"totalIntroduction",
+        label: t('Menu.totalIntroduction'),
+        icon: "el-icon-info-filled",
+        routes: ["/totalIntroduction"],
+      },
       {
         name: "introduction",
         label: t('Menu.swIntroduce'),

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

@@ -106,6 +106,14 @@ const fetchData = async () => {
     if (!processedData) {
       throw new Error('无法解析API返回的数据结构');
     }
+
+    // 从任意字段中获取样本数量(所有字段的 count 应该相同)
+    const firstFieldKey = Object.keys(processedData)[0];
+    if (firstFieldKey && processedData[firstFieldKey].count !== undefined) {
+      stats.value.samples = processedData[firstFieldKey].count;
+    } else {
+      stats.value.samples = 0;
+    }
     
     // console.log('处理后的数据:', processedData);
     return processedData;
@@ -158,9 +166,6 @@ const calculateAllStats = (data) => {
     calculateFieldStats(data, indicator.key, indicator.name)
   );
   
-  // 更新样本数
-  const firstStat = initialCdStats.value[0] || otherIndicatorsStats.value[0];
-  stats.value.samples = firstStat?.count || 0;
 };
 
 
@@ -192,7 +197,6 @@ const initInitialCdChart = () => {
     const boxData = buildBoxplotData(initialCdStats.value);
     
     const option = {
-      title: { text: t('SoilCdStatistics.initialCdDistribution'), left: 'center' },
       tooltip: {
         trigger: "item",
         formatter: (params) => {

+ 2 - 1
src/locales/en.json

@@ -65,7 +65,8 @@
     "soilCadmiumStatistics": "Soil Cadmium Content Statistics",
     "cropRiskAssessment": "Crop Risk Assessment System",
     "soilAcidificationStatistics": "Soil Acidification Statistics",
-    "soilAcidAIAssistant": "Soil Acidification AI Assistant"
+    "soilAcidAIAssistant": "Soil Acidification AI Assistant",
+    "totalIntroduction":"Total Introduction"
   },
   "SoilacidificationStatistics": {
     "Title": "Soil acidification data statistics"

+ 2 - 1
src/locales/zh.json

@@ -65,7 +65,8 @@
     "soilCadmiumStatistics": "土壤镉含量统计",
     "cropRiskAssessment": "作物风险评估系统",
     "soilAcidificationStatistics": "土壤酸化统计",
-    "soilAcidAIAssistant": "土壤酸化 AI 智能体"
+    "soilAcidAIAssistant": "土壤酸化 AI 智能体",
+    "totalIntroduction": "数据看板"
   },
   "SoilacidificationStatistics": {
     "Title": "土壤酸化数据统计"

+ 7 - 0
src/router/index.ts

@@ -55,6 +55,13 @@ const routes = [
           import("@/views/User/introduction/IntroductionUpdate.vue"), // 修复路径
         meta: { title: "更新介绍" },
       },
+      {
+        path: "totalIntroduction",
+        name: "totalIntroduction",
+        component: () =>
+          import("@/views/User/introduction/TotalIntroduction.vue"),
+        meta: { title: "总体介绍" },
+      },
       {
         path: "Calculation",
         name: "Calculation",

+ 260 - 0
src/views/User/introduction/TotalIntroduction.vue

@@ -0,0 +1,260 @@
+<template>
+  <div class="total-introduction">
+    <!-- 背景层 -->
+    <div class="background-layer"></div>
+
+    <!-- 顶部标题 -->
+    <!-- <div class="header-title">
+      <h1>数据看板</h1>
+    </div> -->
+
+    <!-- 主要内容区域 -->
+    <div class="main-content">
+      <!-- 左侧区域 -->
+      <div class="left-section">
+        <CropcdStatictics/>
+      </div>
+
+      <!-- 中间区域 -->
+      <div class="center-section">
+        <!-- 地图区域 -->
+       
+        <div id="map-container" class="map-containter" ref="mapContainer">
+          <div v-if="mapLoading" class="map-loading">
+            <div class="spinner"></div>
+            <p>地图加载中...</p>
+          </div>
+          <div v-if="mapError" class="map-error">
+            <p>地图加载失败,请刷新重试</p>
+            <button @click="reloadMap" class="reload-btn">重新加载</button>
+          </div>
+
+        </div>
+
+        <FluxcdStatictics/>
+
+      </div>
+
+      <!-- 右侧区域 -->
+      <div class="right-section">
+       <EffcdStatistics/>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, nextTick } from 'vue';
+import FluxcdStatictics from "@/components/soilcdStatistics/fluxcdStatictics.vue";
+import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import CropcdStatictics from '@/components/soilcdStatistics/cropcdStatictics.vue';
+
+const { t } = useI18n();
+
+// 地图相关
+let L = null;
+const map = ref(null);
+const mapContainer = ref(null);
+const mapLoading = ref(false);
+const mapError = ref(false);
+const initMap = async () => {
+  mapLoading.value = true;
+  mapError.value = false;
+
+  try {
+    // 动态导入 Leaflet
+    if (!L) {
+      L = await import('leaflet');
+      await import('leaflet/dist/leaflet.css');
+      
+      delete (L.Icon.Default.prototype)._getIconUrl;
+      L.Icon.Default.mergeOptions({
+        iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
+        iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
+        shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
+      });
+    }
+
+    // 清除现有地图
+   const container = document.getElementById('map-container');
+
+    // 创建地图实例
+    map.value = L.map(container, {
+      zoomControl: true,
+      attributionControl: false,
+      center: [25.202903, 113.25383],
+      zoom: 10
+    });
+
+    // WMS 配置
+    const GEOSERVER_CONFIG = {
+      url: "/geoserver/wms",
+      workspace: "acidmap",
+      layerGroup: "mapwithboundary", 
+    };
+
+    // WMS 图层配置
+    const wmsLayer = L.tileLayer.wms(GEOSERVER_CONFIG.url, {
+      layers: `${GEOSERVER_CONFIG.workspace}:${GEOSERVER_CONFIG.layerGroup}`,
+      format: "image/png",
+      transparent: true,
+      version: "1.1.1",
+      crs: L.CRS.EPSG4326,
+      attribution: "Data from GeoServer"
+    });
+
+    // 添加图层到地图
+    wmsLayer.addTo(map.value);
+    // 等待地图渲染完成后调整大小
+    setTimeout(() => {
+      if (map.value) {
+        map.value.invalidateSize();
+      }
+    }, 100);
+
+    mapLoading.value = false;
+
+  } catch (error) {
+    console.error('地图初始化失败:', error);
+    mapError.value = true;
+    mapLoading.value = false;
+    
+    let errorMessage = t('AcidModelMap.mapInitError');
+    if (error instanceof Error) {
+      errorMessage += ': ' + error.message;
+    }
+    ElMessage.error(errorMessage);
+  }
+};
+
+const reloadMap = () => {
+  initMap();
+};
+
+onMounted(() => {
+  // 等待地图渲染完成后调整大小
+    setTimeout(() => {
+      initMap()
+    }, 200);
+});
+
+
+
+</script>
+
+<style scoped>
+.total-introduction {
+  width: 100%;
+  height: 100%;
+  background-color: #0a0a2a;
+  color: white;
+  font-family: 'Arial', sans-serif;
+  position: relative;
+}
+
+.background-layer {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-image: url('https://via.placeholder.com/1920x1080/0a0a2a/0a0a2a');
+  background-size: cover;
+  z-index: -1;
+}
+
+.header-title {
+  text-align: center;
+  padding: 20px 20px 0 20px;
+}
+
+.header-title h1 {
+  font-size: 32px;
+  color: #00bfff;
+  text-shadow: 0 0 10px rgba(0, 187, 255, 0.8);
+  border-bottom: 2px solid #00bfff;
+  padding-bottom: 10px;
+}
+
+.main-content {
+  display: flex;
+  gap: 20px;
+  padding: 20px;
+  overflow-x: auto;
+}
+
+.left-section,
+
+.right-section {
+  flex: 1;
+  width: 200px;
+}
+.center-section {
+  flex: 2;
+  width: 500px;
+}
+
+.map-containter {
+  width: 100%;
+  height: 600px;
+  border: 2px solid #00bfff;
+  border-radius: 10px;
+  overflow: hidden;
+  position: relative;
+  background-color: rgba(10, 10, 42, 0.8);
+}
+
+.map-loading,
+.map-error {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(0, 0, 0, 0.7);
+  z-index: 1000;
+  color: white;
+}
+
+.spinner {
+  width: 40px;
+  height: 40px;
+  border: 4px solid rgba(0, 187, 255, 0.3);
+  border-radius: 50%;
+  border-top-color: #00bfff;
+  animation: spin 1s linear infinite;
+  margin-bottom: 15px;
+}
+
+@keyframes spin {
+  to { transform: rotate(360deg); }
+}
+
+.map-error p {
+  color: #ff6b6b;
+  font-size: 16px;
+  margin-bottom: 15px;
+}
+
+.reload-btn {
+  padding: 10px 20px;
+  background-color: #00bfff;
+  color: white;
+  border: none;
+  border-radius: 5px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.3s;
+}
+
+.reload-btn:hover {
+  background-color: #009acd;
+  transform: translateY(-2px);
+}
+
+</style>