首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >vue3: amap using typescript

vue3: amap using typescript

作者头像
geovindu
发布2026-06-18 21:10:48
发布2026-06-18 21:10:48
240
举报

在vscode创建:

项目结构:

AmapMarker.vue

代码语言:javascript
复制
<template>
  <div ref="mapContainer" class="amap-container" />
</template>
 
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, Ref, reactive } from 'vue';
 
interface HotelData {
  name: string;
  content: string;
  center: string;
  type: number;
  icon: string;
}
 
interface MarkerOptions {
  position: [number, number];
  icon?: any; // 使用AMap.Icon类型
  offset?: [number, number];
  hotelData?: HotelData;
}
 
const props = defineProps<{
  mapKey: string;
  hotelDataUrl?: string;
  mapOptions?: Record<string, any>;
}>();
 
const mapContainer = ref<HTMLElement | null>(null);
let mapInstance: any = null;
let markerInstances: any[] = [];
let infoWindow: any = null;
const hotelData = reactive<HotelData[]>([]);
 
const initMap = async () => {
  if (!mapContainer.value) return;
 
  try {
    await loadAMapSDK(props.mapKey);
     
    mapInstance = new (window as any).AMap.Map(mapContainer.value, {
      zoom: 12,
      center: [114.124224, 22.574958],
      ...props.mapOptions
    });
 
    infoWindow = new (window as any).AMap.InfoWindow({
      isCustom: false,
      autoMove: true,
      offset: new (window as any).AMap.Pixel(0, -30)
    });
 
    await loadHotelData();
     
    if (hotelData.length > 0) {
      const markers = convertHotelDataToMarkers(hotelData);
      addMarkers(markers);
    } else {
      console.warn('没有酒店数据可供显示');
    }
  } catch (error) {
    console.error('初始化地图失败:', error);
  }
};
 
const loadAMapSDK = (key: string) => {
  return new Promise<void>((resolve, reject) => {
    if ((window as any).AMap) {
      resolve();
      return;
    }
 
    const script = document.createElement('script');
    script.src = `https://webapi.amap.com/maps?v=2.0&key=${key}`;
    script.async = true;
    script.onload = () => resolve();
    script.onerror = (err) => reject(err);
    document.head.appendChild(script);
  });
};
 
//加载酒店信息
const loadHotelData = async () => {
  if (!props.hotelDataUrl) {
    console.warn('未提供酒店数据URL');
    return;
  }
 
  try {
    console.log('正在加载酒店数据:', props.hotelDataUrl);
    const response = await fetch(props.hotelDataUrl);
     
    if (!response.ok) {
      throw new Error(`加载酒店数据失败: ${response.status} ${response.statusText}`);
    }
     
    // 在解析前检查内容类型
    const contentType = response.headers.get('content-type');
    if (!contentType || !contentType.includes('application/json')) {
      // 获取响应文本用于调试
      const responseText = await response.text();
      console.error('预期JSON数据,但收到:', responseText.substring(0, 200));
      throw new Error('返回的内容不是有效的JSON格式');
    }
     
    const data = await response.json();
    console.log('成功加载酒店数据,数量:', data.length);
    hotelData.splice(0, hotelData.length, ...data);
  } catch (error) {
    console.error('加载酒店数据错误:', error);
    // 可以添加一个默认数据或错误提示
  }
};
 
const convertHotelDataToMarkers = (hotelData: HotelData[]): MarkerOptions[] => {
  if (!hotelData || hotelData.length === 0) return [];
   
  return hotelData.map(hotel => {
    const [lng, lat] = hotel.center.split(',').map(Number);
     
    // 创建自定义图标,设置尺寸
    const icon = new (window as any).AMap.Icon({
      image: hotel.icon,
      size: new (window as any).AMap.Size(40, 40), // 图标大小
      imageSize: new (window as any).AMap.Size(40, 40) // 图像大小
    });
     
    return {
      position: [lng, lat],
      hotelData: hotel,
      icon: icon,
      offset: [-20, -40] // 根据图标大小调整偏移
    };
  });
};
 
const addMarkers = (markers: MarkerOptions[]) => {
  if (!mapInstance || !markers || markers.length === 0) return;
 
  markerInstances.forEach(marker => marker.setMap(null));
  markerInstances = [];
 
  markers.forEach(markerOpt => {
    const marker = new (window as any).AMap.Marker({
      position: markerOpt.position,
      icon: markerOpt.icon,
      offset: markerOpt.offset || [-15, -30],
      zIndex: 100 // 设置标记的z-index
    });
 
    marker.on('click', (e: any) => {
      if (markerOpt.hotelData) {
        openInfoWindow(markerOpt.hotelData, marker.getPosition());
      }
    });
 
    marker.setMap(mapInstance);
    markerInstances.push(marker);
  });
};
 
const openInfoWindow = (hotelData: HotelData, position: any) => {
  const infoContent = `
    <div class="p-3 max-w-xs">
      <div class="flex items-start gap-3">
        <img src="${hotelData.icon}" alt="${hotelData.name}" class="w-20 h-20 rounded-lg object-cover" />
        <div>
          <h3 class="font-bold text-lg mb-1">${hotelData.name}</h3>
          <div class="text-gray-700 text-sm mb-2">${hotelData.content}</div>
          <button class="mt-1 bg-primary text-white px-2 py-1 text-xs rounded hover:bg-primary/90 transition-colors">
            <i class="fa fa-location-arrow mr-1"></i>导航
          </button>
        </div>
      </div>
    </div>
  `;
 
  infoWindow.setContent(infoContent);
  infoWindow.open(mapInstance, position);
};
 
onMounted(() => {
  initMap();
});
 
onUnmounted(() => {
  if (mapInstance) {
    markerInstances.forEach(marker => marker.setMap(null));
    if (infoWindow) infoWindow.close();
    mapInstance.destroy();
    mapInstance = null;
  }
});
</script>
 
<style scoped>
.amap-container {
  width: 100%;
  height: 850px;
}
</style> 

加上lable标签名称

代码语言:javascript
复制
<template>
  <div ref="mapContainer" class="amap-container" />
</template>

<script lang="ts" setup>
import { ref, onMounted, onUnmounted, Ref, reactive } from 'vue';

interface HotelData {
  name: string;
  content: string;
  center: string;
  type: number;
  icon: string;
}

interface MarkerOptions {
  position: [number, number];
  icon?: any; // 使用AMap.Icon类型
  offset?: [number, number];
  hotelData?: HotelData;
}

const props = defineProps<{
  mapKey: string;
  hotelDataUrl?: string;
  mapOptions?: Record<string, any>;
}>();

const mapContainer = ref<HTMLElement | null>(null);
let mapInstance: any = null;
let markerInstances: any[] = [];
let infoWindow: any = null;
const hotelData = reactive<HotelData[]>([]);

const initMap = async () => {
  if (!mapContainer.value) return;

  try {
    await loadAMapSDK(props.mapKey);
    
    mapInstance = new (window as any).AMap.Map(mapContainer.value, {
      zoom: 12,
      center: [114.124224, 22.574958],
      ...props.mapOptions
    });

    infoWindow = new (window as any).AMap.InfoWindow({
      isCustom: false,
      autoMove: true,
      offset: new (window as any).AMap.Pixel(0, -30)
    });

    await loadHotelData();
    
    if (hotelData.length > 0) {
      const markers = convertHotelDataToMarkers(hotelData);
      addMarkers(markers);
    } else {
      console.warn('没有酒店数据可供显示');
    }
  } catch (error) {
    console.error('初始化地图失败:', error);
  }
};

const loadAMapSDK = (key: string) => {
  return new Promise<void>((resolve, reject) => {
    if ((window as any).AMap) {
      resolve();
      return;
    }

    const script = document.createElement('script');
    script.src = `https://webapi.amap.com/maps?v=2.0&key=${key}`;
    script.async = true;
    script.onload = () => resolve();
    script.onerror = (err) => reject(err);
    document.head.appendChild(script);
  });
};

const loadHotelData = async () => {
  if (!props.hotelDataUrl) {
    console.warn('未提供酒店数据URL');
    return;
  }

  try {
    console.log('正在加载酒店数据:', props.hotelDataUrl);
    const response = await fetch(props.hotelDataUrl);
    
    if (!response.ok) {
      throw new Error(`加载酒店数据失败: ${response.status} ${response.statusText}`);
    }
    
    const contentType = response.headers.get('content-type');
    if (!contentType || !contentType.includes('application/json')) {
      const responseText = await response.text();
      console.error('预期JSON数据,但收到:', responseText.substring(0, 200));
      throw new Error('返回的内容不是有效的JSON格式');
    }
    
    const data = await response.json();
    console.log('成功加载酒店数据,数量:', data.length);
    hotelData.splice(0, hotelData.length, ...data);
  } catch (error) {
    console.error('加载酒店数据错误:', error);
  }
};

const convertHotelDataToMarkers = (hotelData: HotelData[]): MarkerOptions[] => {
  if (!hotelData || hotelData.length === 0) return [];
  
  return hotelData.map(hotel => {
    const [lng, lat] = hotel.center.split(',').map(Number);
    
    const icon = new (window as any).AMap.Icon({
      image: hotel.icon,
      size: new (window as any).AMap.Size(40, 40),
      imageSize: new (window as any).AMap.Size(40, 40)
    });
    
    return {
      position: [lng, lat],
      hotelData: hotel,
      icon: icon,
      offset: [-20, -40]
    };
  });
};

const addMarkers = (markers: MarkerOptions[]) => {
  if (!mapInstance || !markers || markers.length === 0) return;

  markerInstances.forEach(marker => marker.setMap(null));
  markerInstances = [];

  markers.forEach(markerOpt => {
    const marker = new (window as any).AMap.Marker({
      position: markerOpt.position,
      icon: markerOpt.icon,
      offset: markerOpt.offset || [-15, -30],
      zIndex: 100,
      
      // 调整标签位置到右侧并紧挨着
      label: {
        content: `<div class="marker-label">${markerOpt.hotelData?.name || ''}</div>`,
        offset: new (window as any).AMap.Pixel(25, -10), // 向右下方微调
        direction: 'right', // 标签方向为右
        autoRotation: false
      }
    });

    marker.on('click', (e: any) => {
      if (markerOpt.hotelData) {
        openInfoWindow(markerOpt.hotelData, marker.getPosition());
      }
    });

    marker.setMap(mapInstance);
    markerInstances.push(marker);
  });
};

const openInfoWindow = (hotelData: HotelData, position: any) => {
  const infoContent = `
    <div class="p-3 max-w-xs">
      <div class="flex items-start gap-3">
        <img src="${hotelData.icon}" alt="${hotelData.name}" class="w-20 h-20 rounded-lg object-cover" />
        <div>
          <h3 class="font-bold text-lg mb-1">${hotelData.name}</h3>
          <div class="text-gray-700 text-sm mb-2">${hotelData.content}</div>
          <button class="mt-1 bg-primary text-white px-2 py-1 text-xs rounded hover:bg-primary/90 transition-colors">
            <i class="fa fa-location-arrow mr-1"></i>导航
          </button>
        </div>
      </div>
    </div>
  `;

  infoWindow.setContent(infoContent);
  infoWindow.open(mapInstance, position);
};

onMounted(() => {
  initMap();
});

onUnmounted(() => {
  if (mapInstance) {
    markerInstances.forEach(marker => marker.setMap(null));
    if (infoWindow) infoWindow.close();
    mapInstance.destroy();
    mapInstance = null;
  }
});
</script>

<style scoped>
.amap-container {
  width: 100%;
  height: 850px;
}

/* 标记标签样式 */
.marker-label {
  background-color: rgba(255, 255, 255, 0.95);
  border-radius: 4px;
  padding: 3px 8px;
  font-size: 13px;
  font-weight: 500;
  color: #333;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
  white-space: nowrap;
  border: 1px solid #eee;
  
  /* 调整标签位置 */
  display: inline-block;
  transform: translate(0, -5px);
  transition: all 0.2s ease;
}

/* 鼠标悬停时的样式 */
.amap-marker:hover .marker-label {
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
  transform: translate(0, -5px) scale(1.02);
}

/* 定义主色调 */
.bg-primary {
  background-color: #165DFF;
}
</style>  

app.vue

代码语言:javascript
复制
<script setup lang="ts">
import { ref } from 'vue';
import HelloWorld from './components/HelloWorld.vue'
import AmapMarker from './components/AmapMarker.vue';
 
const mapKey = ref('your key');
const hotelDataUrl= ref('hotels.json');
 
const mapOptions = ref({
  zoom: 12,
  viewMode: '3D',
  showBuildingBlock: true
});
 
</script>
 
<template>
  
  <div class="container">
    <h1 class="text-2xl font-bold mb-4">酒店地图展示</h1>
    <AmapMarker
      :mapKey="mapKey"
      :hotelDataUrl="hotelDataUrl"
      :mapOptions="mapOptions"
    />
  </div>
 
 
</template>
 
<style scoped>
.container
{
  height: 800px;
  widows: 100%;
}
</style>

hotel.json

代码语言:javascript
复制
[
    {
      "name": "深圳酒店",
      "content": "深圳市罗湖区东晓街道布心路<br><a href=\"tel:0755-8888888888\">0755-8888888888</a>",
      "center": "114.124224,22.574958",
      "type": 0,
      "icon": "https://picsum.photos/seed/hotel1/200/200"
    },
    {
      "name": "深圳S酒店",
      "content": "深圳市罗湖区<br><a href=\"tel:0755-8888888888\">0755-8888888888</a>",
      "center": "114.106757,22.545005",
      "type": 2,
      "icon": "https://picsum.photos/seed/hotel2/200/200"
    },
    {
      "name": "深圳X酒店",
      "content": "深圳市福田区深南大道<br><a href=\"tel:0755-8888888888\">0755-8888888888</a>",
      "center": "114.057868,22.543099",
      "type": 1,
      "icon": "https://picsum.photos/seed/hotel3/200/200"
    }
  ] 

输出:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-05-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档