
在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"
}
] 输出:
