HomeView.vue 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. <template>
  2. <div class="home-page with-bottom-nav">
  3. <div class="hero-section">
  4. <h1 class="hero-title">许愿树 ✨</h1>
  5. <p class="hero-sub">找到你身边的许愿树,记录美好心愿</p>
  6. <div class="location-bar">
  7. <van-icon name="location-o" />
  8. <span v-if="locating">正在获取位置...</span>
  9. <span v-else-if="locationStore.located">
  10. 已定位 · 附近 {{ nearby.length }} 棵许愿树
  11. </span>
  12. <span v-else>定位失败,显示全部许愿树</span>
  13. </div>
  14. </div>
  15. <van-pull-refresh v-if="!locating" v-model="refreshing" @refresh="onRefresh">
  16. <van-list
  17. v-model:loading="loading"
  18. :finished="finished"
  19. finished-text="已经找到所有许愿树啦 ✨"
  20. @load="loadTrees"
  21. >
  22. <tree-card
  23. v-for="tree in nearby"
  24. :key="tree.id"
  25. :tree="tree"
  26. @click="goTree(tree.id)"
  27. />
  28. </van-list>
  29. </van-pull-refresh>
  30. <div class="empty-hint" v-if="!loading && nearby.length === 0">
  31. <van-empty description="附近暂无许愿树" />
  32. </div>
  33. </div>
  34. </template>
  35. <script setup lang="ts">
  36. import {onMounted, onUnmounted, ref} from 'vue'
  37. import {useRouter} from 'vue-router'
  38. import {useLocationStore} from '@/stores/location'
  39. import {fetchNearbyTrees} from '@/api/tree'
  40. import TreeCard from '@/components/TreeCard.vue'
  41. const router = useRouter()
  42. const locationStore = useLocationStore()
  43. const nearby = ref<any[]>([])
  44. const loading = ref(false)
  45. const finished = ref(false)
  46. const refreshing = ref(false)
  47. const locating = ref(true)
  48. async function loadTrees() {
  49. loading.value = true
  50. try {
  51. const result = await fetchNearbyTrees(
  52. locationStore.lng || 116.4074,
  53. locationStore.lat || 39.9042,
  54. 50000
  55. )
  56. nearby.value = result
  57. finished.value = true
  58. } catch {
  59. finished.value = true // 请求失败后停止,不再重试
  60. } finally {
  61. loading.value = false
  62. refreshing.value = false
  63. }
  64. }
  65. async function onRefresh() {
  66. refreshing.value = true
  67. await locationStore.refreshLocation()
  68. finished.value = false
  69. await loadTrees()
  70. }
  71. function goTree(id: number) {
  72. router.push(`/tree/${id}`)
  73. }
  74. /*const goTrees = (id: number) => {
  75. return router.push(`/tree/${id}`)
  76. }*/
  77. onMounted(async () => {
  78. locationStore.startWatch(5000)
  79. // 等待首次定位
  80. if (!locationStore.located) {
  81. await new Promise<void>((resolve) => {
  82. const check = setInterval(() => {
  83. if (locationStore.located) {
  84. clearInterval(check)
  85. resolve()
  86. }
  87. }, 200)
  88. })
  89. }
  90. locating.value = false
  91. })
  92. onUnmounted(() => {
  93. locationStore.stopWatch()
  94. })
  95. </script>
  96. <style scoped>
  97. .home-page {
  98. min-height: 100vh;
  99. background: #f7f8fa;
  100. }
  101. .hero-section {
  102. background: linear-gradient(135deg, #07c160, #05a650);
  103. padding: 30px 20px 20px;
  104. color: #fff;
  105. text-align: center;
  106. }
  107. .hero-title {
  108. font-size: 28px;
  109. font-weight: 700;
  110. margin-bottom: 6px;
  111. }
  112. .hero-sub {
  113. font-size: 14px;
  114. opacity: 0.85;
  115. margin-bottom: 16px;
  116. }
  117. .location-bar {
  118. display: inline-flex;
  119. align-items: center;
  120. gap: 6px;
  121. background: rgba(255,255,255,0.2);
  122. border-radius: 20px;
  123. padding: 6px 16px;
  124. font-size: 12px;
  125. }
  126. .empty-hint {
  127. padding-top: 60px;
  128. }
  129. </style>