83 lines
3.9 KiB
Vue
83 lines
3.9 KiB
Vue
<template>
|
||
<div class="map-shell">
|
||
<div v-if="canLoadMap" ref="mapRef" class="map-canvas" :aria-label="title"></div>
|
||
<div v-if="!canLoadMap || hasError" class="map-fallback">
|
||
<div>
|
||
<span>公司位置</span>
|
||
<h3>{{ title }}</h3>
|
||
<p>{{ address }}</p>
|
||
<small>经纬度:{{ lng }}, {{ lat }}</small>
|
||
</div>
|
||
<a :href="mapLink" target="_blank" rel="noreferrer">打开高德地图</a>
|
||
</div>
|
||
<div v-if="loading && canLoadMap" class="map-loading">地图加载中...</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||
import AMapLoader from '@amap/amap-jsapi-loader'
|
||
|
||
const props = defineProps({
|
||
lng: { type: Number, required: true },
|
||
lat: { type: Number, required: true },
|
||
zoom: { type: Number, default: 16 },
|
||
title: { type: String, default: '公司位置' },
|
||
address: { type: String, default: '' },
|
||
amapKey: { type: String, default: import.meta.env.VITE_AMAP_KEY || '' },
|
||
securityJsCode: { type: String, default: import.meta.env.VITE_AMAP_SECURITY_CODE || '' }
|
||
})
|
||
|
||
const mapRef = ref(null)
|
||
const mapInstance = ref(null)
|
||
const loading = ref(false)
|
||
const hasError = ref(false)
|
||
const canLoadMap = computed(() => Boolean(props.amapKey && props.securityJsCode))
|
||
const mapLink = computed(() => `https://uri.amap.com/marker?position=${props.lng},${props.lat}&name=${encodeURIComponent(props.title)}&src=xiaokang&coordinate=gaode&callnative=1`)
|
||
|
||
const initMap = async () => {
|
||
if (!canLoadMap.value) return
|
||
loading.value = true
|
||
hasError.value = false
|
||
try {
|
||
window._AMapSecurityConfig = { securityJsCode: props.securityJsCode }
|
||
const AMap = await AMapLoader.load({ key: props.amapKey, version: '2.0', plugins: ['AMap.InfoWindow'] })
|
||
await nextTick()
|
||
if (!mapRef.value) return
|
||
mapInstance.value = new AMap.Map(mapRef.value, { zoom: props.zoom, center: [props.lng, props.lat], viewMode: '3D' })
|
||
const marker = new AMap.Marker({ position: [props.lng, props.lat], title: props.title })
|
||
mapInstance.value.add(marker)
|
||
const infoWindow = new AMap.InfoWindow({
|
||
content: `<div style="padding:8px 10px"><strong>${props.title}</strong><p style="margin:6px 0 0;font-size:12px;line-height:1.6">${props.address}</p></div>`,
|
||
offset: new AMap.Pixel(0, -30)
|
||
})
|
||
infoWindow.open(mapInstance.value, [props.lng, props.lat])
|
||
marker.on('click', () => infoWindow.open(mapInstance.value, [props.lng, props.lat]))
|
||
} catch (error) {
|
||
console.error('地图加载失败', error)
|
||
hasError.value = true
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(initMap)
|
||
onBeforeUnmount(() => {
|
||
mapInstance.value?.destroy?.()
|
||
mapInstance.value = null
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.map-shell { position: relative; min-height: 380px; overflow: hidden; border: 1px solid var(--line); border-radius: 14px; background: var(--panel-bg); }
|
||
.map-canvas { width: 100%; min-height: 380px; }
|
||
.map-loading { position: absolute; inset: 0; display: grid; place-items: center; background: color-mix(in srgb, var(--panel-bg) 70%, transparent); color: var(--text-main); }
|
||
.map-fallback { min-height: 380px; display: flex; align-items: end; justify-content: space-between; gap: 24px; padding: 28px; background: linear-gradient(135deg, color-mix(in srgb, var(--panel-bg) 88%, #fff 12%), var(--page-muted)); }
|
||
.map-fallback span { color: var(--gold); font-size: 13px; font-weight: 800; }
|
||
.map-fallback h3 { margin-top: 10px; color: var(--ink); font-size: clamp(28px, 4vw, 48px); font-weight: 900; }
|
||
.map-fallback p { margin-top: 10px; color: var(--text-main); line-height: 1.8; }
|
||
.map-fallback small { display: block; margin-top: 10px; color: var(--text-muted); }
|
||
.map-fallback a { flex: 0 0 auto; border: 1px solid var(--line); border-radius: 999px; padding: 12px 18px; color: var(--ink); font-weight: 800; }
|
||
@media (max-width: 720px) { .map-fallback { align-items: stretch; flex-direction: column; } .map-fallback a { text-align: center; } }
|
||
</style>
|