Merge branch 'master' of https://gitea.zianxin.com/zax/biz-pc-cloud-zax
This commit is contained in:
commit
0b26c6dbce
1 file changed
+110
-21
@@ -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>
|
||||
Reference in new issue
Block a user