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>