聊聊vue3中怎么使用高德地图api
在我们使用高德地图的时候,官方给我们推荐了很多案例,demo
,但是这些案例都是使用原生方法接入,并没有提供vue
或者react
的demo
,vue2
的 接入网上也很多人都有写过,下面本篇文章就来看看 vue3
怎么使用常用的高德地图api
,希望对大家有所帮助!
前置工作
在开发之前我们需要了解
vue3
中接入高德地图的几个步骤
- 首先安装包并引入
npm i @amap/amap-jsapi-loader --save
import AMapLoader from '@amap/amap-jsapi-loader'
使用官方介绍的方式进行加
vue2
和 vue3
是有区别的,这里我们使用的是 vue3
,但这里 vue3
的方式还是选项式,不是组合式的,我自己写的时候使用的是组合式的,且集成了 ts
, 我后面发布完整 .vue
文件的时候 会去掉标签上的 ts,因为类型还没有完善,等后面完善了再贴更改以后得。为什么要使用 shallowRef
官方也给出了说明原因。 【相关推荐:vuejs视频教程、web前端开发】
示例模块
这里我直接把我前面,写过的 地图业务需求的业务逻辑拿过来的,没有使用框架,直接在一个 html 文件当中引入,链接大家可以点击下面进行查看:
高德地图jsApi的使用
高德地图jsApi的点和线配置
高德地图jsApi的右键设置
高德地图jsApi的点位新增
高德地图jsApi的图例
使用vue3
的时候,实例化的方式,this
的问题, 以及插入字符串模板的时候 事件响应的方式都需要更改,还是很麻烦的
模块的引入
- 首先导入的方式,和官网一样,后面我会贴完整代码, 这里我们使用
plugins
加载插件, 其他配置如Loca
, 直接进行配置, 这里需要注意版本问题, 写成 ‘2.0’ 是不行的,初始化函数在onmounted
生命周期中执行。 - AMap存储 这里我做了很多存储,大家知道
.value
的语法是vue3
获取ref
的语法,我下面使用到的 都是ref
,后面完整代码可以查看, 这里挂载的时候直接存一下,因为很多工具方法都会只用到他,这里后期业务逻辑我会抽离到pinia
中去,所以不需要在初始化函数中写全部的业务逻辑。 - 模版样式不生效问题, 我们在使用的时候, 就像我之前写的文章,点位新增的时候,我们会插入
content
字符串模版,替换点样式,这里有两种方案修改样式,一种是 插入DOM
,不使用字符串,然后在DOM
上通过style
直接修改样式,另一种就是使用模版的时候直接给class
类名,但是这种样式如果我们给vue
的style
加了scoped
就不会生效,这里大家可以自己灵活选择用哪种,我这里暂时先使用模版的方式,去掉了scoped
。 - 图例, 图例这里除了导入的时候,需要配置一下,使用上来说变化不大,样式的修改还是复用了我之前的逻辑。
import AMapLoader from '@amap/amap-jsapi-loader' const initMap = () => { AMapLoader.load({ key: 'b59c490f61a694b9d7576dd864f74d6e', // 申请好的Web端开发者Key,首次调用 load 时必填 version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15 plugins: ['AMap.Scale', 'AMap.ToolBar', 'AMap.MouseTool'], // 需要使用的的插件列表,如比例尺'AMap.Scale'等 Loca:{ version:'2.0.0' } }) .then((res) => { AMap.value = res // 上来就显示的中心点 北京 116.397, 39.918 var lnglat = new res.LngLat(105, 38) map.value = new res.Map('container', { //设置地图容器id viewMode: '3D', //是否为3D地图模式 zoom: 5, //初始化地图级别 center: lnglat, //初始化地图中心点位置 }) map.value.clearMap() // 清除地图覆盖物 // 地图是否可拖拽和缩放 map.value.setStatus({ dragEnable: true, // 是否可拖拽 zoomEnable: true, // 是否可缩放 }) initWindow() // 添加一些分布不均的点到地图上,地图上添加三个点标记,作为参照 coordData.forEach(function (marker) { setMarker(marker) }) let renderLine = setLine(coordData) // 设置线 let polyline = renderLine.reduce((prev, item, index) => { let weight = item.type === 1 ? 5 : 3 let color = item.type === 1 ? headColors[0] : headColors[1] prev.push(setLines(item.current, color, weight)) return prev }, []) map.value.add([...polyline]) // 绘制线 //创建右键菜单 menuInstance.value = new ContextMenu(map.value) let loca = new Loca.Container({ map:map.value, }); window._loca = loca; // 图例, 图例可以实例化多个,使用定位来设置位置 let lengend = new Loca.Legend({ loca: loca, title: { label: '管道类型', fontColor: 'rgba(255,255,255,1)', fontSize: '16px' }, style: { backgroundColor: 'rgba(255,255,255,0.2)', left: '20px', bottom: '40px', fontSize: '12px' }, dataMap: [ { label: '省级管道', color: headColors[1] }, { label: '县级管道', color: headColors[0] }, ], }); //修改图例排列方式 document.getElementsByClassName("amap-loca loca-controls")[0].setAttribute('id', 'testid') var lis = document.querySelectorAll("#testid li"); for (var i = 0; i < lis.length; i++) { console.log(lis[i]); lis[i].setAttribute("class", 'test' ); } }) .catch((e) => { console.log('error', e) }) } onMounted(() => { initMap() })
右键菜单
右键菜单, 右键菜单这里官方给我们的示例是使用一个 函数 进行实例化,里面使用了
this
, 所以这个我单独拿出来,首先我们看一下官方的 demo
- 这里使用了一个函数,但这个函数还不是类,但是他却在里面使用了
this
,实话来讲,这种写法确实不是很优秀,可扩展性很差,不够健壮,但没办法,谁让我们用了人家的东西呢是吧, 在vue3
中这么用就不可以了,首先vue3
里面使用this
就不是官方建议的, 另外这里面还修改了函数原型上的方法,其实我得代码里面一共有两种右键菜单,如下:
一种是在指定点位上打开,另一种是在非点位的空白处打开,指定点位处打开的其实叫信息窗体,只不过是通过右键的方式触发,那个没有上面这个右键菜单麻烦。
- 首先来说
this
问题, 这里的this
实际上就是把我们的实例化对象挂载到上面而已,vue3
中没办法像vue2
那样使用this
, 但也提供给我们了api
来获取当前组件的实例化对象, 然后我没用使用函数, 使用了一个类,类构造这个方法, 模版也不适用字符串模版,因为这里字符串模版的事件绑定写死了,我们使用DOM
来动态绑定事件,代码如下:
const { ctx } = getCurrentInstance() const _this = ctx //自定义菜单类 class ContextMenu { constructor(map) { var me = _this //地图中添加鼠标工具MouseTool插件 _this.mouseTool = new AMap.value.MouseTool(map) _this.contextMenuPositon = null const fragment = document.createElement('div') // 使用 DOM 方式, 方便添加事件 fragment.className = 'info context_menu' const p = document.createElement('p') p.addEventListener('click', this.delMarkerMenu) p.textContent = '移除上次选中信息' fragment.appendChild(p) //通过content自定义右键菜单内容 _this.contextMenu = new AMap.value.ContextMenu({ isCustom: true, content: fragment, }) //地图绑定鼠标右击事件——弹出右键菜单 map.on('rightclick', function (e) { me.contextMenu.open(map, e.lnglat) me.contextMenuPositon = e.lnglat //右键菜单位置 }) } delMarkerMenu() { // 右键菜单上次选中点的信息 clearPoint() _this.mouseTool.close() _this.contextMenu.close() } }
完整代码
<!-- * @Description: 地图 * @Autor: codeBo * @Date: 2023-03-06 16:10:10 * @LastEditors: gjzxlihaibo@163.com * @LastEditTime: 2023-03-07 14:59:08 --> <template> <div id="root"> <div> <h3>添加选点请输入坐标</h3> <label> 经度: <input /> </label> <label> 纬度: <input /> </label> <button>输入完成</button> <button>清空输入</button> </div> <div id="container"></div> </div> </template> <script setup> import { onMounted, reactive, ref, getCurrentInstance } from 'vue' import AMapLoader from '@amap/amap-jsapi-loader' import { shallowRef } from 'vue' import { coordData } from './data' const map = shallowRef(null) const { ctx } = getCurrentInstance() const _this = ctx const menuInstance = ref() // menu 实例 let AMap = ref() // map 实例 let currentPonit = ref<HTMLElement | null>(null) // 存储当前选中点 DOM let currentData = reactive({}) // 当前选重点信息 let sourceInfoWindow = ref() const headColors = ['#3366bb', '#6622FF'] // 工具方法 // 修改DOM 类名 function changeStyle(res, data) { if (currentPonit.value !== null) { currentPonit.value.classList.remove('active') } currentPonit.value = res.children[0] currentData = data currentPonit.value.classList.add('active') } // 清除点信息 function clearPoint() { if (currentPonit.value) { currentPonit.value.classList.remove('active') } currentPonit.value = null currentData = {} } // 设置线信息 function setLines(lnglat, color, weight) { return new AMap.value.Polyline({ path: lnglat, // showDir:true ,// 设置线方向 strokeColor: color, // 线颜色 strokeWeight: weight, // 线宽 strokeOpacity: 0.6, // 透明度 }) } function markerClick(e) { console.log('sourceInfoWindow.value', sourceInfoWindow.value, e.target) sourceInfoWindow.value.setContent(e.target.contents) sourceInfoWindow.value.open(map.value, e.target.getPosition()) } function setInput(e, name) { let text = e.target.parentElement.parentElement.children[0].innerText.split( '供给点', )[0] let current = coordData.filter((item) => { return item.name === text }) window.localStorage.setItem(text + name, e.target.value) } const initWindow = () => { // 信息窗体 let infoWindow = new AMap.value.InfoWindow({ offset: new AMap.value.Pixel(0, -10), retainWhenClose: true, }) sourceInfoWindow.value = infoWindow infoWindow.on('open', function (...arg) { let inputOut = document.getElementById('inputOut') let inputPro = document.getElementById('inputPro') inputOut.addEventListener('change', (e) => { setInput(e, 'inputOut') window.location.reload() }) inputPro.addEventListener('change', (e) => { setInput(e, 'inputPro') window.location.reload() }) }) } // 抽离点位信息设置 function setMarker(marker) { //创建右键菜单 var contextMenu = new AMap.value.ContextMenu() //右键放大 contextMenu.addItem( '放大一级', function () { map.value.zoomIn() }, 0, ) //右键缩小 contextMenu.addItem( '缩小一级', function () { map.value.zoomOut() }, 1, ) contextMenu.addItem('设置起点', function () { console.log('设置起点', marker, markerd.dom) changeStyle(markerd.dom, marker) contextMenu.close() // 关闭右键菜单 }) contextMenu.addItem('与起点连线', function () { if (!currentPonit) { alert('请选择起点') contextMenu.close() return } else { // 这里其实可以根据数据判定线类型了,因为第二个选中点的信息+和第一个选中点的信息都有了,但是过滤方法会比较复杂 let path = [currentData.position, marker.position] const polyline1 = setLines(path, '#3366bb', 5) map.value.add([polyline1]) clearPoint() } contextMenu.close() // 关闭右键菜单 }) let content = '<div></div>' var markerd = new AMap.value.Marker({ map: map.value, // icon: marker?.icon, content, offset: new AMap.value.Pixel(-8, -8), visible: true, // 点标记是否可见 position: [marker.position[0], marker.position[1]], }) let inputO = window.localStorage.getItem(marker.name + 'inputOut') let inputP = window.localStorage.getItem(marker.name + 'inputPro') // 左键点击的信息窗体, 宽度会在碰触到容器边缘的时候自适应的缩小 markerd.contents = ` <div>${marker.name}供给点</div> <div>出口压力:<input id="inputOut" value="${ inputO ?? marker?.pointData?.out }"/>kPa</div> <div>供给量:<input id="inputPro" value="${ inputP ?? marker?.pointData?.provide }" />m³</div> <div>位置:经度${marker.position[0]},纬度${marker.position[1]}</div>` markerd.data = marker markerd.on('click', markerClick) if (marker.name === '新疆') { // 触发上面的点击事件 markerd.emit('click', { target: markerd }) } //绑定鼠标右击事件——弹出右键菜单 markerd.on('rightclick', function (e) { contextMenu.open(map.value, e.lnglat) }) return markerd } //自定义菜单类 class ContextMenu { constructor(map) { var me = _this //地图中添加鼠标工具MouseTool插件 _this.mouseTool = new AMap.value.MouseTool(map) _this.contextMenuPositon = null const fragment = document.createElement('div') // 使用 DOM 方式, 方便添加事件 fragment.className = 'info context_menu' const p = document.createElement('p') p.addEventListener('click', this.delMarkerMenu) p.textContent = '移除上次选中信息' fragment.appendChild(p) //通过content自定义右键菜单内容 _this.contextMenu = new AMap.value.ContextMenu({ isCustom: true, content: fragment, }) //地图绑定鼠标右击事件——弹出右键菜单 map.on('rightclick', function (e) { me.contextMenu.open(map, e.lnglat) me.contextMenuPositon = e.lnglat //右键菜单位置 }) } delMarkerMenu() { // 右键菜单上次选中点的信息 clearPoint() _this.mouseTool.close() _this.contextMenu.close() } } // 过滤线方法 function setLine(arr) { return arr.reduce((prev, item) => { if (item?.line) { prev.push(...item.line) } return prev }, []) } const initMap = () => { AMapLoader.load({ key: 'b59c490f61a694b9d7576dd864f74d6e', // 申请好的Web端开发者Key,首次调用 load 时必填 version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15 plugins: ['AMap.Scale', 'AMap.ToolBar', 'AMap.MouseTool'], // 需要使用的的插件列表,如比例尺'AMap.Scale'等 Loca:{ version:'2.0.0' } }) .then((res) => { AMap.value = res // 上来就显示的中心点 北京 116.397, 39.918 var lnglat = new res.LngLat(105, 38) map.value = new res.Map('container', { //设置地图容器id viewMode: '3D', //是否为3D地图模式 zoom: 5, //初始化地图级别 center: lnglat, //初始化地图中心点位置 }) map.value.clearMap() // 清除地图覆盖物 // 地图是否可拖拽和缩放 map.value.setStatus({ dragEnable: true, // 是否可拖拽 zoomEnable: true, // 是否可缩放 }) initWindow() // 添加一些分布不均的点到地图上,地图上添加三个点标记,作为参照 coordData.forEach(function (marker) { setMarker(marker) }) let renderLine = setLine(coordData) // 设置线 let polyline = renderLine.reduce((prev, item, index) => { let weight = item.type === 1 ? 5 : 3 let color = item.type === 1 ? headColors[0] : headColors[1] prev.push(setLines(item.current, color, weight)) return prev }, []) map.value.add([...polyline]) // 绘制线 //创建右键菜单 menuInstance.value = new ContextMenu(map.value) let loca = new Loca.Container({ map:map.value, }); window._loca = loca; // 图例, 图例可以实例化多个,使用定位来设置位置 let lengend = new Loca.Legend({ loca: loca, title: { label: '管道类型', fontColor: 'rgba(255,255,255,1)', fontSize: '16px' }, style: { backgroundColor: 'rgba(255,255,255,0.2)', left: '20px', bottom: '40px', fontSize: '12px' }, dataMap: [ { label: '省级管道', color: headColors[1] }, { label: '县级管道', color: headColors[0] }, ], }); //修改图例排列方式 document.getElementsByClassName("amap-loca loca-controls")[0].setAttribute('id', 'testid') var lis = document.querySelectorAll("#testid li"); for (var i = 0; i < lis.length; i++) { console.log(lis[i]); lis[i].setAttribute("class", 'test' ); } }) .catch((e) => { console.log('error', e) }) } onMounted(() => { initMap() }) </script> <style> #container { width: 1350px; height: 900px; } #root { display: flex; width: 100%; } #root > div:first-child { width: 200px; margin-right: 10px; padding: 5px; box-shadow: 2px 2px 2px 2px #333; } #root > div:first-child { display: flex; flex-direction: column; } .context_menu { position: relative; min-width: 12rem; padding: 0; background-color: white; } .context_menu p { cursor: pointer; padding: 0.25rem 1.25rem; } .context_menu p:hover { background: #ccc; } .btn { width: 80px; margin-top: 10px; } .marker-route { width: 15px; height: 15px; background-color: #22ddb8; border-radius: 10px; } .active { background-color: #f76809; } .content { background-color: rgba(0, 0, 0, 0.3); padding: 1px; color: white; display: flex; align-items: center; } .content span { display: block; width: 20px; height: 20px; background-color: #3366bb; margin: 0 10px; } .content p { margin-right: 10px; } .test { height: 30px; box-sizing: content-box; padding: 2px 10px; line-height: 30px; display: inline; float: left; } .test a { color: #333 !important; } .test span { width: 80px !important; margin-left: 10px; border-radius: 10px; } .amap-info-content { background-color: rgba(255, 255, 255, 0.6); } .test_container { background-color: rgba(255, 255, 255, 0.6); display: flex; width: 180px; flex-direction: column; padding: 10px 18px 10px 10px; line-height: 1.4; overflow: auto; justify-content: center; align-items: center; border: 1px solid rgba(0, 0, 0, 0.2); } .input_inner { margin-right: 5px; border: 1px solid #333; border-radius: 2px; width: 30px; } </style>
- 这里的业务逻辑还不完善, 输入部分的交互逻辑没有完成, 这个文件直接引入自己的项目,安装一下上面说过的依赖, 就可以使用,不过这里数据源需要自己根据自己的数据来构造就可以了,我引入的事
data
中的一组假数据,在这里给大家两组看一下
export const coordData = [ { name: '黑龙江', position: [127, 47], pointData: { out: 100, provide: 10, }, line: [ { current: [ [127, 47], [126, 43], ], type: 1, }, ], }, { name: '吉林', position: [126, 43], pointData: { out: 120, provide: 11, }, line: [ { current: [ [126, 43], [113, 41], ], type: 1, }, ], }, ]
- 后面我会把业务逻辑抽离到
pinia
中, 并且完善ts
类型, 大家对哪一部分有疑问或者更好的解决方案可以留言一起学习~
(学习视频分享:vuejs入门教程、编程基础视频)
以上就是聊聊vue3中怎么使用高德地图api的详细内容,更多请关注其它相关文章!