From 7504cce7f10f3db7359717a801f31d3b69f5d982 Mon Sep 17 00:00:00 2001 From: Mplan Date: Thu, 21 May 2026 17:25:40 +0800 Subject: [PATCH] feat: add map picker for upload coordinates --- src/views/upload/UploadView.vue | 233 +++++++++++++++++++++++++++++++- 1 file changed, 229 insertions(+), 4 deletions(-) diff --git a/src/views/upload/UploadView.vue b/src/views/upload/UploadView.vue index 950fba6..52e801a 100644 --- a/src/views/upload/UploadView.vue +++ b/src/views/upload/UploadView.vue @@ -4,6 +4,7 @@ import { useRouter } from 'vue-router' import { useCloudsStore } from '@/stores/clouds' import { useAuthStore } from '@/stores/auth' import { downloadCloudBadgeCard } from '@/lib/cloudBadges' +import { loadAMap } from '@/lib/amap' import { useUpload } from '@/composables/useUpload' const router = useRouter() @@ -17,6 +18,16 @@ const successMsg = ref(false) const unlockedBadges = ref>['unlockedBadges']>([]) const errorMsg = ref('') const fileInput = ref(null) +const mapPickerOpen = ref(false) +const mapPickerLoading = ref(false) +const mapPickerError = ref('') +const mapPickerLat = ref(null) +const mapPickerLng = ref(null) +const miniMapEl = ref(null) + +let mapPickerAMap: typeof AMap | null = null +let mapPickerInstance: AMap.Map | null = null +let mapPickerMarker: AMap.Marker | null = null const activeItem = computed(() => { if (!activeId.value) return null @@ -58,6 +69,122 @@ function handleRemove(id: string) { } } +function formatCoordinate(value: number | null) { + return value === null ? '未选择' : value.toFixed(6) +} + +function updateActiveCoordinates(latitude: number | null, longitude: number | null) { + if (!activeItem.value) return + + activeItem.value.latitude = latitude + activeItem.value.longitude = longitude + + if (activeItem.value.errors.latitude) delete activeItem.value.errors.latitude + if (activeItem.value.errors.longitude) delete activeItem.value.errors.longitude +} + +function destroyMapPicker() { + mapPickerMarker?.setMap(null) + mapPickerMarker = null + mapPickerInstance?.destroy() + mapPickerInstance = null +} + +function placeMapPickerMarker(longitude: number, latitude: number) { + if (!mapPickerAMap || !mapPickerInstance) return + + if (!mapPickerMarker) { + mapPickerMarker = new mapPickerAMap.Marker({ + position: [longitude, latitude], + title: '拍摄位置', + } as AMap.MarkerOptions) + mapPickerMarker.setMap(mapPickerInstance) + } else { + mapPickerMarker.setPosition([longitude, latitude]) + } +} + +function extractLngLat(event: unknown) { + const payload = event as { lnglat?: { lng?: number; lat?: number } } | undefined + const lng = payload?.lnglat?.lng + const lat = payload?.lnglat?.lat + + if (typeof lng !== 'number' || typeof lat !== 'number') return null + return { lng, lat } +} + +function handleMapPickerClick(event: unknown) { + const picked = extractLngLat(event) + if (!picked) return + + mapPickerLng.value = picked.lng + mapPickerLat.value = picked.lat + placeMapPickerMarker(picked.lng, picked.lat) +} + +async function openMapPicker() { + if (!activeItem.value) return + + mapPickerOpen.value = true + mapPickerLoading.value = true + mapPickerError.value = '' + mapPickerLat.value = activeItem.value.latitude + mapPickerLng.value = activeItem.value.longitude + + await nextTick() + destroyMapPicker() + + try { + mapPickerAMap = await loadAMap() + + const hasCoordinates = + typeof activeItem.value.latitude === 'number' && + typeof activeItem.value.longitude === 'number' + + const center: [number, number] = hasCoordinates + ? [activeItem.value.longitude as number, activeItem.value.latitude as number] + : [104.07, 30.67] + + mapPickerInstance = new mapPickerAMap.Map(miniMapEl.value!, { + viewMode: '2D', + pitch: 0, + rotation: 0, + zoom: hasCoordinates ? 11 : 4, + center, + mapStyle: 'amap://styles/normal', + resizeEnable: true, + } as AMap.MapOptions) + + mapPickerInstance.on('click', handleMapPickerClick) + + if (hasCoordinates) { + placeMapPickerMarker(activeItem.value.longitude as number, activeItem.value.latitude as number) + } + } catch (error) { + mapPickerError.value = error instanceof Error ? error.message : '地图加载失败' + } finally { + mapPickerLoading.value = false + } +} + +function closeMapPicker() { + mapPickerOpen.value = false + mapPickerLoading.value = false + mapPickerError.value = '' + destroyMapPicker() +} + +function confirmMapPicker() { + if (mapPickerLat.value === null || mapPickerLng.value === null) return + + updateActiveCoordinates(mapPickerLat.value, mapPickerLng.value) + closeMapPicker() +} + +function clearCoordinates() { + updateActiveCoordinates(null, null) +} + async function handleSubmit() { errorMsg.value = '' const allValid = validateAll() @@ -159,7 +286,10 @@ function onCategoryChange(e: Event) { } onMounted(() => { cloudsStore.fetchCloudTypes() }) -onUnmounted(() => { for (const item of items.value) URL.revokeObjectURL(item.preview) }) +onUnmounted(() => { + for (const item of items.value) URL.revokeObjectURL(item.preview) + destroyMapPicker() +}) + + +
+
+
+
+

地图选点

+

点击地图即可回填当前图片的经纬度,也可以继续手动修改。

+
+ +
+ +
+
+ 当前选择: + 纬度 {{ formatCoordinate(mapPickerLat) }} + 经度 {{ formatCoordinate(mapPickerLng) }} +
+ +
+
+ +
+ 正在加载地图... +
+ +
+ {{ mapPickerError }} +
+
+
+ +
+

建议点选大致拍摄位置,上传时会继续做模糊化处理。

+
+ + +
+
+
+
+