Ver código fonte

feat: 5秒定时刷新位置,用户标记使用本地图片,控制台打印经纬度

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tanlie 1 mês atrás
pai
commit
e654b975be

BIN
wishing-tree-h5/src/assets/location-marker.png


+ 25 - 1
wishing-tree-h5/src/stores/location.ts

@@ -1,10 +1,12 @@
 import { defineStore } from 'pinia'
 import { ref } from 'vue'
+import { getUserLocation } from '@/utils/geo'
 
 export const useLocationStore = defineStore('location', () => {
   const lng = ref<number | null>(null)
   const lat = ref<number | null>(null)
   const located = ref(false)
+  let timer: ReturnType<typeof setInterval> | null = null
 
   function setLocation(longitude: number, latitude: number) {
     lng.value = longitude
@@ -12,5 +14,27 @@ export const useLocationStore = defineStore('location', () => {
     located.value = true
   }
 
-  return { lng, lat, located, setLocation }
+  async function refreshLocation() {
+    try {
+      const pos = await getUserLocation()
+      setLocation(pos.lng, pos.lat)
+    } catch {
+      // 保留上次位置
+    }
+  }
+
+  function startWatch(intervalMs = 5000) {
+    stopWatch()
+    refreshLocation()
+    timer = setInterval(refreshLocation, intervalMs)
+  }
+
+  function stopWatch() {
+    if (timer) {
+      clearInterval(timer)
+      timer = null
+    }
+  }
+
+  return { lng, lat, located, setLocation, refreshLocation, startWatch, stopWatch }
 })

+ 19 - 17
wishing-tree-h5/src/views/HomeView.vue

@@ -36,11 +36,10 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, onUnmounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { showDialog } from 'vant'
 import { useLocationStore } from '@/stores/location'
-import { getUserLocation } from '@/utils/geo'
 import { fetchNearbyTrees } from '@/api/tree'
 import TreeCard from '@/components/TreeCard.vue'
 
@@ -53,19 +52,6 @@ const finished = ref(false)
 const refreshing = ref(false)
 const locating = ref(true)
 
-async function loadLocation() {
-  locating.value = true
-  try {
-    const { lng, lat } = await getUserLocation()
-    locationStore.setLocation(lng, lat)
-  } catch {
-    // 定位失败,使用默认坐标(北京)
-    locationStore.setLocation(116.4074, 39.9042)
-  } finally {
-    locating.value = false
-  }
-}
-
 async function loadTrees() {
   loading.value = true
   try {
@@ -84,7 +70,7 @@ async function loadTrees() {
 
 async function onRefresh() {
   refreshing.value = true
-  await loadLocation()
+  await locationStore.refreshLocation()
   finished.value = false
   await loadTrees()
 }
@@ -94,9 +80,25 @@ function goTree(id: number) {
 }
 
 onMounted(async () => {
-  await loadLocation()
+  locationStore.startWatch(5000)
+  // 等待首次定位
+  if (!locationStore.located) {
+    await new Promise<void>((resolve) => {
+      const check = setInterval(() => {
+        if (locationStore.located) {
+          clearInterval(check)
+          resolve()
+        }
+      }, 200)
+    })
+  }
+  locating.value = false
   loadTrees()
 })
+
+onUnmounted(() => {
+  locationStore.stopWatch()
+})
 </script>
 
 <style scoped>

+ 111 - 79
wishing-tree-h5/src/views/MapView.vue

@@ -4,7 +4,14 @@
 
     <!-- 定位按钮 -->
     <div class="map-controls">
-      <van-button icon="aim" round type="primary" size="small" :loading="locating" @click="locateUser">
+      <van-button
+        icon="aim"
+        round
+        type="primary"
+        size="small"
+        :loading="locating"
+        @click="locateUser"
+      >
         定位
       </van-button>
     </div>
@@ -24,10 +31,20 @@
         <h3 class="popup-name">{{ selectedTree.name }}</h3>
         <p class="popup-addr">{{ selectedTree.address }}</p>
         <div class="popup-meta">
-          <van-tag :type="selectedTree.isInRange ? 'success' : 'warning'" round size="medium">
-            {{ selectedTree.isInRange ? '可许愿' : '距离 ' + fmtDist(selectedTree.distance) }}
+          <van-tag
+            :type="selectedTree.isInRange ? 'success' : 'warning'"
+            round
+            size="medium"
+          >
+            {{
+              selectedTree.isInRange
+                ? "可许愿"
+                : "距离 " + fmtDist(selectedTree.distance)
+            }}
           </van-tag>
-          <span class="popup-count">💬 {{ selectedTree.totalWishes }} 个愿望</span>
+          <span class="popup-count"
+            >💬 {{ selectedTree.totalWishes }} 个愿望</span
+          >
         </div>
         <div class="popup-actions">
           <van-button
@@ -47,7 +64,7 @@
             :disabled="!selectedTree.isInRange"
             @click="goMakeWish"
           >
-            {{ selectedTree.isInRange ? '🙏 去许愿' : '需要靠近许愿树' }}
+            {{ selectedTree.isInRange ? "🙏 去许愿" : "需要靠近许愿树" }}
           </van-button>
         </div>
       </div>
@@ -56,114 +73,117 @@
 </template>
 
 <script setup lang="ts">
-import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
-import { useRouter } from 'vue-router'
-import { showToast } from 'vant'
-import { useLocationStore } from '@/stores/location'
-import { getUserLocation } from '@/utils/geo'
-import { loadAMap } from '@/utils/amap'
-import { getTreeGradient, getTreeEmoji } from '@/utils/theme'
-import { fetchNearbyTrees } from '@/api/tree'
+import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
+import { useRouter } from "vue-router";
+import { showToast } from "vant";
+import { useLocationStore } from "@/stores/location";
+import { getUserLocation } from "@/utils/geo";
+import { loadAMap } from "@/utils/amap";
+import { getTreeGradient, getTreeEmoji } from "@/utils/theme";
+import { fetchNearbyTrees } from "@/api/tree";
+import markerImg from "@/assets/location-marker.png";
 
-const router = useRouter()
-const locationStore = useLocationStore()
+const router = useRouter();
+const locationStore = useLocationStore();
 
-const mapContainer = ref<HTMLDivElement>()
-const showPopup = ref(false)
-const selectedTree = ref<any>(null)
-const locating = ref(false)
+const mapContainer = ref<HTMLDivElement>();
+const showPopup = ref(false);
+const selectedTree = ref<any>(null);
+const locating = ref(false);
 
-const popupGradient = computed(() => getTreeGradient(selectedTree.value?.id || 1))
-const popupEmoji = computed(() => getTreeEmoji(selectedTree.value?.id || 1))
+const popupGradient = computed(() =>
+  getTreeGradient(selectedTree.value?.id || 1),
+);
+const popupEmoji = computed(() => getTreeEmoji(selectedTree.value?.id || 1));
 
-let map: any = null
-let userMarker: any = null
-let treeMarkers: any[] = []
-let AMap: any = null
+let map: any = null;
+let userMarker: any = null;
+let treeMarkers: any[] = [];
+let AMap: any = null;
 
 function fmtDist(m: number) {
-  return m >= 1000 ? (m / 1000).toFixed(1) + 'km' : m + 'm'
+  return m >= 1000 ? (m / 1000).toFixed(1) + "km" : m + "m";
 }
 
 function goTreeDetail() {
-  showPopup.value = false
-  router.push(`/tree/${selectedTree.value.id}`)
+  showPopup.value = false;
+  router.push(`/tree/${selectedTree.value.id}`);
 }
 
 function goMakeWish() {
-  showPopup.value = false
-  router.push({ path: '/make-wish', query: { treeId: selectedTree.value.id } })
+  showPopup.value = false;
+  router.push({ path: "/make-wish", query: { treeId: selectedTree.value.id } });
 }
 
 async function initMap() {
   try {
-    AMap = await loadAMap()
+    AMap = await loadAMap();
 
     map = new AMap.Map(mapContainer.value!, {
       zoom: 14,
       center: [116.4074, 39.9042],
-      mapStyle: 'amap://styles/light',
-    })
+      mapStyle: "amap://styles/light",
+    });
 
-    map.addControl(new AMap.Scale())
-    map.addControl(new AMap.ToolBar({ position: 'RT' }))
+    map.addControl(new AMap.Scale());
+    map.addControl(new AMap.ToolBar({ position: "RT" }));
 
-    await locateUser()
+    await locateUser();
   } catch (err) {
-    showToast('地图加载失败')
-    console.error(err)
+    showToast("地图加载失败");
+    console.error(err);
   }
 }
 
 async function locateUser() {
-  locating.value = true
+  locating.value = true;
   try {
-    const { lng, lat } = await getUserLocation()
-    locationStore.setLocation(lng, lat)
+    const { lng, lat } = await getUserLocation();
+    locationStore.setLocation(lng, lat);
 
     if (map) {
-      map.setCenter([lng, lat])
+      map.setCenter([lng, lat]);
 
-      if (userMarker) map.remove(userMarker)
+      if (userMarker) map.remove(userMarker);
       userMarker = new AMap.Marker({
         position: [lng, lat],
         icon: new AMap.Icon({
-          size: new (AMap as any).Size(28, 28),
-          image: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png',
-          imageSize: new (AMap as any).Size(28, 28),
+          size: new (AMap as any).Size(25, 45),
+          image: markerImg,
+          imageSize: new (AMap as any).Size(25, 45),
         }),
         zIndex: 200,
-        anchor: 'center',
-      })
-      map.add(userMarker)
+        anchor: "center",
+      });
+      map.add(userMarker);
     }
 
-    await loadTrees()
+    await loadTrees();
   } catch {
-    await loadTrees()
+    await loadTrees();
   } finally {
-    locating.value = false
+    locating.value = false;
   }
 }
 
 async function loadTrees() {
-  if (!locationStore.lng) return
+  if (!locationStore.lng) return;
 
   const trees = await fetchNearbyTrees(
     locationStore.lng,
     locationStore.lat!,
     50000,
-    100
-  )
+    100,
+  );
 
   // 清除旧标记
-  treeMarkers.forEach((m) => map.remove(m))
-  treeMarkers = []
+  treeMarkers.forEach((m) => map.remove(m));
+  treeMarkers = [];
 
   trees.forEach((tree: any) => {
-    const isIn = tree.isInRange
-    const color = isIn ? '#07c160' : '#ff976a'
-    const label = `${tree.name} ${fmtDist(tree.distance)}`
+    const isIn = tree.isInRange;
+    const color = isIn ? "#07c160" : "#ff976a";
+    const label = `${tree.name} ${fmtDist(tree.distance)}`;
 
     const content = `
       <div style="
@@ -192,40 +212,52 @@ async function loadTrees() {
           border-right:6px solid transparent;
           border-top:6px solid ${color};
         "></div>
-      </div>`
+      </div>`;
 
     const marker = new AMap.Marker({
       position: [tree.longitude, tree.latitude],
       content: content,
-      anchor: 'bottom-center',
+      anchor: "bottom-center",
       offset: new (AMap as any).Pixel(0, -6),
-    })
+    });
 
-    marker.on('click', () => {
-      selectedTree.value = tree
-      showPopup.value = true
-    })
+    marker.on("click", () => {
+      selectedTree.value = tree;
+      showPopup.value = true;
+    });
 
-    marker.setMap(map)
-    treeMarkers.push(marker)
-  })
+    marker.setMap(map);
+    treeMarkers.push(marker);
+  });
 
   // 适配视野
   if (trees.length > 0 && locationStore.lng) {
-    const points = trees.map((t: any) => [t.longitude, t.latitude])
-    points.push([locationStore.lng, locationStore.lat])
-    map.setFitView(points, true, [80, 80, 80, 200])
+    const points = trees.map((t: any) => [t.longitude, t.latitude]);
+    points.push([locationStore.lng, locationStore.lat]);
+    map.setFitView(points, true, [80, 80, 80, 200]);
   }
 }
 
+watch(
+  () => [locationStore.lng, locationStore.lat] as const,
+  ([lng, lat]) => {
+    if (lng != null && lat != null) {
+      console.log(`当前位置:经度 ${lng},纬度 ${lat}`);
+    }
+  },
+  { immediate: true },
+);
+
 onMounted(async () => {
-  await nextTick()
-  initMap()
-})
+  await nextTick();
+  initMap();
+  locationStore.startWatch(5000);
+});
 
 onUnmounted(() => {
-  if (map) map.destroy()
-})
+  locationStore.stopWatch();
+  if (map) map.destroy();
+});
 </script>
 
 <style scoped>
@@ -261,7 +293,7 @@ onUnmounted(() => {
 }
 .popup-emoji {
   font-size: 44px;
-  text-shadow: 0 1px 4px rgba(0,0,0,0.2);
+  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
 }
 .popup-name {
   font-size: 17px;