This commit is contained in:
dzc committed 2026-10-10 12:05:37 +08:00
commit 0b26c6dbce
1 file changed
+110 -21
+110 -21
View File
@@ -3,9 +3,13 @@
v-model="state.visibleMapDialog"
title="地图位置选择"
width="80%"
append-to-body
align-center
:close-on-click-modal="false"
:before-close="handleClose"
class="map-picker-dialog"
destroy-on-close
@opened="onDialogOpened"
>
<div class="map-picker-container">
<div class="search-bar">
@@ -30,7 +34,8 @@
</el-autocomplete>
</div>
<div class="map-container">
<!-- mousedown 阻止焦点落到抽屉/弹窗顶部输入框,避免一点地图就往上滚 -->
<div class="map-container" @mousedown="onMapMouseDown">
<tmap-map
ref="mapRef"
style="width: 100%; height: 480px; cursor: pointer"
@@ -62,17 +67,20 @@
</div>
</div>
<div v-if="state.selectedLocation" class="info-panel">
<div class="info-row">
<span class="label">地址</span>
<span class="value">{{ state.selectedAddress || "解析中…" }}</span>
</div>
<div class="info-row">
<span class="label">坐标</span>
<span class="value">
{{ state.selectedLocation.lng.toFixed(6) }}, {{ state.selectedLocation.lat.toFixed(6) }}
</span>
</div>
<div class="info-panel">
<template v-if="state.selectedLocation">
<div class="info-row">
<span class="label">地址</span>
<span class="value">{{ state.selectedAddress || "解析中…" }}</span>
</div>
<div class="info-row">
<span class="label">坐标</span>
<span class="value">
{{ state.selectedLocation.lng.toFixed(6) }}, {{ state.selectedLocation.lat.toFixed(6) }}
</span>
</div>
</template>
<div v-else class="info-placeholder">点击地图选点,或上方搜索定位</div>
</div>
</div>
<template #footer>
@@ -87,7 +95,7 @@
</template>
<script setup lang="ts" name="map-picker-dialog">
import { reactive, ref, nextTick } from "vue"
import { reactive, ref } from "vue"
import { ElMessage } from "element-plus"
import { TencentMapAPI } from "/@/utils/jsonp"
@@ -116,6 +124,43 @@ const map = reactive<any>({
const mapRef = ref()
/** 从 vue-tmap 组件上拿到腾讯地图实例(兼容几种挂载方式) */
const getTMapInstance = () => {
const inst: any = mapRef.value
return inst?.map || inst?.$map || inst?.getMap?.() || null
}
/** 平滑平移;勿改 :center 绑定,否则地图重建会导致抽屉/弹窗内容狂滚 */
const panMapTo = (lat: number, lng: number) => {
const tmap = getTMapInstance()
try {
if (tmap?.easeTo) {
tmap.easeTo({ center: { lat, lng } }, { duration: 200 })
return
}
if (tmap?.panTo) {
tmap.panTo({ lat, lng })
return
}
if (tmap?.setCenter) {
tmap.setCenter({ lat, lng })
}
} catch {
// 忽略实例未就绪
}
}
const onMapMouseDown = (e: MouseEvent) => {
// 阻止浏览器把焦点挪到弹窗顶部搜索框,从而带动滚动条往上跳
e.preventDefault()
}
const onDialogOpened = () => {
// 打开后主动失焦搜索框,避免焦点陷阱和地图抢焦点互相打架
const active = document.activeElement as HTMLElement | null
active?.blur?.()
}
const resetState = () => {
state.selectedLocation = null
state.selectedAddress = ""
@@ -131,7 +176,6 @@ const open = (initialLocation?: { lat: number; lng: number }, initialAddress?: s
ElMessage.error("未配置腾讯地图 Key,请在环境变量 VITE_TENCENT_MAP_KEY 中设置")
return
}
state.visibleMapDialog = true
resetState()
if (initialLocation?.lat != null && initialLocation?.lng != null) {
state.selectedLocation = { lat: Number(initialLocation.lat), lng: Number(initialLocation.lng) }
@@ -140,8 +184,11 @@ const open = (initialLocation?: { lat: number; lng: number }, initialAddress?: s
if (!state.selectedAddress) {
resolveAddress(state.selectedLocation.lat, state.selectedLocation.lng)
}
} else {
// 保持默认中心,避免无意义的中心对象抖动
map.center = { lat: 39.908823, lng: 116.39747 }
}
nextTick(() => {})
state.visibleMapDialog = true
}
const handleClose = () => {
@@ -151,7 +198,18 @@ const handleClose = () => {
}
const handleMapMouseMove = (event: any) => {
const { lat, lng } = event.latLng
const latLng = event?.latLng
if (!latLng) return
const { lat, lng } = latLng
// 仅更新展示,避免频繁 new 对象触发无意义重渲染
if (
state.hoverLocation &&
Math.abs(state.hoverLocation.lat - lat) < 1e-7 &&
Math.abs(state.hoverLocation.lng - lng) < 1e-7
) {
state.showHoverCoords = true
return
}
state.hoverLocation = { lat, lng }
state.showHoverCoords = true
}
@@ -161,9 +219,12 @@ const handleMapMouseLeave = () => {
state.hoverLocation = null
}
const applyLocation = async (lat: number, lng: number, addressHint?: string) => {
const applyLocation = async (lat: number, lng: number, addressHint?: string, recenter = false) => {
state.selectedLocation = { lat, lng }
map.center = { lat, lng }
// 搜索定位需要挪视野;点选/拖拽只动标记,不再改 :center
if (recenter) {
panMapTo(lat, lng)
}
if (addressHint) {
state.selectedAddress = addressHint
return
@@ -192,12 +253,12 @@ const resolveAddress = async (lat: number, lng: number) => {
const handleMapClick = (event: any) => {
const { lat, lng } = event.latLng
applyLocation(lat, lng)
applyLocation(lat, lng, undefined, false)
}
const handleMarkerDragEnd = (event: any) => {
const { lat, lng } = event.latLng
applyLocation(lat, lng)
applyLocation(lat, lng, undefined, false)
}
const querySearch = async (queryString: string, cb: (list: any[]) => void) => {
@@ -228,7 +289,7 @@ const handleSearchSelect = (item: any) => {
const lat = Number(item.location.lat)
const lng = Number(item.location.lng)
const address = [item.title, item.address].filter(Boolean).join(" ")
applyLocation(lat, lng, address)
applyLocation(lat, lng, address, true)
state.searchKeyword = item.title || ""
}
@@ -288,10 +349,12 @@ defineExpose({ open })
.map-picker-container {
display: flex;
flex-direction: column;
overflow: hidden;
}
.search-bar {
padding: 0 0 12px;
flex-shrink: 0;
}
.search-input {
@@ -313,6 +376,9 @@ defineExpose({ open })
position: relative;
overflow: hidden;
border-radius: 4px;
flex-shrink: 0;
/* 避免地图内部 canvas 抢焦点时把外层滚动条顶飞 */
touch-action: none;
}
.hover-coords {
@@ -335,6 +401,14 @@ defineExpose({ open })
.info-panel {
padding: 12px 0 0;
min-height: 56px;
flex-shrink: 0;
}
.info-placeholder {
font-size: 13px;
color: #909399;
line-height: 32px;
}
.info-row {
@@ -377,3 +451,18 @@ defineExpose({ open })
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
}
</style>
<style>
/* 弹层挂到 body 后限制 body 区域滚动,防止选点时外层抽屉跟着滚 */
.map-picker-dialog.el-dialog {
margin-top: 0 !important;
display: flex;
flex-direction: column;
max-height: 90vh;
}
.map-picker-dialog .el-dialog__body {
overflow: hidden;
padding-top: 10px;
padding-bottom: 10px;
}
</style>