news 2026/4/18 10:50:10

Vue2实现PC端高德地图选点功能

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue2实现PC端高德地图选点功能

效果图

一、功能概述

基于 Vue2 + 高德地图 JS API 2.0 实现 PC 端地址选点功能,支持定位当前位置、关键词搜索地址、地图点击选点、地址信息回显,采用父子组件分离设计,子组件封装地图核心能力,父组件通过弹窗调用并接收选点结果。

二、核心架构

1. 组件分层

组件类型作用核心交互
子组件(MapComponent)封装高德地图初始化、搜索、选点、定位等核心能力向父组件派发confirmAddress事件传递选点结果
父组件提供弹窗容器、地址临时存储、回显逻辑,触发选点流程通过center属性向子组件传递初始定位中心,监听confirmAddress接收选点结果

2. 技术依赖

  • 基础框架:Vue2
  • UI 组件:Element UI(弹窗、按钮、输入框等)
  • 地图能力:高德地图 JS API 2.0(需配置keysecurityJsCode
  • 核心能力:定位(Geolocation)、地址搜索(PlaceSearch)、逆地理编码(Geocoder)

三、核心功能实现

1. 地图初始化、搜索、选点

<template> <div class="map-container"> <!-- 搜索框 --> <div class="search-box"> <el-input v-model="searchKey" placeholder="输入地点关键词" @keyup.enter="handleSearch" > <template #append> <el-button icon="el-icon-search" @click="handleSearch" /> </template> </el-input> </div> <!-- 地图容器 --> <div id="map-container" style="height: 400px; width: 100%"></div> <!-- 选点信息 --> <div v-if="selectedLocation" class="location-info"> <div> <h3>选点位置信息</h3> <p>地址:{{ selectedLocation.address }}</p> <p>经纬度:{{ selectedLocation.position.join(', ') }}</p> </div> <el-button style="height: 40px;" size="mini" type="primary" @click.native="handleConfirm">确认选点</el-button> </div> <!-- 搜索结果列表 --> <div v-if="searchResults.length" class="result-list"> <div v-for="(item, index) in searchResults" :key="item.id" class="result-item" :class="{ 'selected-item': selectedResultIndex === index }" @click="handleResultClick(index)" > <div class="title">{{ item.name }}</div> <div class="address">{{ item.address }}</div> </div> </div> </div> </template> <script> export default { props: { center: { type: String, default: '' } }, data() { return { map: null, placeSearch: null, geocoder: null, searchKey: '', searchResults: [], markers: [], infoWindow: null, selectedLocation: null, selectedResultIndex: -1, clickMarker: null, chooseInfo:{ latitude: 0, longitude: 0, address: '' } }; }, mounted() { this.initMap(); }, methods: { initMap() { const key = '高德开放平台key'; window._AMapSecurityConfig = { securityJsCode: '高德开放平台密钥' }; const script = document.createElement('script'); script.src = `https://webapi.amap.com/maps?v=2.0&key=${key}`; script.onload = () => { this.$nextTick(() => { // 检查地图容器是否存在 const container = document.getElementById('map-container'); if (!container) { console.error('地图容器不存在'); return; } // 初始化地图 this.map = new AMap.Map('map-container', { zoom: 13, center: this.center ? this.center.split(',') : [] }); /** * 定位当前位置,并将地图中心点移动到定位点 */ console.log("地图中心",this.center); AMap.plugin('AMap.Geolocation', ()=> { var geolocation = new AMap.Geolocation({ enableHighAccuracy: true, // 是否使用高精度定位 timeout: 10000, // 定位超时时间 zoomToAccuracy: true, // 定位成功后自动调整地图视野 showMarker: true, // 定位成功后显示标记 showCircle: true, // 显示定位精度范围 panToLocation: true // 定位成功后将地图中心移动到定位点 }); this.map.addControl(geolocation); // 将定位控件添加到地图 geolocation.getCurrentPosition((status, result) => { if (status === 'complete') { console.log('定位成功', result); this.map.setCenter(result.position); } else { console.error('定位失败', result); } }); }); // 初始化地理编码服务 AMap.plugin('AMap.Geocoder', ()=> { this.geocoder = new AMap.Geocoder({ city: "全国", radius: 1000 }); }); // 初始化地点搜索 AMap.plugin(['AMap.PlaceSearch', 'AMap.AutoComplete'], () => { this.placeSearch = new AMap.PlaceSearch({ pageSize: 10, pageIndex: 1, city: '全国', //加上map即标记搜索结果 map: this.map, autoFitView: true, // 自动调整视野 children: 1, // 显示子级POI extensions: 'base' // 返回基本信息 }); // 绑定事件 AMap.Event.addListener(this.placeSearch, 'complete', this.handleSearchComplete); AMap.Event.addListener(this.placeSearch, 'error', this.handleSearchError); }); // 绑定地图点击事件 this.map.on('click', this.handleMapClick); }); }; document.head.appendChild(script); }, /** * 点击搜索后触发 */ handleSearch() { if (!this.searchKey.trim()) return; // 检查placeSearch是否已初始化 if (!this.placeSearch) { console.error('placeSearch未初始化'); this.$message.error('地图搜索功能未初始化完成,请稍后重试'); return; } this.clearMarkers(); this.selectedResultIndex = -1; try { const searchOptions = { pageSize: 10, pageIndex: 1, city: '全国', extensions: 'base' }; this.placeSearch.search(this.searchKey, searchOptions, (status, result) => { if (status === 'complete') { this.handleSearchComplete(result); } else if (status === 'error') { this.handleSearchError(result); } else { console.warn('未知搜索状态:', status); this.searchResults = []; } }); } catch (error) { console.error('搜索执行出错', error); this.$message.error('搜索执行失败,请检查控制台日志'); } }, handleSearchComplete(data) { if (data && data.poiList && data.poiList.pois) { this.searchResults = data.poiList.pois.map(poi => ({ id: poi.id, name: poi.name, address: poi.address, location: poi.location, distance: Math.round(poi.distance || 0) })); } else { this.searchResults = []; console.warn('搜索结果为空或格式不正确'); } }, handleSearchError(error) { console.error('搜索失败', error); console.error('错误详情:', { info: error.info, message: error.message, type: error.type }); this.searchResults = []; // 根据错误类型显示不同提示 if (error.info === 'INVALID_USER_SCODE') { this.$message.error('API密钥验证失败,请检查密钥配置'); } else if (error.info === 'INVALID_PARAMS') { this.$message.error('搜索参数错误,请重新输入'); } else if (error.info === 'TOO_FREQUENT') { this.$message.error('请求过于频繁,请稍后重试'); } else { this.$message.error(`搜索失败: ${error.info || '未知错误'}`); } }, handleResultClick(index) { this.selectedResultIndex = index; const target = this.searchResults[index]; this.map.setCenter(target.location); this.addMarker(target, true); this.showInfoWindow(target); console.log('选中的地点:', target); //触发地图点击事件 this.handleMapClick({lnglat:target.location}); }, handleMapClick(e) { this.clearMarkers(); this.selectedResultIndex = -1; this.selectedLocation = null; // 添加点击标记 this.addClickMarker(e.lnglat); // 逆地理编码 this.geocoder.getAddress(e.lnglat, (status, result) => { if (status === 'complete' && result.regeocode) { this.selectedLocation = { position: [e.lnglat.lng, e.lnglat.lat], address: result.regeocode.formattedAddress }; } this.map.setCenter([e.lnglat.lng, e.lnglat.lat]); }); }, addMarker(poi, isSearchResult = false) { const marker = new AMap.Marker({ position: poi.location, content: isSearchResult ? '<div class="result-marker">📍</div>' : '<div class="click-marker">📍</div>', offset: new AMap.Pixel(-10, -30) }); marker.on('click', () => { this.showInfoWindow(poi); }); this.map.add(marker); this.markers.push(marker); }, addClickMarker(lnglat) { this.clickMarker = new AMap.Marker({ position: lnglat, content: '<div class="click-marker">📍</div>', offset: new AMap.Pixel(-10, -30) }); this.map.add(this.clickMarker); this.markers.push(this.clickMarker); }, showInfoWindow(poi) { if (this.infoWindow) this.infoWindow.close(); this.infoWindow = new AMap.InfoWindow({ content: `<div class="info-window"> <h4>${poi.name}</h4> <p>${poi.address}</p> ${poi.distance ? `<p>距离:${poi.distance}米</p>` : ''} </div>`, offset: new AMap.Pixel(0, -30) }); this.infoWindow.open(this.map, poi.location); }, clearMarkers() { // 清除所有标记 this.markers.forEach(marker => this.map.remove(marker)); this.markers = []; if (this.clickMarker) { this.map.remove(this.clickMarker); this.clickMarker = null; } }, //确认选点 handleConfirm() { this.$emit('confirmAddress', { address: this.selectedLocation.address, lat: this.selectedLocation.position[1], lng: this.selectedLocation.position[0], }); } }, beforeDestroy() { if (this.map) { this.map.destroy(); this.map = null; } }, }; </script> <style scoped> .map-container { /* display: flex; flex-direction: column; */ } .search-box { padding: 10px 0 20px; background: #fff; z-index: 999; } #map-container { /* flex: 1; min-height: 400px; */ } .location-info { display: flex; align-items: center; justify-content: space-between; padding: 6px; background: #f8f9fa; border-top: 1px solid #eee; } .result-list { height: 300px; overflow-y: auto; background: white; box-shadow: 0 -2px 8px rgba(0,0,0,0.05); } .result-item { padding: 6px; border-bottom: 1px solid #eee; cursor: pointer; transition: all 0.2s; } .result-item:hover { background: #f8f9fa; } .selected-item { background: #e6f7ff !important; border-left: 4px solid #1890ff; } .title { font-weight: 500; color: #333; margin-bottom: 4px; } .address { color: #666; font-size: 0.9em; margin-bottom: 4px; } .distance { color: #1890ff; font-size: 0.85em; } /* 信息窗口样式 */ .info-window { padding: 8px; min-width: 200px; } .info-window h4 { margin: 0 0 8px; color: #333; } .info-window p { margin: 4px 0; color: #666; } /* 标记样式 */ .result-marker { font-size: 24px; color: #1890ff; text-shadow: 0 2px 4px rgba(24,144,255,0.3); } .click-marker { font-size: 24px; color: #ff4d4f; text-shadow: 0 2px 4px rgba(255,77,79,0.3); } </style>

2. 父组件交互逻辑

  • 地图选择弹窗
<!-- 地图选择对话框 --> <el-dialog title="选择地址" :visible.sync="mapDialogVisible" width="800px" append-to-body :before-close="cancelMapSelect" :close-on-click-modal="false" class="map-dialog"> <div class="dialog-content"> <div class="selected-address" v-if="tempAddress.address"> <div class="address-details"> <div><strong>地址信息:</strong>{{ tempAddress.address }}</div> </div> </div> <div v-else class="no-address">请选择或搜索位置</div> <MapComponent v-if="mapDialogVisible" :center="mapCenter" @confirmAddress="handleMapChoose" /> </div> </el-dialog>
  • 数据初始化
data() { return { mapDialogVisible: false, currentItem: null, tempAddress: { address: '', lng: '', lat: '' } }; }, computed: { mapCenter() { if (this.currentItem) { const lng = this.currentItem.longitude || ''; const lat = this.currentItem.latitude || ''; return `${lng},${lat}`; } return ''; } },
  • 触发事件
methods: { // 类型选择 changeJumpType(e, item) { item.functionType = ""; item.content = null; item.appid = null; item.jumpUrl = null; item.jumpMethod = 0; item.jumpLinkType = 0; item.jumpLinkId = null; console.log(e, item); }, addAddress(item) { this.currentItem = item; if (item && item.address) { this.tempAddress = { address: item.content || item.address, lng: item.longitude || '', lat: item.latitude || '' }; } else { this.tempAddress = { address: '', lng: '', lat: '' }; } this.mapDialogVisible = true; }, // 处理地图选择(实时更新临时地址) handleMapChoose(mapData) { console.log('地图选择:', mapData); this.tempAddress = { address: mapData.address || '', lng: mapData.lng || '', lat: mapData.lat || '', url: mapData.url || '' }; // 更新当前项的数据 if (this.currentItem) { this.currentItem.content = this.tempAddress.address; this.currentItem.address = this.tempAddress.address; this.currentItem.longitude = this.tempAddress.lng; this.currentItem.latitude = this.tempAddress.lat; // 更新表单数据,确保响应式 this.$set(this.currentItem, 'content', this.tempAddress.address); this.$set(this.currentItem, 'address', this.tempAddress.address); this.$set(this.currentItem, 'longitude', this.tempAddress.lng); this.$set(this.currentItem, 'latitude', this.tempAddress.lat); console.log('地址已确认:', this.currentItem); this.$message.success('地址选择成功'); this.mapDialogVisible = false; // 触发表单验证更新 this.$forceUpdate(); } }, },
  • 样式
.dialog-content { .selected-address { padding: 10px; background-color: #f5f7fa; border-radius: 4px; margin-bottom: 10px; .address-details { div { margin-bottom: 5px; font-size: 14px; &:last-child { margin-bottom: 0; } } } } .no-address { padding: 20px; text-align: center; color: #999; background-color: #f5f7fa; border-radius: 4px; margin-bottom: 10px; } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 3:16:30

AMD GPU在AI应用中的完整配置指南:从环境搭建到性能优化

AMD GPU在AI应用中的完整配置指南&#xff1a;从环境搭建到性能优化 【免费下载链接】ROCm AMD ROCm™ Software - GitHub Home 项目地址: https://gitcode.com/GitHub_Trending/ro/ROCm 作为一名AI开发者&#xff0c;当你手握强大的AMD GPU却在使用ComfyUI等AI应用时遇…

作者头像 李华
网站建设 2026/4/18 2:21:46

DiT注意力机制:Transformer如何重塑扩散模型的图像生成范式

DiT注意力机制&#xff1a;Transformer如何重塑扩散模型的图像生成范式 【免费下载链接】DiT Official PyTorch Implementation of "Scalable Diffusion Models with Transformers" 项目地址: https://gitcode.com/GitHub_Trending/di/DiT 当传统扩散模型在高…

作者头像 李华
网站建设 2026/4/18 5:03:13

云Agent访问失控?你必须知道的7个AZ-500防护要点,99%的人忽略了第3条

第一章&#xff1a;云Agent访问失控的根源剖析在现代云原生架构中&#xff0c;云Agent作为连接计算实例与管理控制平面的核心组件&#xff0c;承担着配置下发、状态上报和远程执行等关键职责。然而&#xff0c;随着微服务规模扩张和跨云部署的普及&#xff0c;云Agent的访问权限…

作者头像 李华
网站建设 2026/4/18 3:55:15

3步掌握MobilePerf:告别安卓性能测试的迷茫与低效

3步掌握MobilePerf&#xff1a;告别安卓性能测试的迷茫与低效 【免费下载链接】mobileperf Android performance test 项目地址: https://gitcode.com/gh_mirrors/mob/mobileperf 还在为安卓应用的性能问题而头疼吗&#xff1f;卡顿、内存泄漏、CPU过载这些性能瓶颈往往…

作者头像 李华
网站建设 2026/4/18 5:12:55

规范驱动开发(SDD)主流工具与框架深度解析

规范驱动开发&#xff08;SDD&#xff09;主流工具与框架深度解析 1. 规范驱动开发概述 规范驱动开发 (Spec-Driven Development, SDD) 是AI辅助编码流程中的一种新兴方法。其核心理念是在编写代码之前&#xff0c;优先编写结构化的功能规范文档&#xff0c;并将此规范作为开发…

作者头像 李华
网站建设 2026/4/18 5:12:53

IDM永久免费使用攻略:3步搞定激活与试用冻结

IDM永久免费使用攻略&#xff1a;3步搞定激活与试用冻结 【免费下载链接】IDM-Activation-Script IDM Activation & Trail Reset Script 项目地址: https://gitcode.com/gh_mirrors/id/IDM-Activation-Script 还在为IDM试用期到期而烦恼吗&#xff1f;想要永久免费使…

作者头像 李华