Mapboxgl popup 自定义弹窗:动态展示数据,动态更新,样式调整

自定义弹窗的开发

自定义弹窗类CustomPopup.js

export default class CustomPopup {
        // options.coordinates用于地图定位,必须有,其他属性根据具体业务调整
	constructor(options) {
		this.popup = null; // mapboxgl.Popup实例
		this.options = options; // 配置选项
		this.defaultOptions = { // mapboxgl.Popup原生选项
			className: "custom-popup" + (options.type ? " popup-" + options.type : ""), // 默认class是custom-popup,如果需要根据type定制,则用到后面的class popup-xxx
			// closeOnClick: false, // 是否在点击地图地他地方时关闭弹窗
			maxWidth: "296px", // 限制宽度
		};
		this.newPopup(this.defaultOptions, options);
	}

	getPopup() {
		return this.popup;
	}

	getId() {
		return this.options && this.options.id;
	}

	getOptions() {
		return this.options;
	}

	remove() {
		this.popup && this.popup.remove();
		this.popup = null;
	}

	newPopup(defaultOptions, options) {
		let popupHtml = this.createHtml(options); // 根据具体业务需求拼接你需要的html
		this.popup = new mapboxgl.Popup(defaultOptions).setLngLat(options.coordinates).setHTML(popupHtml).addTo(window.map); // window.map mapboxgl全局map对象
	}

        // 更新弹窗内容和弹窗坐标
	updatePopup(options) {
		this.updatePopupContent(options);
		this.updateCoordinates(options.coordinates);
	}

        // 更新弹窗内容
	updatePopupContent(options) {
		this.options = options;
		let updatePopupHtml = this.createHtml(options.type, options.columns, options.properties, options.id, options.followStatus);
		this.popup.setHTML(updatePopupHtml);
	}

        // 更新弹窗坐标
	updateCoordinates(coordinates) {
		this.options.coordinates = coordinates;
		this.popup.setLngLat(coordinates);
	}

        // 拼接html,此处省略了具体业务
	createHtml() {
		let html = `<div class="popup-container">`;
		// 标题
		let headerHtml = this.createHeader();
		html += headerHtml;
		// 主要内容(分段)
		for (let key in columns) {
			if (key !== "标题") {
				html += this.createParagraph();
			}
		}
		// 页脚按钮
		let btnsHtml = this.createFooter();
		html += btnsHtml;
		html += `</div>`;
		return html;
	}

	createHeader() {
		let html = `<div class="popup-header">`;
		html += `<span>` + `你的标题` + `</span>`;
		html += `</div>`;
		return html;
	}

	createParagraph() {
		let html = `<div class="popup-pa">`;
		html += `<div class="popup-pa-title">` + `你的子标题` + `</div>`;
		html += `<div class="popup-pa-content">`;
		for (let key in subCol) {
			html +=
				`<div class="popup-pa-item"><span class="popup-pa-item-label">` +
				`你的label` +
				`: </span><span class="popup-pa-item-value">` +
				`你的value` +
				`</span></div>`;
		}
		html += `</div></div>`;
		return html;
	}

	createFooter() {
		let html = `<div class="popup-footer">`;
		btns.forEach((btn) => {
                        // 写一个 window.clickEvt(a, b, c) 即可接收点击事件
			html += `<button class="popup-btn" onclick="clickEvt('` + a + `','` + b + `','` + c + `')">` + btn + `</button>`;
		});
		html += `</div>`;
		return html;
	}
}

自定义弹窗样式

// 自定义弹窗
:deep(.custom-popup) {
	// 从点击位置引出的小三角
	.mapboxgl-popup-tip {
		border-top-color: @theme-background;
		border-bottom-color: @theme-background;
	}
	// 关闭按钮
	.mapboxgl-popup-close-button {
		height: 32px;
		color: @theme-text;
		font-size: 2em;
		padding: 0 5px;
		margin-right: 9px;
	}
	// 弹窗容器
	.mapboxgl-popup-content {
		width: 296px;
		padding: 0;
		background: @theme-background;
		color: @theme-text;

		// 自定义弹窗内容
		.popup-container {
			font-size: 14px;
			// 标题行
			.popup-header {
				padding: 0 14px;
				display: flex;
				height: 32px;
				line-height: 32px;
				text-align: left;
				border-bottom: 1px solid @theme-border-1;
				padding-right: 60px;

				span {
					overflow: hidden;
					text-overflow: ellipsis;
					white-space: nowrap;
				}

				.popup-icon {
					height: 32px;
					width: 32px;
					padding-right: 10px;
				}
			}
			// 段落,可以多段
			.popup-pa {
				padding: 0 14px;
				.popup-pa-title {
					padding-top: 2px;
					height: 32px;
					line-height: 32px;
					text-align: left;
					color: @theme-text-title;
					border-bottom: 1px solid @theme-border-2;
				}
				.popup-pa-content {
					display: flex;
					flex-flow: row wrap;
					width: 100%;
					.popup-pa-item {
						width: 49%;
						height: 32px;
						line-height: 32px;
						text-align: left;
						overflow: hidden;
						text-overflow: ellipsis;
						white-space: nowrap;

						.popup-pa-item-label {
							color: @theme-text-label;
						}
						.popup-pa-item-value {
							color: @theme-text-value;
						}
					}
				}
			}
			// 弹窗脚,主要放按钮
			.popup-footer {
				padding: 14px;
				height: 32px;
				line-height: 32px;
				text-align: right;
				.popup-btn {
					padding: 0;
					margin-left: 8px;
					width: 40px;
					height: 22px;
					line-height: 22px;
					border: 1px solid @theme-border-3;
					border-radius: 2px;
					font-size: 12px;
					color: @theme-text;
					background-color: @theme-input-background;
					cursor: pointer;
				}
			}
		}
	}
}

固定弹窗在窗口显示的位置

默认弹窗是跟随地图移动的,但是如果有固定弹窗位置的需求(这个需求如果一开始提出的话建议你直接不要用popup实现,这是我开发完以后经理才提出的),可以将样式小做以下修改:

:deep(.custom-popup) {
	transform: unset !important;
	left: 280px;
	top: 20px;

	// 从点击位置引出的小三角
	.mapboxgl-popup-tip {
		display: none;
	}
}

自定义弹窗的使用和管理

添加和删除

const popup = new CustomPopup({
	coordinates: [120, 30],
	... // 其他配置项
});
popup.remove();

缓存

可以借助pinia等工具缓存CustomPopup对象

// mapStore
const popupInstance = ref(null); // 该实例为封装过的CustomPopup
const memoPopup = (popup) => {
	popupInstance.value = popup;
};
const removePopup = () => {
	popupInstance.value && popupInstance.value.remove();
	popupInstance.value = null;
};

// 使用
const popup = ...
mapStore.memoPopup(popup);

更新

以使用pinia管理时更新为例

if (mapStore.popupInstance) {
	let popupInstance = mapStore.popupInstance;
	if (popupInstance.getId() === layerId + ":" + props.id) { // 判断你要更新的数据此时是否正在展示弹窗,我这里用的是图层id+数据id
		let op = popupInstance.getOptions();
		op.coordinates = coordinates; // 换成新的坐标
		op.properties = Object.assign(op.properties, props); // props是新的数据,用Object.assign前后对象只换掉有更新的数据
		popupInstance.updatePopup(op);
	}
}
posted @ 2023-03-30 16:45  宇宙野牛  阅读(1528)  评论(0编辑  收藏  举报