vue3+高德地图+UI组件库,自定义信息窗体InfoWindow内容,自定义事件等

2,328 阅读6分钟

最近在准备面试,之前的项目做过高德地图的相关内容,但是时间过去得太久有些遗忘了,所以写一篇文章,简单实现一下标题的功能,顺便复习一下。

一、准备工作

自己创建一个vue3项目也不多说了,官网都有

装一下包

npm i @amap/amap-jsapi-loader --save

我用的是pnpm,差不多的

pnpm add @amap/amap-jsapi-loader

按照高德地图官网的教程获取key等,这里不作过多介绍 lbs.amap.com/api/webserv…

比如我创建完的项目是这样的 image.png

image.png 这里主要是为了得到Key和密钥,后面有用

二、简单的地图

新建一个.vue的文件

<template>
  <div class="container" id="container" />
</template>

<script setup>
import AMapLoader from '@amap/amap-jsapi-loader';
import { onMounted, onUnmounted } from "vue";
window._AMapSecurityConfig = {
  securityJsCode: '你的密钥'
}
let map = null;
onMounted(() => {
  AMapLoader.load({
    key: "你的key", // 申请好的Web端开发者Key,首次调用 load 时必填
    version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
    // plugins: [], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
  })
    .then((AMap) => {
      map = new AMap.Map("container", {
        viewMode: "3D", // 是否为3D地图模式
        zoom: 11, // 初始化地图级别
        center: [116.397428, 39.90923], // 初始化地图中心点位置
      });
    })
    .catch((e) => {
      console.log(e);
    });

});
onUnmounted(() => {
  map?.destroy();
});
</script>

<style>
#container {
  width: 500px;
  height: 500px;
}
</style>

这样就创建出一个简单的地图了,但是离我们的要求还很远,我希望当我点击地图的某个区域时,弹出窗体显示我点击区域的地名等相关信息,窗体的样式能够自定义。

image.png

三、获取点击时地点信息

地图的交互具体可见其官网的api,我这里用的是map.on,可以获取当前点击区域的相关信息

image.png

但是此时的信息并不是我想要的,我想要获得更加详细的中文信息 image.png 此时代码如下

<template>
  <div class="container" id="container" />
</template>

<script setup>
import AMapLoader from '@amap/amap-jsapi-loader';
import { onMounted, onUnmounted } from "vue";
import { createWindow } from './utils/index'
window._AMapSecurityConfig = {
  securityJsCode: 'xxx'
}
let map = null;
onMounted(() => {
  AMapLoader.load({
    key: "xxx", // 申请好的Web端开发者Key,首次调用 load 时必填
    version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
    // plugins: [], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
  })
    .then((AMap) => {
      map = new AMap.Map("container", {
        viewMode: "3D", // 是否为3D地图模式
        zoom: 11, // 初始化地图级别
        center: [116.397428, 39.90923], // 初始化地图中心点位置
      });
      map.on('click', function (ev) {
        console.log(ev)
      });
    })
    .catch((e) => {
      console.log(e);
    });

});
onUnmounted(() => {
  map?.destroy();
});
</script>

<style>
#container {
  width: 500px;
  height: 500px;
}
</style>

想要获取中文信息可以使用插件AMap.Geocoder,可以根据经纬度获取地理信息,插件的使用官网教程如下,但是我直接使用官网的教程,会有一些问题,所以换了写法

image.png

可以这样写

onMounted(() => {
  AMapLoader.load({
    key: "xxx", // 申请好的Web端开发者Key,首次调用 load 时必填
    version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
    // plugins: [], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
  })
    .then((AMap) => {
      map = new AMap.Map("container", {
        viewMode: "3D", // 是否为3D地图模式
        zoom: 11, // 初始化地图级别
        center: [116.397428, 39.90923], // 初始化地图中心点位置
      });

      let geocoder = null
      AMap.plugin('AMap.Geocoder', function () {//异步加载插件
        geocoder = new AMap.Geocoder();
      });

      map.on('click', function (ev) {
        console.log(ev)
        geocoder.getAddress(ev.lnglat, (status, result) => {
          if (status === 'complete' && result.info === 'OK') {
            const locationName = result.regeocode.formattedAddress
            console.log(locationName)
          } else {
            console.log('获取地理位置信息失败')
          }
        })
      });
    })
    .catch((e) => {
      console.log(e);
    });

});

这时候可以获取详细信息了,如图

image.png

四、自定义窗体与事件#

现在只剩最后一步,也是最麻烦的地方了,绘制自定义的窗体。 官网的自定义窗体是这样写的,直接传了一串html代码进去,写一些比较简单的内容还好,有时候想要做一些复杂的内容就不太好了,希望能够写一个vue的组件,组件中还可以使用别的UI组件,比如ant-design,echarts之类的,这样就可以绘制比较漂亮的内容了

image.png

这时候先创建一个窗体组件吧,我建立了一个position组件,就按平时的写法

image.png

组件内部的代码是这样的

<template>
  <div class="position">
    <div class="position-title">
      <div>
        {{ locationName }}
      </div>
      <a-button @click="showResult">
        测试按钮
      </a-button>
      <div v-show="isShow">
        {{ result.regeocode.addressComponent.district }}
      </div>
    </div>
    <icon-close size="20" @click="close" />
  </div>
</template>
<script setup lang="ts">
import { defineProps, ref } from 'vue';
const isShow = ref(false)
// 定义 props 类型
const props = defineProps({
  locationName: String, // 字符串类型的 prop1
  result: Object,
  closeApp: {
    type: Function,
    required: true
  }
});
const close = () => {
  props.closeApp()
}
const showResult = () => {
  isShow.value = !isShow.value
}
</script>

<style lang="less" scoped>
.position {
  width: 250px;
  border-radius: 20px;
  border: 1px solid #999;
  background-color: #fff;
  padding: 20px;
  display: flex;

  &-title {
    width: 0;
    flex: 1;
  }

  :last-child {
    cursor: pointer
  }
}
</style>

这个组件可以从父组件获取props,并展示相应的内容 但是高德地图的AMap.InfoWindow传入的是传入 dom 对象,或者 html 字符串,直接传刚才的代码是不行的,这时候可以用createApp,自己创建一个实例,然后挂载到div上就可以了

image.png

创建一个index.ts的文件 如果你想要在自己的子组件中使用UI组件库的组件,这个文件夹里要重新引入一下,我用的arco-design,所以把这个组件库引入进来了,可以抄一下main.ts这个文件的内容,原理是类似的

import { h ,createApp} from 'vue';
import ArcoVueIcon from '@arco-design/web-vue/es/icon';
import ArcoVue from '@arco-design/web-vue';
import Position from '../components/position.vue'
function createWindow(locationName:string,result:any,closeApp:Function){
  const div = document.createElement('div');
  const app = createApp({
    render() {
      return h(Position, { locationName,result,closeApp })
    }
  })
  app.use(ArcoVueIcon);
  app.use(ArcoVue);
  app.mount(div)

  return {div,app}
}

export{
  createWindow
}

然后回到父组件,把createWindow方法引入 map.on中的代码如下

      map.on('click', function (ev) {
        console.log(ev)
        geocoder.getAddress(ev.lnglat, (status, result) => {
          if (status === 'complete' && result.info === 'OK') {
            const locationName = result.regeocode.formattedAddress
            // 创建信息窗体
            const { div, app } = createWindow(locationName, result, closeApp)
            const infoWindow = new AMap.InfoWindow({
              isCustom: true,
              content: div  //传入 dom 对象,或者 html 字符串
            });
            infoWindow.open(map, ev.lnglat);
            function closeApp () {
              infoWindow.close();
            }
          } else {
            console.log('获取地理位置信息失败')
          }
        })
      });

AMap.InfoWindow的isCustom是true,所以高德地图自带的关闭按钮没有了,想要关闭窗体的话就props里传入一个function就可以了,这样大致就实现了想要的需求。

image.png

image.png

点击地图的时候出现了自定义的窗体,也出现了想要的测试按钮,点击按钮时也可以动态显示district。 点击×时,也可以正常关闭。 本来想全部用ts写的,不过ts确实不太好,父组件的内容还是用js写了

五、父子组件通信

如果想要子组件给父组件发送消息,我这里用的是pinia,和vuex差不多,不过语法更加简单一些,我看其他人有用事件总线的,mitt之类的,这个我平时工作也没用过,所以也不多说了。 官网网站: pinia.web3doc.top/getting-sta… 安装如下: image.png

可是使用选项式,也可以用组合式,例如官网给出的教程,如下图,比如count就是子组件想给父组件传递的值,increment就是事件 image.png

我写了这样一段代码

export const myStore = defineStore('', () => {
  const childData:any = ref(null)
  function changeData(newData:Number) {
    childData.value = newData
  }

  return { childData, changeData }
})

在子组件中将store引入使用

const store = myStore()
const sendMessage = () => {
  store.changeData(Date.now())
}

父组件引入store并监听

import { myStore } from '@/stores/index'
const store = myStore()
watch(() => store.childData, (newValue, oldValue) => {
  console.log('子组件事件:', newValue, oldValue);
  // 在这里执行你想要的操作
});