最近在准备面试,之前的项目做过高德地图的相关内容,但是时间过去得太久有些遗忘了,所以写一篇文章,简单实现一下标题的功能,顺便复习一下。
一、准备工作
自己创建一个vue3项目也不多说了,官网都有
装一下包
npm i @amap/amap-jsapi-loader --save
我用的是pnpm,差不多的
pnpm add @amap/amap-jsapi-loader
按照高德地图官网的教程获取key等,这里不作过多介绍 lbs.amap.com/api/webserv…
比如我创建完的项目是这样的
这里主要是为了得到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>
这样就创建出一个简单的地图了,但是离我们的要求还很远,我希望当我点击地图的某个区域时,弹出窗体显示我点击区域的地名等相关信息,窗体的样式能够自定义。
三、获取点击时地点信息
地图的交互具体可见其官网的api,我这里用的是map.on,可以获取当前点击区域的相关信息
但是此时的信息并不是我想要的,我想要获得更加详细的中文信息
此时代码如下
<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,可以根据经纬度获取地理信息,插件的使用官网教程如下,但是我直接使用官网的教程,会有一些问题,所以换了写法
可以这样写
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);
});
});
这时候可以获取详细信息了,如图
四、自定义窗体与事件#
现在只剩最后一步,也是最麻烦的地方了,绘制自定义的窗体。 官网的自定义窗体是这样写的,直接传了一串html代码进去,写一些比较简单的内容还好,有时候想要做一些复杂的内容就不太好了,希望能够写一个vue的组件,组件中还可以使用别的UI组件,比如ant-design,echarts之类的,这样就可以绘制比较漂亮的内容了
这时候先创建一个窗体组件吧,我建立了一个position组件,就按平时的写法
组件内部的代码是这样的
<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上就可以了
创建一个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就可以了,这样大致就实现了想要的需求。
点击地图的时候出现了自定义的窗体,也出现了想要的测试按钮,点击按钮时也可以动态显示district。 点击×时,也可以正常关闭。 本来想全部用ts写的,不过ts确实不太好,父组件的内容还是用js写了
五、父子组件通信
如果想要子组件给父组件发送消息,我这里用的是pinia,和vuex差不多,不过语法更加简单一些,我看其他人有用事件总线的,mitt之类的,这个我平时工作也没用过,所以也不多说了。
官网网站:
pinia.web3doc.top/getting-sta…
安装如下:
可是使用选项式,也可以用组合式,例如官网给出的教程,如下图,比如count就是子组件想给父组件传递的值,increment就是事件
我写了这样一段代码
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);
// 在这里执行你想要的操作
});