当前位置: 首页 > news >正文

vue3 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

布局结构

<template> <el-dialog title="选择位置" v-model="dialogVisible" width="70%" @close="handleClose" :close-on-click-modal="false" destroy-on-close > <div class="map-container"> <div id="container" class="amap-container"></div> <div class="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> <div class="location-info" v-if="selectedAddress"> <div class="info-title">📍 当前位置信息</div> <div class="info-grid"> <div class="info-item"> <span class="label">详细地址:</span> <span class="value">{{ selectedAddress.formattedAddress || '--' }}</span> </div> <div class="info-item"> <span class="label">省:</span> <span class="value">{{ selectedAddress.province || '--' }}</span> </div> <div class="info-item"> <span class="label">市:</span> <span class="value">{{ selectedAddress.city || '--' }}</span> </div> <div class="info-item"> <span class="label">区/县:</span> <span class="value">{{ selectedAddress.district || '--' }}</span> </div> <div class="info-item"> <span class="label">街道/乡镇:</span> <span class="value">{{ selectedAddress.township || '--' }}</span> </div> <div class="info-item"> <span class="label">道路:</span> <span class="value">{{ selectedAddress.street || '--' }}</span> </div> <div class="info-item"> <span class="label">建筑:</span> <span class="value">{{ selectedAddress.building || '--' }}</span> </div> <div class="info-item"> <span class="label">经纬度:</span> <span class="value">{{ selectedAddress.lng?.toFixed(6) }}, {{ selectedAddress.lat?.toFixed(6) }}</span> </div> </div> </div> </div> <template #footer> <span class="dialog-footer"> <el-button @click="handleClose">取消</el-button> <el-button type="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) => { return new Promise((resolve, reject) => { if (!geocoder) { reject(new Error('地理编码器未初始化')); 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(new Error('获取地址信息失败')); } }); }); }; // 通过POI信息获取详细地址(用于搜索) const getAddressByPoi = (poi) => { return new Promise((resolve, reject) => { if (!geocoder) { reject(new Error('地理编码器未初始化')); 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 结构

<style scoped> .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: 0 2px 12px 0 rgba(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: 16px 20px; box-shadow: 0 4px 16px 0 rgba(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: 10px 16px; } .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: 12px 16px; } .info-item { font-size: 12px; } } </style>

在页面使用

// 引入自己封装的文件 map import Map from '@/components/map.vue'; <Map v-model="modelValue" :lng="initialLng" :lat="initialLat" @confirm="handleLocationSelect"></Map> // 初始化经纬度 const initialLng = ref(); const initialLat = ref(); // 方法 const handleLocationSelect = (location) => { consloe.log(location) //返回信息 根据自己需求去获取赋值 };
http://www.jsqmd.com/news/1146586/

相关文章:

  • Java毕设选题推荐:基于 SpringBoot 的在线学习资源共享平台的设计与实现 基于 SpringBoot+Vue 的网课学习进度管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 企业级大模型网关选型的取舍之道:2026年六大API中转平台核心考量因素剖析
  • 为什么雨天施工更需要全防水安全鞋?老师傅分享真实经验!
  • python [] Python脚本秒变可执行?这招shebang甩开命令行十条街
  • 【Bug已解决】Git operation failed / husky pre-commit hook blocked — Claude Code Git 操作被钩子拦截解决方案
  • 单县女装店测评指南 看面料性价比避开高价踩坑
  • 原来市面上这些热门的谷歌SEO优化公司都有啥门道?
  • 【工控与机器人干货】一文彻底搞懂关节空间与笛卡尔空间:Joint、Base、TCP 运动底层逻辑全解
  • 远程开发 Docker 环境标准化:让本地配置不再随电脑迁移丢失
  • 2026图片更换背景底色全平台实操指南:手机,APP、电脑,PS、在线小程序完整制作教程
  • 新能源汽车电机技术解析:可变磁通与轴向磁通电机对比
  • 终极文档下载方案:kill-doc如何让你看到多少就能下载多少
  • 3步掌握RPG Maker资源解密:浏览器端的革命性解决方案
  • RPG-Maker-MV-Decrypter:三步掌握游戏资源提取与解密的终极指南
  • Paperxie 论文改写专区:四类定制化文本优化方案,搞定重复率与 AIGC 双重检测关卡
  • 法律服务需求持续增长,这些平台如何帮助当事人找到合适的律师
  • Oracle新老SQL写法对比
  • IPC机制如何调整和控制权限
  • Windows热键冲突终极解决方案:5分钟精准定位占用快捷键的程序
  • GPT训练我的第三天,明白了应该咋说满分回答!
  • LiteLLM 接入 Claude API 排障完全指南:anthropic-beta、Streaming、路由与 MCP OAuth(2026)
  • 费用报销审核和财务稽核能用AI吗?深度拆解AI Agent在企业财务自动化中的落地实践与选型指南
  • G-Helper:华硕笔记本终极轻量级控制中心,告别臃肿官方软件
  • 收藏!小白程序员必看:大模型时代如何转型全栈工程师?
  • 2026年API中转平台选型:从高并发到白盒计费的全面拆解
  • 如何快速获取百度网盘提取码:面向新手的完整指南
  • 拼多多虚拟类目自动化发货,零人工操作轻松盈利
  • 哔哩下载姬技术解析:逆向工程与平台合规的边界探索
  • 被问爆的物流降本方案!用“一盘货+分仓”,帮知名品牌降本20%+
  • 京东抢购自动化终极指南:3分钟上手JDspyder实现秒杀自由