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>
哲学管理(学)人生, 文学艺术生活, 自动(计算机学)物理(学)工作, 生物(学)化学逆境, 历史(学)测绘(学)时间, 经济(学)数学金钱(理财), 心理(学)医学情绪, 诗词美容情感, 美学建筑(学)家园, 解构建构(分析)整合学习, 智商情商(IQ、EQ)运筹(学)生存.---Geovin Du(涂聚文)