home.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  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. <up-scroll-list indicatorColor="#fae6a4" indicatorActiveColor="#F3C11D" :indicatorWidth="30" :indicatorBarWidth="13">
  76. <view class="item-type">
  77. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon.png" alt="" />
  78. <view class="type-text">
  79. 全部
  80. </view>
  81. </view>
  82. <view class="item-type">
  83. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon-2.png" alt="" />
  84. <view class="type-text">
  85. 龙眼
  86. </view>
  87. </view>
  88. <view class="item-type">
  89. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon-3.png" alt="" />
  90. <view class="type-text">
  91. 葡萄
  92. </view>
  93. </view>
  94. <view class="item-type">
  95. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon.png" alt="" />
  96. <view class="type-text">
  97. 水蜜桃
  98. </view>
  99. </view>
  100. <view class="item-type">
  101. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon-2.png" alt="" />
  102. <view class="type-text">
  103. 龙眼
  104. </view>
  105. </view>
  106. <view class="item-type">
  107. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon-3.png" alt="" />
  108. <view class="type-text">
  109. 葡萄
  110. </view>
  111. </view>
  112. <view class="item-type">
  113. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon-3.png" alt="" />
  114. <view class="type-text">
  115. 葡萄1
  116. </view>
  117. </view>
  118. <view class="item-type">
  119. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon.png" alt="" />
  120. <view class="type-text">
  121. 水蜜桃2
  122. </view>
  123. </view>
  124. <view class="item-type">
  125. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon-3.png" alt="" />
  126. <view class="type-text">
  127. 葡萄
  128. </view>
  129. </view>
  130. <view class="item-type">
  131. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon.png" alt="" />
  132. <view class="type-text">
  133. 水蜜桃
  134. </view>
  135. </view>
  136. <view class="item-type">
  137. <image class="type-img" src="https://birdseye-img.sysuimars.com/youwei-uniapp/home/type-icon-2.png" alt="" />
  138. <view class="type-text">
  139. 龙眼
  140. </view>
  141. </view>
  142. </up-scroll-list>
  143. </view>
  144. <!-- 好味热卖 -->
  145. <view class="hot-wrap">
  146. <view class="hot-content">
  147. <view class="hot-title">
  148. <view class="title-l">
  149. 好味<text class="title-color">热卖</text>
  150. </view>
  151. <view class="title-btn">
  152. 限时抢购中<up-icon size="10" name="arrow-right"></up-icon>
  153. </view>
  154. </view>
  155. <view class="hot-list">
  156. <up-scroll-list indicatorColor="#fae6a4" indicatorActiveColor="#F3C11D" :indicatorWidth="30" :indicatorBarWidth="13">
  157. <view class="hot-panel">
  158. <view class="hot-item">
  159. <image class="hot-img" src="/static/home/hot-1.png" mode=""></image>
  160. <view class="item-info">
  161. <view class="info-text">
  162. <!-- <up-text :lines="2" color="#000000" size="12" text="海南妃子笑新鲜顺丰发货海南妃子笑海南妃子笑"></up-text> -->
  163. <view class="ellipsis-l2">
  164. 海南妃子笑新鲜顺丰发货海南妃子笑海南妃子笑
  165. </view>
  166. </view>
  167. <view class="info-price">
  168. <view class="price-text">
  169. <text class="price-unit">¥</text>108
  170. </view>
  171. <view class="info-sold">
  172. 已售1251
  173. </view>
  174. </view>
  175. </view>
  176. </view>
  177. <view class="hot-item">
  178. <image class="hot-img" src="/static/home/hot-2.png" mode=""></image>
  179. <view class="item-info">
  180. <view class="info-text">
  181. <view class="ellipsis-l2">
  182. 海南妃子笑新鲜顺丰发货海南妃子笑海南妃子笑
  183. </view>
  184. </view>
  185. <view class="info-price">
  186. <view class="price-text">
  187. <text class="price-unit">¥</text>108
  188. </view>
  189. <view class="info-sold">
  190. 已售1251
  191. </view>
  192. </view>
  193. </view>
  194. </view>
  195. <view class="hot-item">
  196. <image class="hot-img" src="/static/home/hot-1.png" mode=""></image>
  197. <view class="item-info">
  198. <view class="info-text">
  199. <view class="ellipsis-l2">
  200. 海南妃子笑新鲜顺丰发货海南妃子笑海南妃子笑新鲜顺丰发货
  201. </view>
  202. </view>
  203. <view class="info-price">
  204. <view class="price-text">
  205. <text class="price-unit">¥</text>108
  206. </view>
  207. <view class="info-sold">
  208. 已售1251
  209. </view>
  210. </view>
  211. </view>
  212. </view>
  213. <view class="hot-item">
  214. <image class="hot-img" src="/static/home/hot-2.png" mode=""></image>
  215. <view class="item-info">
  216. <view class="info-text">
  217. <view class="ellipsis-l2">
  218. 海南妃子笑新鲜顺丰发货海南妃子笑海南妃子笑新鲜顺丰发货
  219. </view>
  220. </view>
  221. <view class="info-price">
  222. <view class="price-text">
  223. <text class="price-unit">¥</text>108
  224. </view>
  225. <view class="info-sold">
  226. 已售1251
  227. </view>
  228. </view>
  229. </view>
  230. </view>
  231. </view>
  232. </up-scroll-list>
  233. </view>
  234. </view>
  235. </view>
  236. </view>
  237. </template>
  238. <script setup>
  239. import { ref, onMounted, reactive } from 'vue'
  240. import QQMapWX from 'qqmap-wx-jssdk'
  241. // 广州中心坐标
  242. const GUANGZHOU_CENTER = {
  243. latitude: 23.12911,
  244. longitude: 113.26436
  245. }
  246. // 模拟广州的景点数据
  247. const GUANGZHOU_POIS = [
  248. { id: 1, name: '广州塔', latitude: 23.10641, longitude: 113.32466 },
  249. { id: 2, name: '白云山', latitude: 23.19746, longitude: 113.30249 },
  250. { id: 3, name: '越秀公园', latitude: 23.13927, longitude: 113.26436 },
  251. { id: 4, name: '沙面岛', latitude: 23.10788, longitude: 113.24365 },
  252. { id: 5, name: '陈家祠', latitude: 23.12632, longitude: 113.24849 },
  253. { id: 6, name: '北京路', latitude: 23.12389, longitude: 113.26799 },
  254. { id: 7, name: '上下九', latitude: 23.11696, longitude: 113.24899 },
  255. { id: 8, name: '荔枝博览园', latitude: 22.99405, longitude: 113.32486 },
  256. { id: 9, name: '中山纪念堂', latitude: 23.13146, longitude: 113.26336 },
  257. { id: 10, name: '海心沙', latitude: 23.11446, longitude: 113.32136 }
  258. ]
  259. // 响应式数据
  260. const qqmapsdk = ref(null)
  261. const mapContext = ref(null)
  262. const center = ref(GUANGZHOU_CENTER)
  263. const allMarkers = ref([])
  264. const clusters = ref([])
  265. const includePoints = ref([])
  266. const currentZoom = ref(16)
  267. // 初始化地图
  268. const initMap = () => {
  269. qqmapsdk.value = new QQMapWX({
  270. key: 'Q5GBZ-2LP6I-LOKGM-UE3UC-TXH7Z-WCFG2' // key
  271. })
  272. }
  273. // 加载点位数据
  274. const loadPoints = () => {
  275. allMarkers.value = GUANGZHOU_POIS.map(item => ({
  276. ...item,
  277. iconPath: '../../../static/map/point.png',
  278. width: 30,
  279. height: 30,
  280. callout: {
  281. content: item.name,
  282. color: '#ffffff',
  283. bgColor: '#007AFF',
  284. padding: 5,
  285. borderRadius: 4,
  286. display: 'ALWAYS'
  287. }
  288. }))
  289. updateClusters()
  290. }
  291. // 更新聚合点
  292. const updateClusters = () => {
  293. if (currentZoom.value >= 15) {
  294. // 放大时显示所有点
  295. clusters.value = allMarkers.value
  296. } else {
  297. // 缩小时显示聚合点
  298. const clusterRadius = 60 / currentZoom.value
  299. const clustered = []
  300. allMarkers.value.forEach(marker => {
  301. let isClustered = false
  302. clustered.forEach(cluster => {
  303. const distance = getDistance(
  304. cluster.latitude, cluster.longitude,
  305. marker.latitude, marker.longitude
  306. )
  307. if (distance < clusterRadius) {
  308. isClustered = true
  309. cluster.markers.push(marker)
  310. cluster.callout.content = `${cluster.markers.length}个地点`
  311. }
  312. })
  313. if (!isClustered) {
  314. clustered.push({
  315. ...marker,
  316. markers: [marker],
  317. callout: {
  318. ...marker.callout,
  319. content: marker.name
  320. }
  321. })
  322. }
  323. })
  324. clusters.value = clustered.map(cluster => ({
  325. id: cluster.id,
  326. latitude: cluster.latitude,
  327. longitude: cluster.longitude,
  328. iconPath: cluster.markers.length > 1 ? '../../../static/map/point.png' : '../../../static/map/point.png',
  329. width: cluster.markers.length > 1 ? 40 : 30,
  330. height: cluster.markers.length > 1 ? 40 : 30,
  331. callout: cluster.callout,
  332. clusterData: cluster.markers
  333. }))
  334. }
  335. // 设置地图包含所有点
  336. includePoints.value = allMarkers.value.map(m => ({
  337. latitude: m.latitude,
  338. longitude: m.longitude
  339. }))
  340. }
  341. // 计算两点间距离
  342. // 更精确的距离计算函数(Haversine公式)
  343. const getDistance = (lat1, lng1, lat2, lng2) => {
  344. // 将经纬度转换为弧度
  345. const toRad = d => d * Math.PI / 180
  346. const R = 6371 // 地球半径(km)
  347. const dLat = toRad(lat2 - lat1)
  348. const dLng = toRad(lng2 - lng1)
  349. const a =
  350. Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  351. Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
  352. Math.sin(dLng / 2) * Math.sin(dLng / 2)
  353. const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
  354. return R * c * 1000 // 返回米
  355. }
  356. // 地图区域变化事件
  357. const onRegionChange = (e) => {
  358. if (e.type === 'end') {
  359. uni.createMapContext('myMap').getScale({
  360. success: (res) => {
  361. currentZoom.value = res.scale
  362. updateClusters()
  363. }
  364. })
  365. }
  366. }
  367. // 搜索地点
  368. const onSearch = (e) => {
  369. qqmapsdk.value.search({
  370. keyword: e.value,
  371. location: center.value,
  372. success: (res) => {
  373. allMarkers.value = res.data.map(item => ({
  374. id: item.id,
  375. name: item.title,
  376. latitude: item.location.lat,
  377. longitude: item.location.lng,
  378. iconPath: '../../../static/map/point.png',
  379. callout: {
  380. content: item.title,
  381. color: '#ffffff',
  382. bgColor: '#007AFF',
  383. padding: 5,
  384. borderRadius: 4,
  385. display: 'ALWAYS'
  386. }
  387. }))
  388. updateClusters()
  389. }
  390. })
  391. }
  392. // 清除搜索
  393. const clearSearch = () => {
  394. loadPoints()
  395. }
  396. // 生命周期钩子
  397. onMounted(() => {
  398. // #ifdef MP-WEIXIN
  399. mapContext.value = uni.createMapContext('myMap', this)
  400. initMap()
  401. loadPoints()
  402. // 获取地图区域
  403. if (mapContext.value && mapContext.value.getRegion) {
  404. mapContext.value.getRegion({
  405. success: res => console.log('地图范围:', res),
  406. fail: err => console.error('地图异常:', err)
  407. })
  408. }
  409. // #endif
  410. })
  411. const typeList = reactive([
  412. 'https://cdn.uviewui.com/uview/swiper/swiper3.png',
  413. 'https://cdn.uviewui.com/uview/swiper/swiper2.png',
  414. 'https://cdn.uviewui.com/uview/swiper/swiper1.png',
  415. ]);
  416. </script>
  417. <style lang="scss" scoped>
  418. .home-wrap {
  419. background-color: #F2F3F5;
  420. padding: 0;
  421. .home-top {
  422. padding: 20rpx 24rpx;
  423. background: linear-gradient(#FFFFFF, rgba(242, 243, 245, 0));
  424. }
  425. .home-search {
  426. display: flex;
  427. align-items: center;
  428. width: 100%;
  429. border: 2rpx solid #FFD95E;
  430. border-radius: 40rpx;
  431. margin-bottom: 40rpx;
  432. .search-wrap {
  433. flex: 1;
  434. padding-left: 22rpx;
  435. }
  436. .search-btn {
  437. text-align: center;
  438. line-height: 52rpx;
  439. border-radius: 40rpx;
  440. font-size: 28rpx;
  441. background-color: #FFD95E;
  442. margin: 6rpx 8rpx;
  443. width: 112rpx;
  444. height: 52rpx;
  445. }
  446. }
  447. .map-wrap {
  448. height: 280rpx;
  449. .map-dom {
  450. width: 100%;
  451. height: 100%;
  452. }
  453. }
  454. .type-wrap {
  455. margin: 20rpx;
  456. padding: 20rpx;
  457. background-color: #fff;
  458. border-radius: 16rpx;
  459. height: 200rpx;
  460. box-sizing: border-box;
  461. .swiper-item {
  462. display: flex;
  463. align-items: center;
  464. }
  465. .swiper {
  466. height: 170rpx;
  467. }
  468. ::v-deep {
  469. .u-scroll-list__indicator {
  470. margin-top: 20rpx;
  471. }
  472. }
  473. .item-type {
  474. text-align: center;
  475. font-size: 24rpx;
  476. .type-img {
  477. width: 92rpx;
  478. height: 92rpx;
  479. }
  480. }
  481. .item-type + .item-type {
  482. padding-left: 22rpx;
  483. }
  484. }
  485. .hot-wrap {
  486. margin: 0 20rpx 20rpx;
  487. background: linear-gradient(#FFFFFF, rgba(255, 255, 255, 0));
  488. padding: 2rpx;
  489. border-radius: 16rpx;
  490. .hot-content {
  491. background: linear-gradient(#fff1c3 4%, #FFFFFF 28%);
  492. border-radius: 16rpx;
  493. padding: 20rpx;
  494. height: 236rpx;
  495. .hot-title {
  496. display: flex;
  497. align-items: center;
  498. justify-content: space-between;
  499. padding-bottom: 20rpx;
  500. .title-l {
  501. font-family: 'PangMenZhengDao';
  502. font-size: 32rpx;
  503. .title-color {
  504. color: #F3C11D ;
  505. }
  506. }
  507. .title-btn {
  508. color: rgba(0, 0, 0, 0.6);
  509. font-size: 24rpx;
  510. display: inline-flex;
  511. align-items: center;
  512. }
  513. }
  514. .hot-list {
  515. .hot-panel {
  516. width: 100%;
  517. display: flex;
  518. .hot-item {
  519. width: calc(50% - 10rpx);
  520. display: flex;
  521. white-space: nowrap;
  522. .hot-img {
  523. flex: none;
  524. width: 116rpx;
  525. height: 116rpx;
  526. object-fit: cover;
  527. border-radius: 10rpx;
  528. }
  529. .item-info {
  530. padding-left: 10rpx;
  531. width: 200rpx;
  532. .info-text {
  533. color: #000000;
  534. font-size: 24rpx;
  535. }
  536. .info-price {
  537. display: flex;
  538. align-items: baseline;
  539. justify-content: space-between;
  540. .price-text {
  541. color: #FF7700;
  542. font-size: 36rpx;
  543. .price-unit {
  544. font-size: 23rpx;
  545. }
  546. }
  547. .info-sold {
  548. font-size: 20rpx;
  549. color: #C4C4C4;
  550. }
  551. }
  552. }
  553. }
  554. .hot-item + .hot-item {
  555. margin-left: 20rpx;
  556. }
  557. }
  558. }
  559. }
  560. }
  561. }
  562. </style>