1.需求描述
需求类型:GIS点位标注
- 在地图上点击哪里,哪里就出现一个点位,且点位上出现一个输入框,用于输入点位名称;
- 输入框自动聚焦,只允许输入大小写英文、数字、符号,必填;
- 鼠标点击其他区域时,刚刚输入的点位添加到地图上,标点上方显示文本
- 标记的点位在左侧统计已添加的标记列表;
- 左侧删除了某个点位,地图上同步删除点位;
- 地图上有一个标记按钮,点击按钮后可在地图上添加点位,取消标记后点击地图无效
- 地图支持拖拽、缩放
- 最后,统一保存已添加的点位;
大概效果图如下:
2. 需求分析
- 标记点位可以用
L.marker()
方法来实现; - 点位上显示的文本可以用
bindTooltip()
方法来绑定; - 输入框,由于
leaflet
没有直接支持,所以只能自定义了【麻烦】; - 点击其他区域时,输入的内容和点位合并显示在地图上,我们不能每次点击地图一次就生成一个点
*- 如果地图上无输入框的聚焦状态,则地图上添加一个点位;
-
- 如果有聚焦输入框,则不做操作,仅仅让它失去聚焦,否则体验感不好;
-
- 地图在拖拽时,没有触发点击事件,但是也会失去焦点,需要单独处理;
- 在失去焦点时,需要做好几项判断:
- 需要获取输入框的内容;
- 需要判断这个点是否加在地图上;
- 需要判断输入框的内容是否符合要求并给出提示;
- 需要判断是在点击其余区域失焦的、还是拖拽失焦的。处理方式不一样;
- 已添加的点位传给左侧的组件,这个不难;
- 左侧的点位列表删除某个点位时,地图同步删除,理论上也不难;
- 控制地图是否可点击的,加一个标记判断即可;
3. 开发步骤
以下省略地图初始化的相关代码,仅列出关键字段及方法
注:本例演示版本:vue@2.6.12 + leaflet@1.9.4
3.1 基础数据声明
先在data()里声明一下变量
vue
data() {
return {
// 新增点位的输入框是否聚焦,默认不聚焦,显示时才聚焦; true 为聚焦,false 为失去焦点
isInputFocus: false,
// 输入框的点名称
inputPointName: null,
// 是否允许标记点位,默认否
isAllowEdit: false,
// 地图上GIS点位列表
GisPointList: [],
// GIS点图层
GISLayers: [],
// GIS组
GISGroup: [],
// 地图
miniMapView: null,
// 地图id
id: null,
}
}
3.2初始化地图
js
initMap(){
let mapOptions = {
center: [40.222, 119.111], //初始地图中心
zoom: 15, //初始缩放等级
maxZoom: 18, //最大缩放等级
minZoom: 13, //最小缩放等级
zoomControl: false, //不显示缩放小控件
attributionControl: false
};
this.miniMapView = new L.Map(this.id, mapOptions);
}
3.3 事件监听
3.3.1. 拖拽事件
如果当前为聚焦状态,把聚焦变为false,否则拖拽的时候触发了输入框失去焦点,但是变量isInputFocus
还是true值......(此处为体验优化处理)
js
this.miniMapView.on('drag', e => {
if(this.isInputFocus) {
this.isInputFocus = false
}
})
3.3.2.点击事件
- 如果当前为聚焦状态,把聚焦变为false,否则当你点击其他地方的时候,又会出现一个点位,不停地点不停地添加点位......(此处为体验优化处理)
- 如果当前非聚焦状态,则进行点位添加;
kotlin
this.miniMapView.on('click', e => {
// 允许编辑时才生效
if(this.isAllowEdit) {
// 如果是聚焦的,点击别处时先失去焦点,让它变为失去焦点
if(this.isInputFocus) {
this.isInputFocus = false
} else {
// 不是聚焦状态,则新添加其他点
const latlng = e.latlng
this.addIconByMarkPoint(latlng, NormalPoint, [28, 36], new Date().getTime())
}
}
})
3.4 添加点位
用基本的L.marker()
方法在地图上添加点位,传入坐标、点位图标、点位尺寸、点位id等信息
3.4.1 添加点位方法
还要自定义html内容,把它放入Popup方法中来显示,即下面声明的customPopupHtml
,到那时这里有个问题,刚点击地图时、显示的点位及输入框是无法聚焦的,即使你在customPopupHtml
中声明了autofocus
属性也没用,所以需要特殊处理一下。
js
/**
* 标记点位,复制的函数来修改的
* @param location 图标位置 [lat,lon]
* @param iconUrl 图标类型及路径 ../icon/防爆2.png
* @param iconSize 图标尺寸 [100,120]
* @param pointId 图标id 不可重复
*/
addIconByMarkPoint(
location,
iconUrl,
iconSize,
pointId,
) {
let markerIcon = L.icon({
iconUrl: iconUrl,
iconSize: iconSize,
});
this.normalUrl = iconUrl;
this.iconSize = iconSize;
let Marker = {};
Marker.pointId = pointId;
/** 自定义弹框内容 */
const customPopupHtml = `<div class='popup-input'>
<input type="text" placeholder="输入点位名称" style="width: 100%;" autofocus>
</div>`
// 是否点击了关闭按钮,因为点击击关闭按钮,会触发popupclose事件,所以需要一个变量来判断是否点击了关闭按钮
let isClickCloseBtn = false
const tempPopup = L.popup()
.setLatLng(location)
.setContent(customPopupHtml)
.openOn(this.miniMapView)
Marker = L.marker(location, { icon: markerIcon })
.addTo(this.miniMapView)
.bindPopup(tempPopup)
.on('popupopen', () => {
this.isInputFocus = true
})
.on('popupclose', () => {
isClickCloseBtn = true
})
Marker.openPopup()
},
3.4.2 自动聚焦
既然无法聚焦,那就让它自动聚焦,加个延时就好了。并且把isInputFocus
变量设置为true(因为多处需要监听)
ini
setTimeout(() => {
const inputElement = tempPopup.getElement().querySelector('input');
this.isInputFocus = true
if (inputElement) {
inputElement.focus(); // 自动聚焦
}
}, 80)
3.4.3 失去焦点处理
在上面自动聚焦后,需要对失去焦点进行监听。
监听时需要获取点位名称等基础信息、并且校验,如果不符合校验,把该点位删除
如果是符合规范的情况,失去焦点时,把该点添加进GISLayers
,之后就需要把地图上所有的点位进行重绘了,因为这些点位没有bindTooltip
,此处逻辑是先删除所有点位,再重新绘制带有bindTooltip
的点位(此方法可能非最佳)
同时给父组件传递事件,再处理左侧列表的点位数据
kotlin
inputElement.addEventListener('blur', () => {
setTimeout(() => {
this.inputPointName = inputElement.value
if(!inputElement.value) {
// 没有填写,则提示,且移除该点
this.$message.error('请输入新标注点位名称!')
this.miniMapView.removeLayer(Marker);
return
} else if(!inputRuleRegExp.test(inputElement.value)) {
this.$message.error('只允许输入大小写英文、数字、符号')
this.miniMapView.removeLayer(Marker);
return
} else {
this.GisPointList.push({
pointId: pointId,
pointName: inputElement.value || null,
lng: location.lng,
lat: location.lat,
})
this.$emit('gisClick', {
pointName: inputElement.value,
lng: location.lng,
lat: location.lat,
pointId: pointId,
isAdd: true
})
}
// 如果点击了关闭按钮,删除这个临时点
if(isClickCloseBtn) {
this.GISGroup = L.layerGroup(this.GISLayers);
this.miniMapView?.addLayer(this.GISGroup);
this.removeGISPointById()
} else {
// 其他情况的失去焦点,地图添加点位
this.GISLayers.push(Marker);
this.GISGroup = L.layerGroup(this.GISLayers);
this.miniMapView?.addLayer(this.GISGroup);
// this.miniMapView.removeLayer(Marker);
this.removeGISPointById(pointId)
}
}, 200)
// 这里可以添加失去焦点后的逻辑
this.miniMapView.removeLayer(Marker);
});
3.4.4 删除指定点位
也可用于左侧列表的删除,根据id删除地图上的点位
kotlin
/**
* 删除指定id的点位,其他点位重画
* */
removeGISPointById(pointId) {
// const tempArr = this.GisPointList.filter(item => item.pointId !== pointId)
const tempArr = pointId ? this.GisPointList.filter(item => item.pointId !== pointId) : this.GisPointList
//处理添加的marker图标组
this.GISLayers.splice(0, this.GisPointList.length);
//清除marker图层组
this.GISGroup.clearLayers();
this.GisPointList = tempArr
tempArr.forEach(item => {
this.reDrawGISPoint(
[item.lat, item.lng],
NormalPoint,
[28, 32],
item.pointId,
item.pointName,
)
})
},
至此,基本功能完成。