pc端高德选择地址(可复制)
问渠那得清如许?为有源头活水来。
我们在开发中, 总会有客户需求,需要用户定位,门店定位,商家定位等,这些是常见的一些需求,所以自己写了一个组件记录一下。
组件效果:

前期准备工作, 首先需要安装高德插件 @amap/amap-jsapi-loader
npm install @amap/amap-jsapi-loader
pnpm install @amap/amap-jsapi-loader
yarn add @amap/amap-jsapi-loader
// 三种方式都可以
还需要到获取自己的高德应用的web 端的 密钥和key

vue3 布局结构
<template>
<el-dialog
title="选择位置"
v-model="dialogVisible"
width="70%"
@close="handleClose"
:close-on-click-modal="false"
destroy-on-close
>
<divclass="map-container">
<divid="container"class="amap-container"></div>
<divclass="search-box">
<el-input
v-model="searchKeyword"
placeholder="请输入关键字搜索全国地址(例如:上海市南京路、北京朝阳区)"
clearable
@keyup.enter="handleSearch"
class="search-input"
>
<template #append>
<el-button @click="handleSearch":icon="Search">搜索</el-button>
</template>
</el-input>
</div>
<divclass="location-info"v-if="selectedAddress">
<divclass="info-title">📍 当前位置信息</div>
<divclass="info-grid">
<divclass="info-item">
<spanclass="label">详细地址:</span>
<spanclass="value">{{ selectedAddress.formattedAddress || '--' }}</span>
</div>
<divclass="info-item">
<spanclass="label">省:</span>
<spanclass="value">{{ selectedAddress.province || '--' }}</span>
</div>
<divclass="info-item">
<spanclass="label">市:</span>
<spanclass="value">{{ selectedAddress.city || '--' }}</span>
</div>
<divclass="info-item">
<spanclass="label">区/县:</span>
<spanclass="value">{{ selectedAddress.district || '--' }}</span>
</div>
<divclass="info-item">
<spanclass="label">街道/乡镇:</span>
<spanclass="value">{{ selectedAddress.township || '--' }}</span>
</div>
<divclass="info-item">
<spanclass="label">道路:</span>
<spanclass="value">{{ selectedAddress.street || '--' }}</span>
</div>
<divclass="info-item">
<spanclass="label">建筑:</span>
<spanclass="value">{{ selectedAddress.building || '--' }}</span>
</div>
<divclass="info-item">
<spanclass="label">经纬度:</span>
<spanclass="value">{{ selectedAddress.lng?.toFixed(6) }}, {{ selectedAddress.lat?.toFixed(6) }}</span>
</div>
</div>
</div>
</div>
<template #footer>
<spanclass="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-buttontype="primary" @click="handleConfirm":disabled="!selectedAddress">确认选择</el-button>
</span>
</template>
</el-dialog>
</template>
js结构
<script setup>
import { ref, watch, nextTick, onUnmounted } from'vue';
import AMapLoader from'@amap/amap-jsapi-loader';
import { ElMessage } from'element-plus';
import { Search } from'@element-plus/icons-vue';
const props = defineProps({
modelValue: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue', 'confirm']);
const dialogVisible = ref(props.modelValue);
const searchKeyword = ref('');
const selectedAddress = ref(null);
let map = null;
let geocoder = null;
let marker = null;
let infoWindow = null;
let isMapReady = false;
// 监听 modelValue 变化
watch(() => props.modelValue, (val) => {
dialogVisible.value = val
if (val) {
nextTick(() => {
initMap()
})
}
});
// 监听 dialogVisible 变化
watch(dialogVisible, (val) => {
emit('update:modelValue', val);
});
// 解析高德地址组件,提取省市区详细地址
const parseAddressComponent = (addressComponent) => {
return {
province: addressComponent.province,
city: addressComponent.city,
district: addressComponent.district,
township: addressComponent.township || '',
street: addressComponent.street || '',
streetNumber: addressComponent.streetNumber || '',
building: addressComponent.building || '',
citycode: addressComponent.citycode,
adcode: addressComponent.adcode
};
};
// 获取完整的地址信息(通过经纬度)
const getAddressByLngLat = (lnglat) => {
returnnewPromise((resolve, reject) => {
if (!geocoder) {
reject(newError('地理编码器未初始化'));
return
}
geocoder.getAddress(lnglat, (status, result) => {
if (status === 'complete' && result.regeocode) {
const regeocode = result.regeocode;
const addressComponent = regeocode.addressComponent;
const addressInfo = {
formattedAddress: regeocode.formattedAddress,
...parseAddressComponent(addressComponent),
lat: lnglat.lat,
lng: lnglat.lng,
location: lnglat,
adcode: addressComponent.adcode,
citycode: addressComponent.citycode
};
// 添加道路信息
if (regeocode.roads && regeocode.roads.length > 0) {
addressInfo.road = regeocode.roads[0].name;
}
// 添加POI信息
if (regeocode.pois && regeocode.pois.length > 0) {
addressInfo.poi = regeocode.pois[0].name;
}
resolve(addressInfo);
} else {
reject(newError('获取地址信息失败'));
}
});
});
};
// 通过POI信息获取详细地址(用于搜索)
const getAddressByPoi = (poi) => {
returnnewPromise((resolve, reject) => {
if (!geocoder) {
reject(newError('地理编码器未初始化'));
return;
}
const lnglat = new AMap.LngLat(poi.location.lng, poi.location.lat);
geocoder.getAddress(lnglat, (status, result) => {
if (status === 'complete' && result.regeocode) {
const regeocode = result.regeocode;
const addressComponent = regeocode.addressComponent;
const addressInfo = {
formattedAddress: poi.address || regeocode.formattedAddress,
name: poi.name,
province: addressComponent.province,
city: addressComponent.city,
district: addressComponent.district,
township: addressComponent.township || '',
street: addressComponent.street || '',
building: poi.building || addressComponent.building || '',
lat: poi.location.lat,
lng: poi.location.lng,
location: lnglat,
adcode: addressComponent.adcode,
citycode: addressComponent.citycode,
pcode: addressComponent.pcode
};
resolve(addressInfo);
} else {
// 如果逆地理编码失败,至少返回POI的基本信息
resolve({
formattedAddress: poi.address || poi.name,
name: poi.name,
province: poi.pname || '',
city: poi.cityname || '',
district: poi.adname || '',
lat: poi.location.lat,
lng: poi.location.lng,
location: lnglat
});
}
});
});
};
// 搜索位置(支持全国)- 修复中心点问题
const handleSearch = async () => {
if (!searchKeyword.value.trim()) {
ElMessage.warning('请输入搜索关键字');
return;
}
if (!map || !isMapReady) {
ElMessage.warning('地图未初始化完成,请稍后重试');
return;
}
try {
const AMap = await AMapLoader.load({
key: '454c8e2f4a5acbed2215f8d85fba8188',
version: '2.0'
});
// 使用地点搜索服务进行全国搜索
const placeSearch = new AMap.PlaceSearch({
city: '全国',
citylimit: false,
pageSize: 1,
pageIndex: 1
});
ElMessage.info('正在搜索...');
placeSearch.search(searchKeyword.value, async (status, result) => {
if (status === 'complete' && result.poiList && result.poiList.pois.length > 0) {
const poi = result.poiList.pois[0];
const location = new AMap.LngLat(poi.location.lng, poi.location.lat);
console.log('搜索结果位置:', location);
// 关键修复:确保地图中心点正确移动到搜索结果
// 方法1:使用 setCenter 和 setZoom
map.setCenter(location, true); // 第二个参数表示是否使用动画
map.setZoom(15);
// 方法2:同时使用 panTo 确保平滑移动(可选)
// map.panTo(location)
// 强制刷新地图视图
setTimeout(() => {
map.setCenter(location);
}, 100);
// 获取详细地址信息
try {
const addressInfo = await getAddressByPoi(poi);
selectedAddress.value = addressInfo;
addMarker(location, addressInfo);
ElMessage.success(`找到:${poi.name}`);
} catch (error) {
console.error('获取地址详情失败:', error);
// 即使获取详情失败,也显示基本信息
selectedAddress.value = {
formattedAddress: poi.address || poi.name,
name: poi.name,
province: poi.pname || '',
city: poi.cityname || '',
district: poi.adname || '',
lat: poi.location.lat,
lng: poi.location.lng,
location: location
};
addMarker(location, poi.name);
ElMessage.warning('获取详细地址信息失败,已使用基本信息');
}
} else {
ElMessage.warning(`未找到"${searchKeyword.value}"相关的位置,请换个关键词试试`);
}
});
} catch (error) {
console.error('搜索失败:', error);
ElMessage.error('搜索失败,请稍后重试');
}
};
// 添加地图标记
const addMarker = (position, addressInfo) => {
if (marker) {
map.remove(marker);
}
const AMap = window.AMap;
if (!AMap) return;
const title = addressInfo.name || addressInfo.formattedAddress || '选中位置';
marker = new AMap.Marker({
position: position,
title: title,
draggable: true,
cursor: 'move',
raiseOnDrag: true,
bubble: true,
offset: new AMap.Pixel(-13, -30)
});
marker.on('dragend', async (e) => {
const lnglat = e.target.getPosition();
try {
const addressInfo = await getAddressByLngLat(lnglat);
selectedAddress.value = addressInfo;
updateMarkerContent(addressInfo);
ElMessage.success('已更新位置信息');
} catch (error) {
console.error('拖拽获取位置失败:', error);
ElMessage.error('获取位置信息失败');
}
});
marker.on('click', (e) => {
const content = `
<div style="padding: 12px; max-width: 300px;">
<div style="font-weight: bold; margin-bottom: 8px; color: #333;">${title}</div>
<div style="font-size: 12px; color: #666; line-height: 1.5;">${addressInfo.formattedAddress || ''}</div>
</div>
`
infoWindow.setContent(content);
infoWindow.open(map, position);
});
map.add(marker);
// 打开信息窗口
const content = `
<div style="padding: 12px; max-width: 300px;">
<div style="font-weight: bold; margin-bottom: 8px; color: #333;">${title}</div>
<div style="font-size: 12px; color: #666; line-height: 1.5;">${addressInfo.formattedAddress || ''}</div>
</div>
`
infoWindow.setContent(content);
infoWindow.open(map, position);
};
// 更新标记内容
const updateMarkerContent = (addressInfo) => {
if (marker) {
const title = addressInfo.name || addressInfo.formattedAddress || '选中位置';
marker.setTitle(title);
}
};
// 点击地图选点
const onMapClick = async (e) => {
const lnglat = e.lnglat;
console.log('点击地图位置:', lnglat);
try {
const addressInfo = await getAddressByLngLat(lnglat);
selectedAddress.value = addressInfo;
addMarker(lnglat, addressInfo);
ElMessage.success('已获取位置信息');
} catch (error) {
console.error('获取地址失败:', error);
ElMessage.error('获取位置信息失败');
}
};
// 初始化地图
const initMap = async () => {
if (map) {
map.destroy();
map = null;
isMapReady = false;
}
const container = document.getElementById('container');
if (!container) {
console.error('地图容器不存在');
return
}
try {
window._AMapSecurityConfig = {
securityJsCode: ''//自己高德的密钥
}
const AMap = await AMapLoader.load({
key: '', // 密钥对应的key
version: '2.0',
plugins: ["AMap.Scale", "AMap.ToolBar", "AMap.Geocoder", "AMap.PlaceSearch"]
})
map = new AMap.Map("container", {
zoom: 12,
center: [116.397428, 39.90923],
viewMode: "2D",
resizeEnable: true
});
// 添加工具条
map.addControl(new AMap.ToolBar({
position: 'RB'// 定位到右上角
}));
map.addControl(new AMap.Scale({
position: 'LB'// 定位到左下角
}));
// 初始化地理编码器
geocoder = new AMap.Geocoder({
city: "全国",
radius: 1000
});
// 初始化信息窗口
infoWindow = new AMap.InfoWindow({
offset: new AMap.Pixel(0, -30),
autoMove: true
});
// 添加点击地图事件
map.on('click', onMapClick);
// 地图加载完成标志
map.on('complete', () => {
isMapReady = true;
console.log('地图加载完成');
});
console.log('地图初始化成功');
} catch (error) {
console.error('地图加载失败:', error);
ElMessage.error('地图加载失败,请检查网络或配置');
}
};
// 确认选择
const handleConfirm = () => {
if (selectedAddress.value) {
emit('confirm', selectedAddress.value);
handleClose();
} else {
ElMessage.warning('请先选择一个位置');
}
};
// 关闭弹窗
const handleClose = () => {
dialogVisible.value = false;
selectedAddress.value = null;
searchKeyword.value = '';
isMapReady = false;
if (marker) {
map?.remove(marker);
marker = null;
}
};
// 组件卸载时销毁地图
onUnmounted(() => {
if (map) {
map.destroy();
map = null;
}
});
css 部分
<stylescoped>
.map-container {
position: relative;
width: 100%;
height: 600px;
}
.amap-container {
width: 100%;
height: 100%;
border-radius: 8px;
overflow: hidden;
}
.search-box {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 100;
width: 80%;
max-width: 500px;
min-width: 300px;
}
.search-input {
width: 100%;
box-shadow: 02px12px0rgba(0, 0, 0, 0.15);
}
.location-info {
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 16px20px;
box-shadow: 04px16px0rgba(0, 0, 0, 0.12);
backdrop-filter: blur(8px);
z-index: 100;
border-left: 4px solid #409eff;
max-height: 280px;
overflow-y: auto;
}
.info-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #e4e7ed;
}
.info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px16px;
}
.info-item {
font-size: 13px;
line-height: 1.6;
word-break: break-all;
}
.label {
color: #909399;
margin-right: 6px;
}
.value {
color: #303133;
font-weight: 500;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
/* 滚动条样式 */
.location-info::-webkit-scrollbar {
width: 6px;
}
.location-info::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.location-info::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.location-info::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 响应式调整 */
@media (max-width:768px) {
.map-container {
height: 500px;
}
.search-box {
width: 90%;
}
.info-grid {
grid-template-columns: 1fr;
gap: 8px;
}
.location-info {
padding: 12px16px;
}
.info-item {
font-size: 12px;
}
}
</style>
在页面使用
// 引入自己封装的文件 map
importMapfrom'@/components/map.vue';
<Mapv-model="modelValue":lng="initialLng":lat="initialLat" @confirm="handleLocationSelect"></Map>
// 初始化经纬度
const initialLng = ref();
const initialLat = ref();
// 方法
const handleLocationSelect = (location) => {
consloe.log(location) //返回信息 根据自己需求去获取赋值
};
夜雨聆风