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(涂聚文)
浙公网安备 33010602011771号