一个好习惯,先给结论

有5种方法:

  1. iframe嵌套,这种最简单
  2. vue模板字符串创建vue实例
  3. 使用.vue单文件创建vue实例
  4. 使用vue3新方法defineCustomElement,这种最推荐
  5. 使用jsx

本文首发于blog.gis1024.com

预览(点击试试)

完整代码库→ github跳转

这里才是引言

在gis项目开发中,经常遇到需要在地图弹窗中显示信息的需求。几乎所有地图框架的弹窗api,都是用html字符串的形式填写内容。

以百度地图为例,可以看到BMapGL.InfoWindow后跟的是一个字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 百度地图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的组合演示几种方法,其他地图如百度地图高德地图mapboxopenlayersleafletmaptalks等地图框架和vue2的组合同理。

方法1:iframe嵌套

  1. 将弹窗中的内容写成一个.vue组件,并且在路由中将这个组件指定为一个路由
    router.js文件如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ...其他代码省略
    {
    path: '/1',
    component: () => import('../views/1_iframe/index.vue')
    },
    {
    path: '/1_inner',
    component: () => import('../views/1_iframe/iframe.vue')
    }
    ...其他代码省略
  2. 地图主文件views/1_iframe/index.vue文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <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>
  3. 弹窗页面/views/1_iframe/iframe.vue如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <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实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<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文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<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文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<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文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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使用也是挺方便的,大家可以自己试试。

本文首发于blog.gis1024.com