乐于分享
好东西不私藏

pc端高德选择地址(可复制)

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: {
typeBoolean,
defaultfalse
  }
});

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'全国',
citylimitfalse,
pageSize1,
pageIndex1
    });

    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,
draggabletrue,
cursor'move',
raiseOnDragtrue,
bubbletrue,
offsetnew 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", {
zoom12,
center: [116.39742839.90923],
viewMode"2D",
resizeEnabletrue
    });

// 添加工具条
    map.addControl(new AMap.ToolBar({
position'RB'// 定位到右上角
    }));
    map.addControl(new AMap.Scale({
position'LB'// 定位到左下角
    }));

// 初始化地理编码器
    geocoder = new AMap.Geocoder({
city"全国",
radius1000
    });

// 初始化信息窗口
    infoWindow = new AMap.InfoWindow({
offsetnew AMap.Pixel(0-30),
autoMovetrue
    });

// 添加点击地图事件
    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;
width100%;
height600px;
}

.amap-container {
width100%;
height100%;
border-radius8px;
overflow: hidden;
}

.search-box {
position: absolute;
top20px;
left50%;
transformtranslateX(-50%);
z-index100;
width80%;
max-width500px;
min-width300px;
}

.search-input {
width100%;
box-shadow02px12px0rgba(0000.15);
}

.location-info {
position: absolute;
bottom20px;
left20px;
right20px;
backgroundrgba(2552552550.95);
border-radius12px;
padding16px20px;
box-shadow04px16px0rgba(0000.12);
backdrop-filterblur(8px);
z-index100;
border-left4px solid #409eff;
max-height280px;
overflow-y: auto;
}

.info-title {
font-size14px;
font-weight600;
color#303133;
margin-bottom12px;
padding-bottom8px;
border-bottom1px solid #e4e7ed;
}

.info-grid {
display: grid;
grid-template-columnsrepeat(21fr);
gap10px16px;
}

.info-item {
font-size13px;
line-height1.6;
word-break: break-all;
}

.label {
color#909399;
margin-right6px;
}

.value {
color#303133;
font-weight500;
}

.dialog-footer {
display: flex;
justify-content: flex-end;
gap12px;
}

/* 滚动条样式 */
.location-info::-webkit-scrollbar {
width6px;
}

.location-info::-webkit-scrollbar-track {
background#f1f1f1;
border-radius3px;
}

.location-info::-webkit-scrollbar-thumb {
background#c1c1c1;
border-radius3px;
}

.location-info::-webkit-scrollbar-thumb:hover {
background#a8a8a8;
}

/* 响应式调整 */
@media (max-width:768px) {
.map-container {
height500px;
  }

.search-box {
width90%;
  }

.info-grid {
grid-template-columns1fr;
gap8px;
  }

.location-info {
padding12px16px;
  }

.info-item {
font-size12px;
  }
}
</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) //返回信息  根据自己需求去获取赋值
};