news 2026/8/28 3:54:28

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/8/28 19:33:25

GSE2宏编译器:魔兽世界玩家的终极技能序列管理方案

GSE2宏编译器&#xff1a;魔兽世界玩家的终极技能序列管理方案 【免费下载链接】GSE-Advanced-Macro-Compiler GSE is an alternative advanced macro editor and engine for World of Warcraft. It uses Travis for UnitTests, Coveralls to report on test coverage and the …

作者头像 李华
网站建设 2026/8/26 5:55:30

揭秘VSCode量子开发环境崩溃原因:3个关键修复步骤助你高效重启项目

第一章&#xff1a;VSCode量子开发环境崩溃现象解析在构建基于量子计算的开发工作流时&#xff0c;VSCode 作为主流编辑器被广泛集成于 Q#、Qiskit 等框架中。然而&#xff0c;开发者频繁报告其在加载大型量子电路项目或启用特定扩展后出现无响应、自动重启甚至进程终止的现象。…

作者头像 李华
网站建设 2026/8/27 0:09:42

子树节点-–-behaviac

原文 子树的介绍 通过子树节点&#xff0c;一个行为树可以作为另一个行为树的子树。作为子树的那个行为树被“调用”。如同一个动作节点一样&#xff0c;子树节点根据子树的执行结果也会返回执行结果&#xff08;成功、失败或运行。&#xff09;&#xff0c;其父节点按照自己…

作者头像 李华
网站建设 2026/8/28 23:01:35

【量子电路可视化进阶指南】:掌握缩放功能的5大核心技巧

第一章&#xff1a;量子电路可视化的缩放功能概述在量子计算领域&#xff0c;随着量子电路复杂度的提升&#xff0c;可视化工具必须支持灵活的缩放功能&#xff0c;以便研究人员和开发者能够高效地观察与分析电路结构。缩放功能不仅影响用户对整体电路布局的把握&#xff0c;还…

作者头像 李华
网站建设 2026/8/27 23:22:45

Video Download Helper 高级版 - 无120分钟时间限制

Video Download Helper 高级版 - 无120分钟时间限制 【免费下载链接】VideoDownloadHelper高级版-无120分钟时间限制 本仓库提供了一个名为 VideoDownloadHelper去除120分钟时间限制-高级版.zip 的资源文件。该文件是 Video Download Helper 的高级版&#xff0c;去除了原有的1…

作者头像 李华