起因
之前公司有个调用地图选址组件来实现快速定位的需求。
原本是一个很简单的需求,之前调用的是腾讯地图的选点组件,通过 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条数据)
positionPicker.on("success", function (positionResult: any) { console.log("success", positionResult); addressList.value = positionResult.regeocode.pois; }); positionPicker.on('fail', function(positionResult) { console.log("fail", positionResult); });
-
开启拖拽选址
positionPicker.start();
-
渲染选址列表
<template> <div class="map-container"> <div ref="mapView" class="map-view"></div> <div class="address-wrapper"> <div class="address-list"> <div class="address-list-item" v-for="item in addressList" :key="item.id" @click="sureAddress(item)" > <p>{{ item.name }}</p> <p>{{ item.address }}</p> </div> </div> </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; } .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>
-
注意:如果控制台报错:
INVALID_USER_SCODE
,只需要添加一下高德安全密钥,安全密钥是和 key 一起申请的window._AMapSecurityConfig = { securityJsCode: "你的安全密钥" };
5. 使用 PoiPicker 实现搜索功能
PoiPicker(POI选点)在给定的输入框上集成输入提示和关键字搜索功能,方便用户选取特定地点(即POI)。
-
加载 PoiPicker(模块名:
ui/misc/PoiPicker
)AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", AMapUI: { version: "1.1", plugins: ["misc/PoiPicker"] // 需要加载的 AMapUI ui插件 } })
-
创建 PoiPicker 实例,参考官方文档:developer.amap.com/api/amap-ui…
AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", AMapUI: { version: "1.1", plugins: ["misc/PoiPicker"] } }).then(AMap => { ………… let poiPicker = new AMapUI.PoiPicker({ input: instance.refs.pickerInput // 输入框id }); // 监听poi选中信息 poiPicker.on("poiPicked", function (poiResult) { // 用户选中的poi点信息 map.setCenter(poiResult.item.location); }); }) .catch(() => { throw "地图加载失败,请重新加载"; });
-
渲染搜索框组件
<div class="search"> <input type="text" ref="pickerInput" placeholder="搜索地点" /> </div>
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; }
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);
});
positionPicker.start();
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>
<template>
<div class="map-container">
<div ref="mapView" class="map-view"></div>
<div class="search">
<input type="text" ref="pickerInput" placeholder="搜索地点" />
</div>
<div class="address-wrapper">
<div class="address-list">
<div
class="address-list-item"
v-for="item in addressList"
:key="item.id"
@click="sureAddress(item)"
>
<p>{{ item.name }}</p>
<p>{{ item.address }}</p>
</div>
</div>
</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;
}
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。
<template>
<div class="map-container">
<div ref="mapView" class="map-view"></div>
</div>
</template>
<script>
let map = null;
export default {
name: "map-view",
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],
});
},
}
};
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;
}
}
</style>
2. 使用 Geolocation 实现定位
initMap() {
map = new AMap.Map(this.$refs.mapView, {
resizeEnable: true,
zoom: 15,
// center: [121.553958, 29.869472],
});
this.getLocation();
},
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);
}
},
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);
});
positionPicker.start();
});
},
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 = [];
},
<template>
<div class="map-container">
<div ref="mapView" class="map-view"></div>
<div class="search">
<input type="text" ref="pickerInput" placeholder="搜索地点" />
</div>
<div class="address-wrapper">
<div class="address-list">
<div
class="address-list-item"
v-for="item in addressList"
:key="item.id"
@click="sureAddress(item)"
>
<p>{{ item.name }}</p>
<p>{{ item.address }}</p>
</div>
</div>
</div>
</div>
</template>
4. 完整代码
<template>
<div class="map-container">
<div ref="mapView" class="map-view"></div>
<div class="search">
<input type="text" ref="pickerInput" placeholder="搜索地点" />
</div>
<div class="address-wrapper">
<div class="address-list">
<div
class="address-list-item"
v-for="item in addressList"
:key="item.id"
@click="sureAddress(item)"
>
<p>{{ item.name }}</p>
<p>{{ item.address }}</p>
</div>
</div>
</div>
</div>
</template>
<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);
});
positionPicker.start();
});
},
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>
写在最后
其实地图组件的坑实在是太多了,还是需要继续学习不断的探索。
如果你也对地图组件有兴趣的话,欢迎大家一起来交流学习。