index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. <template>
  2. <view class='app-container'>
  3. <!-- 定位 + 城市选择器 -->
  4. <view class="tab" flex between>
  5. <view class="address" @click="getRep">
  6. <uni-icons :type="location === '定位中...' ? 'spinner-cycle' : 'location'" />
  7. <text>{{ location }}</text>
  8. </view>
  9. <picker class="list" mode="multiSelector" :range="locationList" range-key="name" :value="pickerValue"
  10. @columnchange="handleColumn" @change="handleChange">
  11. <view flex style="width: 100%">
  12. <view class="item" flex>
  13. <view class="name">{{ province }}</view>
  14. <uni-icons type="bottom" />
  15. </view>
  16. <view class="item" flex>
  17. <view class="name">{{ city }}</view>
  18. <uni-icons type="bottom" />
  19. </view>
  20. <view class="item" flex>
  21. <view class="name">{{ district }}</view>
  22. <uni-icons type="bottom" />
  23. </view>
  24. </view>
  25. </picker>
  26. </view>
  27. <!-- 地图 -->
  28. <view id="container" />
  29. <!-- 范围内的门店 -->
  30. <scroll-view scroll-y class="poi" @scrolltolower="scrollTolower">
  31. <view v-if="list.length > 0" style="height: 100%; padding: 16px">
  32. <view :class="['list', active === index ? 'active' : '']" flex center v-for="(item, index) in list" :key="item.id"
  33. @click="changeActive(item, index)">
  34. <img :src="item.icon" width="80" height="80" />
  35. <view class="info">
  36. <view class="name">{{ item.name }}</view>
  37. <view class="distance">距离:{{ item.distance / 1000 }}KM</view>
  38. <view class="address">地址:{{ item.address }}</view>
  39. </view>
  40. <view class="button" flex around>
  41. <img class="icon" v-show="item.linkPhone" src="../../static/phone.png" @click="getPhone(item.linkPhone)" />
  42. <img class="icon" src="../../static/address.png" @click="getMap(item)" />
  43. </view>
  44. </view>
  45. </view>
  46. <view v-else style="height: 100%; padding: 0 16px">
  47. <view class="storeList" v-for="item in storeList" :key="item.id" flex between>
  48. <img :src="item.icon" width="56" height="56" />
  49. <view class="info">
  50. <view class="name">{{ item.name }}</view>
  51. </view>
  52. <button type="submit" @click="getStore(item.url)">进店逛逛</button>
  53. </view>
  54. </view>
  55. </scroll-view>
  56. <!-- 打开地图app弹窗 -->
  57. <uni-popup ref="popup" type="bottom">
  58. <view class="popup">
  59. <view class="title" flex center>请选择地图</view>
  60. <view class="item" flex center v-for="item in mapList" :key="item.value" @click="openMap(item)">
  61. {{ item.name }}
  62. </view>
  63. </view>
  64. </uni-popup>
  65. </view>
  66. </template>
  67. <script>
  68. import AMapLoader from '@amap/amap-jsapi-loader'
  69. // 安全密钥
  70. window._AMapSecurityConfig = {
  71. securityJsCode: 'fd78b73eba80ab3f887c9ba60744048f'
  72. }
  73. import { options, list, storeList } from '@/api/map'
  74. export default {
  75. data() {
  76. return {
  77. // 定位
  78. location: '定位中...',
  79. // 省市区列表
  80. locationList: [],
  81. // 选中焦点
  82. pickerValue: [18, 0, 0],
  83. province: '',
  84. city: '',
  85. district: '',
  86. // 表单
  87. form: {
  88. pageNum: 1,
  89. pageSize: 10,
  90. longitude: 116.397428,
  91. latitude: 39.90923
  92. },
  93. // 线下店铺
  94. list: [],
  95. // 默认选中第一家店铺
  96. active: 0,
  97. // 第三方店铺
  98. storeList: [],
  99. // 店铺坐标
  100. toLng: '',
  101. toLat: '',
  102. toName: '',
  103. // 地图
  104. AMap: null,
  105. map: null,
  106. geolocation: null,
  107. // 地图App
  108. mapList: [{
  109. id: 1,
  110. name: '腾讯地图',
  111. value: 'qqMap'
  112. }, {
  113. id: 2,
  114. name: '百度地图',
  115. value: 'baiduMap'
  116. }, {
  117. id: 3,
  118. name: '高德地图',
  119. value: 'aMap'
  120. }],
  121. // 只触发一次
  122. only: true
  123. }
  124. },
  125. onLoad() {
  126. this.initAMap()
  127. },
  128. methods: {
  129. async initAMap() {
  130. // 调用高德
  131. this.AMap = await this.getMapLoader()
  132. // 自定义定位按钮
  133. this.geolocation = new AMap.Geolocation({
  134. enableHighAccuracy: true, // 高精度定位
  135. timeout: 3000, // 定位超时时间
  136. zoomToAccuracy: true, // 定位成功后再调整视野
  137. showCircle: false, // 定位成功有个圆形范围
  138. showButton: false // 是否显示按钮
  139. })
  140. const locationRes = await this.getLocation(this.geolocation)
  141. // 当前定位
  142. if (locationRes.status === 'complete') {
  143. this.form.longitude = locationRes.data.lng
  144. this.form.latitude = locationRes.data.lat
  145. }
  146. // 获取城市信息
  147. this.getGeocoder([this.form.longitude, this.form.latitude], locationRes.status)
  148. // 绘制地图
  149. this.map = new AMap.Map('container', {
  150. zoom: 16,
  151. center: [this.form.longitude, this.form.latitude]
  152. })
  153. // 调用插件
  154. this.map.addControl(this.geolocation)
  155. // 地图加载完
  156. this.map.on('complete', () => {
  157. // 自动定位一次
  158. this.geolocation.getCurrentPosition()
  159. })
  160. // this.map.on('dragend', this.moveMapHandler)
  161. },
  162. // 调用高德
  163. getMapLoader() {
  164. return new Promise((resolve, reject) => {
  165. AMapLoader.load({
  166. key: '5dcf4cf0260059dade51d0d9b5e3c1ef',
  167. version: '2.0',
  168. plugins: ['AMap.Geolocation', 'AMap.Geocoder']
  169. }).then((AMap) => {
  170. resolve(AMap)
  171. }).catch((err) => {
  172. reject(err)
  173. })
  174. })
  175. },
  176. // 获取定位
  177. getLocation(geolocation) {
  178. return new Promise((resolve, reject) => {
  179. try {
  180. mapLocation.postMessage('获取定位')
  181. window['setMapLocation'] = res => {
  182. let e = JSON.parse(res)
  183. resolve({
  184. status: 'complete',
  185. data: {
  186. lng: e.longitude,
  187. lat: e.latitude
  188. }
  189. })
  190. }
  191. } catch {
  192. this.AMap.plugin('AMap.Geolocation', function () {
  193. geolocation.getCurrentPosition(function (status, res) {
  194. resolve({
  195. status: status,
  196. data: res.position
  197. })
  198. })
  199. })
  200. }
  201. })
  202. },
  203. // 获取城市信息
  204. async getGeocoder(lnglat, locationStatus) {
  205. const that = this
  206. this.AMap.plugin('AMap.Geocoder', function () {
  207. var geocoder = new AMap.Geocoder()
  208. geocoder.getAddress(lnglat, function (status, resolve) {
  209. if (status === 'complete' && resolve.info === 'OK' && resolve.regeocode.formattedAddress !== '中华人民共和国') {
  210. let e = resolve.regeocode.addressComponent
  211. that.location = locationStatus === 'complete' ? `${e.city ? e.city : e.province}${e.district}` : that.location = '重新定位'
  212. that.form.city = null
  213. that.city = '全部'
  214. that.form.area = null
  215. that.district = '全部'
  216. that.locationList = []
  217. options().then(res => {
  218. if (res.data.code === 0) {
  219. let value = []
  220. // 市
  221. that.locationList[0] = res.data.data
  222. value[0] = that.locationList[0].findIndex(i => e.province.includes(i.name))
  223. that.form.province = that.locationList[0][value[0]].id
  224. that.province = that.locationList[0][value[0]].name
  225. that.locationList[1] = that.locationList[0][value[0]].childList
  226. value[1] = that.locationList[1].findIndex(i => i.citycode === e.citycode)
  227. // 省
  228. if (value[1] !== -1) {
  229. that.form.city = that.locationList[1][value[1]].id
  230. that.city = that.locationList[1][value[1]].name
  231. // 区
  232. that.locationList[2] = that.locationList[1][value[1]].childList
  233. value[2] = that.locationList[2].findIndex(i => i.adcode === e.adcode)
  234. if (value[2] !== -1) {
  235. that.form.area = that.locationList[2][value[2]].id
  236. that.district = that.locationList[2][value[2]].name
  237. }
  238. }
  239. that.pickerValue = value
  240. that.getList()
  241. }
  242. })
  243. }
  244. })
  245. })
  246. },
  247. // // 移动地图 中心坐标
  248. // moveMapHandler() {
  249. // const center = this.map.getCenter()
  250. // this.form.longitude = center.lng
  251. // this.form.latitude = center.lat
  252. // this.getGeocoder([center.lng, center.lat], 'complete')
  253. // },
  254. // 添加标点
  255. getMarker(lng, lat) {
  256. const icon = new AMap.Icon({
  257. size: new AMap.Size(30, 30),
  258. imageSize: new AMap.Size(30, 30),
  259. image: '../../static/location.png'
  260. })
  261. const marker = new AMap.Marker({
  262. position: new AMap.LngLat(lng, lat),
  263. offset: new AMap.Pixel(-5, -25),
  264. icon: icon,
  265. zoom: 16
  266. })
  267. marker.setMap(this.map)
  268. },
  269. // 打开地图
  270. openMap(e) {
  271. this.$refs.popup.close()
  272. let url = ''
  273. if (getApp().globalData.inside) {
  274. openOtherApp.postMessage(`?page=${e.value}&longitude=${this.toLng}&latitude=${this.toLat}&name=${this.toName}`)
  275. } else {
  276. // 腾讯地图
  277. if (e.id === 1) {
  278. url = `https://apis.map.qq.com/uri/v1/routeplan?type=drive&from=我的位置&fromcoord=${this.form.latitude},${this.form.longitude}&to=${this.toName}&tocoord=${this.toLat},${this.toLng}&coord_type=2&referer=猫王妙播Pro`
  279. }
  280. // 百度地图
  281. if (e.id === 2) {
  282. url = `http://api.map.baidu.com/direction?origin=latlng:${this.form.latitude},${this.form.longitude}|name:我的位置&destination=latlng:${this.toLat},${this.toLng}|name:${this.toName}&mode=driving&region=${this.province}&output=html&coord_type=gcj02&src=webapp.airsmart.猫王妙播Pro`
  283. }
  284. // 高德地图
  285. if (e.id === 3) {
  286. url = `https://uri.amap.com/navigation?from=${this.form.longitude},${this.form.latitude},我的位置&to=${this.toLng},${this.toLat},${this.toName}&callnative=1`
  287. }
  288. window.location.href = url
  289. }
  290. },
  291. // 重新定位
  292. getRep() {
  293. this.location = '定位中...'
  294. this.initAMap()
  295. },
  296. // 选择省
  297. handleColumn(e) {
  298. let column = e.detail.column
  299. let index = e.detail.value
  300. if (column === 0) {
  301. this.pickerValue = [index, 0, 0]
  302. this.locationList[1] = this.locationList[0][index].childList
  303. this.locationList[2] = this.locationList[1].length > 0 ? this.locationList[1][0].childList : []
  304. }
  305. if (column === 1) {
  306. this.pickerValue = [this.pickerValue[0], index, 0]
  307. this.locationList[2] = this.locationList[1][index].childList
  308. }
  309. },
  310. // 确定
  311. handleChange(e) {
  312. this.form.province = ''
  313. this.form.city = ''
  314. this.city = '全部'
  315. this.form.area = ''
  316. this.district = '全部'
  317. let index = e.detail.value
  318. this.form.province = this.locationList[0][index[0]].id
  319. this.province = this.locationList[0][index[0]].name
  320. if (this.locationList[1].length > 0) {
  321. this.form.city = this.locationList[1][index[1]].id
  322. this.city = this.locationList[1][index[1]].name
  323. }
  324. if (this.locationList[2].length > 0) {
  325. this.form.area = this.locationList[2][index[2]].id
  326. this.district = this.locationList[2][index[2]].name
  327. }
  328. this.getList()
  329. },
  330. // 线下店铺
  331. getList() {
  332. this.list = []
  333. this.storeList = []
  334. this.map.clearMap()
  335. list(this.form).then(res => {
  336. if (res.data.code === 0) {
  337. if (res.data.data.records.length > 0) {
  338. res.data.data.records.map(i => {
  339. this.list.push(i)
  340. this.getMarker(i.longitude, i.latitude)
  341. })
  342. this.hasMore = res.data.data.hasMore
  343. this.map.setCenter([this.list[0].longitude, this.list[0].latitude])
  344. } else {
  345. this.getStoreList()
  346. }
  347. }
  348. })
  349. },
  350. // 切换店铺
  351. changeActive(item, index) {
  352. this.active = index
  353. this.map.setCenter([item.longitude, item.latitude])
  354. },
  355. // 第三方店铺
  356. getStoreList() {
  357. storeList().then(res => {
  358. if (res.data.code === 0) {
  359. this.storeList = res.data.data
  360. }
  361. })
  362. },
  363. // 跳转第三方店铺
  364. getStore(url) {
  365. window.location.href = url
  366. },
  367. // 拨打电话
  368. getPhone(e) {
  369. uni.makePhoneCall({
  370. phoneNumber: e
  371. })
  372. },
  373. // 打开地图
  374. getMap(e) {
  375. this.$refs.popup.open()
  376. // 店铺坐标
  377. this.toLng = e.longitude
  378. this.toLat = e.latitude
  379. this.toName = e.name
  380. },
  381. // 触底
  382. scrollTolower() {
  383. if (this.hasMore) {
  384. this.form.pageNum++
  385. this.getList()
  386. } else {
  387. if (this.only) {
  388. uni.showToast({
  389. icon: 'none',
  390. title: '暂无更多店铺'
  391. })
  392. this.only = false
  393. }
  394. }
  395. }
  396. }
  397. }
  398. </script>
  399. <style lang="scss" scoped>
  400. .app-container {
  401. margin: 0;
  402. padding: 0;
  403. height: 100%;
  404. position: relative;
  405. color: #000;
  406. font-size: 28rpx;
  407. overflow-y: hidden;
  408. display: flex;
  409. flex-direction: column;
  410. .tab {
  411. width: 100%;
  412. height: 88rpx;
  413. line-height: 88rpx;
  414. padding: 0 12px;
  415. background-color: #FFF;
  416. white-space: nowrap;
  417. .address {
  418. width: 100px;
  419. overflow: hidden;
  420. white-space: nowrap;
  421. text-overflow: ellipsis;
  422. }
  423. uni-picker {
  424. width: calc(100% - 100px);
  425. .item {
  426. width: calc(100% / 3);
  427. margin: 0 5px;
  428. .name {
  429. width: 100%;
  430. overflow: hidden;
  431. white-space: nowrap;
  432. text-overflow: ellipsis;
  433. text-align: right;
  434. }
  435. }
  436. }
  437. }
  438. #container {
  439. width: 100%;
  440. height: 100%;
  441. flex: 1;
  442. }
  443. .poi {
  444. width: 100%;
  445. height: 270px;
  446. overflow-y: auto;
  447. z-index: 99;
  448. background-color: #FFF;
  449. .list,
  450. .storeList {
  451. width: 100%;
  452. height: 104px;
  453. padding: 12px;
  454. border-bottom: 1px solid #e8e8e8;
  455. img {
  456. border-radius: 8px;
  457. }
  458. button {
  459. height: auto;
  460. line-height: 1;
  461. font-size: 12px;
  462. padding: 8px 10px;
  463. border-radius: 8px;
  464. margin: 0;
  465. }
  466. .info {
  467. flex: 1;
  468. padding: 0 8px;
  469. overflow: hidden;
  470. .name {
  471. font-weight: bold;
  472. overflow: hidden;
  473. text-overflow: ellipsis;
  474. white-space: nowrap;
  475. }
  476. .distance,
  477. .address {
  478. font-size: 10px;
  479. color: #999;
  480. }
  481. }
  482. .button {
  483. flex-direction: column;
  484. .icon:first-child {
  485. margin-bottom: 20px;
  486. }
  487. .icon {
  488. width: 24px;
  489. background-color: #a4d099;
  490. border-radius: 50%;
  491. }
  492. }
  493. }
  494. .list {
  495. border-radius: 16px;
  496. margin-bottom: 12px;
  497. }
  498. .storeList:last-child {
  499. border: none;
  500. }
  501. .active {
  502. background-color: #F2F5F7;
  503. }
  504. }
  505. .uni-icons {
  506. vertical-align: bottom;
  507. }
  508. }
  509. .uni-popup {
  510. z-index: 999;
  511. .popup {
  512. width: 90%;
  513. background-color: #FFF;
  514. border-radius: 16px;
  515. margin: 0 auto;
  516. position: absolute;
  517. bottom: 20px;
  518. left: 50%;
  519. transform: translate(-50%);
  520. .title {
  521. height: 40px;
  522. font-size: 12px;
  523. border-bottom: 1px solid #e8e8e8;
  524. }
  525. .item {
  526. width: 100%;
  527. height: 60px;
  528. border-bottom: 1px solid #e8e8e8;
  529. font-size: 18px;
  530. }
  531. .item:last-child {
  532. border: none;
  533. }
  534. }
  535. }
  536. </style>