lipenghui vor 2 Monaten
Ursprung
Commit
54c1bf87b6
1 geänderte Dateien mit 317 neuen und 115 gelöschten Zeilen
  1. 317 115
      src/views/pms/map/Map.vue

+ 317 - 115
src/views/pms/map/Map.vue

@@ -1,140 +1,342 @@
+<template>
+  <div class="map-container">
+    <div id="baidu-map" ref="mapContainer"></div>
+    <div class="map-controls">
+      <button @click="zoomIn">放大</button>
+      <button @click="zoomOut">缩小</button>
+      <button @click="toggleMapType">切换地图类型({{ mapType === 'BMAP_NORMAL_MAP' ? '地图' : '卫星' }})</button>
+    </div>
+    <div v-if="selectedDevice" class="device-info-popup">
+      <div class="popup-header">
+        <h3>设备详情</h3>
+        <button @click="closeDeviceInfo">×</button>
+      </div>
+      <div class="popup-content">
+        <p><strong>设备ID:</strong> {{ selectedDevice.id }}</p>
+        <p><strong>设备名称:</strong> {{ selectedDevice.name }}</p>
+        <p><strong>位置:</strong> {{ selectedDevice.location }}</p>
+        <p><strong>状态:</strong> {{ deviceStatusMap[selectedDevice.status] }}</p>
+      </div>
+    </div>
+  </div>
+</template>
 
-<script setup lang="ts">
-import { onMounted } from 'vue'
+<script lang="ts">
+import { defineComponent, onMounted, onBeforeUnmount, ref } from 'vue';
 
-interface DeviceData {
-  count: number
-  children?: Record<string, number>
+interface Device {
+  id: string;
+  name: string;
+  location: string;
+  status: number;
+  lng: number;
+  lat: number;
 }
 
-interface Window {
-  BMapGL: any
-  onBMapCallback: () => void
+interface Cluster {
+  lng: number;
+  lat: number;
+  count: number;
+  devices?: Device[];
 }
 
-const deviceData: Record<string, DeviceData> = {
-  '新疆': {
-    count: 30,
-    children: {
-      '克拉玛依': 10,
-      '乌鲁木齐': 10,
-      '库尔勒': 10
-    }
-  },
-  '北京': { count: 50 },
-  '上海': { count: 45 },
-  '广东': { count: 60 }
-}
+export default defineComponent({
+  name: 'ChinaDeviceMap',
+  setup() {
+    const mapContainer = ref<HTMLElement | null>(null);
+    const map = ref<any>(null);
+    const mapType = ref<'BMAP_NORMAL_MAP' | 'BMAP_SATELLITE_MAP'>('BMAP_NORMAL_MAP');
+    const selectedDevice = ref<Device | null>(null);
+    const hoverDevice = ref<Device | null>(null);
+    const clusters = ref<Cluster[]>([]);
 
-const loadBMap = (ak: string): Promise<any> => {
-  return new Promise((resolve) => {
-    if (window.BMapGL) return resolve(window.BMapGL)
+    // 设备数据示例
+    const devices = ref<Device[]>([
+      { id: 'D001', name: '设备1', location: '北京', status: 1, lng: 116.404, lat: 39.915 },
+      { id: 'D002', name: '设备2', location: '上海', status: 2, lng: 121.474, lat: 31.230 },
+      { id: 'D003', name: '设备3', location: '广州', status: 1, lng: 113.264, lat: 23.129 },
+      { id: 'D004', name: '设备4', location: '深圳', status: 3, lng: 114.058, lat: 22.543 },
+      { id: 'D005', name: '设备5', location: '成都', status: 1, lng: 104.065, lat: 30.659 },
+      { id: 'D006', name: '设备6', location: '武汉', status: 2, lng: 114.305, lat: 30.593 },
+      { id: 'D007', name: '设备7', location: '西安', status: 1, lng: 108.948, lat: 34.263 },
+      { id: 'D008', name: '设备8', location: '杭州', status: 3, lng: 120.155, lat: 30.274 },
+      { id: 'D009', name: '设备9', location: '南京', status: 1, lng: 118.797, lat: 32.060 },
+      { id: 'D010', name: '设备10', location: '重庆', status: 2, lng: 106.505, lat: 29.533 },
+    ]);
 
-    const script = document.createElement('script')
-    script.src = `https://api.map.baidu.com/api?type=webgl&v=3.0&ak=c0crhdxQ5H7WcqbcazGr7mnHrLa4GmO0&callback=onBMapCallback`
-    document.head.appendChild(script)
+    const deviceStatusMap = {
+      1: '在线',
+      2: '离线',
+      3: '异常'
+    };
 
-    window.onBMapCallback = () => resolve(window.BMapGL)
-  })
-}
+    // 初始化地图
+    const initMap = () => {
+      if (!mapContainer.value) return;
 
-const setupMap = async () => {
-  try {
-    const BMapGL = await loadBMap('您的AK密钥')
-    const map = new BMapGL.Map('map-container')
-
-    // 地球模式配置
-    map.centerAndZoom(new BMapGL.Point(105, 35), 5)
-    map.enableScrollWheelZoom()
-    // map.setMapType(BMapGL.constants.MapType.EARTH)
-
-    // 中国边界绘制
-    new BMapGL.Boundary().get('中国', (rs: any) => {
-      rs.boundaries.forEach((boundary: string) => {
-        map.addOverlay(new BMapGL.Polygon(boundary, {
-          strokeWeight: 2,
-          strokeColor: "#ff0000"
-        }))
-      })
-    })
-
-    // 初始省级标注
-    addProvinceLabels(map, BMapGL)
-
-    // 缩放事件监听
-    map.addEventListener('zoomend', () => {
-      map.getZoom() >= 7
-        ? showCityLabels(map, BMapGL)
-        : addProvinceLabels(map, BMapGL)
-    })
-  } catch (error) {
-    console.error('地图加载失败:', error)
-  }
-}
+      const script = document.createElement('script');
+      script.src = `https://api.map.baidu.com/api?v=3.0&ak=c0crhdxQ5H7WcqbcazGr7mnHrLa4GmO0&type=webgl`;
+      script.async = true;
+      script.onload = () => {
+        if ((window as any).BMap) {
+          map.value = new (window as any).BMap.Map(mapContainer.value);
+          const point = new (window as any).BMap.Point(104.114129, 37.550339);
+          map.value.centerAndZoom(point, 5);
+
+          map.value.enableScrollWheelZoom(true);
+          map.value.setMapType((window as any)[mapType.value]);
+
+          map.value.addControl(new (window as any).BMap.NavigationControl());
+          map.value.addControl(new (window as any).BMap.ScaleControl());
+
+          initDeviceMarkers();
+          map.value.addEventListener('zoomend', initDeviceMarkers);
+        } else {
+          console.error('百度地图API加载失败');
+        }
+      };
+      script.onerror = () => {
+        console.error('百度地图API加载失败');
+      };
+      document.head.appendChild(script);
+    };
+
+    const initDeviceMarkers = () => {
+      if (!map.value) return;
+
+      map.value.clearOverlays();
+
+      const zoomLevel = map.value.getZoom();
+
+      if (zoomLevel > 10) {
+        // 高缩放级别下显示单个设备标记
+        devices.value.forEach(device => {
+          const point = new (window as any).BMap.Point(device.lng, device.lat);
+          const marker = createDeviceMarker(device, point);
+          map.value.addOverlay(marker);
+        });
+      } else {
+        // 低缩放级别下进行聚合
+        clusters.value = clusterDevices(devices.value, map.value);
+        clusters.value.forEach(cluster => {
+          if (cluster.count === 1) {
+            // 只有一个设备时显示设备图标
+            const device = cluster.devices?.[0];
+            if (device) {
+              const point = new (window as any).BMap.Point(device.lng, device.lat);
+              const marker = createDeviceMarker(device, point);
+              map.value.addOverlay(marker);
+            }
+          } else if (cluster.count > 1) {
+            // 多个设备时显示聚合标签
+            const point = new (window as any).BMap.Point(cluster.lng, cluster.lat);
+            const label = createClusterLabel(cluster, point);
+            map.value.addOverlay(label);
+          }
+        });
+      }
+    };
+
+    const createDeviceMarker = (device: Device, point: any) => {
+      debugger
+      const marker = new (window as any).BMap.Marker(point, {
+        icon: new (window as any).BMap.Icon('/images/dingweida.png', new (window as any).BMap.Size(103, 105),
+          {
+            anchor: new (window as any).BMap.Size(10, 25),
+            imageOffset: new (window as any).BMap.Size(0, 0 - device.status * 25)
+          }
+        )
+      });
 
-const addProvinceLabels = (map: any, BMapGL: any) => {
-  map.clearOverlays()
-  Object.entries(deviceData).forEach(([province, data]) => {
-    const point = getCenterPoint(province)
-    if (point) {
-      const label = new BMapGL.Label(`${province}: ${data.count}台`, {
+      // 添加点击事件
+      marker.addEventListener('click', () => {
+        showDeviceInfo(device);
+      });
+
+      // 添加鼠标悬停事件
+      marker.addEventListener('mouseover', () => {
+        hoverDevice.value = device;
+      });
+
+      marker.addEventListener('mouseout', () => {
+        hoverDevice.value = null;
+      });
+
+      return marker;
+    };
+
+    const createClusterLabel = (cluster: Cluster, point: any) => {
+      const label = new (window as any).BMap.Label(cluster.count.toString(), {
         position: point,
-        offset: new BMapGL.Size(20, -10)
-      })
-      label.setStyle(labelStyle)
-      map.addOverlay(label)
-    }
-  })
-}
+        offset: new (window as any).BMap.Size(-10, -10)
+      });
+
+      label.setStyle({
+        color: '#fff',
+        backgroundColor: '#1890ff',
+        borderRadius: '50%',
+        width: '20px',
+        height: '20px',
+        textAlign: 'center',
+        lineHeight: '20px',
+        cursor: 'pointer',
+        fontSize: '12px',
+        fontWeight: 'bold'
+      });
+
+      // 添加点击事件
+      label.addEventListener('click', () => {
+        map.value?.setZoom(11);
+        map.value?.panTo(point);
+      });
+
+      return label;
+    };
+
+    const clusterDevices = (devices: Device[], map: any): Cluster[] => {
+      const clusters: Cluster[] = [];
+      const gridSize = getGridSize(map.getZoom());
+
+      const gridMap = new Map<string, Cluster>();
+
+      devices.forEach(device => {
+        const gridKey = `${Math.floor(device.lng / gridSize)}_${Math.floor(device.lat / gridSize)}`;
+
+        if (!gridMap.has(gridKey)) {
+          gridMap.set(gridKey, {
+            lng: Math.floor(device.lng / gridSize) * gridSize + gridSize / 2,
+            lat: Math.floor(device.lat / gridSize) * gridSize + gridSize / 2,
+            count: 0,
+            devices: []
+          });
+        }
+
+        const cluster = gridMap.get(gridKey)!;
+        cluster.count++;
+        if (!cluster.devices) cluster.devices = [];
+        cluster.devices.push(device);
+      });
+
+      return Array.from(gridMap.values());
+    };
+
+    const getGridSize = (zoom: number): number => {
+      if (zoom <= 5) return 2;
+      if (zoom <= 8) return 1;
+      if (zoom <= 10) return 0.5;
+      return 0.1;
+    };
 
-const showCityLabels = (map: any, BMapGL: any) => {
-  const xinjiang = deviceData['新疆']
-  if (xinjiang?.children) {
-    Object.entries(xinjiang.children).forEach(([city, count]) => {
-      const point = getCenterPoint(city)
-      if (point) {
-        const label = new BMapGL.Label(`${city}: ${count}台`, {
-          position: point,
-          offset: new BMapGL.Size(20, -10)
-        })
-        label.setStyle({...labelStyle, fontSize: '12px'})
-        map.addOverlay(label)
+    const showDeviceInfo = (device: Device) => {
+      selectedDevice.value = device;
+    };
+
+    const closeDeviceInfo = () => {
+      selectedDevice.value = null;
+    };
+
+    const zoomIn = () => {
+      if (map.value) {
+        map.value.zoomIn();
+      }
+    };
+
+    const zoomOut = () => {
+      if (map.value) {
+        map.value.zoomOut();
       }
-    })
+    };
+
+    const toggleMapType = () => {
+      if (!map.value) return;
+
+      mapType.value = mapType.value === 'BMAP_NORMAL_MAP'
+        ? 'BMAP_SATELLITE_MAP'
+        : 'BMAP_NORMAL_MAP';
+      map.value.setMapType((window as any)[mapType.value]);
+    };
+
+    onMounted(() => {
+      initMap();
+    });
+
+    onBeforeUnmount(() => {
+      if (map.value) {
+        map.value.destroy();
+      }
+    });
+
+    return {
+      mapContainer,
+      selectedDevice,
+      hoverDevice,
+      deviceStatusMap,
+      zoomIn,
+      zoomOut,
+      toggleMapType,
+      showDeviceInfo,
+      closeDeviceInfo
+    };
   }
+});
+</script>
+
+<style scoped>
+.map-container {
+  position: relative;
+  width: 100%;
+  height: 100vh;
 }
 
-const labelStyle = {
-  color: '#333',
-  fontSize: '14px',
-  border: '1px solid #ccc',
-  backgroundColor: 'white',
-  padding: '5px'
+#baidu-map {
+  width: 100%;
+  height: 100%;
 }
 
-const getCenterPoint = (name: string): any => {
-  const points: Record<string, [number, number]> = {
-    '新疆': [87.6168, 43.8256],
-    '北京': [116.4074, 39.9042],
-    '上海': [121.4737, 31.2304],
-    '广东': [113.2644, 23.1291],
-    '克拉玛依': [84.8739, 45.5886],
-    '乌鲁木齐': [87.6168, 43.8256],
-    '库尔勒': [86.1467, 41.7686]
-  }
-  return points[name] && new BMapGL.Point(...points[name])
+.map-controls {
+  position: absolute;
+  top: 20px;
+  left: 20px;
+  z-index: 1000;
+  display: flex;
+  gap: 10px;
 }
 
-onMounted(setupMap)
-</script>
+.map-controls button {
+  padding: 5px 10px;
+  background: #fff;
+  border: 1px solid #ccc;
+  border-radius: 3px;
+  cursor: pointer;
+}
 
-<template>
-  <div id="map-container"></div>
-</template>
+.device-info-popup {
+  position: absolute;
+  top: 20px;
+  right: 20px;
+  z-index: 1000;
+  width: 300px;
+  background: #fff;
+  border-radius: 5px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+  padding: 15px;
+}
 
-<style scoped>
-#map-container {
-  width: 100%;
-  height: 100vh;
+.popup-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.popup-header button {
+  background: none;
+  border: none;
+  font-size: 18px;
+  cursor: pointer;
+}
+
+.popup-content p {
+  margin: 5px 0;
 }
 </style>