|
|
@@ -1,260 +1,418 @@
|
|
|
-<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>
|
|
|
+<script setup>
|
|
|
+ import { ref, watch, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
|
|
|
+ import L from 'leaflet'
|
|
|
+ import 'leaflet/dist/leaflet.css'
|
|
|
+ import { api8000 } from '@/utils/request'
|
|
|
+
|
|
|
+ const samplePointsData = ref([])
|
|
|
+ const mapRef = ref(null)
|
|
|
+ let map = null
|
|
|
+ let wmsLayer = null
|
|
|
+
|
|
|
+ const statistics = ref({
|
|
|
+ totalBlocks: 0,
|
|
|
+ avgPH: 0,
|
|
|
+ strongAcidCount: 0,
|
|
|
+ mildAcidCount: 0,
|
|
|
+ normalCount: 0,
|
|
|
+ maxPH: 0,
|
|
|
+ minPH: 0
|
|
|
+ })
|
|
|
+
|
|
|
+ const CONFIG = {
|
|
|
+ center:[25.202903, 113.25383],
|
|
|
+ zoom:9.5,
|
|
|
+ getPoint:'/api/vector/export/all?table_name=le_soil_data&format=geojson',
|
|
|
+ geoserver:{
|
|
|
+ url:'/geoserver',
|
|
|
+ workspace:'acidmap',
|
|
|
+ layerGroup:'leshujukanbanmap',
|
|
|
+ dataLayer:'le_soil_data',
|
|
|
+ wmsUrl:'/geoserver/acidmap/wms'
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- <!-- 中间区域 -->
|
|
|
- <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>
|
|
|
+ // 获取 pH 等级对应的 CSS 类
|
|
|
+function getPHLevelClass(ph) {
|
|
|
+ if (!ph || ph === 0) return ''
|
|
|
+ if (ph <= 5.2) return 'danger'
|
|
|
+ if (ph < 6.0) return 'warning'
|
|
|
+ return 'success'
|
|
|
+}
|
|
|
|
|
|
- <FluxcdStatictics/>
|
|
|
|
|
|
- </div>
|
|
|
+// 获取综合评语
|
|
|
+function getPHComment(avgPH) {
|
|
|
+ if (avgPH <= 4.5) return '🔴 土壤严重酸化,需立即治理!'
|
|
|
+ if (avgPH <= 5.2) return '🟠 土壤酸化明显,建议尽快改良'
|
|
|
+ if (avgPH < 6.0) return '🟡 土壤微酸性,注意保持'
|
|
|
+ return '🟢 土壤酸碱度适宜,状态良好'
|
|
|
+}
|
|
|
|
|
|
- <!-- 右侧区域 -->
|
|
|
- <div class="right-section">
|
|
|
- <EffcdStatistics/>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
+ async function initMap(){
|
|
|
+ await nextTick()
|
|
|
|
|
|
-<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;
|
|
|
+ if(!mapRef.value) return
|
|
|
+ map = L.map(mapRef.value).setView(CONFIG.center,CONFIG.zoom)
|
|
|
+
|
|
|
+ wmsLayer = L.tileLayer.wms(CONFIG.geoserver.wmsUrl, {
|
|
|
+ layers: `${CONFIG.geoserver.workspace}:${CONFIG.geoserver.layerGroup}`,
|
|
|
+ format: 'image/png',
|
|
|
+ transparent: true,
|
|
|
+ version: '1.1.0',
|
|
|
+ srs:'EPSG:4326',
|
|
|
+ attribution: '© GeoServer - Acidmap'
|
|
|
+ }).addTo(map)
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
+ async function loadLeSoilData() {
|
|
|
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 response = await fetch(
|
|
|
+ `${CONFIG.geoserver.url}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${CONFIG.geoserver.workspace}:${CONFIG.geoserver.dataLayer}&outputFormat=application/json`
|
|
|
+ );
|
|
|
+
|
|
|
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
+
|
|
|
+ const geoJsonData = await response.json();
|
|
|
+
|
|
|
+ console.log('✅ le_soil_data 数据:', geoJsonData.features);
|
|
|
+
|
|
|
+ if (geoJsonData.features && geoJsonData.features.length > 0) {
|
|
|
+ // 处理数据...
|
|
|
+ geoJsonData.features.forEach(feature => {
|
|
|
+ const ph = feature.properties.ph || feature.properties.value;
|
|
|
+ if (ph && ph > 0) {
|
|
|
+ // console.log('📌 采样点:', feature.properties.TXZQMC, 'pH:', ph);
|
|
|
+ }
|
|
|
});
|
|
|
+ } else {
|
|
|
+ console.warn('⚠️ 没有找到 le_soil_data 数据');
|
|
|
}
|
|
|
+ } catch (err) {
|
|
|
+ console.error('❌ 加载 le_soil_data 失败:', err);
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- // 清除现有地图
|
|
|
- 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();
|
|
|
+ async function loadStatistics() {
|
|
|
+ try {
|
|
|
+ const response = await fetch(
|
|
|
+ `${CONFIG.geoserver.url}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${CONFIG.geoserver.workspace}:${CONFIG.geoserver.dataLayer}&outputFormat=application/json`
|
|
|
+ );
|
|
|
+ const geoJsonData = await response.json();
|
|
|
+
|
|
|
+ if (geoJsonData.features && geoJsonData.features.length > 0) {
|
|
|
+ let phCount = 0
|
|
|
+ let avgPH = 0
|
|
|
+ let strongAcidCount = 0
|
|
|
+ let mildAcidCount = 0
|
|
|
+ let normalCount = 0
|
|
|
+ let maxPH = -Infinity
|
|
|
+ let minPH = Infinity
|
|
|
+
|
|
|
+
|
|
|
+ geoJsonData.features.forEach(feature => {
|
|
|
+ const ph = feature.properties.ph || feature.properties.value
|
|
|
+ if (ph && ph > 0) {
|
|
|
+ phCount++
|
|
|
+
|
|
|
+ // ✅ 使用在线算法计算平均值,避免大数累加
|
|
|
+ const delta = ph - avgPH;
|
|
|
+ avgPH = avgPH + delta / phCount;
|
|
|
+
|
|
|
+
|
|
|
+ maxPH = Math.max(maxPH, ph)
|
|
|
+ minPH = Math.min(minPH, ph)
|
|
|
+
|
|
|
+ if (ph <= 5.2) strongAcidCount++
|
|
|
+ else if (ph < 6.0) mildAcidCount++
|
|
|
+ else normalCount++
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ statistics.value = {
|
|
|
+ totalBlocks: geoJsonData.features.length,
|
|
|
+ avgPH:avgPH.toFixed(2),
|
|
|
+ strongAcidCount,
|
|
|
+ mildAcidCount,
|
|
|
+ normalCount,
|
|
|
+ maxPH: maxPH === -Infinity ? 0 : parseFloat(maxPH.toFixed(2)),
|
|
|
+ minPH: minPH === Infinity ? 0 : parseFloat(minPH.toFixed(2))
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log('✅ 统计数据加载完成:', statistics.value);
|
|
|
}
|
|
|
- }, 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;
|
|
|
+ } catch (err) {
|
|
|
+ console.error('加载统计数据失败:', err);
|
|
|
}
|
|
|
- ElMessage.error(errorMessage);
|
|
|
}
|
|
|
-};
|
|
|
|
|
|
-const reloadMap = () => {
|
|
|
- initMap();
|
|
|
-};
|
|
|
+ onMounted(async()=>{
|
|
|
+ await initMap()
|
|
|
+ await loadLeSoilData()
|
|
|
+ await loadStatistics()
|
|
|
+ })
|
|
|
|
|
|
-onMounted(() => {
|
|
|
- // 等待地图渲染完成后调整大小
|
|
|
- setTimeout(() => {
|
|
|
- initMap()
|
|
|
- }, 200);
|
|
|
-});
|
|
|
+ onUnmounted(()=>{
|
|
|
+ if(map) map.remove()
|
|
|
+ })
|
|
|
|
|
|
+</script>
|
|
|
|
|
|
+<template>
|
|
|
+ <div class="map-container">
|
|
|
+ <div class="ph-map" ref="mapRef"></div>
|
|
|
|
|
|
-</script>
|
|
|
+ <div class="statistics-panel">
|
|
|
+ <h4>📊 乐昌县土壤 pH 统计</h4>
|
|
|
+
|
|
|
+ <div class="stat-row">
|
|
|
+ <span class="stat-label">采样点总数:</span>
|
|
|
+ <span class="stat-value">{{ statistics.totalBlocks }} 个</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="stat-row highlight">
|
|
|
+ <span class="stat-label">平均 pH 值:</span>
|
|
|
+ <span class="stat-value" :class="getPHLevelClass(statistics.avgPH)">
|
|
|
+ {{ statistics.avgPH || '-' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="stat-row">
|
|
|
+ <span class="stat-label">强酸性 (pH≤5.2):</span>
|
|
|
+ <span class="stat-value danger">{{ statistics.strongAcidCount }} 个</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="stat-row">
|
|
|
+ <span class="stat-label">弱酸性 (pH 5.2~6.0): </span>
|
|
|
+ <span class="stat-value warning">{{ statistics.mildAcidCount }} 个</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="stat-row">
|
|
|
+ <span class="stat-label">正常 (pH≥6.0):</span>
|
|
|
+ <span class="stat-value success">{{ statistics.normalCount }} 个</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="stat-divider"></div>
|
|
|
+
|
|
|
+ <div class="stat-row small">
|
|
|
+ <span class="stat-label">最高 pH:</span>
|
|
|
+ <span class="stat-value success">{{ statistics.maxPH || '-' }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="stat-row small">
|
|
|
+ <span class="stat-label">最低 pH:</span>
|
|
|
+ <span class="stat-value danger">{{ statistics.minPH || '-' }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="stat-comment" v-if="statistics.avgPH > 0">
|
|
|
+ <div class="comment-title">📝 综合评估:</div>
|
|
|
+ <div class="comment-text" :class="getPHLevelClass(statistics.avgPH)">
|
|
|
+ {{ getPHComment(statistics.avgPH) }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ <div class="legend">
|
|
|
+ <h4>pH 值图例</h4>
|
|
|
+ <div class="legend-item">
|
|
|
+ <span class="legend-color" style="background: #ef4444;"></span>
|
|
|
+ <span>≤ 5.2 (强酸性)</span>
|
|
|
+ </div>
|
|
|
+ <div class="legend-item">
|
|
|
+ <span class="legend-color" style="background: #eab308;"></span>
|
|
|
+ <span>5.2 - 6.0 (弱酸性)</span>
|
|
|
+ </div>
|
|
|
+ <div class="legend-item">
|
|
|
+ <span class="legend-color" style="background: #22c55e;"></span>
|
|
|
+ <span>≥ 6.0 (中性/碱性)</span>
|
|
|
+ </div>
|
|
|
+ <div class="legend-item">
|
|
|
+ <span class="legend-color" style="background: #cccccc;"></span>
|
|
|
+ <span>无数据</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
|
|
|
-<style scoped>
|
|
|
-.total-introduction {
|
|
|
+<style scoped>
|
|
|
+.map-container{
|
|
|
width: 100%;
|
|
|
- height: 100%;
|
|
|
- background-color: #0a0a2a;
|
|
|
- color: white;
|
|
|
- font-family: 'Arial', sans-serif;
|
|
|
- position: relative;
|
|
|
+ height: 100vh;
|
|
|
+ position: absolute;
|
|
|
+ left:0;
|
|
|
+ top:0;
|
|
|
+ z-index: 1000;
|
|
|
}
|
|
|
|
|
|
-.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;
|
|
|
+.ph-map{
|
|
|
+ width: 60%;
|
|
|
+ height: 95%;
|
|
|
+ min-height: 500px;
|
|
|
}
|
|
|
|
|
|
-.header-title {
|
|
|
- text-align: center;
|
|
|
- padding: 20px 20px 0 20px;
|
|
|
+/* ✅ 统计面板样式 */
|
|
|
+.statistics-panel {
|
|
|
+ position: absolute;
|
|
|
+ top: 20px;
|
|
|
+ right: 10px;
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ padding: 20px;
|
|
|
+ border-radius: 12px;
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
|
+ z-index: 1000;
|
|
|
+ min-width: 280px;
|
|
|
+ backdrop-filter: blur(10px);
|
|
|
+ border: 2px solid rgba(16, 146, 216, 0.2);
|
|
|
}
|
|
|
|
|
|
-.header-title h1 {
|
|
|
- font-size: 32px;
|
|
|
- color: #00bfff;
|
|
|
- text-shadow: 0 0 10px rgba(0, 187, 255, 0.8);
|
|
|
- border-bottom: 2px solid #00bfff;
|
|
|
+.statistics-panel h4 {
|
|
|
+ margin: 0 0 15px 0;
|
|
|
+ font-size: 18px;
|
|
|
+ color: #1092d8;
|
|
|
+ border-bottom: 2px solid #1092d8;
|
|
|
padding-bottom: 10px;
|
|
|
+ font-weight: bold;
|
|
|
}
|
|
|
|
|
|
-.main-content {
|
|
|
+.stat-row {
|
|
|
display: flex;
|
|
|
- gap: 20px;
|
|
|
- padding: 20px;
|
|
|
- overflow-x: auto;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ font-size: 14px;
|
|
|
}
|
|
|
|
|
|
-.left-section,
|
|
|
+.stat-row.highlight {
|
|
|
+ background: linear-gradient(to right, rgba(16, 146, 216, 0.1), transparent);
|
|
|
+ padding: 8px 12px;
|
|
|
+ border-radius: 6px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+}
|
|
|
|
|
|
-.right-section {
|
|
|
- flex: 1;
|
|
|
- width: 200px;
|
|
|
+.stat-row.small {
|
|
|
+ font-size: 12px;
|
|
|
+ margin-bottom: 6px;
|
|
|
}
|
|
|
-.center-section {
|
|
|
- flex: 2;
|
|
|
- width: 500px;
|
|
|
+
|
|
|
+.stat-label {
|
|
|
+ color: #666;
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
|
|
|
|
-.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);
|
|
|
+.stat-value {
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #333;
|
|
|
}
|
|
|
|
|
|
-.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;
|
|
|
+.stat-value.danger {
|
|
|
+ color: #ef4444;
|
|
|
}
|
|
|
|
|
|
-.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;
|
|
|
+.stat-value.warning {
|
|
|
+ color: #f59e0b;
|
|
|
}
|
|
|
|
|
|
-@keyframes spin {
|
|
|
- to { transform: rotate(360deg); }
|
|
|
+.stat-value.success {
|
|
|
+ color: #22c55e;
|
|
|
}
|
|
|
|
|
|
-.map-error p {
|
|
|
- color: #ff6b6b;
|
|
|
- font-size: 16px;
|
|
|
- margin-bottom: 15px;
|
|
|
+.stat-divider {
|
|
|
+ height: 1px;
|
|
|
+ background: linear-gradient(to right, transparent, #ddd, transparent);
|
|
|
+ margin: 15px 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 综合评语 */
|
|
|
+.stat-comment {
|
|
|
+ margin-top: 15px;
|
|
|
+ padding: 12px;
|
|
|
+ background: rgba(243, 244, 246, 0.8);
|
|
|
+ border-radius: 8px;
|
|
|
+ border-left: 4px solid #1092d8;
|
|
|
+}
|
|
|
+
|
|
|
+.comment-title {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #666;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.comment-text {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.comment-text.danger {
|
|
|
+ color: #ef4444;
|
|
|
+}
|
|
|
+
|
|
|
+.comment-text.warning {
|
|
|
+ color: #f59e0b;
|
|
|
}
|
|
|
|
|
|
-.reload-btn {
|
|
|
- padding: 10px 20px;
|
|
|
- background-color: #00bfff;
|
|
|
- color: white;
|
|
|
- border: none;
|
|
|
- border-radius: 5px;
|
|
|
- cursor: pointer;
|
|
|
+.comment-text.success {
|
|
|
+ color: #22c55e;
|
|
|
+}
|
|
|
+
|
|
|
+/* 图例样式 */
|
|
|
+.legend {
|
|
|
+ position: absolute;
|
|
|
+ top: 30px;
|
|
|
+ left: 30px;
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ padding: 15px;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
+ z-index: 1000;
|
|
|
+ min-width: 180px;
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+ z-index: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.legend h4 {
|
|
|
+ margin: 0 0 10px 0;
|
|
|
font-size: 14px;
|
|
|
- transition: all 0.3s;
|
|
|
+ color: #333;
|
|
|
+ border-bottom: 2px solid #1092d8;
|
|
|
+ padding-bottom: 6px;
|
|
|
}
|
|
|
|
|
|
-.reload-btn:hover {
|
|
|
- background-color: #009acd;
|
|
|
- transform: translateY(-2px);
|
|
|
+.legend-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #555;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-item:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
}
|
|
|
|
|
|
+.legend-color {
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 自定义 Tooltip 样式 */
|
|
|
+:deep(.custom-tooltip) {
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
+ border: 2px solid #1092d8;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 8px 12px;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
|
+ font-family: 'Microsoft YaHei', sans-serif;
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
+}
|
|
|
</style>
|