Просмотр исходного кода

[Feat 0000] 地图标记点位对接,详情聚焦功能开发完毕

houzekong 2 месяцев назад
Родитель
Сommit
5034e8e192

+ 64 - 2
src/api/sys/map.ts

@@ -9,8 +9,70 @@ enum Api {
  * 列表接口
  * @param params
  */
-export function list(params: any): Promise<{ records: any[] }> {
-  return defHttp.post({ url: Api.list, params: omitBy(params, isNil) }, { joinParamsToUrl: true });
+export function getMarkers(params: any): Promise<any> {
+  return defHttp.post({ url: Api.list, params: omitBy(params, isNil) }, { joinParamsToUrl: true }).then(({ records }) => {
+    // const presets = [
+    //   ['36.644181586865905', '109.48980355362514', '1566106'],
+    //   ['36.644181586865905', '109.48980355362514', '1566106'],
+    //   // [33.063924198120645, 107.01782226562501, '1566101'],
+    //   // [34.34116826510752, 108.93468369998695, '1566103'],
+    //   ['34.361576287484176', '107.23571011212084', '1566107'],
+    //   ['34.361576287484176', '107.23571011212084', '1566107'],
+    // ];
+    // const presets2 = [
+    //   ['36.534181586865905', '109.39980355362514', '1566106'],
+    //   ['36.434181586865905', '109.29980355362514', '1566106'],
+    //   ['34.461576287484176', '107.33571011212084', '1566107'],
+    //   ['34.561576287484176', '107.43571011212084', '1566107'],
+    // ];
+    const presets = {
+      '810321182c6a40fa92d5acccdba34ce3': [
+        ['36.534181586865905', '109.39980355362514', '1566106'],
+        ['36.434181586865905', '109.29980355362514', '1566106'],
+        ['36.644181586865905', '109.48980355362514', '1566106'],
+      ],
+      '63afd272d0e24c7e9c1475c6498f0c80': [
+        ['34.461576287484176', '107.33571011212084', '1566103'],
+        ['34.561576287484176', '107.43571011212084', '1566103'],
+        ['34.361576287484176', '107.23571011212084', '1566103'],
+      ],
+    };
+
+    const results: any = {};
+
+    records.forEach((ele) => {
+      const temp = {
+        name: ele.mineName,
+        radius: 10,
+        ...ele,
+      };
+      if (results[ele.managementId]) {
+        results[ele.managementId].children.push(temp);
+      } else {
+        results[ele.managementId] = {
+          ...temp,
+          children: [temp],
+        };
+      }
+    });
+
+    return Object.values(results).map((ele: any) => {
+      return {
+        ...ele,
+        lat: presets[ele.managementId][2][0],
+        lng: presets[ele.managementId][2][1],
+        gb: presets[ele.managementId][2][2],
+        children: ele.children.map((ele2: any, index) => {
+          return {
+            ...ele2,
+            lat: presets[ele.managementId][index][0],
+            lng: presets[ele.managementId][index][1],
+            gb: presets[ele.managementId][index][2],
+          };
+        }),
+      };
+    });
+  });
 }
 
 export function getGoafAlarmLevel(params: any): Promise<any> {

+ 190 - 102
src/layouts/default/feature/SimpleMap.vue

@@ -1,5 +1,8 @@
 <template>
   <div ref="mapContainer" class="map-container"></div>
+  <div class="map-reset-btn">
+    <Button type="primary" @click="reset"> 重置 </Button>
+  </div>
 </template>
 
 <script lang="ts" setup>
@@ -7,17 +10,58 @@
   // 引入 Leaflet
   import L from 'leaflet';
   import 'leaflet/dist/leaflet.css';
-  import { list, getGoafAlarmLevel } from '/@/api/sys/map';
+  import { getGoafAlarmLevel, getMarkers } from '/@/api/sys/map';
   import { StatusColorEnum } from '/@/enums/jeecgEnum';
+  import { Button } from 'ant-design-vue';
   // Ant Design Vue 图标
-
   // --- 1. 组件引用和状态定义 ---
   const mapContainer = ref(null);
+
+  const DEFAULT_LAYER_ID = 'default';
+  // 邮政编码,GeoJSON中存有完整编码,下面是去掉了后两位00的编码
+  const layerMap = new Map<string, L.Layer>([
+    [DEFAULT_LAYER_ID, L.featureGroup()],
+    // 西安
+    ['1566101', L.featureGroup()],
+    // 铜川
+    ['1566102', L.featureGroup()],
+    // 宝鸡
+    ['1566103', L.featureGroup()],
+    // 咸阳
+    ['1566104', L.featureGroup()],
+    // 渭南
+    ['1566105', L.featureGroup()],
+    // 延安
+    ['1566106', L.featureGroup()],
+    // 汉中
+    ['1566107', L.featureGroup()],
+    // 榆林
+    ['1566108', L.featureGroup()],
+    // 安康
+    ['1566109', L.featureGroup()],
+    // 商洛
+    ['1566111', L.featureGroup()],
+  ]);
+  const colorMap = new Map<string, string>([
+    ['1566101', '#14baff'],
+    ['1566102', '#14baff'],
+    ['1566103', '#ffff12'],
+    ['1566104', '#ffff12'],
+    ['1566105', '#ffab12'],
+    ['1566106', '#ffab12'],
+    ['1566107', '#fe1414'],
+    ['1566108', '#fe1414'],
+    ['1566109', '#fe1414'],
+    ['1566111', '#fe1414'],
+    ['810321182c6a40fa92d5acccdba34ce3', '#ffff12'],
+    ['63afd272d0e24c7e9c1475c6498f0c80', '#ffab12'],
+  ]);
+
   let map: typeof L = null; // Leaflet 地图实例
 
-  // --- 2. 瓦片图层配置 ---
-  const tileLayers = {
-    custom: {
+  // --- 2. 配置项声明 ---
+  const tileLayers: any[] = [
+    {
       name: '基准瓦片',
       layer: null,
       url: 'https://shaanxizhxx.chinamine-safety.gov.cn/zh1/8/33/17.png',
@@ -30,7 +74,7 @@
         // crossOrigin: true,
       },
     },
-    satellite: {
+    {
       name: '卫星图',
       layer: null,
       url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
@@ -40,7 +84,7 @@
         attribution: '© Esri',
       },
     },
-    gaode: {
+    {
       name: '高德地图',
       layer: null,
       url: 'https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
@@ -52,24 +96,13 @@
         attribution: '© 高德地图', // 版权声明(必填,遵守使用规范)
       },
     },
-  };
-
-  // 添加一个风险点示例(类似第一张图的红色气泡)
+  ];
 
   // --- 3. 地图初始化 ---
   onMounted(() => {
-    initMap();
-  });
-
-  onUnmounted(() => {
-    map && map.remove();
-  });
-
-  // 初始化地图
-  function initMap() {
     if (!mapContainer.value) return;
 
-    // 创建地图实例,设置太原市中心和初始缩放级别
+    // 创建地图实例,设置中心和初始缩放级别
     map = L.map(mapContainer.value, {
       center: [35.841, 108.94], // 西安市中心坐标 [纬度, 经度]
       zoom: 7, // 初始缩放级别,适合城市级别查看
@@ -83,12 +116,17 @@
     initMarkers();
     // 初始化陕西省GeoJSON图层
     initGeoJSON();
-  }
+
+    layerMap.get(DEFAULT_LAYER_ID).addTo(map);
+  });
+
+  onUnmounted(() => {
+    map && map.remove();
+  });
 
   // 初始化瓦片图层
   function initTileLayers() {
-    Object.keys(tileLayers).forEach((key) => {
-      const config = tileLayers[key];
+    tileLayers.forEach((config) => {
       config.layer = L.tileLayer(config.url, config.options);
 
       if (config.addByDefault) {
@@ -101,67 +139,46 @@
     const response = await fetch('/js/shanxi.geo.json');
     const ShanXiGeoJSON = await response.json();
 
-    const getFeatureStyle = (feature) => {
-      const getFeatureColor = (feature) => {
-        const gb = feature.properties.gb;
-
-        const map = {
-          1566101: '#14baff',
-          1566102: '#14baff',
-          1566103: '#ffff12',
-          1566104: '#ffff12',
-          1566105: '#ffab12',
-          1566106: '#ffab12',
-          1566107: '#fe1414',
-        };
-
-        for (const key in map) {
-          if (gb && gb.startsWith(key)) {
-            return map[key];
-          }
-        }
+    layerMap.forEach((group, code) => {
+      const features = ShanXiGeoJSON.features.filter((feature) => {
+        if (code === DEFAULT_LAYER_ID) return true;
+        return feature.properties.gb.startsWith(code);
+      });
 
-        return '#14baff';
-      };
-
-      return {
-        fillColor: getFeatureColor(feature),
-        fillOpacity: 0.2,
-        weight: 2,
-        opacity: 1,
-        color: '#ff9100',
-      };
-    };
-
-    const getFeatureEventHandlers = (feature, layer) => {
-      const highlightFeature = (e) => {
-        // console.log('debug sss', e);
-        // if (feature.properties && feature.properties.name === '陕西省') return;
-        const layer = e.target;
-
-        layer.setStyle({
-          weight: 2,
-          fillOpacity: 0.6,
-        });
-
-        layer.bringToFront();
-      };
-
-      const resetHighlight = (e) => {
-        geoJSON.resetStyle(e.target);
-      };
-
-      layer.on({
-        mouseover: highlightFeature,
-        mouseout: resetHighlight,
-        // click: zoomToFeature,
+      const GeoJSON = L.geoJSON(features, {
+        style(feature) {
+          const gb = feature.properties.gb.slice(0, -2);
+          return {
+            fillColor: colorMap.get(gb) || '#14baff',
+            fillOpacity: 0.2,
+            weight: 2,
+            opacity: 1,
+            color: '#ff9100',
+          };
+        },
+        onEachFeature(__, layer) {
+          layer.on({
+            mouseover: (e) => {
+              const layer = e.target;
+
+              layer.setStyle({
+                weight: 2,
+                fillOpacity: 0.6,
+              });
+
+              layer.bringToFront();
+            },
+            mouseout: (e) => {
+              GeoJSON.resetStyle(e.target);
+            },
+            // click: zoomToFeature,
+          });
+        },
       });
-    };
 
-    const geoJSON = L.geoJSON(ShanXiGeoJSON, {
-      style: getFeatureStyle,
-      onEachFeature: getFeatureEventHandlers,
-    }).addTo(map);
+      // layerMap.set(code, GeoJSON);
+      GeoJSON.addTo(group);
+    });
   }
 
   /** 生成一个标准的标记点,通过传入的数据,请求详情信息并生成监测详情框 */
@@ -219,29 +236,91 @@
     );
   }
 
+  function generateRootMarker(marker: any, options: any) {
+    const size = 40;
+    const icon = L.divIcon({
+      className: 'numbered-marker',
+      html: `
+        <svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" style="overflow: visible;">
+          <!-- 圆形背景 -->
+          <circle
+            cx="${size / 2}"
+            cy="${size / 2}"
+            r="${size / 2 - 2}"
+            fill="${colorMap.get(marker.gb)}"
+          />
+          <!-- 数字文本 -->
+          <text
+            x="${size / 2}"
+            y="${size / 2}"
+            text-anchor="middle"
+            dominant-baseline="middle"
+            fill="gray"
+            font-size="16"
+            font-weight="bold"
+            font-family="Arial, sans-serif"
+          >${marker.children.length}</text>
+        </svg>
+      `,
+      iconSize: [size, size],
+      iconAnchor: [size / 2, size / 2],
+      popupAnchor: [0, -size / 2],
+    });
+
+    return L.marker([marker.lat, marker.lng], {
+      icon,
+      color: colorMap.get(marker.gb),
+      fillColor: colorMap.get(marker.gb),
+      ...options,
+    }).on('click', () => {
+      layerMap.forEach((group, code) => {
+        map.removeLayer(group);
+
+        if (code === marker.gb) {
+          map.addLayer(group);
+          console.log('debug bounds', group, marker);
+          map.flyToBounds(group.getBounds());
+        }
+      });
+    });
+  }
+
   /** 请求矿区数据并初始化标记点 */
   async function initMarkers() {
-    const { records } = await list({ pageNo: 0, paseSize: 9999 });
-    records.forEach((item, index) => {
-      const presets = [
-        [36.644181586865905, 109.48980355362514],
-        [33.063924198120645, 107.01782226562501],
-        [34.34116826510752, 108.93468369998695],
-        [34.361576287484176, 107.23571011212084],
-      ];
-
-      generateStandardMarker(
-        {
-          name: item.mineName,
-          lat: presets[index][0],
-          lng: presets[index][1],
-          // color: 'red',
-          radius: 10,
-          type: 'marker',
-          ...item,
-        },
-        {}
-      ).then((r) => r.addTo(map));
+    const records = await getMarkers({ pageNo: 0, paseSize: 9999 });
+    const details: Promise<any>[] = [];
+    records.forEach((item) => {
+      generateRootMarker(item, {
+        riseOnHover: true,
+      }).addTo(layerMap.get(DEFAULT_LAYER_ID));
+
+      if (!item.children.length) return;
+
+      item.children.forEach((child) => {
+        details.push(
+          generateStandardMarker(child, {
+            riseOnHover: false,
+          }).then((marker) => {
+            if (layerMap.has(child.gb)) {
+              marker.addTo(layerMap.get(child.gb));
+            }
+          })
+        );
+      });
+    });
+
+    await Promise.all(details);
+  }
+
+  function reset() {
+    layerMap.forEach((layer, code) => {
+      map.removeLayer(layer);
+
+      if (code === DEFAULT_LAYER_ID) {
+        map.addLayer(layer);
+
+        map.flyToBounds(layer.getBounds());
+      }
     });
   }
 </script>
@@ -319,4 +398,13 @@
       }
     }
   }
+
+  .map-reset-btn {
+    position: absolute;
+    right: 26%; /* 距离右边 20px */
+    z-index: 1000; /* 确保在地图控件之上 */
+    top: @header-height;
+    // padding: 10px 15px;
+    margin: 10px 0;
+  }
 </style>

+ 7 - 2
src/layouts/default/feature/SystemSelect.vue

@@ -48,8 +48,13 @@
     width: 100%;
     min-width: 10px;
     margin-bottom: 10px;
-    position: relative;
-    z-index: @layout-basic-z-index;
+    // position: relative;
+    // z-index: @layout-basic-z-index;
+    z-index: @layout-header-fixed-z-index;
+    position: fixed;
+    top: @header-height;
+    left: 0;
+    // z-index: '100';
   }
 
   .@{prefix-cls}.ant-select {

+ 1 - 1
src/layouts/default/plain.vue

@@ -3,7 +3,7 @@
   <Layout :class="prefixCls" v-bind="lockEvents">
     <LayoutFeatures />
     <LayoutHeader />
-    <SystemSelect :style="{ position: 'fixed', top: '60px', left: '0', zIndex: '100' }" />
+    <SystemSelect />
     <SimpleMap />
     <LayoutContent />
   </Layout>