index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. <template>
  2. <view class="jnpf-location-map">
  3. <u-top-tips ref="uTips" />
  4. <view class="content">
  5. <view class="user-select u-flex-col">
  6. <view class="user-select-search">
  7. <u-search :placeholder="$t('common.searchText')" v-model="keyword" height="72" :show-action="false" bg-color="#f0f2f6"
  8. shape="square" @change='search'>
  9. </u-search>
  10. </view>
  11. </view>
  12. </view>
  13. <view class="header">
  14. <view class="map-container">
  15. <map class='map' id="maps" :latitude="location.latitude" :longitude="location.longitude"
  16. :circles="circles" :polygons="polygons" :scale='15' @regionchange="regionChange">
  17. <!-- #ifdef H5 -->
  18. <cover-image class="map-marker h5-map-marker" src="/static/image/mark.png" />
  19. <!-- #endif -->
  20. <!-- #ifndef H5-->
  21. <cover-image class="map-marker" src="/static/image/mark.png" />
  22. <!-- #endif -->
  23. <cover-view class="map-locate" @click="handleLocate">
  24. <cover-image v-if="!locateLoading" src="/static/image/locate.png" />
  25. <cover-image v-else class="map-locate-img" src="/static/image/waite.png" />
  26. </cover-view>
  27. </map>
  28. </view>
  29. </view>
  30. <view class="around-contain">
  31. <scroll-view style="height:100%" id="scroll-view-h" class="scroll-view2" :refresher-enabled="false"
  32. :refresher-threshold="50" :scroll-with-animation='true' @scrolltolower="handleScrollToLower"
  33. :scroll-y="true">
  34. <radio-group class="around-contain-item" v-for="(item,index) in list" :key="index" v-if="list.length"
  35. @change="onSelectValueChange(item,index)">
  36. <label class="u-radio-label">
  37. <radio class="u-radio" :value="item.id" :checked="item.id === selectId" />
  38. <view class="around-item-title-box">
  39. <view class="around-item-title u-line-1"> {{ item.name }}</view>
  40. <view class="around-item-sub-title u-line-1"> {{ item.address }}</view>
  41. </view>
  42. </label>
  43. </radio-group>
  44. <u-loading class="loading" mode="circle" size="44" v-if="loading" />
  45. <view v-if="!loading&&!list.length" class="nodata u-flex-col">
  46. <image :src="noDataIcon" mode="widthFix" class="noDataIcon" />
  47. {{$t('common.noData')}}
  48. </view>
  49. </scroll-view>
  50. </view>
  51. <view class="jnpf-bottom-actions">
  52. <u-button class="buttom-btn" @click="close()">{{$t('common.cancelText')}}</u-button>
  53. <u-button class="buttom-btn" type="primary" @click.stop="handleConfirm()">{{$t('common.okText')}}</u-button>
  54. </view>
  55. </view>
  56. </template>
  57. <script>
  58. import resources from '@/libs/resources.js'
  59. import {
  60. getAroundList,
  61. getTextList
  62. } from '@/api/common.js'
  63. export default {
  64. data() {
  65. return {
  66. loading: false,
  67. noDataIcon: resources.message.nodata,
  68. tabWidth: 150,
  69. tabIndex: 0,
  70. keyword: '',
  71. location: {
  72. longitude: 116.404, // 经度
  73. latitude: 39.915, // 纬度
  74. },
  75. circles: [],
  76. list: [],
  77. pagination: {
  78. currentPage: 1,
  79. pageSize: 50
  80. },
  81. total: 0,
  82. currentLocation: {},
  83. selectId: '',
  84. selectItem: {},
  85. enableLocation: '',
  86. showPopup: false,
  87. locateLoading: false,
  88. polygons: [],
  89. enableLocationScope: false,
  90. adjustmentScope: 500,
  91. enableDesktopLocation: false,
  92. locationScope: [],
  93. emitKey: '',
  94. // #ifdef APP
  95. dragLoading: false
  96. // #endif
  97. };
  98. },
  99. onLoad(e) {
  100. const data = e.data ? JSON.parse(e.data) : {}
  101. this.enableLocationScope = data.enableLocationScope || false
  102. this.adjustmentScope = data.adjustmentScope || 500
  103. this.enableDesktopLocation = data.enableDesktopLocation || false
  104. this.locationScope = data.locationScope || []
  105. this.emitKey = data.emitKey
  106. this.init()
  107. },
  108. methods: {
  109. init() {
  110. this.circles = []
  111. this.polygons = []
  112. this.selectId = ''
  113. this.list = []
  114. this.locateLoading = false
  115. // #ifdef APP
  116. this.dragLoading = false
  117. // #endif
  118. this.getLocation()
  119. },
  120. getLocation() {
  121. this.loading = true;
  122. uni.getLocation({
  123. type: 'gcj02',
  124. isHighAccuracy: true,
  125. success: (res) => {
  126. this.location.longitude = res.longitude // 经度
  127. this.location.latitude = res.latitude // 纬度
  128. //查询附近位置
  129. this.getList()
  130. //添加可选区域圆形
  131. this.handelCircle();
  132. //添加微调区域圆形
  133. this.handleScopeCircle();
  134. },
  135. fail: (err) => {
  136. //查询附近位置
  137. this.getList()
  138. //添加可选区域圆形
  139. this.handelCircle();
  140. //添加微调区域圆形
  141. this.handleScopeCircle();
  142. }
  143. });
  144. },
  145. handleGetCenter() {
  146. this.mapContext = uni.createMapContext("maps", this);
  147. this.mapContext.getCenterLocation({
  148. type: 'gcj02',
  149. geocode: true,
  150. isHighAccuracy: true,
  151. altitude: true,
  152. success: (res) => {
  153. this.location.longitude = res.longitude
  154. this.location.latitude = res.latitude
  155. if (this.enableLocationScope) {
  156. const discount = this.jnpf.getDistance(this.currentLocation.latitude, this
  157. .currentLocation.longitude, this.location.latitude, this.location.longitude
  158. ) || 0;
  159. if (discount > (this.adjustmentScope || 500)) return this.$refs.uTips.show({
  160. title: '超出微调范围',
  161. type: 'warning',
  162. });
  163. }
  164. this.getList()
  165. }
  166. })
  167. },
  168. handelCircle() {
  169. if (!this.enableDesktopLocation || !this.locationScope.length) return;
  170. for (let i = 0; i < this.locationScope.length; i++) {
  171. const o = this.locationScope[i];
  172. if (!o.lng || !o.lat || !o.radius) continue;
  173. o.longitude = o.lng
  174. o.latitude = o.lat
  175. this.addCircle({
  176. ...o,
  177. color: '#54d65e99',
  178. fillColor: '#54d65e66',
  179. });
  180. }
  181. },
  182. handleScopeCircle() {
  183. if (!this.enableLocationScope) return;
  184. this.currentLocation = this.$u.deepClone(this.location);
  185. this.addCircle({
  186. ...this.location,
  187. radius: this.adjustmentScope || 500,
  188. color: '#1890ff99',
  189. fillColor: '#1890ff66'
  190. });
  191. },
  192. addCircle(o) {
  193. // #ifdef H5
  194. this.polygons.push({
  195. points: this.CreateSimpleCircle(o.latitude, o.longitude, o.radius, 100),
  196. strokeColor: o.color,
  197. fillColor: o.fillColor,
  198. strokeWidth: 1
  199. })
  200. // #endif
  201. // #ifndef H5
  202. this.circles.push({
  203. ...o,
  204. strokeWidth: 1,
  205. })
  206. // #endif
  207. },
  208. // #ifdef H5
  209. CreateSimpleCircle(lat, lng, radius, pointCount) {
  210. var km = radius / 1000;
  211. var a = km < 5 ? 0.01 : km < 50 ? 0.1 : km < 500 ? 1 : 10;
  212. var b = this.getCircleDistance(lng, lat, lng + a, lat);
  213. var c = this.getCircleDistance(lng, lat, lng, lat + a);
  214. var rb = radius / b * a;
  215. var rc = radius / c * a;
  216. var arr = [];
  217. var n = 0,
  218. step = 360.0 / pointCount,
  219. N = 360 - step / 2; //注意浮点数±0.000000001的差异
  220. for (var i = 0; n < N; i++, n += step) {
  221. var x = lng + rb * Math.cos(n * Math.PI / 180);
  222. var y = lat + rc * Math.sin(n * Math.PI / 180);
  223. arr[i] = {
  224. latitude: y,
  225. longitude: x
  226. }
  227. }
  228. arr.push({
  229. latitude: arr[0].latitude,
  230. longitude: arr[0].longitude
  231. });
  232. return arr;
  233. },
  234. getCircleDistance(lng1, lat1, lng2, lat2) {
  235. var d = Math.PI / 180;
  236. var f = lat1 * d,
  237. h = lat2 * d;
  238. var i = lng2 * d - lng1 * d;
  239. var e = (1 - Math.cos(h - f) + (1 - Math.cos(i)) * Math.cos(f) * Math.cos(h)) / 2;
  240. return 2 * 6378137 * Math.asin(Math.sqrt(e));
  241. },
  242. // #endif
  243. regionChange(e) {
  244. // #ifdef APP
  245. if (this.dragLoading) return (this.dragLoading = false);
  246. this.list = [];
  247. this.handleGetCenter()
  248. // #endif
  249. // #ifndef APP
  250. if (e.detail.causedBy == 'drag' && e.type == 'end') {
  251. this.list = [];
  252. this.handleGetCenter()
  253. }
  254. // #endif
  255. },
  256. handleScrollToLower() {
  257. if (this.pagination.pageSize * this.pagination.currentPage < this.total) {
  258. this.pagination.currentPage = this.pagination.currentPage + 1;
  259. this.getList()
  260. } else {
  261. this.$u.toast('没有更多信息啦!')
  262. }
  263. },
  264. getList() {
  265. this.loading = true;
  266. const query = {
  267. key: this.define.aMapWebKey,
  268. location: this.location.longitude + ',' + this.location.latitude,
  269. radius: -1,
  270. offset: this.pagination.pageSize,
  271. page: this.pagination.currentPage,
  272. };
  273. getAroundList(query).then(res => {
  274. this.handleResult(res)
  275. }).catch(() => {
  276. this.loading = false;
  277. })
  278. },
  279. handleResult(res) {
  280. this.loading = false;
  281. if (res.data.status == '1') {
  282. this.list = [...this.list, ...res.data.pois || []];
  283. this.total = Number(res.data.count || 0)
  284. } else {
  285. this.$u.toast(res.data.info)
  286. }
  287. },
  288. onSelectValueChange(item, index) {
  289. // #ifdef APP
  290. this.dragLoading = true
  291. // #endif
  292. this.selectStatus = true
  293. this.selectId = item.id
  294. this.selectItem = item
  295. const [longitude, latitude] = (item.location || '').split(',');
  296. this.location = {
  297. longitude,
  298. latitude
  299. };
  300. // #ifdef APP
  301. setTimeout(() => {
  302. this.dragLoading = false
  303. }, 800)
  304. // #endif
  305. },
  306. handleConfirm() {
  307. if (!this.selectId) return this.$u.toast('请选择地址')
  308. const data = this.selectItem
  309. const [lng, lat] = data.location.split(',');
  310. if (this.enableLocationScope) {
  311. const discount = this.jnpf.getDistance(this.currentLocation.latitude, this.currentLocation
  312. .longitude, lat,
  313. lng) || 0;
  314. if (discount > (this.adjustmentScope || 500)) return this.$refs.uTips.show({
  315. title: '超出微调范围',
  316. type: 'warning',
  317. });
  318. }
  319. //判断可选范围
  320. if (this.enableDesktopLocation && this.locationScope.length) {
  321. let list = [];
  322. for (let i = 0; i < this.locationScope.length; i++) {
  323. const o = this.locationScope[i];
  324. const discount = this.jnpf.getDistance(o.lat, o.lng, lat, lng) || 0;
  325. list.push(discount > o.radius);
  326. }
  327. if (list.every(o => o === true)) return this.$refs.uTips.show({
  328. title: '超出规定范围',
  329. type: 'warning',
  330. });
  331. }
  332. const address = data.address && data.address.length ? data.address : '';
  333. //台湾、北京、上海、重庆、深圳地址特殊处理
  334. let fullAddress = data.pname + data.cityname + data.adname + address + data.name;
  335. if (data.pname == data.cityname) fullAddress = data.pname + data.adname + address + data.name;
  336. if (data.pname == data.cityname && data.pname == data.adname) fullAddress = data.pname + address +
  337. data.name;
  338. this.innerValue = {
  339. pName: data.pname,
  340. cName: data.cityname,
  341. adName: data.adname,
  342. address,
  343. name: data.name,
  344. lng,
  345. lat,
  346. fullAddress,
  347. };
  348. uni.$emit(this.emitKey, JSON.stringify(this.innerValue))
  349. this.close();
  350. },
  351. close() {
  352. uni.navigateBack({
  353. delta: 1
  354. });
  355. },
  356. getDistance(lat1, lon1, lat2, lon2) {
  357. const toRadians = (degrees) => {
  358. return degrees * (Math.PI / 180);
  359. }
  360. const R = 6371;
  361. const dLat = toRadians(lat2 - lat1);
  362. const dLon = toRadians(lon2 - lon1);
  363. const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  364. Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *
  365. Math.sin(dLon / 2) * Math.sin(dLon / 2);
  366. const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  367. const distance = R * c;
  368. return distance * 1000;
  369. },
  370. search() {
  371. // 节流,避免输入过快多次请求
  372. this.searchTimer && clearTimeout(this.searchTimer)
  373. this.searchTimer = setTimeout(() => {
  374. this.list = [];
  375. this.pagination.currentPage = 1
  376. this.keyword ? this.handleSearch() : this.getList();
  377. }, 300)
  378. },
  379. handleSearch() {
  380. this.loading = true;
  381. const query = {
  382. key: this.define.aMapWebKey,
  383. keywords: this.keyword,
  384. radius: this.enableLocationScope ? this.adjustmentScope || 500 : -1,
  385. offset: this.pagination.pageSize,
  386. page: this.pagination.currentPage,
  387. };
  388. getTextList(query).then(res => {
  389. this.handleResult(res);
  390. });
  391. },
  392. handleLocate() {
  393. if (this.locateLoading) return
  394. this.locateLoading = true
  395. uni.getLocation({
  396. type: 'gcj02',
  397. isHighAccuracy: true,
  398. success: (res) => {
  399. this.locateLoading = false
  400. if (!res.longitude || !res.latitude) return
  401. this.mapContext = uni.createMapContext("maps", this);
  402. this.mapContext.moveToLocation({
  403. longitude: res.longitude,
  404. latitude: res.latitude,
  405. })
  406. },
  407. fail: (res) => {
  408. this.locateLoading = false
  409. this.$u.toast('获取定位失败')
  410. }
  411. })
  412. },
  413. }
  414. };
  415. </script>
  416. <style scoped lang="scss">
  417. .jnpf-location-map {
  418. /* #ifdef H5 */
  419. height: calc(100vh - 44px);
  420. /* #endif */
  421. /* #ifndef H5 */
  422. height: 100vh;
  423. /* #endif */
  424. display: flex;
  425. flex-direction: column;
  426. .header {
  427. .map-container {
  428. position: relative;
  429. padding: 0rpx 20rpx;
  430. .map {
  431. width: 100%;
  432. height: 600rpx;
  433. }
  434. .map-marker {
  435. width: 38rpx;
  436. height: 64rpx;
  437. position: absolute;
  438. top: 50%;
  439. left: 50%;
  440. transform: translate(-50%, calc(-50% - 20rpx));
  441. z-index: 9999;
  442. }
  443. .h5-map-marker {
  444. transform: translate(-50%, calc(-50% - 30rpx));
  445. }
  446. .map-locate {
  447. position: absolute;
  448. bottom: 10px;
  449. right: 10px;
  450. height: 24px;
  451. width: 24px;
  452. padding: 4px;
  453. background-color: #fff;
  454. border-radius: 50%;
  455. box-shadow: 0 0 5px silver;
  456. z-index: 999;
  457. .map-locate-img {
  458. -webkit-animation: rotate 2s linear infinite;
  459. }
  460. @keyframes rotate {
  461. 0% {
  462. -webkit-transform: rotate(0deg);
  463. }
  464. 25% {
  465. -webkit-transform: rotate(90deg);
  466. }
  467. 50% {
  468. -webkit-transform: rotate(180deg);
  469. }
  470. 75% {
  471. -webkit-transform: rotate(270deg);
  472. }
  473. 100% {
  474. -webkit-transform: rotate(1turn);
  475. }
  476. }
  477. }
  478. }
  479. }
  480. .content {
  481. width: 100%;
  482. .user-select {
  483. .user-select-search {
  484. padding: 0rpx 20rpx;
  485. margin: 20rpx 0;
  486. }
  487. }
  488. }
  489. .around-contain {
  490. flex: 1;
  491. width: 100%;
  492. overflow: hidden;
  493. .loading {
  494. display: flex;
  495. justify-content: center;
  496. margin: 250rpx auto 0;
  497. }
  498. .around-contain-item {
  499. display: flex;
  500. align-items: center;
  501. padding: 10rpx 0;
  502. height: 60px;
  503. line-height: 22px;
  504. border-bottom: 1px solid #f2f2f6;
  505. .u-radio-label {
  506. width: 100%;
  507. display: flex;
  508. align-items: center;
  509. // #ifdef MP
  510. :deep(.u-radio) {
  511. margin: 0 16rpx 0 20rpx;
  512. }
  513. // #endif
  514. // #ifndef MP
  515. :deep(.uni-radio-input) {
  516. margin: 0 16rpx 0 20rpx;
  517. }
  518. // #endif
  519. .around-item-title-box {
  520. flex: 1;
  521. min-width: 0;
  522. padding-right: 16rpx;
  523. .around-item-title {
  524. font-size: 30rpx;
  525. color: #171a1d;
  526. }
  527. .around-item-sub-title {
  528. font-size: 28rpx;
  529. color: #b9babb;
  530. padding-top: 8rpx;
  531. }
  532. }
  533. }
  534. }
  535. }
  536. }
  537. </style>