vue3: amap using typescript

在vscode创建:

 

项目结构:

 

 

 


AmapMarker.vue

<template>
  <div ref="mapContainer" class="amap-container" />
</template>

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

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

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

const mapContainer = ref<HTMLElement | null>(null);
let mapInstance: any = null;
let markers: any[] = [];
let infoWindow: any = null;

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

  try {
    await loadAmapSdk(props.mapKey);
    
    mapInstance = new window.AMap.Map(mapContainer.value, {
      zoom: 12,
      center: [114.124224, 22.574958],
      ...props.mapOptions
    });
    
    const hotelData = await loadHotelData (props.hotelDataUrl);
    addHotelMarkers(hotelData);
    
  } catch (error) {
    console.error('地图初始化失败:', error);
  }
};

const loadAmapSdk = (key: string) => {
  return new Promise<void>((resolve, reject) => {
    if (window.AMap) return resolve();
    
    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 (url: string) => {
  if (!props.hotelDataUrl) {
    console.warn('未提供酒店数据URL');
    return;
  }

  try {
    console.log('正在加载酒店数据:', props.hotelDataUrl);
    const response = await fetch(url);
    
    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() as HotelData[];
    console.log('成功加载酒店数据,数量:', data.length);
    //HotelData.splice(0, HotelData.length, ...data);
    return data;
  } catch (error) {
    console.error('加载酒店数据错误:', error);
    return [];
    // 可以添加一个默认数据或错误提示
  }
};

const addHotelMarkers = (hotelData: HotelData[]) => {
  if (markers.length) {
    markers.forEach(marker => marker.setMap(null));
    markers = [];
  }
  
  hotelData.forEach(hotel => {
    const [lng, lat] = hotel.center.split(',').map(Number);
    
    const icon = new window.AMap.Icon({
      image: hotel.icon,
      size: new window.AMap.Size(48, 48),
      imageSize: new window.AMap.Size(48, 48)
    });
    
    const marker = new window.AMap.Marker({
      position: [lng, lat],
      icon: icon,
      offset: new window.AMap.Pixel(-24, -48),
      
      label: {
        content: `<div class="marker-label">${hotel.name}</div>`,
        offset: new window.AMap.Pixel(0, -50),
        direction: 'top'
      },
      
      zIndex: 100,
      cursor: 'pointer'
    });
    
    marker.on('click', () => {
      openInfoWindow(hotel, marker.getPosition());
    });
    
    marker.setMap(mapInstance);
    markers.push(marker);
  });
};

const openInfoWindow = (hotel: HotelData, position: any) => {
  // 使用通用的容器ID,确保样式可以应用
  const content = `
    <div id="amap-info-window" class="p-4 bg-white rounded-lg shadow-xl max-w-xs break-words">
      <div class="flex items-start gap-3">
        <img src="${hotel.icon}" alt="${hotel.name}" class="w-20 h-20 rounded-lg object-cover shadow-md" />
        <div class="flex-1">
          <h3 class="font-bold text-lg mb-1 text-gray-800">${hotel.name}</h3>
          <div class="text-gray-600 text-sm mb-2 leading-relaxed">${hotel.content}</div>
          <button class="mt-1 bg-primary text-white px-3 py-1 rounded text-sm hover:bg-primary/90 transition-colors flex items-center">
            <i class="fa fa-location-arrow mr-1"></i>导航
          </button>
        </div>
      </div>
    </div>
  `;
  
  // 确保只创建一个信息窗口实例
  if (!infoWindow) {
    infoWindow = new window.AMap.InfoWindow({
      offset: new window.AMap.Pixel(0, -30),
      autoMove: true,
      isCustom: false, // 设置为false,让高德地图处理样式
      content: ''
    });
  }
  
  infoWindow.setContent(content);
  infoWindow.open(mapInstance, position);
};

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

onUnmounted(() => {
  if (mapInstance) {
    mapInstance.destroy();
    mapInstance = null;
  }
});
</script>

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

.marker-label {
  background-color: rgba(255, 255, 255, 0.9);
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 12px;
  font-weight: bold;
  color: #333;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
  white-space: nowrap;
  transform: translateY(-5px);
  opacity: 0;
  transition: all 0.2s ease;
}

.amap-marker:hover .marker-label {
  opacity: 1;
  transform: translateY(0);
}

/* 全局样式,确保信息窗口样式生效 */
#amap-info-window {
  @apply p-4 bg-white rounded-lg shadow-xl max-w-xs break-words;
}

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

.text-primary {
  color: #165DFF;
}
</style>  

  

 

无lable

<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: "mark_r.png",//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>  

  

 

 

 

<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>  

  

<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>  

  

app.vue

<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>

  

json:
[
    {
      "name": "亚朵酒店",
      "content": "深圳市罗湖区东晓街道布心路3008号水贝珠宝总部大厦A座26-31层<br><a href=\"tel:0755-8180 8180\">0755-8180 8180</a>",
      "center": "114.124224,22.574958",
      "type": 0,
      "icon": "https://picsum.photos/seed/hotel1/200/200"
    },
    {
      "name": "深圳亚朵S酒店",
      "content": "深圳市罗湖区京基水贝城市广场1栋1单元<br><a href=\"tel:0755-81808180\">0755-81808180</a>",
      "center": "114.106757,22.545005",
      "type": 2,
      "icon": "https://picsum.photos/seed/hotel2/200/200"
    },
    {
      "name": "亚朵X酒店",
      "content": "深圳市福田区深南大道1006号深圳国际创新中心A座<br><a href=\"tel:0755-81808181\">0755-81808181</a>",
      "center": "114.057868,22.543099",
      "type": 1,
      "icon": "https://picsum.photos/seed/hotel3/200/200"
    }
  ]  

  

输出:

 







AmapMarker.vue

<template>
  <div ref="mapContainer" class="amap-container" />
</template>

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

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

interface MarkerOptions {
  position: [number, number];
  content?: string | HTMLElement;
  offset?: [number, number];
  hotelData?: HotelData;
}

const props = defineProps<{
  mapKey: string;
  hotelData: HotelData[];
  mapOptions?: Record<string, any>;
}>();

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

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)
    });

    // 转换酒店数据为标记点,添加空值检查
    if (props.hotelData && props.hotelData.length > 0) {
      const markers = convertHotelDataToMarkers(props.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 convertHotelDataToMarkers = (hotelData: HotelData[]): MarkerOptions[] => {
  // 添加空值检查
  if (!hotelData || hotelData.length === 0) return [];
  
  return hotelData.map(hotel => {
    const [lng, lat] = hotel.center.split(',').map(Number);
    return {
      position: [lng, lat],
      hotelData: hotel,
      offset: [-15, -30],
      content: createCustomMarkerContent(hotel)
    };
  });
};

const createCustomMarkerContent = (hotel: HotelData) => {
  const div = document.createElement('div');
  div.className = 'custom-marker';
  div.innerHTML = `
    <div class="marker-bg rounded-full w-10 h-10 flex items-center justify-center ${getMarkerColor(hotel.type)} text-white shadow-lg transform transition-transform hover:scale-110 cursor-pointer">
      <i class="fa fa-building-o"></i>
    </div>
    <div class="marker-label mt-1 bg-white px-2 py-1 rounded text-sm shadow-md">
      ${hotel.name}
    </div>
  `;
  return div;
};

const getMarkerColor = (type: number) => {
  const colors = ['bg-blue-500', 'bg-green-500', 'bg-orange-500'];
  return colors[type] || 'bg-blue-500';
};

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,
      content: markerOpt.content,
      offset: markerOpt.offset || [-15, -30],
    });

    // 绑定点击事件
    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">
      <h3 class="font-bold text-lg mb-2">${hotelData.name}</h3>
      <div class="text-gray-700 mb-2">${hotelData.content}</div>
      <button class="mt-2 bg-primary text-white px-3 py-1 rounded hover:bg-primary/90 transition-colors">
        <i class="fa fa-location-arrow mr-1"></i>导航
      </button>
    </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: 500px;
}

.custom-marker {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.marker-label {
  white-space: nowrap;
}
</style>  

  

app.vue

<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 hotelData = ref([
  {
    "name": "亚朵酒店",
    "content": "深圳市罗湖区东晓街道布心路3008号水贝珠宝总部大厦A座26-31层<br><a href=\"tel:0755-8180 8180\">0755-8180 8180</a>",
    "center": "114.124224,22.574958",
    "type": 0
  },
  {
    "name": "深圳亚朵S酒店",
    "content": "深圳市罗湖区京基水贝城市广场1栋1单元<br><a href=\"tel:0755-81808180\">0755-81808180</a>",
    "center": "114.106757,22.545005",
    "type": 2
  }
]);

const mapOptions = ref({
  zoom: 12,
  viewMode: '3D',
  showBuildingBlock: true
});
</script>

<template>
 
    <div class="container mx-auto p-4">
      <h1 class="text-2xl font-bold mb-4">亚朵酒店地图展示</h1>
      <AmapMarker
        :mapKey="mapKey"
        :hotelData="hotelData"
        :mapOptions="mapOptions"
      />
    </div>

  <div>
    <a href="https://vite.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <HelloWorld msg="Vite + Vue" />
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

  

 

AmapMarker.vue
<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];
  content?: string | HTMLElement;
  offset?: [number, number];
  hotelData?: HotelData;
}

const props = defineProps<{
  mapKey: string;
  hotelDataUrl?: string; // JSON文件URL
  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);
  });
};

// 从JSON文件加载酒店数据
const loadHotelData = async () => {
  if (!props.hotelDataUrl) {
    console.warn('未提供酒店数据URL');
    return;
  }

  try {
    const response = await fetch(props.hotelDataUrl);
    if (!response.ok) {
      throw new Error(`加载酒店数据失败: ${response.status}`);
    }
    const data = await response.json();
    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);
    return {
      position: [lng, lat],
      hotelData: hotel,
      offset: [-30, -70], // 调整偏移以适应图片和标签
      content: createCustomMarkerContent(hotel)
    };
  });
};

// 创建包含图片和标签的自定义标记
const createCustomMarkerContent = (hotel: HotelData) => {
  const div = document.createElement('div');
  div.className = 'custom-marker';
  div.innerHTML = `
    <div class="marker-wrapper flex flex-col items-center group cursor-pointer">
      <div class="marker-image relative w-16 h-16 overflow-hidden rounded-lg shadow-lg transform transition-transform group-hover:scale-110">
        <img src="${hotel.icon}" alt="${hotel.name}" class="w-full h-full object-cover" />
        <div class="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent" />
      </div>
      <div class="marker-label mt-1 bg-white px-2 py-1 rounded text-sm shadow-md transform transition-all opacity-0 group-hover:opacity-100">
        ${hotel.name}
      </div>
    </div>
  `;
  return div;
};

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,
      content: markerOpt.content,
      offset: markerOpt.offset || [-15, -30],
    });

    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: 500px;
}

.custom-marker {
  position: relative;
}

.marker-wrapper {
  transition: all 0.2s ease;
}

.marker-label {
  white-space: nowrap;
  transform: translateY(-5px);
  transition: all 0.2s ease;
}
</style>  

  

<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 mx-auto p-4">
    <h1 class="text-2xl font-bold mb-4">亚朵酒店地图展示</h1>
    <AmapMarker
      :mapKey="mapKey"
      :hotelDataUrl="hotelDataUrl"
      :mapOptions="mapOptions"
    />
  </div>

  <div>
    <a href="https://vite.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <HelloWorld msg="Vite + Vue" />
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

  

 

posted @ 2025-05-17 10:13  ®Geovin Du Dream Park™  阅读(58)  评论(0)    收藏  举报