【每日一拳】打在 vue+高德地图api (@amap/amap-jsapi-loader 、vue-amap) 实践上
前言
之前也有做过地图组件(貌似使用百度 api),但大多都是cv也没有去深究。
刚好之前公司有个项目需要点击地址跳转到地图页,在地图页显示相关地址信息,也借此机会重新学习了一下高德地图 api。
找了一下相关文档感觉也不是很详细,那就写点什么记录一下吧,开干!
目前而言,对于高德地图 api 的使用看到过三种方式:
- 引入 @amap/amap-jsapi-loader
- 引入原生的高德地图 api
- 引入 vue-amap
@amap/amap-jsapi-loader
无意中在网上看到一个 @amap/amap-jsapi-loader 的高德地图依赖,有兴趣就去研究了一下。
npm 社区是下图这样描述的,但是 高德地图官方文档 感觉也不是很详细。
所以也自己用 vue3 跑了一个 demo,小试一下。
1. 安装依赖并引入
pnpm install @amap/amap-jsapi-loader
然后在 components 下创建 Amap 组件并且引入之
import AMapLoader from "@amap/amap-jsapi-loader";
2. 初始化地图
参照 官方文档 AMapLoader.load 方法参数说明
AMapLoader.load({ "key": "", // 申请好的Web端开发者Key,首次调用 load 时必填 "version": "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15 "plugins": [] // 需要使用的的插件列表,如比例尺'AMap.Scale'等 "AMapUI": { // 是否加载 AMapUI,缺省不加载 "version": '1.1', // AMapUI 缺省 1.1 "plugins":[], // 需要加载的 AMapUI ui插件 }, "Loca":{ // 是否加载 Loca, 缺省不加载 "version": '1.3.2' // Loca 版本,缺省 1.3.2 }, }).then(()=>{ window.AMap.xx; window.AMapUI.xx; window.Loca.xx });
使用 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 id="mapview" ref="mapview"></div>
</template>
3. 使用 SimpleMarker 创建标记点
SimpleMarker(简单标注)继承自 AMap.Marker,在已有功能的基础上,额外增加如下的支持:
-
加载 SimpleMarker(模块名:
ui/overlay/SimpleMarker
)AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", AMapUI: { version: "1.1", plugins: ["overlay/SimpleMarker"] // 需要加载的 AMapUI ui插件 } })
-
创建 SimpleMarker 实例
AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", AMapUI: { version: "1.1", plugins: ["overlay/SimpleMarker"] } }).then((AMap) => { map = new AMap.Map(instance.refs.mapView, options); // !!! 通过 AMap.SimpleMarker 获取组件 new AMapUI.SimpleMarker({ //前景文字 iconLabel: 'A', //图标主题 iconTheme: 'default', //背景图标样式 iconStyle: 'red', map: map, position: map.getCenter() }); }).catch((e) => { throw "地图加载失败,请重新加载"; });
-
可以更改标记点 marker 的样式,参考官方文档:lbs.amap.com/api/amap-ui…
AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", AMapUI: { version: "1.1", plugins: ["overlay/SimpleMarker"] } }).then((AMap) => { map = new AMap.Map(instance.refs.mapView, options); let marker = new AMapUI.SimpleMarker({ // 前景文字 // iconLabel: "天一", // 自定义图标地址 iconStyle: { src: "http://webapi.amap.com/theme/v1.3/markers/b/mark_r.png", style: { width: "20px", height: "30px" } }, // 设置基点偏移 // offset: new AMap.Pixel(0, -30), // showPositionPoint: true, map: map, position: map.getCenter(), }); }).catch((e) => { throw "地图加载失败,请重新加载"; });
4. 使用 SimpleInfoWindow 创建信息窗体
SimpleInfoWindow(简单信息窗体)继承自 AMap.InfoWindow,提供一种简单的“标题+内容”构造的信息窗体;内容的构建支持使用模板。
-
加载 SimpleInfoWindow(模块名:
ui/overlay/SimpleInfoWindow
)AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", AMapUI: { version: "1.1", plugins: ["overlay/SimpleInfoWindow"] // 需要加载的 AMapUI ui插件 } })
-
创建 SimpleInfoWindow 实例,参考官方文档:lbs.amap.com/api/amap-ui…
AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", AMapUI: { version: "1.1", plugins: ["overlay/SimpleInfoWindow"] } }).then(AMap => { ………… let infoWindow = new AMapUI.SimpleInfoWindow({ infoTitle: '<strong>这里是标题</strong>', // 标题内容,html代码 infoBody: '<p>这里是内容。</p>', // 主体内容,html代码 }); infoWindow.open(map, map.getCenter()); map.add(infoWindow); // 添加图层到地图 }) .catch(() => { throw "地图加载失败,请重新加载"; });
-
自定义窗体内容
import square from "../../assets/images/square.jpeg"; const info = { name: "天一广场", address: "浙江省宁波市海曙区中山东路188号", phone: "(0574)87683088", time: "10:00-22:00", imageUrl: square }; AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", AMapUI: { version: "1.1", plugins: ["overlay/SimpleMarker", "overlay/SimpleInfoWindow"] } }).then(AMap => { ………… let infoWindow = new AMapUI.SimpleInfoWindow({ infoTitle: `<span style="font-size: 14px;">${info.name}</span>`, infoBody: instance.refs.infoWindow.innerHTML, offset: new AMap.Pixel(0, -40), // position: map.getCenter(), }); infoWindow.open(map, map.getCenter()); map.add(infoWindow); // 添加图层到地图 }) .catch(() => { throw "地图加载失败,请重新加载"; });
<template> <div id="mapView" ref="mapView"> <!--自定义窗体--> <div class="info" ref="infoWindow"> <!-- <div class="info-head">{{ info.name }}</div> --> <div style="display: flex; align-items: center"> <div style="width: 120px; height: 80px; margin-right: 10px"> <img style="width: 100%; height: 100%" :src="info.imageUrl" :alt="info.name" /> </div> <div style="font-size: 13px"> <p>地址:{{ info.address }}</p> <p>电话:{{ info.phone }}</p> <p>营业时间: {{ info.time }}</p> </div> </div> </div> </div> </template>
-
给 marker 添加点击事件,也可以给窗体内容绑定监听事件,参考文档:lbs.amap.com/demo/amap-u…
AMapLoader.load({ key: mapConfigure.amapKey, version: "2.0", AMapUI: { version: "1.1", plugins: ["overlay/SimpleMarker", "overlay/SimpleInfoWindow"] } }).then(AMap => { ………… const openInfoWin = () => { infoWindow.open(map, map.getCenter()); }; // marker 点击时打开 marker.on("click", function () { openInfoWin(); }); map.add(infoWindow); // 添加图层到地图 }) .catch(() => { throw "地图加载失败,请重新加载"; });
5. 添加地图控件
在线插件是在基础地图服务上增加的额外功能,您可以根据自己的需要选择添加。插件分为两类:一类是地图控件,它们是用户与地图交互的UI元素,例如缩放控制条(ToolBar)等;一类是功能型插件,用来完成某些特定地图功能,比如鼠标工具(MouseTool)等。
控件名称 说明 是否插件 AMap.ControlBar 组合了旋转、倾斜、复位、缩放在内的地图控件,在3D地图模式下会显示(自V1.4.0版本新增) 是 AMap.MapType 地图类型切换插件,用来切换固定的几个常用图层 是 AMap.OverView 地图鹰眼插件,默认在地图右下角显示缩略图 是 AMap.Scale 地图比例尺插件 是 AMap.ToolBar 地图工具条插件,可以用来控制地图的缩放和平移 是
可以参考官方文档:lbs.amap.com/api/javascr…
AMapLoader.load({
key: mapConfigure.amapKey,
version: "2.0",
plugins: ["AMap.MarkerCluster"],
AMapUI: {
version: "1.1",
plugins: ["overlay/SimpleMarker", "overlay/SimpleInfoWindow"]
}
}).then(AMap => {
…………
map.plugin(
[
"AMap.ToolBar",
"AMap.MapType",
"AMap.Geolocation",
"AMap.Scale",
"AMap.ControlBar"
],
// [],
() => {
// 地图工具条插件,可以用来控制地图的缩放和平移
map.addControl(new AMap.ToolBar());
// 地图类型切换插件,用来切换固定的几个常用图层
map.addControl(
new AMap.MapType({
defaultType: 0
})
);
// 比例尺插件, 位于地图左下角
map.addControl(new AMap.Scale());
// map.addControl(new AMap.Geolocation());
map.addControl(new AMap.ControlBar());
}
);
…………
})
.catch(() => {
throw "地图加载失败,请重新加载";
});
});
6. 选址组件
可以查看我的另一篇文章 《vue+高德地图api 封装选址组件》
7. 其他功能
- PointSimplifier:是一个针对海量点展示场景的组件,能够支持较大规模的经纬度数据,以及配置丰富的展示效果。
- MarkerCluster:使用 AMap.MarkerCluster 展示大量点标记,可灵活设定聚合样式。
- ……
暂时没有去深究,以后有时间补上,通通补上。
8. 完整代码
<script setup lang="ts">
import AMapLoader from "@amap/amap-jsapi-loader";
import { getCurrentInstance, onBeforeMount, onUnmounted } from "vue";
import square from "../../assets/images/square.jpeg";
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: MapConfigureInter;
const mapConfigure: MapConfigure = {
amapKey: "b445fc42ae1593929170feca71b3e3b9",
options: {
resizeEnable: true,
center: [121.553958, 29.869472],
zoom: 14
}
};
const instance = getCurrentInstance();
const info = {
name: "天一广场",
address: "浙江省宁波市海曙区中山东路188号",
phone: "(0574)87683088",
time: "10:00-22:00",
imageUrl: square
};
onBeforeMount(() => {
if (!instance) return;
let { options } = mapConfigure;
AMapLoader.load({
key: mapConfigure.amapKey,
version: "2.0",
plugins: ["AMap.MarkerCluster"],
AMapUI: {
version: "1.1",
plugins: ["overlay/SimpleMarker", "overlay/SimpleInfoWindow"]
}
})
.then(AMap => {
// 创建地图实例
map = new AMap.Map(instance.refs.mapView, options);
// 显示标记点
let marker = new AMapUI.SimpleMarker({
//前景文字
// iconLabel: "天一广场",
//自定义图标地址
iconStyle: {
src: "http://webapi.amap.com/theme/v1.3/markers/b/mark_r.png",
style: {
width: "20px",
height: "30px"
}
},
// iconStyle: "lightblue",
//设置基点偏移
// offset: new AMap.Pixel(0, -30),
// showPositionPoint: true,
map: map,
position: map.getCenter()
});
// 地图中添加地图操作ToolBar插件
map.plugin(
[
"AMap.ToolBar",
"AMap.MapType",
"AMap.Geolocation",
"AMap.Scale",
"AMap.ControlBar"
],
// [],
() => {
// 地图工具条插件,可以用来控制地图的缩放和平移
map.addControl(new AMap.ToolBar());
// 地图类型切换插件,用来切换固定的几个常用图层
map.addControl(
new AMap.MapType({
defaultType: 0
})
);
// 比例尺插件, 位于地图左下角
map.addControl(new AMap.Scale());
// map.addControl(new AMap.Geolocation());
map.addControl(new AMap.ControlBar());
}
);
let infoWindow = new AMapUI.SimpleInfoWindow({
infoTitle: `<span style="font-size: 14px;">${info.name}</span>`,
infoBody: instance.refs.infoWindow.innerHTML,
offset: new AMap.Pixel(0, -40)
// position: map.getCenter(),
});
infoWindow.open(map, map.getCenter());
const openInfoWin = () => {
infoWindow.open(map, map.getCenter());
};
// marker 点击时打开
marker.on("click", function () {
openInfoWin();
});
map.add(infoWindow); // 添加图层到地图
})
.catch(() => {
throw "地图加载失败,请重新加载";
});
});
onUnmounted(() => {
if (map) {
// 销毁地图实例
map.destroy() && map.clearEvents("click");
}
});
</script>
<template>
<div id="mapView" ref="mapView">
<!--自定义窗体-->
<div class="info" ref="infoWindow">
<!-- <div class="info-head">{{ info.name }}</div> -->
<div style="display: flex; align-items: center">
<div style="width: 120px; height: 80px; margin-right: 10px">
<img
style="width: 100%; height: 100%"
:src="info.imageUrl"
:alt="info.name"
/>
</div>
<div style="font-size: 13px">
<p>地址:{{ info.address }}</p>
<p>电话:{{ info.phone }}</p>
<p>营业时间: {{ info.time }}</p>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
#mapView {
height: calc(100vh - 86px);
}
.info {
display: none;
}
:deep(.amap-marker-label) {
border: none !important;
}
</style>
引入原生的高德 api
1. vue2 + 移动端
1.1 初始化地图
在 components 下创建 Amap 组件,使用 <script> 标签导入高德地图 api。
<template>
<div id="mapview" ref="mapview"></div>
</template>
<script>
import { mapState } from "vuex";
let map = null;
let infoWindow = null;
export default {
name: "map-view",
computed: {
...mapState(["info"]),
},
mounted() {
const amap_key = '', cb = "amap_callback";
const scriptUrl = `https://webapi.amap.com/maps?v=1.4.18&key=${amap_key}&callback=${cb}`;
// 导入script
importScript(scriptUrl);
window[cb] = () => {
// 初始化地图
this.initMap();
};
},
methods: {
initMap() {
const info = this.info;
const position = [info.longitude, info.latitude];
map = new AMap.Map("map-view", {
resizeEnable: true,
zoom: 15,
center: position,
});
AMap.plugin(
["AMap.ToolBar", "AMap.Geolocation", "AMap.Scale"],
function () {
map.addControl(new AMap.ToolBar());
map.addControl(new AMap.Scale());
map.addControl(new AMap.Geolocation());
}
);
},
}
};
</script>
1.2 添加 marker 标记和信息窗口
<template>
<div id="mapView" ref="mapView">
<!--自定义窗体-->
<div class="info" ref="infoWindow">
<div class="info-head">
<span class="info-title">{{ info.name }}</span>
<span
class="info-close iconfont icon-guanbi"
@click="closeHandler"
></span>
</div>
<div class="info-body">
<div class="info-img-wrap">
<img :src="info.imageUrl" :alt="info.name" class="info-img" />
</div>
<div>
<p>地址:{{ info.address | defaultInfo }}</p>
<p>电话:{{ info.phone | defaultInfo }}</p>
<p>营业时间: {{ info.time | defaultInfo }}</p>
</div>
</div>
</div>
</div>
</template>
methods: {
initMap() {
…………
let marker = new AMap.Marker({
title: info.address,
position,
map
});
// 自定义窗体
infoWindow = new AMap.InfoWindow({
isCustom: true,
content: this.$refs["infoWindow"],
offset: new AMap.Pixel(0, -50),
});
infoWindow.open(map, position);
let closeBtn = document.querySelector(".info-close");
marker.on("click", this.closeHandler);
},
closeHandler() {
const info = this.info;
const position = [info.longitude, info.latitude];
if (infoWindow.getIsOpen()) {
infoWindow.close();
return;
}
infoWindow.open(map, position);
},
}
<style lang="scss" scoped>
#mapView {
position: relative;
height: 100%;
width: 100%;
min-height: 500px;
.info {
position: relative;
border: solid 1px silver;
max-width: 270px;
&-head {
display: flex;
position: relative;
background: #f9f9f9;
height: 30px;
border-bottom: 0.5px solid #ccc;
border-radius: 5px 5px 0 0;
padding: 0 5px;
align-items: center;
justify-content: space-between;
.info-title {
flex: 1;
max-width: 200px;
font-size: 14px;
font-weight: 500;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
&-body {
display: flex;
background: #ffffff;
font-size: 12px;
padding: 6px;
line-height: 20px;
.info-img-wrap {
width: 100px;
height: 80px;
margin-right: 5px;
flex-shrink: 0;
.info-img {
width: 100%;
height: 100%;
}
}
p {
word-break: break-all;
}
}
}
}
</style>
1.3 完整代码
<template>
<div id="mapView" ref="mapView">
<!--自定义窗体-->
<div class="info" ref="infoWindow">
<div class="info-head">
<span class="info-title">{{ info.name }}</span>
<span
class="info-close iconfont icon-guanbi"
@click="closeHandler"
></span>
</div>
<div class="info-body">
<div class="info-img-wrap">
<img :src="info.imageUrl" :alt="info.name" class="info-img" />
</div>
<div>
<p>地址:{{ info.address | defaultInfo }}</p>
<p>电话:{{ info.phone | defaultInfo }}</p>
<p>营业时间: {{ info.time | defaultInfo }}</p>
</div>
</div>
</div>
</div>
</template>
<script>
import square from "../assets/img/square.jpeg";
import { mapState } from "vuex";
let infoWindow = null;
let map = null;
export default {
name: "map-view",
computed: {
...mapState(["info"]),
},
mounted() {
const amap_key = '', cb = "amap_callback";
const scriptUrl = `https://webapi.amap.com/maps?v=1.4.18&key=${amap_key}&callback=${cb}`;
// 导入script
importScript(scriptUrl);
window[cb] = () => {
// 初始化地图
this.initMap();
};
},
methods: {
initMap() {
const info = this.info;
const position = [info.longitude, info.latitude];
map = new AMap.Map("mapView", {
resizeEnable: true,
zoom: 15,
center: position,
});
AMap.plugin(
["AMap.ToolBar", "AMap.Geolocation", "AMap.Scale"],
function () {
map.addControl(new AMap.ToolBar());
map.addControl(new AMap.Scale());
map.addControl(new AMap.Geolocation());
}
);
// 自定义窗体
infoWindow = new AMap.InfoWindow({
isCustom: true,
content: this.$refs["infoWindow"],
offset: new AMap.Pixel(0, -50),
});
infoWindow.open(map, arr);
let closeBtn = document.querySelector(".info-close");
marker.on("click", this.closeHandler);
},
closeHandler() {
const info = this.info;
const position = [info.longitude, info.latitude];
if (infoWindow.getIsOpen()) {
infoWindow.close();
return;
}
infoWindow.open(map, position);
},
},
};
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>
#mapView {
position: relative;
height: 100%;
width: 100%;
min-height: 500px;
.info {
position: relative;
border: solid 1px silver;
max-width: 270px;
&-head {
display: flex;
position: relative;
background: #f9f9f9;
height: 30px;
border-bottom: 0.5px solid #ccc;
border-radius: 5px 5px 0 0;
padding: 0 5px;
align-items: center;
justify-content: space-between;
.info-title {
flex: 1;
max-width: 200px;
font-size: 14px;
font-weight: 500;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
&-body {
display: flex;
background: #ffffff;
font-size: 12px;
padding: 6px;
line-height: 20px;
.info-img-wrap {
width: 100px;
height: 80px;
margin-right: 5px;
flex-shrink: 0;
.info-img {
width: 100%;
height: 100%;
}
}
p {
word-break: break-all;
}
}
}
}
</style>
2. vue3 + pc端
这个本人没有去深入研究,在 githug 一个项目上看到过,都是导入 script
标签,大致效果也是一样的,也顺便贴一下代码,有兴趣的可以自己去研究哈。
-
组件代码:
<template> <div ref="wrapRef" :style="{ height, width }"></div> </template> <script lang="ts"> import { defineComponent, ref, nextTick, unref, onMounted } from 'vue'; import { useScript } from '/@/hooks/web/useScript'; const A_MAP_URL = 'https://webapi.amap.com/maps?v=2.0&key=申请好的Web端开发者Key'; export default defineComponent({ name: 'AMap', props: { width: { type: String, default: '100%', }, height: { type: String, default: 'calc(100vh - 78px)', }, }, setup() { const wrapRef = ref<HTMLDivElement | null>(null); const { toPromise } = useScript({ src: A_MAP_URL }); async function initMap() { await toPromise(); await nextTick(); const wrapEl = unref(wrapRef); if (!wrapEl) return; const AMap = (window as any).AMap; new AMap.Map(wrapEl, { zoom: 11, center: [116.397428, 39.90923], viewMode: '3D', }); } onMounted(() => { initMap(); }); return { wrapRef }; }, }); </script>
-
hooks 文件:
import { onMounted, onUnmounted, ref } from 'vue'; interface ScriptOptions { src: string; } export function useScript(opts: ScriptOptions) { const isLoading = ref(false); const error = ref(false); const success = ref(false); let script: HTMLScriptElement; const promise = new Promise((resolve, reject) => { onMounted(() => { script = document.createElement('script'); script.type = 'text/javascript'; script.onload = function () { isLoading.value = false; success.value = true; error.value = false; resolve(''); }; script.onerror = function (err) { isLoading.value = false; success.value = false; error.value = true; reject(err); }; script.src = opts.src; document.head.appendChild(script); }); }); onUnmounted(() => { script && script.remove(); }); return { isLoading, error, success, toPromise: () => promise, }; }
vue-amap
另一种使用 vue-amap 这个依赖的也人较多,但是 npm 社区最近的一次更新已经是三年前的事情了,而且 vue-amap 是一套基于 Vue 2.0 和高德地图的地图组件,如果使用 vue3 开发这个依赖就已经不适用了。
1. 安装依赖
首先在 main.js 引入依赖,然后使用 Vue.use 调用,并初始化 vue-amap
// 高德地图
import Amap from "vue-amap";
Vue.use(Amap);
Amap.initAMapApiLoader({
key: "", // 申请好的Web端开发者Key
plugin: [ // 插件集合
"AMap.Autocomplete",
"AMap.PlaceSearch",
"AMap.Scale",
"AMap.OverView",
"AMap.ToolBar",
"AMap.MapType",
"AMap.PolyEditor",
"AMap.CircleEditor",
],
// 默认高德 sdk 版本为 1.4.4
v: "1.4.4",
});
2. 引用组件
<el-amap
class="amap-box"
:vid="'amap-vue'"
:zoom="zoom"
:center="center"
>
<el-amap-marker :position="center"></el-amap-marker>
</el-amap>
写在最后
无论什么方法,只要可以实现最终的诉求都是一个好方法,都值得一试。
接下来我也会继续去深究更多的东西,向大佬们看齐,也希望大家喜欢。
likeNum++;
readingQuantity++;
加油!
转载自:https://juejin.cn/post/7073297619761496072