vue3: amap using typescript
在vscode创建:
项目结构:
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];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标签名称
<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
<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
[{"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"}]
输出: