124 lines
3.3 KiB
Vue
124 lines
3.3 KiB
Vue
<script setup lang="ts">
|
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
|
import { loadAMap } from '@/lib/amap'
|
|
|
|
const props = defineProps<{
|
|
latitude: number | null
|
|
longitude: number | null
|
|
locationName?: string | null
|
|
}>()
|
|
|
|
const mapEl = ref<HTMLDivElement | null>(null)
|
|
const loading = ref(false)
|
|
const errorMsg = ref('')
|
|
|
|
let amapLib: typeof AMap | null = null
|
|
let mapInstance: AMap.Map | null = null
|
|
let marker: AMap.Marker | null = null
|
|
|
|
const hasCoordinates = computed(() => {
|
|
return Number.isFinite(props.latitude) && Number.isFinite(props.longitude)
|
|
})
|
|
|
|
const center = computed<[number, number] | null>(() => {
|
|
if (!hasCoordinates.value) return null
|
|
return [props.longitude as number, props.latitude as number]
|
|
})
|
|
|
|
function destroyMap() {
|
|
marker?.setMap(null)
|
|
marker = null
|
|
mapInstance?.destroy()
|
|
mapInstance = null
|
|
}
|
|
|
|
async function initializeMap() {
|
|
if (!center.value) {
|
|
destroyMap()
|
|
return
|
|
}
|
|
|
|
loading.value = true
|
|
errorMsg.value = ''
|
|
|
|
await nextTick()
|
|
destroyMap()
|
|
|
|
if (!mapEl.value) {
|
|
loading.value = false
|
|
return
|
|
}
|
|
|
|
try {
|
|
amapLib = await loadAMap()
|
|
if ('getConfig' in amapLib) {
|
|
amapLib.getConfig().appname = 'amap-jsapi-skill'
|
|
}
|
|
|
|
mapInstance = new amapLib.Map(mapEl.value, {
|
|
viewMode: '2D',
|
|
zoom: 11,
|
|
center: center.value,
|
|
mapStyle: 'amap://styles/normal',
|
|
features: ['bg', 'road', 'building', 'point'],
|
|
resizeEnable: true,
|
|
dragEnable: true,
|
|
zoomEnable: true,
|
|
keyboardEnable: false,
|
|
doubleClickZoom: true,
|
|
scrollWheel: true,
|
|
} as AMap.MapOptions)
|
|
|
|
mapInstance.addControl(new amapLib.ToolBar({
|
|
position: 'RT',
|
|
offset: [8, 8],
|
|
} as Record<string, unknown>))
|
|
|
|
marker = new amapLib.Marker({
|
|
position: center.value,
|
|
title: props.locationName || '拍摄位置',
|
|
content: '<div class="h-4 w-4 rounded-full border-2 border-white bg-teal-500 shadow-[0_0_0_4px_rgba(52,116,93,0.24)]"></div>',
|
|
offset: new amapLib.Pixel(-8, -8),
|
|
} as AMap.MarkerOptions)
|
|
marker.setMap(mapInstance)
|
|
} catch (error) {
|
|
errorMsg.value = error instanceof Error ? error.message : '地图加载失败'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
watch(center, () => {
|
|
initializeMap()
|
|
}, { immediate: true })
|
|
|
|
onBeforeUnmount(() => {
|
|
destroyMap()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="hasCoordinates" class="mt-5 overflow-hidden border border-slate-200 bg-white">
|
|
<div class="flex items-center justify-between border-b border-slate-200 px-4 py-3">
|
|
<div>
|
|
<p class="oc-section-label">位置地图</p>
|
|
<p class="mt-1 text-sm font-medium text-slate-900">{{ locationName || '拍摄位置' }}</p>
|
|
</div>
|
|
<p class="text-right text-[11px] leading-5 text-slate-500">
|
|
{{ latitude?.toFixed(4) }}<br />
|
|
{{ longitude?.toFixed(4) }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="relative h-40 bg-sky-50">
|
|
<div ref="mapEl" class="h-full w-full"></div>
|
|
<div v-if="loading" class="absolute inset-0 flex items-center justify-center bg-white/70 text-sm text-slate-500">
|
|
地图加载中...
|
|
</div>
|
|
<div v-else-if="errorMsg" class="absolute inset-0 flex items-center justify-center bg-white/85 px-4 text-center text-sm text-slate-500">
|
|
{{ errorMsg }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|