起因
之前公司有个调用地图选址组件来实现快速定位的需求。
原本是一个很简单的需求,之前调用的是腾讯地图的选点组件,通过 iframe 内嵌调用,具体可以查看 腾讯地图 的官网,代码如下。一开始都是可以正常使用的,但是最近发现腾讯地图 api 会疯狂报错,提示 “您已关闭GPS,请在设置>隐私>位置里打开”,目前还没有找到解决方案。
<iframe id="mapView" width="100%" height="100%" frameborder=0 src="https://apis.map.qq.com/tools/locpicker?search=1&type=1&key=your key&referer=myapp"></iframe><script> window.addEventListener('message', function(event) { // 接收位置信息,用户选择确认位置点后选点组件会触发该事件,回传用户的位置信息 var loc = event.data; if (loc && loc.module == 'locationPicker') {//防止其他应用也会向该页面post信息,需判断module是否为'locationPicker' console.log('location', loc); } }, false);</script>
于是转战高德地图的选址组件,高德同样封装了一个组件可以供我们使用 iframe 内嵌调用,具体可以查看 高德地图 的官网,代码如下。但是这个选址组件功能过于简易,定位不会根据选址列表的选中而移动,由于是通过 iframe 内嵌使用,还不易修改代码功能,还是不满足既定需求。
<iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>
(function(){ var iframe = document.getElementById('test').contentWindow; document.getElementById('test').onload = function(){ iframe.postMessage('hello','https://m.amap.com/picker/'); }; window.addEventListener("message", function(e){ alert('您选择了:' + e.data.name + ',' + e.data.location) }, false);}())
最终决定,使用零碎的高德地图 api 来自己封装一个简易的选址组件。就目前而言,对于高德地图 api 的使用看到过三种方式:
引入 @amap/amap-jsapi-loader
引入原生的高德地图 api
引入 vue-amap
由于 vue-amap 是一套基于 Vue 2.0 和高德地图的地图组件且已经很久不维护了,所以不予考虑。
对于高德地图 api 三种方式的基本使用,我之前有一片文章 《vue+高德地图api基础实践》 已经非常详细讲解了,大家有兴趣可以参考一下,接下来就直接开始封装。
@amap/amap-jsapi-loader
@amap/amap-jsapi-loader 这个依赖是目前高德地图主流的一种使用方式。
1. 安装依赖并引入
pnpm install @amap/amap-jsapi-loader
然后在 components 下创建 Amap 组件并且引入之
import AMapLoader from "@amap/amap-jsapi-loader";
2. 初始化地图
使用 AMapLoader.load 来初始化渲染地图组件,使用 AMap.Map 类创建和展示地图对象。
let map;const mapConfigure = { amapKey: "", // 申请好的Web端开发者Key options: { resizeEnable: true, // 是否监控地图容器尺寸变化 center: [121.553958, 29.869472], // 初始地图中心点 zoom: 14, // 初始地图级别 }};onBeforeMount(() => { if (!instance) return; let { options } = MapConfigure; AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", plugins: [], AMapUI: { version: "1.1", plugins: [] } }) .then(AMap => { // 创建地图实例 map = new AMap.Map(instance.refs.mapView, options); }) .catch(() => { throw "地图加载失败,请重新加载"; });});
<template> <div class="map-container"> <div ref="mapView" class="map-view"></div> </div></template><style lang="scss" scoped>.map-container { background-color: #fff; width: 100%; height: 100vh; .map-view { position: relative; width: 100%; height: 50vh; position: fixed !important; top: 0; }}</style>
3. 使用 Geolocation 实现定位
初始化地图以后,可以使用高德地图提供的 Geolocation 插件来实现定位功能。
AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", plugins: [], AMapUI: { version: "1.1", plugins: [] }}) .then(AMap => { // 创建地图实例 map = new AMap.Map(instance.refs.mapView, options); map.plugin(["AMap.Geolocation"], () => { let geolocation = new AMap.Geolocation({ // 是否使用高精度定位,默认:true enableHighAccuracy: true, // 设置定位超时时间,默认:无穷大 timeout: 10000, // 定位按钮的停靠位置的偏移量,默认:Pixel(10, 20) buttonOffset: new AMap.Pixel(10, 20), // 定位成功后调整地图视野范围使定位位置及精度范围视野内可见,默认:false zoomToAccuracy: true, // 定位按钮的排放位置, RB表示右下 buttonPosition: "RB" }); map.addControl(geolocation); geolocation.getCurrentPosition(function (status, result) { if (status == "complete") { onComplete(result); } else { onError(result); } }); }); function onComplete(data) { map.setCenter(data.position); } function onError(error) { console.log("error", error); } }) .catch(() => { throw "地图加载失败,请重新加载"; });
4. 使用 PositionPicker 拖拽选址创建列表
PositionPicker(拖拽选址),用于在地图上选取位置,并获取所选位置的地址信息,以及周边POI、周边道路、周边路口等信息。
加载 PositionPicker(模块名:ui/misc/PositionPicker
)
AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", AMapUI: { version: "1.1", plugins: ["misc/PositionPicker"] // 需要加载的 AMapUI ui插件 }})
创建 PositionPicker 实例
let positionPicker = new AMapUI.PositionPicker({ mode: "dragMap", // 设定为拖拽地图模式,可选'dragMap'、'dragMarker',默认为'dragMap' map: map // 依赖地图对象});
绑定事件处理函数,获取选址列表(默认展示30条数据)
<iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>0
开启拖拽选址
<iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>1
渲染选址列表
<iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>2
注意:如果控制台报错:INVALID_USER_SCODE
,只需要添加一下高德安全密钥,安全密钥是和 key 一起申请的
<iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>3
5. 使用 PoiPicker 实现搜索功能
PoiPicker(POI选点)在给定的输入框上集成输入提示和关键字搜索功能,方便用户选取特定地点(即POI)。
加载 PoiPicker(模块名:ui/misc/PoiPicker
)
<iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>4
创建 PoiPicker 实例,参考官方文档:https://developer.amap.com/api/amap-ui/reference-amap-ui/other/poipicker
<iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>5
渲染搜索框组件
<iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>6
<iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>7
7. 完整代码
<script setup lang="ts">import AMapLoader from "@amap/amap-jsapi-loader";import { getCurrentInstance, onBeforeMount, onUnmounted, ref } from "vue";interface MapConfigOption { resizeEnable?: boolean; center?: number[]; zoom?: number;}interface MapConfigure { amapKey: string; options: MapConfigOption;}interface MapConfigureInter { on?: Fn; destroy?: Fn; clearEvents?: Fn; addControl?: Fn; getCenter?: Fn; setCenter?: Fn; setZoom?: Fn; plugin?: Fn;}let map;const mapConfigure = { amapKey: "key", options: { resizeEnable: true, // center: [121.553958, 29.869472], zoom: 16 }};window._AMapSecurityConfig = { securityJsCode: "安全密钥"};const instance = getCurrentInstance();const addressList = ref<any[]>([]);const sureAddress = (data: any) => { map.setCenter(data.location); addressList.value = [];};onBeforeMount(() => { if (!instance) return; let { options } = mapConfigure; AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", plugins: [], AMapUI: { version: "1.1", plugins: ["misc/PositionPicker", "misc/PoiPicker"] } }) .then(AMap => { map = new AMap.Map(instance.refs.mapView, options); map.plugin(["AMap.Geolocation"], () => { let geolocation = new AMap.Geolocation({ enableHighAccuracy: true, timeout: 10000, buttonOffset: new AMap.Pixel(10, 20), zoomToAccuracy: true, buttonPosition: "RB" }); map.addControl(geolocation); geolocation.getCurrentPosition(function (status: any, result: unknown) { if (status == "complete") { onComplete(result); } else { onError(result); } }); }); function onComplete(data: any) { map.setCenter(data.position); } function onError(error: unknown) { console.log("error", error); } let positionPicker = new AMapUI.PositionPicker({ mode: "dragMap", map: map }); positionPicker.on("success", function (positionResult: any) { addressList.value = positionResult.regeocode.pois; }); positionPicker.on("fail", function (positionResult: any) { console.log("positionResult", positionResult); }); <iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>1 let poiPicker = new AMapUI.PoiPicker({ input: instance.refs.pickerInput }); poiPicker.on("poiPicked", function (poiResult: any) { map.setCenter(poiResult.item.location); }); }) .catch(() => { throw "地图加载失败,请重新加载"; });});onUnmounted(() => { if (map) { // 销毁地图实例 map.destroy() && map.clearEvents("click"); }});</script>(function(){ var iframe = document.getElementById('test').contentWindow; document.getElementById('test').onload = function(){ iframe.postMessage('hello','https://m.amap.com/picker/'); }; window.addEventListener("message", function(e){ alert('您选择了:' + e.data.name + ',' + e.data.location) }, false);}())2<style lang="scss" scoped>.map-container { background-color: #fff; width: 100%; height: 100vh; .map-view { position: relative; width: 100%; height: 50vh; position: fixed !important; top: 0; } input { width: 100%; height: 47px; padding: 8px 15px; position: fixed; z-index: 10; top: 0; outline: none; border: 1px solid #d3d3d3; border-radius: 5px; background-color: #fff; } .address-wrapper { padding-top: 50vh; .address-list { position: relative; height: 50vh; overflow: auto; &-item { font-size: 12px; padding: 6px 12px; border-bottom: 1px solid #e8e8e8; p:first-child { color: #333; font-size: 13px; } p:last-child { color: #666; } } } }}</style>
引入原生的高德 api
接下来使用原生的高德地图 api + vue2 也来浅试一下封装一个选址组件,代码其实也大同小异。
1. 初始化地图
在 components 下创建 Amap 组件,使用 \<script> 标签导入高德地图 api。
<iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>9
2. 使用 Geolocation 实现定位
(function(){ var iframe = document.getElementById('test').contentWindow; document.getElementById('test').onload = function(){ iframe.postMessage('hello','https://m.amap.com/picker/'); }; window.addEventListener("message", function(e){ alert('您选择了:' + e.data.name + ',' + e.data.location) }, false);}())0
3. 使用 PoiPicker 和 PositionPicker
initMap() { map = new AMap.Map(this.$refs.mapView, { resizeEnable: true, zoom: 16, }); this.loadPositionPicker(); this.loadPoiPicker(); this.getLocation();},loadPositionPicker() { let that = this; AMapUI.loadUI(["misc/PositionPicker"], function (PositionPicker) { let positionPicker = new PositionPicker({ mode: "dragMap", map: map, }); positionPicker.on("success", function (positionResult) { console.log("success", positionResult); that.addressList = positionResult.regeocode.pois; }); positionPicker.on("fail", function (positionResult) { console.log("fail", positionResult); }); <iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>1 });},loadPoiPicker() { let that = this; AMapUI.loadUI(["misc/PoiPicker"], function (PoiPicker) { let poiPicker = new PoiPicker({ input: that.$refs.pickerInput, }); poiPicker.on("poiPicked", function (poiResult) { map.setCenter(poiResult.item.location); }); });},sureAddress(data) { map.setCenter(data.location); this.addressList = [];},
(function(){ var iframe = document.getElementById('test').contentWindow; document.getElementById('test').onload = function(){ iframe.postMessage('hello','https://m.amap.com/picker/'); }; window.addEventListener("message", function(e){ alert('您选择了:' + e.data.name + ',' + e.data.location) }, false);}())2
4. 完整代码
(function(){ var iframe = document.getElementById('test').contentWindow; document.getElementById('test').onload = function(){ iframe.postMessage('hello','https://m.amap.com/picker/'); }; window.addEventListener("message", function(e){ alert('您选择了:' + e.data.name + ',' + e.data.location) }, false);}())2<script>let map = null;export default { name: "map-view", data() { return { addressList: [], }; }, mounted() { const amap_key = '', cb = "amap_callback"; const scriptUrl = `https://webapi.amap.com/maps?v=1.4.18&key=${amap_key}&callback=${cb}`; const mapuiUrl = "https://webapi.amap.com/ui/1.0/main.js?v=1.0.11"; // 导入script importScript(scriptUrl); importScript(mapuiUrl); window[cb] = () => { this.initMap(); }; }, methods: { initMap() { map = new AMap.Map(this.$refs.mapView, { resizeEnable: true, zoom: 15, // center: [121.553958, 29.869472], }); this.loadPositionPicker(); this.loadPoiPicker(); this.getLocation(); }, loadPositionPicker() { let that = this; AMapUI.loadUI(["misc/PositionPicker"], function (PositionPicker) { let positionPicker = new PositionPicker({ mode: "dragMap", map: map, }); positionPicker.on("success", function (positionResult) { console.log("success", positionResult); that.addressList = positionResult.regeocode.pois; }); positionPicker.on("fail", function (positionResult) { console.log("fail", positionResult); }); <iframe id="mapView" width="100%" height="100%" src="https://m.amap.com/picker/?key=your key"></iframe>1 }); }, loadPoiPicker() { let that = this; AMapUI.loadUI(["misc/PoiPicker"], function (PoiPicker) { let poiPicker = new PoiPicker({ input: that.$refs.pickerInput, }); poiPicker.on("poiPicked", function (poiResult) { map.setCenter(poiResult.item.location); }); }); }, getLocation() { map.plugin("AMap.Geolocation", function () { let geolocation = new AMap.Geolocation({ enableHighAccuracy: true, timeout: 10000, buttonOffset: new AMap.Pixel(10, 20), zoomToAccuracy: true, buttonPosition: "RB", }); map.addControl(geolocation); geolocation.getCurrentPosition(function (status, result) { if (status == "complete") { onComplete(result); } else { onError(result); } }); }); function onComplete(data) { map.setCenter(data.position); } function onError(error) { console.log("error", error); } }, sureAddress(data) { map.setCenter(data.location); this.addressList = []; }, },};function importScript(sSrc, success) { function loadError(err) { throw new URIError("The script " + err.target.src + " is not accessible."); } var oScript = document.createElement("script"); oScript.type = "text\/javascript"; oScript.onerror = loadError; if (success) oScript.onload = success; document.body.appendChild(oScript); oScript.src = sSrc;}</script><style lang="scss" scoped>.map-container { background-color: #fff; width: 100%; height: 100vh; .map-view { position: relative; width: 100%; height: 50vh; position: fixed !important; top: 0; } input { width: 100%; height: 47px; padding: 8px 15px; position: fixed; z-index: 10; top: 0; outline: none; border: 1px solid #d3d3d3; border-radius: 5px; background-color: #fff; } .address-wrapper { padding-top: 50vh; .address-list { position: relative; height: 50vh; overflow: auto; &-item { font-size: 12px; padding: 6px 12px; border-bottom: 1px solid #e8e8e8; p:first-child { color: #333; font-size: 13px; } p:last-child { color: #666; } } } }}</style>
写在最后
其实地图组件的坑实在是太多了,还是需要继续学习不断的探索。
如果你也对地图组件有兴趣的话,欢迎大家一起来交流学习。
原文:https://juejin.cn/post/7099272711239073822