master
lion 2 weeks ago
parent a9787af6cc
commit cb333fda21

@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch } from 'vue' import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { import {
getTiandituKey, getTiandituKey,
invalidateMapSize,
loadTianditu, loadTianditu,
parseTiandituPoiLonlat, parseTiandituPoiLonlat,
SUZHOU_MAP_CENTER, SUZHOU_MAP_CENTER,
@ -33,12 +34,46 @@ let map: TiandituMap | null = null
let marker: TiandituMarker | null = null let marker: TiandituMarker | null = null
let localSearch: TiandituLocalSearch | null = null let localSearch: TiandituLocalSearch | null = null
let tiandituApi: NonNullable<typeof window.T> | null = null let tiandituApi: NonNullable<typeof window.T> | null = null
let resizeTimers: ReturnType<typeof setTimeout>[] = []
function parseCoord(v: string) { function parseCoord(v: string) {
const n = Number(v) const n = Number(v)
return Number.isFinite(n) ? n : null return Number.isFinite(n) ? n : null
} }
function clearResizeTimers() {
for (const timer of resizeTimers) clearTimeout(timer)
resizeTimers = []
}
function scheduleMapResize() {
clearResizeTimers()
const delays = [0, 80, 240, 480]
for (const delay of delays) {
resizeTimers.push(
setTimeout(() => {
if (map) invalidateMapSize(map)
}, delay),
)
}
}
function destroyMapInstance() {
clearResizeTimers()
if (map && marker) {
try {
map.removeOverLay(marker)
} catch {
/* ignore */
}
}
marker = null
map?.destroy?.()
map = null
localSearch = null
if (containerRef.value) containerRef.value.innerHTML = ''
}
function applyPick(lng: number, lat: number, zoom = 16) { function applyPick(lng: number, lat: number, zoom = 16) {
longitude.value = lng.toFixed(6) longitude.value = lng.toFixed(6)
latitude.value = lat.toFixed(6) latitude.value = lat.toFixed(6)
@ -104,6 +139,7 @@ function selectPoi(poi: TiandituSearchPoi) {
} }
async function initMap() { async function initMap() {
await nextTick()
if (!containerRef.value) return if (!containerRef.value) return
mapError.value = '' mapError.value = ''
@ -114,14 +150,10 @@ async function initMap() {
mapLoading.value = true mapLoading.value = true
try { try {
destroyMapInstance()
const T = await loadTianditu() const T = await loadTianditu()
tiandituApi = T tiandituApi = T
if (map) {
if (marker) map.removeOverLay(marker)
marker = null
map = null
}
localSearch = null
const container = containerRef.value const container = containerRef.value
container.innerHTML = '' container.innerHTML = ''
@ -148,6 +180,8 @@ async function initMap() {
searchResults.value = [] searchResults.value = []
}) })
scheduleMapResize()
if (props.defaultKeyword.trim()) { if (props.defaultKeyword.trim()) {
searchKeyword.value = props.defaultKeyword.trim() searchKeyword.value = props.defaultKeyword.trim()
runSearch() runSearch()
@ -156,6 +190,7 @@ async function initMap() {
mapError.value = e instanceof Error ? e.message : '地图加载失败' mapError.value = e instanceof Error ? e.message : '地图加载失败'
} finally { } finally {
mapLoading.value = false mapLoading.value = false
scheduleMapResize()
} }
} }
@ -168,14 +203,15 @@ watch([longitude, latitude], async () => {
}) })
onMounted(() => { onMounted(() => {
void initMap() requestAnimationFrame(() => {
requestAnimationFrame(() => {
void initMap()
})
})
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (map && marker) map.removeOverLay(marker) destroyMapInstance()
marker = null
map = null
localSearch = null
tiandituApi = null tiandituApi = null
}) })
</script> </script>
@ -202,20 +238,22 @@ onBeforeUnmount(() => {
</ul> </ul>
<div v-if="mapError" class="pick-map-error">{{ mapError }}</div> <div v-if="mapError" class="pick-map-error">{{ mapError }}</div>
<div <div v-else class="pick-map-shell" v-loading="mapLoading">
v-else <div
ref="containerRef" ref="containerRef"
v-loading="mapLoading" class="pick-map"
class="pick-map" :style="{ height: `${props.height}px` }"
:style="{ height: `${props.height}px` }" />
/> </div>
<p v-if="!mapError" class="pick-hint"></p> <p v-if="!mapError" class="pick-hint"></p>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.pick-map-wrap { .pick-map-wrap {
position: relative;
margin-top: 4px; margin-top: 4px;
overflow: hidden;
} }
.pick-search-bar { .pick-search-bar {
@ -270,12 +308,27 @@ onBeforeUnmount(() => {
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
} }
.pick-map { .pick-map-shell {
position: relative;
width: 100%; width: 100%;
overflow: hidden;
border: 1px solid var(--el-border-color-light); border: 1px solid var(--el-border-color-light);
border-radius: 6px; border-radius: 6px;
background: #e8eef5;
}
.pick-map {
position: relative;
z-index: 0;
width: 100%;
overflow: hidden; overflow: hidden;
cursor: crosshair; cursor: crosshair;
isolation: isolate;
}
.pick-map :deep(.tdt-map) {
width: 100% !important;
height: 100% !important;
} }
.pick-map-error { .pick-map-error {

@ -98,6 +98,13 @@ export interface TiandituMap {
removeEventListener?(type: string, handler: (e?: TiandituMapClickEvent) => void): void removeEventListener?(type: string, handler: (e?: TiandituMapClickEvent) => void): void
lngLatToLayerPoint?(lnglat: TiandituLngLat): TiandituPoint lngLatToLayerPoint?(lnglat: TiandituLngLat): TiandituPoint
getPanes?(): { overlayPane: HTMLElement } getPanes?(): { overlayPane: HTMLElement }
checkResize?(): void
destroy?(): void
}
/** 弹窗内地图在容器尺寸变化后需重算布局,避免瓦片错位到页面顶部 */
export function invalidateMapSize(map: TiandituMap) {
map.checkResize?.()
} }
interface SchoolMapOverlayInstance { interface SchoolMapOverlayInstance {

@ -22,6 +22,7 @@ const filterRegion = ref('')
const dialog = ref(false) const dialog = ref(false)
const mapPickVisible = ref(false) const mapPickVisible = ref(false)
const mapPickReady = ref(false)
const editing = ref<UniversityRow | null>(null) const editing = ref<UniversityRow | null>(null)
const pickDraft = ref({ longitude: '', latitude: '' }) const pickDraft = ref({ longitude: '', latitude: '' })
const form = ref({ const form = ref({
@ -90,9 +91,18 @@ function openMapPick() {
longitude: form.value.longitude, longitude: form.value.longitude,
latitude: form.value.latitude, latitude: form.value.latitude,
} }
mapPickReady.value = false
mapPickVisible.value = true mapPickVisible.value = true
} }
function onMapPickOpened() {
mapPickReady.value = true
}
function onMapPickClosed() {
mapPickReady.value = false
}
function confirmMapPick() { function confirmMapPick() {
if (!pickDraft.value.longitude || !pickDraft.value.latitude) { if (!pickDraft.value.longitude || !pickDraft.value.latitude) {
ElMessage.warning('请先在地图上选点') ElMessage.warning('请先在地图上选点')
@ -232,15 +242,20 @@ usePageLoad(load)
<el-dialog <el-dialog
v-model="mapPickVisible" v-model="mapPickVisible"
class="map-pick-dialog"
title="地图选点" title="地图选点"
width="720px" width="720px"
destroy-on-close destroy-on-close
append-to-body append-to-body
align-center
@opened="onMapPickOpened"
@closed="onMapPickClosed"
> >
<p v-if="pickDraft.longitude && pickDraft.latitude" class="pick-coord-preview"> <p v-if="pickDraft.longitude && pickDraft.latitude" class="pick-coord-preview">
当前选点{{ pickDraft.longitude }}{{ pickDraft.latitude }} 当前选点{{ pickDraft.longitude }}{{ pickDraft.latitude }}
</p> </p>
<TiandituPickMap <TiandituPickMap
v-if="mapPickReady"
v-model:longitude="pickDraft.longitude" v-model:longitude="pickDraft.longitude"
v-model:latitude="pickDraft.latitude" v-model:latitude="pickDraft.latitude"
:default-keyword="form.name" :default-keyword="form.name"
@ -276,3 +291,10 @@ usePageLoad(load)
font-size: 13px; font-size: 13px;
} }
</style> </style>
<style>
/* 弹窗动画 transform 会导致天地图瓦片错位,打开后取消 transform */
.map-pick-dialog.el-dialog {
transform: none !important;
}
</style>

Loading…
Cancel
Save