腾讯地图TMap标记反显,新增标记

功能:

1. 根据省市区获取对于的经纬度,设置为地图的center

2. 如果传入了经纬度,则在地图中反显

3. 根据输入内容,调用接口,获取关联关键字的地址列表,点击列表项后,根据地址经纬度,设置地图的center,并显示选中地址的标记

javascript 复制代码
// index.html
	<script src="https://map.qq.com/api/gljs?v=1.exp&key=腾讯KEY&libraries=drawing,geometry,autocomplete,convertor"></script>
javascript 复制代码
<template>
	<el-dialog v-model="visible" title="选择位置" width="60%">
		<div style="margin-bottom: 8px; display: flex; align-items: center; gap: 8px">
			<el-select
				v-model="searchKey"
				filterable
				remote
				reserve-keyword
				placeholder="搜索地址/关键字,按回车搜索"
				@change="handleSelect"
				:remote-method="remoteMethod"
				:loading="loading"
				value-key="address"
				style="width: 240px"
			>
				<el-option v-for="(item, index) in options" :key="index" :label="item.address" :value="item">
					<div style="display: flex; align-items: center; gap: 4px">
						<span>{{ item.address }}</span>
						<!-- <span style="color: var(--el-text-color-secondary)">{{ item.latitude.toFixed(6) }}, {{ item.longitude.toFixed(6) }}</span> -->
					</div>
				</el-option>
			</el-select>
			<div style="margin-left: 12px">
				<div>
					<span style="color: var(--el-text-color-secondary)">坐标</span>
					:
					<strong v-if="selected.lat">{{ selected.lat.toFixed(6) }}, {{ selected.lng.toFixed(6) }}</strong>
					<span v-else>未选中</span>
				</div>
				<!-- <div style="max-width: 520px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap">地址: {{ selected.address || '无' }}</div> -->
			</div>
		</div>

		<div ref="mapContainer" style="width: 100%; height: 520px; border: 1px solid #eee"></div>

		<template #footer>
			<div style="text-align: right">
				<el-button @click="handleCancel">取消</el-button>
				<el-button type="primary" :disabled="!selected.lat" @click="handleConfirm">确认</el-button>
			</div>
		</template>
	</el-dialog>
</template>

<script setup lang="ts">
import { ref, reactive, computed, nextTick, onBeforeUnmount, watch } from 'vue';
import { ElMessage } from 'element-plus';
import type { PropType } from 'vue';
import { getGeocoder, GetSuggestion } from '/@/api/store';

const props = defineProps({
	modelValue: {
		type: Object as PropType<{ lat?: number; lng?: number; address?: string }>,
		default: () => ({}),
	},
	initZoom: { type: Number, default: 15 },
});
const emit = defineEmits(['update:modelValue', 'confirm', 'cancel']);

const options = ref<{ address: string; latitude: number; longitude: number }[]>([]);
const visible = ref(false);
const mapContainer = ref<HTMLDivElement | null>(null);
const mapInstance: any = ref(null); // 地图实例
const marker: any = ref(null); // 地图上的 marker 实例
// let geocoder: any = null; // 地图上的 geocoder 实例

const searchKey = ref({});
const selected = reactive({ lat: 0 as number | null, lng: 0 as number | null, address: '' });

const loading = ref(false);
const remoteMethod = (query: string) => {
	console.log('🚀 ~ remoteMethod ~ query:', query);
	if (query) {
		loading.value = true;

		if (!mapInstance.value) {
			ElMessage.error('地图尚未初始化');
			return;
		}
		try {
			GetSuggestion({ keyword: query })
				.then((res: any) => {
					console.log('🚀 ~ remoteMethod ~ res:', res);
					if (res.data && res.data.result) {
						options.value = res.data.result;
						console.log('🚀 ~ remoteMethod ~ options.value:', options.value);
					} else {
						ElMessage.error('地址解析失败,未返回有效坐标');
					}
				})
				.finally(() => {
					loading.value = false;
				});
		} catch (e) {
			console.error('getLocation error', e);
			ElMessage.error('搜索失败');
		}
	} else {
		options.value = [];
	}
};
function handleSelect() {
	console.log('111111111111111', searchKey.value);
	// selectNow.value = selectNow.value.address;
	selected.address = searchKey.value.address;
	selected.lat = searchKey.value.latitude;
	selected.lng = searchKey.value.longitude;
	nextTick(() => {
		addMarker(new TMap.LatLng(selected.lat, selected.lng));
		// mapInstance.value.clearMarkers(); // 清除之前的标记
		mapInstance.value.setCenter(new TMap.LatLng(selected.lat, selected.lng));
	});
}
function openDialog(data: any) {
	console.log('searchKey111111111111111', searchKey);
	searchKey.value = {};
	options.value = [];
	visible.value = true;
	initMap(data);
}

const TMap = (window as any).TMap;

// 初始化地图
async function initMap(data: any) {
	console.log('🚀 ~ initMap ~ data:', data);
	await nextTick();
	if (!TMap) {
		ElMessage.error('未检测到腾讯地图 SDK,请确认已在 index.html 中引入');
		return;
	}

	let defaultCenter: any;

	// 1. 确定中心点坐标
	if (!data.lat && !data.lng && data.address) {
		console.log('适配:使用地址解析');
		// 调用地址解析接口,根据地址获取坐标
		try {
			const res: any = await getGeocoder({ address: data.address });
			if (res.data && res.data.result) {
				defaultCenter = new TMap.LatLng(res.data.result.latitude, res.data.result.longitude);
			} else {
				ElMessage.error('地址解析失败,未返回有效坐标');
				return;
			}
		} catch (error) {
			console.error('地址解析出错', error);
			ElMessage.error('地址解析出错');
			return;
		}
	} else if (data.lat && data.lng) {
		console.log('直接使用传入的经纬度');
		defaultCenter = new TMap.LatLng(data.lat, data.lng);
	} else {
		// 如果既没有坐标也没有地址,设置一个默认中心点(例如北京)
		defaultCenter = new TMap.LatLng(39.98412, 116.307484);
	}

	// 2. 初始化或更新地图
	if (!mapInstance.value) {
		// 首次初始化
		mapInstance.value = new TMap.Map(mapContainer.value as HTMLElement, {
			center: defaultCenter,
			zoom: props.initZoom,
		});
	} else {
		// 如果地图实例已存在,先清理旧的标记,再更新中心点和缩放级别
		if (marker.value) {
			marker.value.setMap(null);
			marker.value = null;
		}
		mapInstance.value.setCenter(defaultCenter);
		mapInstance.value.setZoom(props.initZoom);
	}

	// 3. 绑定点击事件(每次重新绑定确保逻辑最新)
	// 注意:腾讯地图的 on 方法通常会覆盖之前的同名事件监听,但为了保险起见,先 off 再 on
	console.log('🚀 ~ initMap ~ mapInstance.value:', mapInstance.value);
	// mapInstance.value.off('click');
	mapInstance.value.on('click', (e: any) => {
		addMarker(e.latLng);
	});

	// 4. 如果有初始坐标,添加标记
	if (data.lat && data.lng) {
		addMarker(new TMap.LatLng(data.lat, data.lng));
	}
}

function addMarker(latLng: any) {
	// 如果已存在marker,则先移除
	if (marker.value) {
		marker.value.setMap(null);
		marker.value = null;
	}
	selected.lat = latLng.lat;
	selected.lng = latLng.lng;

	// 创建新的marker
	marker.value = new TMap.MultiMarker({
		map: mapInstance.value,
		styles: {
			// 定义marker样式
			marker: new TMap.MarkerStyle({
				width: 25,
				height: 35,
				anchor: { x: 12.5, y: 35 },
			}),
		},
		geometries: [
			{
				position: latLng,
				id: 'marker1',
			},
		],
	});
}

function handleConfirm() {
	const payload = { lat: selected.lat as number, lng: selected.lng as number };
	emit('update:modelValue', payload);
	emit('confirm', payload);
	visible.value = false;
}
function handleCancel() {
	// 还原为父级传入值
	if (props.modelValue && props.modelValue.lat && props.modelValue.lng) {
		selected.lat = props.modelValue.lat;
		selected.lng = props.modelValue.lng;
		selected.address = props.modelValue.address || '';
	} else {
		selected.lat = null;
		selected.lng = null;
		selected.address = '';
	}
	emit('cancel');
	searchKey.value = {};
	visible.value = false;
}

onBeforeUnmount(() => {
	try {
		const TMap = (window as any).TMap;
		if (TMap && TMap.maps && mapInstance) {
			mapInstance.value.off('click');
			searchKey.value = {};
			options.value = [];
		}
	} catch (error) {
		console.error('地图销毁出错', error);
	}
});

defineExpose({
	openDialog,
});
</script>

<style scoped>
.location-picker {
	display: flex;
	align-items: center;
}
</style>
相关推荐
未知原色2 小时前
web worker使用总结(包含多个worker)
前端·javascript·react.js·架构·node.js
ttod_qzstudio2 小时前
CSS改变图片颜色方法介绍
前端·css
幽络源小助理2 小时前
SpringBoot+Vue摄影师分享社区源码 – Java项目免费下载 | 幽络源
java·vue.js·spring boot
curdcv_po2 小时前
我接入了微信小说小程序官方阅读器
前端·微信小程序
程序员鱼皮2 小时前
什么是 RESTful API?凭什么能流行 20 多年?
前端·后端·程序员
+VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue健身房管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
www_stdio3 小时前
让大语言模型拥有“记忆”:多轮对话与 LangChain 实践指南
前端·langchain·llm
inferno3 小时前
JavaScript 基础
开发语言·前端·javascript
cindershade3 小时前
Intersection Observer 的实战方案
前端