目录标题
1、前置准备:获取Key和密钥以及安装依赖
访问高德开放平台注册账号并创建应用,选择应用类型为"Web端(JS API)"。创建成功后获取Key和安全密钥。
在Vue3项目中安装@amap/amap-jsapi-loader依赖,用于异步加载高德地图API。(代码示例使用的版本是: "@amap/amap-jsapi-loader": "^1.0.1")
bash
npm install @amap/amap-jsapi-loader --save
# 或
pnpm add @amap/amap-jsapi-loader

2、核心代码实现流程解析
组件初始化与地图加载
设置安全密钥至全局变量 window._AMapSecurityConfig,确保在加载JS API脚本前完成配置。
通过 AMapLoader.load 异步加载地图核心库及插件(如地点搜索、输入提示)。在回调中创建地图实例,配置初始缩放级别和中心点。
地点搜索与输入提示
输入提示功能通过绑定 AMap.AutoComplete 实例实现,用户选择提示项时自动触发搜索。
搜索功能调用 placeSearch.search 清理旧标记后发起请求,成功返回POI列表后,遍历添加标记点并调整地图视野以展示所有结果。
标记点与信息窗口交互
标记点使用自定义图标创建,附带地点数据(名称、地址、经纬度)以便后续交互。
点击标记点触发事件处理函数,提取位置信息并保存至响应式变量,同时通过事件传递数据至父组件。
信息窗口通过 AMap.InfoWindow 展示,内容为自定义HTML模板,包含地址和经纬度信息。
数据回显功能
初始化时检查父组件传入的 selectedLocation 属性,若有效则作为初始位置,否则使用默认坐标。
通过监听属性变化实现外部数据更新时同步地图显示,确保标记点和信息窗口动态更新。
3、完整组件示例
整合以上功能,提供一个完整的高德地图组件示例。
<script setup lang="ts">
import {
ref,
reactive,
onMounted,
onUnmounted,
watch,
getCurrentInstance,
nextTick
} from "vue";
import AMapLoader from "@amap/amap-jsapi-loader";
import { message } from "@/utils/message";
// --------------------------------
// 地图相关实例和变量定义
// --------------------------------
// 地图实例
let map: any = null;
// 地点搜索实例
let placeSearch: any = null;
// 自动完成实例
let autoComplete: any = null;
// 信息窗口实例
let infoWindow: any = null;
// 所有标记点数组,用于管理地图上的标记点
let markers: any[] = [];
interface sLocation {
lng?: number;
lat?: number;
address?: string;
}
// 当前选中的位置信息,响应式变量
const selectedLocation = ref<sLocation | null>(null);
// 默认坐标(北京天安门)
const DEFAULT_CENTER = [116.397428, 39.90923];
// 默认位置信息
const DEFAULT_LOCATION = {
lng: 116.397428,
lat: 39.90923,
address: "北京市东城区天安门"
};
// --------------------------------
// 组件属性定义
// --------------------------------
// 定义组件属性,父组件可以传递选中的位置
const props = defineProps<{
selectedLocation?: sLocation;
}>();
// --------------------------------
// 组件事件定义
// --------------------------------
// 定义组件事件,向父组件传递选中的位置信息
const emit = defineEmits<{
(
e: "selectLocation",
location: {
lng: number;
lat: number;
address: string;
}
): void;
}>();
// --------------------------------
// 响应式变量定义
// --------------------------------
// 地图容器引用
const mapContainer = ref<HTMLElement | null>(null);
// 搜索结果列表
const searchResults = ref<any[]>([]);
// 地图加载状态
const loading = ref<boolean>(true);
// 搜索关键词
const keyword = ref<string>("");
// 搜索输入框的DOM引用
const searchInputRef = ref<HTMLInputElement | null>(null);
// --------------------------------
// 辅助函数和工具函数
// --------------------------------
/**
* 获取更美观的标记点图标
* 使用更小尺寸(20x30像素)的红色标记点图标
* @returns AMap.Icon 图标实例
*/
const getMarkerIcon = (AMap: any) => {
return new AMap.Icon({
size: new AMap.Size(20, 30), // 图标尺寸:宽20px,高30px
image: "https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png", // 红色标记点图标
imageSize: new AMap.Size(20, 30) // 图标显示尺寸
});
};
/**
* 获取当前组件实例
*/
const instance = getCurrentInstance();
// --------------------------------
// 地图初始化函数
// --------------------------------
/**
* 初始化地图和相关插件
* 这是组件的核心初始化函数,负责:
* 1. 加载高德地图API
* 2. 创建地图实例
* 3. 初始化搜索和自动完成插件
* 4. 设置事件监听
* 5. 处理数据回显
*/
const initMap = () => {
// 安全检查:确保组件实例和地图容器存在
if (!instance || !mapContainer.value) return;
// 从全局配置中获取地图配置
const { MapConfigure } = instance.appContext.config.globalProperties.$config;
console.log("地图配置信息:", MapConfigure);
// --------------------------------
// 步骤1: 设置安全密钥(必须在加载JS API之前设置)
// --------------------------------
window._AMapSecurityConfig = {
securityJsCode: MapConfigure.securityJsCode
};
// --------------------------------
// 步骤2: 加载高德地图API和插件
// --------------------------------
AMapLoader.load({
key: MapConfigure.amapKey, // 地图API密钥
version: "2.0", // API版本
plugins: [
"AMap.ToolBar", // 工具条插件:提供缩放、平移等控件
"AMap.PlaceSearch", // 地点搜索插件:提供关键词搜索功能
"AMap.AutoComplete" // 自动完成插件:提供输入提示功能
]
})
.then(AMap => {
console.log("✅ 高德地图API加载成功");
// --------------------------------
// 步骤3: 创建地图实例
// --------------------------------
map = new AMap.Map(mapContainer.value, {
zoom: 13, // 初始缩放级别
center: DEFAULT_CENTER, // 初始中心点坐标
resizeEnable: true // 允许地图自适应窗口大小
});
// --------------------------------
// 步骤4: 添加地图控件
// --------------------------------
// 添加工具条控件(缩放、平移按钮)
map.addControl(new AMap.ToolBar());
// --------------------------------
// 步骤5: 创建地点搜索实例
// --------------------------------
// 注意:这里不传递map参数,避免自动添加标记点
placeSearch = new AMap.PlaceSearch({
pageSize: 20, // 每页显示结果数量
pageIndex: 1, // 当前页码
city: "全国", // 搜索城市范围
//map:map, 注意:这里不设置map参数,避免自动添加标记点
autoFitView: true // 搜索结果后自动调整地图视野
});
// --------------------------------
// 步骤6: 创建信息窗口实例
// --------------------------------
infoWindow = new AMap.InfoWindow({
offset: new AMap.Pixel(0, 0), // 信息窗口偏移量
closeWhenClickMap: true // 点击地图时关闭信息窗口
});
// --------------------------------
// 步骤7: 初始化自动完成插件(输入提示)
// --------------------------------
// 等待DOM渲染完成后初始化
nextTick(() => {
if (searchInputRef.value) {
// 创建自动完成实例,直接绑定到输入框
autoComplete = new AMap.AutoComplete({
input: searchInputRef.value, // 绑定到输入框
city: "全国" // 搜索城市范围
});
// --------------------------------
// 步骤8: 监听自动完成选择事件
// --------------------------------
autoComplete.on("select", (e: any) => {
console.log("用户选择了自动完成建议:", e.poi);
// 当用户选择搜索建议时,执行搜索
keyword.value = e.poi.name;
searchByKeyword(e.poi.name);
});
}
});
// --------------------------------
// 步骤9: 监听地图点击事件
// --------------------------------
map.on("click", () => {
// 点击地图时关闭信息窗口
if (infoWindow) {
infoWindow.close();
}
});
// --------------------------------
// 步骤10: 监听地图加载完成事件
// --------------------------------
map.on("complete", () => {
console.log("✅ 地图加载完成");
loading.value = false;
});
// --------------------------------
// 步骤11: 数据回显处理
// --------------------------------
// 检查父组件是否传递了选中位置
if (props.selectedLocation?.lng && props.selectedLocation?.lat) {
console.log("收到父组件传递的位置:", props.selectedLocation);
// 使用父组件传递的位置
const location = {
lng: props.selectedLocation.lng,
lat: props.selectedLocation.lat,
address: props.selectedLocation.address
};
// 将地图中心移动到该位置
map.setCenter([location.lng, location.lat]);
// 添加标记点
addMarker([location.lng, location.lat], location);
// 显示信息窗口
showInfoWindow(location);
} else {
// 如果没有传递位置,使用默认位置
console.log("使用默认位置:", DEFAULT_LOCATION);
map.setCenter(DEFAULT_CENTER);
addMarker(DEFAULT_CENTER, DEFAULT_LOCATION);
showInfoWindow(DEFAULT_LOCATION);
}
})
.catch(error => {
console.error("❌ 地图加载失败:", error);
loading.value = false;
});
};
// --------------------------------
// 搜索功能相关函数
// --------------------------------
/**
* 执行关键词搜索
* @param keyword - 搜索关键词
* 功能:
* 1. 清空之前的标记点
* 2. 执行搜索
* 3. 处理搜索结果
* 4. 在地图上添加标记点
*/
const searchByKeyword = (keyword: string) => {
// 安全检查
if (!placeSearch || !keyword.trim()) return;
console.log("执行搜索,关键词:", keyword);
// 清空之前的标记点
clearMarkers();
// 执行搜索
placeSearch.search(keyword, (status: string, result: any) => {
if (status === "complete" && result.poiList && result.poiList.pois) {
console.log("搜索成功:", result);
console.log("搜索成功,找到结果:", result.poiList.pois.length);
// 保存搜索结果
searchResults.value = result.poiList.pois;
// 在地图上为每个搜索结果添加标记点
result.poiList.pois.forEach((poi: any) => {
console.log(poi, "poi");
const location = {
lng: poi.location.lng,
lat: poi.location.lat,
address: `${poi.name} (${poi.address})`,
fullInfo: poi // 保存完整信息用于InfoWindow显示
};
addMarker([location.lng, location.lat], location);
});
// 自动调整地图视野以显示所有标记点
map.setFitView();
} else {
console.log("搜索失败或无结果");
message("搜索失败或无结果", { type: "warning" });
searchResults.value = [];
}
});
};
// --------------------------------
// 标记点管理相关函数
// --------------------------------
/**
* 在地图上添加标记点
* @param position - 标记点位置 [经度, 纬度]
* @param data - 标记点相关数据
* 功能:
* 1. 创建标记点实例
* 2. 设置标记点样式
* 3. 绑定点击事件
* 4. 将标记点添加到地图
*/
const addMarker = (position: [number, number], data: any) => {
if (!map) return;
// 创建标记点实例
const marker = new AMap.Marker({
position: position, // 标记点位置
title: data.address, // 鼠标悬停时显示的标题
icon: getMarkerIcon(window.AMap), // 使用自定义图标
extData: data // 将数据附加到标记点上,便于后续获取
});
// --------------------------------
// 监听标记点点击事件
// --------------------------------
marker.on("click", () => {
console.log("点击了标记点:", data);
handleMarkerClick(data);
});
// 将标记点添加到数组和地图
markers.push(marker);
map.add(marker);
};
/**
* 处理标记点点击事件
* @param data - 标记点相关数据
* 功能:
* 1. 保存并显示位置信息
* 2. 将地图中心移动到标记点位置
*/
const handleMarkerClick = (data: any) => {
// 保存并显示位置信息
saveAndShowLocation(data);
// 将地图中心移动到标记点位置
const position = [data.lng, data.lat];
map.setCenter(position);
};
// --------------------------------
// 位置信息管理相关函数
// --------------------------------
/**
* 保存并显示位置信息
* @param data - 位置相关数据
* 功能:
* 1. 构建位置信息对象
* 2. 保存到响应式变量
* 3. 触发事件传递给父组件
* 4. 显示信息窗口
*/
const saveAndShowLocation = (data: any) => {
console.log(data, "saveAndShowLocation");
// 构建位置信息对象
const location = {
lng: data.lng,
lat: data.lat,
address: data.address
};
console.log("保存位置信息:", location);
// 保存到选中位置变量
selectedLocation.value = location;
// 触发事件给父组件(只传递必需的信息)
emit("selectLocation", {
lng: location.lng,
lat: location.lat,
address: location.address
});
console.log("触发事件给父组件(只传递必需的信息):", location);
// 显示信息窗口
showInfoWindow(location);
};
/**
* 显示信息窗口
* @param location - 位置信息
* 功能:
* 1. 构建信息窗口内容
* 2. 关闭之前的信息窗口
* 3. 打开新的信息窗口
*/
const showInfoWindow = (location: any) => {
if (!infoWindow || !map) return;
console.log(location, "location");
// 构建信息窗口内容
const content = `
<div style="padding: 10px; min-width: 250px; max-width: 300px; color: #666; font-size: 12px; line-height: 1.5;">
<div style="margin-bottom: 8px;">
<strong>已选位置:</strong> ${location.address}
</div>
<div style="margin-top: 8px;">
<strong>经纬度:</strong> ${location.lng.toFixed(6)}, ${location.lat.toFixed(6)}
</div>
</div>
`;
// 关闭之前的信息窗口
infoWindow.close();
// 打开新的信息窗口
infoWindow.setContent(content);
infoWindow.open(map, [location.lng, location.lat]);
};
// --------------------------------
// 清理和工具函数
// --------------------------------
/**
* 清空所有标记点
* 功能:
* 1. 从地图中移除所有标记点
* 2. 清空标记点数组
* 3. 清空搜索结果
* 4. 关闭信息窗口
*/
const clearMarkers = () => {
if (!map) return;
console.log("清空标记点");
// 从地图中移除所有标记点
markers.forEach(marker => {
map.remove(marker);
});
// 清空标记点数组
markers = [];
// 清空搜索结果列表
searchResults.value = [];
// 清空搜索关键词
// keyword.value = "";
// 关闭信息窗口
if (infoWindow) {
infoWindow.close();
}
};
/**
* 处理搜索按钮点击
* 功能:执行关键词搜索
*/
const handleSearch = () => {
if (keyword.value.trim()) {
console.log("进行搜索,关键词:", keyword.value);
searchByKeyword(keyword.value);
}
};
/**
* 处理键盘事件
* @param e - 键盘事件对象
* 功能:监听Enter键执行搜索
*/
const handleKeydown = (e: KeyboardEvent) => {
if (e.key === "Enter") {
console.log("按下Enter键执行搜索");
handleSearch();
}
};
// --------------------------------
// 监听父组件传递的选中位置变化
// --------------------------------
/**
* 监听props.selectedLocation变化
* 功能:当父组件传递的位置变化时,更新地图显示
*/
watch(
() => props.selectedLocation,
newVal => {
if (newVal && newVal.lng && newVal.lat && map) {
console.log("selectedLocation变化", newVal);
}
},
{ deep: true }
);
// --------------------------------
// 生命周期钩子
// --------------------------------
/**
* 组件挂载时初始化地图
*/
onMounted(() => {
console.log("地图组件挂载");
// 确保DOM已经渲染
nextTick(() => {
initMap();
});
});
/**
* 组件卸载时清理资源
*/
onUnmounted(() => {
console.log("地图组件卸载");
if (map) {
map.destroy();
map = null;
}
});
</script>
<template>
<div class="map-container">
<!-- 地图实例容器 -->
<div ref="mapContainer" class="map-view" :class="{ loading }">
<!-- 搜索框 -->
<div class="simple-search-box">
<input
ref="searchInputRef"
v-model="keyword"
type="text"
placeholder="请输入关键字搜索"
class="simple-search-input"
@blur="handleKeydown"
/>
</div>
<!-- 地图加载遮罩 -->
<div v-if="loading" class="loading-mask">地图加载中...</div>
</div>
</div>
</template>
<style>
.map-container {
width: 100%;
height: 300px;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
.map-view {
flex: 1;
position: relative;
background: #f5f7fa;
}
.loading-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.9);
font-size: 16px;
color: #606266;
z-index: 999;
}
/* 搜索框样式 */
.simple-search-box {
position: absolute;
top: 10px;
left: 10px;
z-index: 1000;
width: 300px;
background: white;
border-radius: 2px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
display: flex;
}
.simple-search-input {
flex: 1;
height: 36px;
padding: 0 12px;
border: none;
border-right: 1px solid #ddd;
font-size: 14px;
color: #333;
outline: none;
}
.amap-sug-result {
position: absolute;
z-index: 9999 !important;
}
</style>




4、关键实现技巧与优化建议
安全地管理密钥
避免将key和securityJsCode硬编码在源码中。生产环境中推荐使用环境变量(如.env文件)存储,通过import.meta.env动态读取,确保敏感信息的安全性。
资源销毁
在组件的onUnmounted生命周期钩子中调用map.destroy(),主动销毁地图实例。这一操作能有效释放内存,防止因实例未清理导致的内存泄漏问题。
优化标记点图标
默认的红色标记点图标可替换为自定义图片URL。通过调整图标样式与项目UI设计保持一致,提升视觉统一性。支持动态修改图标属性,适应不同场景需求。
增强错误处理
基础示例已包含搜索无结果的提示。实际项目中需扩展更多容错机制,例如网络请求失败、API加载超时等场景。建议增加用户友好的反馈界面,结合日志记录辅助排查问题。
扩展功能方向
当前实现聚焦搜索、标记和信息弹窗功能。后续可集成地理编码服务转换地址坐标,添加路线绘制或区域覆盖物(如多边形)等高级功能。通过组合高德地图API与Vue3响应式特性,灵活扩展业务逻辑。
5、常见问题与解决方案
- z-index问题的根源: 当组件在弹窗或对话框中使用时,高德地图的自动完成下拉框(如.amap-sug-result)可能被其他元素的样式覆盖。这主要是由于以下原因:①弹窗通常具有较高的z-index值。②高德地图生成的DOM元素无法直接受Vue组件的scoped样式影响 。
- 安全密钥设置:在调用AMapLoader.load()方法之前,必须先设置window._AMapSecurityConfig。
- 域名白名单:在高德控制台中,将本地开发域名(例如localhost和127.0.0.1)添加到白名单。
- key与安全密钥匹配:确认使用的key和安全密钥来自同一高德应用。
- 网络问题:检查是否能正常访问高德地图的API服务,确保网络连接畅通。
如何获取技术支持
如果遇到无法解决的问题,可以通过以下方式寻求帮助:
- 访问高德开发者工单系统(直接向高德工作人员资讯,回复很快):https://console.amap.com/dev/ticket/list
- 参考高德官方文档:
