Răsfoiți Sursa

智能柜项目提交

skyline 2 luni în urmă
părinte
comite
056626f8e5

+ 73 - 28
haha-admin-web/src/components/AmapWithSearch/src/index.vue

@@ -11,6 +11,7 @@ interface Props {
   enableClickMark?: boolean;
   securityJsCode?: string;
   showSearch?: boolean; // 是否显示地址搜索框
+  initialMarker?: boolean; // 是否在初始化时添加标记点
 }
 
 const props = withDefaults(defineProps<Props>(), {
@@ -18,7 +19,8 @@ const props = withDefaults(defineProps<Props>(), {
   height: "400px",
   enableClickMark: false,
   securityJsCode: "",
-  showSearch: false
+  showSearch: false,
+  initialMarker: true // 默认添加初始标记点
 });
 
 const emit = defineEmits<{
@@ -84,49 +86,84 @@ const addMarker = (lng: number, lat: number) => {
   map.setCenter([lng, lat]);
 };
 
-// 添加自定义标记(支持点击事件和自定义颜色)
-const addCustomMarker = (lng: number, lat: number, title: string, onClick?: () => void, iconColor: string = 'red') => {
+// 添加自定义标记(支持悬停事件和自定义颜色)
+const addCustomMarker = (
+  lng: number, 
+  lat: number, 
+  title: string, 
+  events?: { 
+    onClick?: () => void;
+    onMouseEnter?: () => void;
+    onMouseLeave?: () => void;
+  }, 
+  iconColor: string = 'red'
+) => {
   if (!map || !AMap) {
     console.error('[addCustomMarker] map or AMap not initialized');
     return null;
   }
 
+  // 使用 HTML 内容创建红色标记点(更可靠)
+  let markerContent = '';
+  if (iconColor === 'red') {
+    markerContent = `<div style="
+      position: relative;
+      width: 25px;
+      height: 34px;
+    ">
+      <svg width="25" height="34" viewBox="0 0 25 34" xmlns="http://www.w3.org/2000/svg">
+        <path d="M12.5 0C5.6 0 0 5.6 0 12.5c0 9.4 12.5 21.5 12.5 21.5S25 21.9 25 12.5C25 5.6 19.4 0 12.5 0z" fill="#e74c3c"/>
+        <circle cx="12.5" cy="12" r="5" fill="white"/>
+      </svg>
+    </div>`;
+  }
+
   // 创建标记点
   const customMarker = new AMap.Marker({
     position: [lng, lat],
     title: title,
-    cursor: "pointer", // 设置鼠标样式为 pointer
-    animation: "AMAP_ANIMATION_DROP"
+    cursor: "pointer",
+    animation: "AMAP_ANIMATION_DROP",
+    content: markerContent || undefined
   });
 
-  // 设置红色图标(使用高德地图官方图标)
-  if (iconColor === 'red') {
-    try {
-      const icon = new AMap.Icon({
-        image: 'https://webapi.amap.com/theme/v1.3/markers/n_red_b.png',
-        size: new AMap.Size(25, 34),
-        imageSize: new AMap.Size(25, 34),
-        anchor: 'bottom-center'
-      });
-      customMarker.setIcon(icon);
-      console.log('[addCustomMarker] 红色图标设置成功');
-    } catch (error) {
-      console.error('[addCustomMarker] 设置红色图标失败:', error);
-    }
-  }
+  console.log('[addCustomMarker] 标记点创建成功:', { lng, lat, title, iconColor });
 
-  // 添加点击事件
-  if (onClick) {
-    customMarker.on("click", onClick);
+  // 添加事件监听
+  if (events) {
+    if (events.onClick) {
+      customMarker.on("click", events.onClick);
+    }
+    if (events.onMouseEnter) {
+      customMarker.on("mouseover", events.onMouseEnter);
+    }
+    if (events.onMouseLeave) {
+      customMarker.on("mouseout", events.onMouseLeave);
+    }
   }
 
   // 添加到地图
   map.add(customMarker);
   
-  console.log('[addCustomMarker] 标记点添加成功:', { lng, lat, title });
   return customMarker;
 };
 
+// 获取经纬度对应的像素坐标
+const lngLatToPixel = (lng: number, lat: number): { x: number; y: number } | null => {
+  if (!map) {
+    console.error('[lngLatToPixel] 地图未初始化');
+    return null;
+  }
+  
+  try {
+    const pixel = map.lngLatToContainer([lng, lat]);
+    return { x: pixel.x, y: pixel.y };
+  } catch (error) {
+    console.error('[lngLatToPixel] 转换失败:', error);
+    return null;
+  }
+};
+
 const reloadMap = () => {
   if (map) {
     map.destroy();
@@ -355,9 +392,12 @@ const initMap = async () => {
       });
     });
 
-    if (hasValidCoords()) {
-      console.log('[地图初始化] 添加标记点');
+    if (hasValidCoords() && props.initialMarker) {
+      console.log('[地图初始化] 添加初始标记点');
       addMarker(props.longitude!, props.latitude!);
+    } else if (hasValidCoords()) {
+      console.log('[地图初始化] 只设置中心点,不添加标记点');
+      map.setCenter([props.longitude!, props.latitude!]);
     } else if (props.showSearch) {
       // 如果是新数据且显示搜索框,获取当前位置
       console.log('[地图初始化] 无坐标数据,获取当前位置');
@@ -463,9 +503,13 @@ const loadAMap = () => {
 watch(
   () => [props.longitude, props.latitude],
   ([newLng, newLat]) => {
-    if (newLng && newLat && map) {
+    // 只有在 initialMarker 为 true 时才自动添加标记点
+    if (newLng && newLat && map && props.initialMarker) {
       addMarker(newLng as number, newLat as number);
       map.setCenter([newLng, newLat]);
+    } else if (newLng && newLat && map) {
+      // 只更新中心点,不添加标记
+      map.setCenter([newLng, newLat]);
     }
   }
 );
@@ -486,7 +530,8 @@ defineExpose({
   reloadMap,
   addMarker,
   addCustomMarker, // 暴露添加自定义标记的方法
-  searchAddress // 暴露搜索方法
+  searchAddress, // 暴露搜索方法
+  lngLatToPixel // 暴露坐标转换方法
 });
 </script>
 

+ 72 - 34
haha-admin-web/src/views/distribution/index.vue

@@ -36,13 +36,13 @@
         :zoom="12"
         :enable-click-mark="false"
         :show-search="false"
+        :initial-marker="false"
       />
       
       <!-- 门店信息浮层 -->
       <div v-if="selectedShop" class="shop-info-panel" :style="infoPanelStyle">
         <div class="panel-header">
           <h3>{{ selectedShop.name }}</h3>
-          <el-button type="info" size="small" circle @click="closeShopInfo">✕</el-button>
         </div>
         <div class="panel-content">
           <p><strong>📍 地址:</strong>{{ selectedShop.province }}{{ selectedShop.city }}{{ selectedShop.district }}{{ selectedShop.address }}</p>
@@ -90,11 +90,16 @@ const loadShopList = async () => {
       shopList.value = res.data?.list || [];
       console.log('加载门店列表成功,数量:', shopList.value.length);
       
-      // 不立即添加标记,等待用户手动触发或地图组件准备好后再添加
+      // 设置地图中心点
       if (shopList.value.length > 0 && shopList.value[0].longitude && shopList.value[0].latitude) {
         centerLongitude.value = Number(shopList.value[0].longitude);
         centerLatitude.value = Number(shopList.value[0].latitude);
       }
+      
+      // 门店列表加载成功后,延迟添加标记点(等待地图初始化)
+      setTimeout(() => {
+        addShopMarkers();
+      }, 1500);
     } else {
       console.warn('门店列表 API 返回异常 code:', res.code, 'message:', res.message);
     }
@@ -134,13 +139,30 @@ const addShopMarkers = async () => {
       
       console.log(`添加门店标记 ${index + 1}:`, shop.name, lng, lat);
       
-      // 使用组件暴露的 addCustomMarker 方法添加带点击事件的红色标记
+      // 使用组件暴露的 addCustomMarker 方法添加带悬停事件的红色标记
       if (typeof mapInstance.addCustomMarker === 'function') {
-        const result = mapInstance.addCustomMarker(lng, lat, shop.name, () => {
-          console.log('点击门店标记:', shop.name);
-          // 设置选中的门店并计算面板位置
-          selectedShop.value = shop;
-          markerPosition.value = { lng, lat };
+        const result = mapInstance.addCustomMarker(lng, lat, shop.name, {
+          // 鼠标移入显示信息面板
+          onMouseEnter: () => {
+            console.log('鼠标移入门店标记:', shop.name);
+            selectedShop.value = shop;
+            markerPosition.value = { lng, lat };
+            
+            // 获取像素坐标并更新面板位置
+            if (typeof mapInstance.lngLatToPixel === 'function') {
+              const pixel = mapInstance.lngLatToPixel(lng, lat);
+              if (pixel) {
+                panelPixelPosition.value = pixel;
+              }
+            }
+          },
+          // 鼠标移出隐藏信息面板
+          onMouseLeave: () => {
+            console.log('鼠标移出门店标记:', shop.name);
+            selectedShop.value = null;
+            markerPosition.value = null;
+            panelPixelPosition.value = null;
+          }
         }, 'red'); // 使用红色标记
         
         if (result) {
@@ -159,12 +181,6 @@ const addShopMarkers = async () => {
   console.log('[addShopMarkers] 门店标记添加完成');
 };
 
-// 关闭门店信息面板
-const closeShopInfo = () => {
-  selectedShop.value = null;
-  markerPosition.value = null;
-};
-
 // 处理地址搜索
 const handleAddressSearch = async () => {
   if (!searchAddress.value.trim()) {
@@ -176,14 +192,23 @@ const handleAddressSearch = async () => {
   }
 };
 
-// 计算信息面板样式(显示在标记点右上方)
+// 面板像素位置
+const panelPixelPosition = ref<{ x: number; y: number } | null>(null);
+
+// 计算信息面板样式(显示在标记点旁边)
 const infoPanelStyle = computed(() => {
-  // 简单方案:始终显示在地图容器的右上角
-  // 这样更稳定,不受地图缩放和移动影响
+  if (!panelPixelPosition.value) {
+    return {
+      display: 'none'
+    };
+  }
+  
+  const { x, y } = panelPixelPosition.value;
+  
   return {
     position: 'absolute' as const,
-    top: '20px',
-    right: '20px',
+    left: `${x + 30}px`, // 标记点右侧 30px
+    top: `${y - 60}px`, // 标记点上方 60px
     maxWidth: '320px',
     zIndex: 1000
   };
@@ -192,10 +217,6 @@ const infoPanelStyle = computed(() => {
 // 初始化加载门店列表
 onMounted(() => {
   loadShopList();
-  // 等待地图组件初始化完成后再添加标记
-  setTimeout(() => {
-    addShopMarkers();
-  }, 1000); // 延迟 1 秒确保地图已初始化
 });
 </script>
 
@@ -240,31 +261,48 @@ onMounted(() => {
 
 .shop-info-panel {
   position: absolute;
-  top: 20px;
-  right: 20px;
   background: white;
   border-radius: 8px;
   box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
   z-index: 1000;
-  min-width: 300px;
-  max-width: 400px;
+  min-width: 280px;
+  max-width: 350px;
   border: 1px solid #e4e7ed;
 }
 
+/* 左侧小箭头 */
+.shop-info-panel::before {
+  content: '';
+  position: absolute;
+  left: -10px;
+  top: 50px;
+  border-width: 10px;
+  border-style: solid;
+  border-color: transparent white transparent transparent;
+}
+
+.shop-info-panel::after {
+  content: '';
+  position: absolute;
+  left: -11px;
+  top: 49px;
+  border-width: 11px;
+  border-style: solid;
+  border-color: transparent #e4e7ed transparent transparent;
+  z-index: -1;
+}
+
 .panel-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 12px 16px;
+  padding: 10px 14px;
   border-bottom: 1px solid #e4e7ed;
-  background: #f5f7fa;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
   border-radius: 8px 8px 0 0;
 }
 
 .panel-header h3 {
   margin: 0;
-  font-size: 16px;
-  color: #303133;
+  font-size: 15px;
+  color: white;
   font-weight: 600;
 }