一个好习惯,先给结论
有5种方法:
- iframe嵌套,这种最简单
- vue模板字符串创建vue实例
- 使用.vue单文件创建vue实例
- 使用vue3新方法defineCustomElement,这种最推荐
- 使用jsx
线上预览地址→点我
完整代码库→ github跳转
这里才是引言
在gis项目开发中,经常遇到需要在地图弹窗中显示信息的需求。几乎所有地图框架的弹窗api,都是用html字符串的形式填写内容。
以百度地图为例,可以看到BMapGL.InfoWindow
后跟的是一个字符串。
// 百度地图API功能
var map = new BMapGL.Map("allmap");
var point = new BMapGL.Point(116.404, 39.925);
map.centerAndZoom(point, 15);
var marker = new BMapGL.Marker(point); // 创建标注
map.addOverlay(marker); // 将标注添加到地图中
var opts = {
width : 200, // 信息窗口宽度
height: 100, // 信息窗口高度
title : "故宫博物院" , // 信息窗口标题
message:"这里是故宫"
}
var infoWindow = new BMapGL.InfoWindow("<div>地址:北京市东城区王府井大街88号乐天银泰百货八层</div>", opts); // 创建信息窗口对象
marker.addEventListener("click", function(){
map.openInfoWindow(infoWindow, point); //开启信息窗口
});
现代前端的开发,几乎没有原生js或者jq拼写字符串这种效率低下的形式了,那我们怎么可以将我们的vue组件(或react组件)传递给地图弹窗呢?
下面以vue3
+ leaflet
的组合演示几种方法,其他地图如百度地图
、高德地图
、mapbox
、openlayers
、leaflet
、maptalks
等地图框架和vue2
的组合同理。
方法1:iframe嵌套
- 将弹窗中的内容写成一个.vue组件,并且在路由中将这个组件指定为一个路由
router.js
文件如下
...其他代码省略
{
path: '/1',
component: () => import('../views/1_iframe/index.vue')
},
{
path: '/1_inner',
component: () => import('../views/1_iframe/iframe.vue')
}
...其他代码省略
- 地图主文件
views/1_iframe/index.vue
文件如下:
<template>
<div style="width: 100%;height: 100%;" id="map"></div>
</template>
<script setup>
import 'leaflet/dist/leaflet.css';
import * as L from 'leaflet';
import {onMounted} from "vue";
onMounted(() => {
// 创建地图
const map = L.map('map').setView([31.491064, 120.311889], 13);
// 添加图层
const baseLayer = L.tileLayer("http://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}", {
subdomains: "1234"
});
map.addLayer(baseLayer);
// 创建poi点标记
const marker = L.marker([31.491064, 120.311889]).addTo(map);
// 通过iframe将另一个组件引用进来,可以通过路由传递参数
marker.bindPopup(`<iframe style="border: 0;height: 100px;width: 200px;" src="/#/1_inner?id=666"></iframe>`).openPopup();
})
</script>
- 弹窗页面
/views/1_iframe/iframe.vue
如下
<template>
<div>
<span>接受传进来的值为:</span>
<span>{{ route.query.id }}</span>
</div>
<div>这种方法真简单</div>
</template>
<script setup>
import {useRoute} from 'vue-router'
const route = useRoute();
</script>
<style scoped>
</style>
方法2:vue模板字符串创建vue实例
<template>
<div style="width: 100%;height: 100%;" id="map"></div>
</template>
<script setup>
import 'leaflet/dist/leaflet.css';
import * as L from 'leaflet';
import {createApp, onMounted} from "vue";
onMounted(() => {
// 创建地图
const map = L.map('map').setView([31.491064, 120.311889], 13);
// 添加图层
const baseLayer = L.tileLayer("http://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}", {
subdomains: "1234"
});
map.addLayer(baseLayer);
// 创建poi点标记
const marker = L.marker([31.491064, 120.311889]).addTo(map);
/* -----------
该方法需要在vite.config.js中配置如下参数,是为了改变引入的vue运行时
alias: {
'vue': 'vue/dist/vue.esm-bundler.js' // 使用模板字符串时需要设置
}
----------------------*/
// 需要指定id,vue才能找到对应渲染节点
marker.bindPopup(`<div id="my_popup" style="width: 200px;height: 100px;"></div>`)
// 通过vue的模板字符串创建并渲染到#my_popup节点上
const id = 666;
let vm;
marker.on('popupopen', e => {
vm = createApp({
template: `
<div>通过vue的字符串模板创建,可以将外部的参数直接传递到data中</div>
<div>接收到的参数值:{{ count }}</div>
<div>
<button @click="count++">+1</button>
</div>
`,
data() {
return {
count: id
}
}
})
vm.mount('#my_popup')
})
// 弹窗关闭时,一定需要销毁vue实例!非常重要!
marker.on('popupclose', e => {
vm.unmount();
})
// 自动打开marker,方便观察
marker.openPopup();
})
</script>
方法3:使用.vue单文件创建vue实例
这种方法和方法2本质上一样,不过是将写的一堆模板提取到单文件中了
index.vue
文件如下:
<template>
<div style="width: 100%;height: 100%;" id="map"></div>
</template>
<script setup>
import 'leaflet/dist/leaflet.css';
import * as L from 'leaflet';
import {createApp, onMounted} from "vue";
import component from './component.vue';
onMounted(() => {
// 创建地图
const map = L.map('map').setView([31.491064, 120.311889], 13);
// 添加图层
const baseLayer = L.tileLayer("http://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}", {
subdomains: "1234"
});
map.addLayer(baseLayer);
// 创建poi点标记
const marker = L.marker([31.491064, 120.311889]).addTo(map);
// 需要指定id,vue才能找到对应渲染节点
marker.bindPopup(`<div id="my_popup" style="width: 200px;height: 100px;"></div>`)
// 使用引入的sfc文件创建vue实例
const id = 666;
let vm;
marker.on('popupopen', e => {
vm = createApp(component, {id: id})
vm.mount('#my_popup')
})
// 弹窗关闭时,一定需要销毁vue实例!非常重要!
marker.on('popupclose', e => {
vm.unmount();
})
// 自动打开marker,方便观察
marker.openPopup();
})
</script>
component.vue
文件如下:
<template>
<div>
<span>接受传进来的值为:</span>
<span>{{ props.id }}</span>
</div>
<div>这种方法也很简单</div>
</template>
<script setup>
const props = defineProps(['id']);
</script>
<style scoped>
</style>
方法4:使用vue3新方法defineCustomElement
这种方法最推荐,和方法3比较类似,但是不用手动销毁vue实例。原理是利用了vue3的新方法,创建了现代浏览器能够默认识别的html组件。
index.vue
文件如下
<template>
<div style="width: 100%;height: 100%;" id="map"></div>
</template>
<script setup>
import 'leaflet/dist/leaflet.css';
import * as L from 'leaflet';
import {defineCustomElement, onMounted} from "vue";
import component from './component.vue';
onMounted(() => {
// 创建地图
const map = L.map('map').setView([31.491064, 120.311889], 13);
// 添加图层
const baseLayer = L.tileLayer("http://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}", {
subdomains: "1234"
});
map.addLayer(baseLayer);
// 创建poi点标记
const marker = L.marker([31.491064, 120.311889]).addTo(map);
const MyVueElement = defineCustomElement(component);
if(!customElements.get('my-vue-element'))
customElements.define('my-vue-element', MyVueElement)
marker.bindPopup(`<my-vue-element j-id="666" style="width: 200px;height: 100px;"></my-vue-element>`)
// 自动打开marker,方便观察
marker.openPopup();
})
</script>
component.vue
文件如下
<template>
<div>
<span>接受传进来的值为:</span>
<span>{{ props["jId"] }}</span>
</div>
<div>这种方法最推荐</div>
</template>
<script setup>
const props = defineProps(['jId']);
</script>
<style scoped>
</style>
方法5:使用jsx
实在写不动了🤣,jsx使用也是挺方便的,大家可以自己试试。