home.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. <template>
  2. <view class="home-wrap base-container">
  3. <view class="home-top">
  4. <view class="home-search">
  5. <view class="search-wrap">
  6. <input />
  7. </view>
  8. <view class="search-btn">
  9. 搜索
  10. </view>
  11. </view>
  12. <view class="map-wrap">
  13. <map
  14. id="myMap"
  15. class="map-dom"
  16. :latitude="center.latitude"
  17. :longitude="center.longitude"
  18. :markers="clusters"
  19. :polyline="[]"
  20. :include-points="includePoints"
  21. @regionchange="onRegionChange"
  22. show-location
  23. ></map>
  24. </view>
  25. </view>
  26. <view class="type-wrap">
  27. <swiper class="swiper" circular :indicator-dots="true" :autoplay="false" :interval="5000"
  28. :duration="duration">
  29. <swiper-item>
  30. <view class="swiper-item">
  31. <view class="item-type">
  32. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon.png" alt="" />
  33. <view class="type-text">
  34. 全部
  35. </view>
  36. </view>
  37. <view class="item-type">
  38. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon-2.png" alt="" />
  39. <view class="type-text">
  40. 龙眼
  41. </view>
  42. </view>
  43. <view class="item-type">
  44. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon-3.png" alt="" />
  45. <view class="type-text">
  46. 葡萄
  47. </view>
  48. </view>
  49. <view class="item-type">
  50. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon.png" alt="" />
  51. <view class="type-text">
  52. 水蜜桃
  53. </view>
  54. </view>
  55. <view class="item-type">
  56. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon-2.png" alt="" />
  57. <view class="type-text">
  58. 龙眼
  59. </view>
  60. </view>
  61. <view class="item-type">
  62. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon-3.png" alt="" />
  63. <view class="type-text">
  64. 葡萄
  65. </view>
  66. </view>
  67. </view>
  68. </swiper-item>
  69. <swiper-item>
  70. <view class="swiper-item">
  71. <van-button type="primary">按钮</van-button>
  72. </view>
  73. </swiper-item>
  74. </swiper>
  75. </view>
  76. </view>
  77. </template>
  78. <script setup>
  79. import { ref, onMounted } from 'vue'
  80. import QQMapWX from 'qqmap-wx-jssdk'
  81. // 广州中心坐标
  82. const GUANGZHOU_CENTER = {
  83. latitude: 23.12911,
  84. longitude: 113.26436
  85. }
  86. // 模拟广州的景点数据
  87. const GUANGZHOU_POIS = [
  88. { id: 1, name: '广州塔', latitude: 23.10641, longitude: 113.32466 },
  89. { id: 2, name: '白云山', latitude: 23.19746, longitude: 113.30249 },
  90. { id: 3, name: '越秀公园', latitude: 23.13927, longitude: 113.26436 },
  91. { id: 4, name: '沙面岛', latitude: 23.10788, longitude: 113.24365 },
  92. { id: 5, name: '陈家祠', latitude: 23.12632, longitude: 113.24849 },
  93. { id: 6, name: '北京路', latitude: 23.12389, longitude: 113.26799 },
  94. { id: 7, name: '上下九', latitude: 23.11696, longitude: 113.24899 },
  95. { id: 8, name: '荔枝博览园', latitude: 22.99405, longitude: 113.32486 },
  96. { id: 9, name: '中山纪念堂', latitude: 23.13146, longitude: 113.26336 },
  97. { id: 10, name: '海心沙', latitude: 23.11446, longitude: 113.32136 }
  98. ]
  99. // 响应式数据
  100. const qqmapsdk = ref(null)
  101. const mapContext = ref(null)
  102. const center = ref(GUANGZHOU_CENTER)
  103. const allMarkers = ref([])
  104. const clusters = ref([])
  105. const includePoints = ref([])
  106. const currentZoom = ref(16)
  107. // 初始化地图
  108. const initMap = () => {
  109. qqmapsdk.value = new QQMapWX({
  110. key: 'Q5GBZ-2LP6I-LOKGM-UE3UC-TXH7Z-WCFG2' // key
  111. })
  112. }
  113. // 加载点位数据
  114. const loadPoints = () => {
  115. allMarkers.value = GUANGZHOU_POIS.map(item => ({
  116. ...item,
  117. iconPath: '../../../static/map/point.png',
  118. width: 30,
  119. height: 30,
  120. callout: {
  121. content: item.name,
  122. color: '#ffffff',
  123. bgColor: '#007AFF',
  124. padding: 5,
  125. borderRadius: 4,
  126. display: 'ALWAYS'
  127. }
  128. }))
  129. updateClusters()
  130. }
  131. // 更新聚合点
  132. const updateClusters = () => {
  133. if (currentZoom.value >= 15) {
  134. // 放大时显示所有点
  135. clusters.value = allMarkers.value
  136. } else {
  137. // 缩小时显示聚合点
  138. const clusterRadius = 60 / currentZoom.value
  139. const clustered = []
  140. allMarkers.value.forEach(marker => {
  141. let isClustered = false
  142. clustered.forEach(cluster => {
  143. const distance = getDistance(
  144. cluster.latitude, cluster.longitude,
  145. marker.latitude, marker.longitude
  146. )
  147. if (distance < clusterRadius) {
  148. isClustered = true
  149. cluster.markers.push(marker)
  150. cluster.callout.content = `${cluster.markers.length}个地点`
  151. }
  152. })
  153. if (!isClustered) {
  154. clustered.push({
  155. ...marker,
  156. markers: [marker],
  157. callout: {
  158. ...marker.callout,
  159. content: marker.name
  160. }
  161. })
  162. }
  163. })
  164. clusters.value = clustered.map(cluster => ({
  165. id: cluster.id,
  166. latitude: cluster.latitude,
  167. longitude: cluster.longitude,
  168. iconPath: cluster.markers.length > 1 ? '../../../static/map/point.png' : '../../../static/map/point.png',
  169. width: cluster.markers.length > 1 ? 40 : 30,
  170. height: cluster.markers.length > 1 ? 40 : 30,
  171. callout: cluster.callout,
  172. clusterData: cluster.markers
  173. }))
  174. }
  175. // 设置地图包含所有点
  176. includePoints.value = allMarkers.value.map(m => ({
  177. latitude: m.latitude,
  178. longitude: m.longitude
  179. }))
  180. }
  181. // 计算两点间距离
  182. // 更精确的距离计算函数(Haversine公式)
  183. const getDistance = (lat1, lng1, lat2, lng2) => {
  184. // 将经纬度转换为弧度
  185. const toRad = d => d * Math.PI / 180
  186. const R = 6371 // 地球半径(km)
  187. const dLat = toRad(lat2 - lat1)
  188. const dLng = toRad(lng2 - lng1)
  189. const a =
  190. Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  191. Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
  192. Math.sin(dLng / 2) * Math.sin(dLng / 2)
  193. const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
  194. return R * c * 1000 // 返回米
  195. }
  196. // 地图区域变化事件
  197. const onRegionChange = (e) => {
  198. if (e.type === 'end') {
  199. uni.createMapContext('myMap').getScale({
  200. success: (res) => {
  201. currentZoom.value = res.scale
  202. updateClusters()
  203. }
  204. })
  205. }
  206. }
  207. // 搜索地点
  208. const onSearch = (e) => {
  209. qqmapsdk.value.search({
  210. keyword: e.value,
  211. location: center.value,
  212. success: (res) => {
  213. allMarkers.value = res.data.map(item => ({
  214. id: item.id,
  215. name: item.title,
  216. latitude: item.location.lat,
  217. longitude: item.location.lng,
  218. iconPath: '../../../static/map/point.png',
  219. callout: {
  220. content: item.title,
  221. color: '#ffffff',
  222. bgColor: '#007AFF',
  223. padding: 5,
  224. borderRadius: 4,
  225. display: 'ALWAYS'
  226. }
  227. }))
  228. updateClusters()
  229. }
  230. })
  231. }
  232. // 清除搜索
  233. const clearSearch = () => {
  234. loadPoints()
  235. }
  236. // 生命周期钩子
  237. onMounted(() => {
  238. // #ifdef MP-WEIXIN
  239. mapContext.value = uni.createMapContext('myMap', this)
  240. initMap()
  241. loadPoints()
  242. // 获取地图区域
  243. if (mapContext.value && mapContext.value.getRegion) {
  244. mapContext.value.getRegion({
  245. success: res => console.log('地图范围:', res),
  246. fail: err => console.error('地图异常:', err)
  247. })
  248. }
  249. // #endif
  250. })
  251. </script>
  252. <style lang="scss" scoped>
  253. .home-wrap {
  254. background-color: #F2F3F5;
  255. padding: 0;
  256. .home-top {
  257. padding: 20rpx 24rpx;
  258. background: linear-gradient(#FFFFFF, rgba(242, 243, 245, 0));
  259. }
  260. .home-search {
  261. display: flex;
  262. align-items: center;
  263. width: 100%;
  264. border: 2rpx solid #FFD95E;
  265. border-radius: 40rpx;
  266. margin-bottom: 40rpx;
  267. .search-wrap {
  268. flex: 1;
  269. padding-left: 22rpx;
  270. }
  271. .search-btn {
  272. text-align: center;
  273. line-height: 52rpx;
  274. border-radius: 40rpx;
  275. font-size: 28rpx;
  276. background-color: #FFD95E;
  277. margin: 6rpx 8rpx;
  278. width: 112rpx;
  279. height: 52rpx;
  280. }
  281. }
  282. .map-wrap {
  283. height: 280rpx;
  284. .map-dom {
  285. width: 100%;
  286. height: 100%;
  287. }
  288. }
  289. .type-wrap {
  290. margin: 20rpx;
  291. padding: 20rpx 20rpx 10rpx 20rpx;
  292. background-color: #fff;
  293. border-radius: 16rpx;
  294. height: 200rpx;
  295. box-sizing: border-box;
  296. .swiper-item {
  297. display: flex;
  298. align-items: center;
  299. .item-type {
  300. text-align: center;
  301. font-size: 24rpx;
  302. .type-img {
  303. width: 92rpx;
  304. height: 92rpx;
  305. }
  306. }
  307. .item-type + .item-type {
  308. padding-left: 22rpx;
  309. }
  310. }
  311. ::v-deep {
  312. .uni-swiper-dot {
  313. width: 12rpx;
  314. height: 6rpx;
  315. border-radius: 24rpx;
  316. margin-right: 4rpx;
  317. }
  318. .uni-swiper-dot.uni-swiper-dot-active {
  319. background-color: #F3C11D;
  320. width: 26rpx;
  321. }
  322. }
  323. .swiper {
  324. height: 170rpx;
  325. }
  326. }
  327. }
  328. </style>