教你用Uniapp實(shí)現(xiàn)微信小程序的GPS定位打卡
寫在開頭
哈嘍,隔了幾天沒寫文章,小編又回來了(?ω?)。最近接了一個(gè)校園的需求,主要功能是希望學(xué)生每天進(jìn)行定位打卡,幫助班導(dǎo)確認(rèn)學(xué)生是否在校的情況。
上面圖片是大致的交互過程,定位打卡是個(gè)比較常見的功能了,只是很多時(shí)候都是在 APP
上完成的,這次需求方是希望專門做個(gè)小程序來使用,當(dāng)然,整個(gè)小程序還有其他很多功能模塊,本章我們先來分享一下定位打卡功能,前端具體需要做哪些事情。
開通相關(guān)API權(quán)限
首先,因?yàn)檫@次定位打卡功能使用的是 GPS
來定位的,這就需要獲取用戶的地理位置信息。在小程序中,要獲取用戶的地理位置,微信官方提供了部分 API ,但是這些 API
有權(quán)限要求,我們需要先登陸 小程序后臺 去申請。
登陸后,按路徑「開發(fā)」-「開發(fā)管理」-「接口設(shè)置」中找到相關(guān) API
,填寫你使用 API
的理由,提交申請即可。
本次的功能小編一共會(huì)使用到了以下兩個(gè) API
:
- wx.chooseLocation:用于打開微信小程序自帶的地圖,能選擇一個(gè)位置,獲取目標(biāo)位置的經(jīng)緯度。
- wx.getLocation:用于獲取用戶當(dāng)前所在的地理位置信息,主要為了拿到經(jīng)緯度;不過,這個(gè)
API
有點(diǎn)難申請通過,小編也是申請了三次才過的,真是挺麻煩-.-,好像一般小程序主體是政府、學(xué)校或者大企業(yè)等機(jī)構(gòu)就比較容易通過(●—●)。
API
權(quán)限申請好了后,我們就能進(jìn)入正題了,開始正式的編碼工作。
項(xiàng)目初始化
項(xiàng)目小編直接使用 uniapp 的 HBuilderX
工具創(chuàng)建的,并使用了@dcloudio/uni-ui 作為 UI
庫。
定位打卡功能的具體交互過程很簡單,先由管理人員選取學(xué)校的位置,獲取到學(xué)校經(jīng)緯度信息保存起來,然后學(xué)生每次打卡也會(huì)獲取經(jīng)緯度坐標(biāo),然后計(jì)算兩個(gè)經(jīng)緯度坐標(biāo)的距離,就能推算出學(xué)生是否在校了。
API 配置聲明
項(xiàng)目初始化后,我們還需要進(jìn)行一步很關(guān)鍵的配置聲明,在項(xiàng)目的根目錄下,找到 manifest.json
文件,進(jìn)行如下配置:
{ ... "mp-weixin": { "appid": "", "setting": { "urlCheck": false }, "usingComponents": true, "permission": { "scope.userLocation": { "desc": "測試-" } }, "requiredPrivateInfos": ["getLocation", "chooseLocation"] }, ... }
主要是 requiredPrivateInfos
字段的配置,至于為什么可以看看官方說明,傳送門 。
選取學(xué)校位置
那么,接下來進(jìn)行我們的第一步,選取學(xué)校的位置,代碼比較簡單,直接來看:
<template> <view> <uni-section title="學(xué)校" type="line"> <uni-card title="選點(diǎn)"> <button @tap="chooseLocation">請?jiān)诘貓D中選擇學(xué)校的位置</button> <view v-if="isChooseTarget" class="info"> <view>{{ schoolInfo.address }}</view> <view>{{ `(${schoolInfo.latitude},${schoolInfo.longitude})` }}</view> </view> </uni-card> </uni-section> </view> </template> <script> export default { data() { return { schoolInfo: { latitude: '', longitude: '', address: '', }, } }, computed: { isChooseTarget() { return this.schoolInfo.latitude && this.schoolInfo.longitude }, }, methods: { // 選點(diǎn) chooseLocation() { uni.chooseLocation({ success: res => { this.schoolInfo.latitude = res.latitude; this.schoolInfo.longitude = res.longitude; this.schoolInfo.address = res.address; } }); }, } } </script> <style> .info{ display: flex; justify-content: center; align-items: center; flex-direction: column; margin-top: 20rpx; } </style>
獲取用戶的地理位置信息
搞定完學(xué)校的位置后,接下來就要來獲取學(xué)生的地理位置了,主要是使用 wx.chooseLocation 來獲取。
不過,因?yàn)檫@個(gè) API
初次調(diào)用時(shí),會(huì)有一個(gè)主動(dòng)詢問動(dòng)作。
如果你選擇允許授權(quán),那么后續(xù)你可以直接調(diào)用該 API
,但是如果選擇拒絕,那么調(diào)用該 API
就會(huì)直接進(jìn)入錯(cuò)誤的回調(diào),并不會(huì)有再次主動(dòng)詢問的動(dòng)作。
那么我們要如何重新授權(quán)呢?總不能拒絕后就不能使用了吧?
也就因?yàn)檫@么一個(gè)行為,我們還會(huì)牽扯出好幾個(gè)權(quán)限相關(guān)的 API
,才能完成整個(gè)權(quán)限的閉環(huán)操作。
- wx.getSetting:獲取用戶的當(dāng)前設(shè)置。
- wx.openSetting:調(diào)起用戶的設(shè)置界面。
- wx.authorize:提前向用戶發(fā)起授權(quán)請求。
上面小編簡單注釋了每個(gè) API
的作用,詳細(xì)信息還是要參考官方文檔為準(zhǔn)。
然后,為了更好的組織代碼,小編把權(quán)限這塊相關(guān)的進(jìn)行簡單的封裝,新建 /utils/location.js
文件:
/** * 獲取是否授權(quán)了定位權(quán)限 * @param { Boolean } launchAuth: 是否發(fā)起授權(quán)請求, 初次有效 * @return { Boolean } */ export function getLocationAuth(launchAuth) { return new Promise(resolve => { uni.getSetting({ success: res => { if(launchAuth && res.authSetting['scope.userLocation'] === undefined) { return uni.authorize({ scope: 'scope.userLocation', success: () => { resolve(true); }, fail: () => { resolve(false); } }) } resolve(res.authSetting['scope.userLocation']); }, fail: err => { console.err(err); } }) }) }
具體的使用:
<template> <view> ... <uni-section v-if="isChooseTarget" title="學(xué)生" type="line"> <uni-card title="當(dāng)前位置實(shí)時(shí)信息"> <template v-slot:title> <uni-list> <uni-list-item title="當(dāng)前位置實(shí)時(shí)信息"> <template v-slot:footer v-if="isAuth === 0"> <text @tap="reGrantAuth" class="text">重新授權(quán)</text> </template> </uni-list-item> </uni-list> </template> <view class="block"> <view class="title">經(jīng)緯度:</view> <view class="value"> <text v-if="!loading">{{ jwText || '-' }}</text> <view v-else class="loading"> <uni-icons type="spinner-cycle" size="20"/> </view> </view> </view> </uni-card> </uni-section> </view> </template> <script> import { getLocationAuth } from '@/utils/location'; export default { data() { return { ..., loading: false, isAuth: -1, // -1: 未授權(quán) 0: 拒絕授權(quán) 1:已授權(quán) studentInfo: { latitude: '', longitude: '', }, } }, computed: { ..., jwText() { const { latitude, longitude } = this.studentInfo; if(latitude && longitude) return `(${latitude},${longitude})`; return '' }, }, async onLoad() { if(!await getLocationAuth()) { this.isAuth = 0; } }, methods: { chooseLocation() { uni.chooseLocation({ success: async res => { ... // 判斷是否授權(quán) const authRes = await getLocationAuth(true); if(authRes) { // 獲取用戶當(dāng)前位置 this.getLocationInfo(); this.isAuth = 1; }else { this.isAuth = 0; } } }); }, // 獲取當(dāng)前位置信息 getLocationInfo() { this.loading = true; uni.getLocation({ type: 'gcj02', success: ({ latitude, longitude }) => { this.studentInfo.latitude = latitude; this.studentInfo.longitude = longitude; this.loading = false; } }); }, // 重新授權(quán) reGrantAuth() {} } } </script> <style> .info{ display: flex; justify-content: center; align-items: center; flex-direction: column; margin-top: 20rpx; } .block{ margin-bottom: 20rpx; } .title{ color: #000; font-weight: bold; } .value{ width: 100%; min-height: 40rpx; } .text{ font-size: 24rpx; color: #287DE1; } .loading { width: 40rpx; height: 40rpx; transform: rotate(360deg); animation: rotation 3s linear infinite; } @keyframes rotation{ 0%{ transform: rotate(0deg); } 100%{ transform: rotate(360deg); } } </style>
上面我們成功獲取到用戶的當(dāng)前位置信息,當(dāng)然,如果用戶選擇了拒絕,我們也提供了重新授權(quán)的方式。
export default { ..., methods: { ..., // 重新授權(quán) async reGrantAuth() { const authRes = await getLocationAuth(); if(authRes) { uni.showToast({ title: '已授權(quán)', duration: 500, icon: 'none' }); }else { wx.openSetting({ success: (res) => { if(res.authSetting['scope.userLocation']) { this.getLocationInfo(); this.isAuth = 1; } }, }) } }, } }
經(jīng)緯度轉(zhuǎn)化成具體地址
上面我們已經(jīng)拿到了學(xué)生用戶的當(dāng)前經(jīng)緯度坐標(biāo)了,本來我們接下來只要計(jì)算兩個(gè)經(jīng)緯度坐標(biāo)之間的距離就能完成功能了,奈何需求方還想要學(xué)生具體位置的中文信息,這就比較麻煩了,唉,但是麻煩也得做,否則沒飯吃呀-.-。
這個(gè)需求本質(zhì)就是讓我們把經(jīng)緯度轉(zhuǎn)成具體地址,這里需要使用額外的插件來處理,方式有很多,小編選擇 騰訊的位置服務(wù)。
我們直接按照他官網(wǎng)的介紹操作即可。
具體使用:
<template> <view> ... <uni-section v-if="isChooseTarget" title="學(xué)生" type="line"> <uni-card title="當(dāng)前位置實(shí)時(shí)信息"> ... <view class="block"> <view class="title">詳細(xì)地址:</view> <view class="value"> <text v-if="!loading">{{ studentInfo.address || '-' }}</text> <view v-else class="loading"> <uni-icons type="spinner-cycle" size="20"/> </view> </view> </view> </uni-card> </uni-section> </view> </template> <script> import { getLocationAuth } from '@/utils/location'; const QQMapWX = require('@/utils/qqmap-wx-jssdk.min.js'); export default { data() { return { ... studentInfo: { latitude: '', longitude: '', address: '', }, mapInstance: null, } }, computed: { ... }, async onLoad() { this.mapInstance = new QQMapWX({ key: '你的密鑰', }); if(!await getLocationAuth()) { this.isAuth = 0; } }, methods: { chooseLocation() { ... }, getLocationInfo() { this.loading = true; uni.getLocation({ type: 'gcj02', success: ({ latitude, longitude }) => { this.studentInfo.latitude = latitude; this.studentInfo.longitude = longitude; // 經(jīng)緯度轉(zhuǎn)成具體地址 this.mapInstance.reverseGeocoder({ location: { latitude, longitude }, success: res => { console.log(res) this.studentInfo.address = res.result.formatted_addresses.recommend; this.loading = false; } }); } }); }, // 重新授權(quán) async reGrantAuth() { ... }, } } </script>
計(jì)算兩個(gè)經(jīng)緯度坐標(biāo)之間的距離
具體位置也搞定后,就剩下最終的功能,計(jì)算兩個(gè)經(jīng)緯度坐標(biāo)之間的距離,這聽起來好像很難,但實(shí)際很簡單,網(wǎng)上有大把現(xiàn)成的方法,我們直接抄個(gè)來耍就行了。
/** * 根據(jù)經(jīng)緯度獲取兩點(diǎn)距離 * @param la1 第一個(gè)坐標(biāo)點(diǎn)的緯度 如:24.445676 * @param lo1 第一個(gè)坐標(biāo)點(diǎn)的經(jīng)度 如:118.082745 * @param la2 第二個(gè)坐標(biāo)點(diǎn)的緯度 * @param lo2 第二個(gè)坐標(biāo)點(diǎn)的經(jīng)度 * @return { Object } { km: 千米/公里, m: 米 } * @tips 注意經(jīng)度和緯度參數(shù)別傳反了, 一般經(jīng)度為0~180、緯度為0~90 */ export function calcDistanceLL(la1, lo1, la2, lo2) { let La1 = la1 * Math.PI / 180.0; let La2 = la2 * Math.PI / 180.0; let La3 = La1 - La2; let Lb3 = lo1 * Math.PI / 180.0 - lo2 * Math.PI / 180.0; let s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(La3 / 2), 2) + Math.cos(La1) * Math.cos(La2) * Math.pow(Math.sin( Lb3 / 2), 2))); s = s * 6378.137; s = Math.round(s * 10000) / 10000; return { km: s, m: Math.round(s * 1000) }; }
具體使用:
<template> <view> ... <uni-section v-if="isChooseTarget" title="學(xué)生" type="line"> <uni-card title="當(dāng)前位置實(shí)時(shí)信息"> ... <view class="block"> <view class="title">距離學(xué)校距離:</view> <view class="value"> <text v-if="!loading">{{ distanceToText || '-' }}</text> <view v-else class="loading"> <uni-icons type="spinner-cycle" size="20"/> </view> </view> </view> <view class="block"> <view class="title">是否可打卡:</view> <view class="value"> <text v-if="studentInfo.distance > 500 || studentInfo.distance === ''">否</text> <view @click="punchClock" v-else class="button yd-flex-h-hC-vC">打卡</view> </view> </view> </uni-card> </uni-section> </view> </template> <script> import { getLocationAuth, calcDistanceLL } from '@/utils/location'; const QQMapWX = require('@/utils//qqmap-wx-jssdk.min.js'); export default { data() { return { ... studentInfo: { latitude: '', longitude: '', address: '', distance: '', }, mapInstance: null, } }, computed: { distanceToText() { if(this.mainInfo.distance !== '') { return `${this.mainInfo.distance} 米`; } return ''; }, }, async onLoad() { this.mapInstance = new QQMapWX({ key: '你的密鑰', }); if(!await getLocationAuth()) { this.isAuth = 0; } }, methods: { punchClock() { uni.showToast({ title: '打卡成功', duration: 500, }); }, chooseLocation() { ... }, getLocationInfo() { this.loading = true; uni.getLocation({ type: 'gcj02', success: ({ latitude, longitude }) => { this.studentInfo.latitude = latitude; this.studentInfo.longitude = longitude; // 經(jīng)緯度轉(zhuǎn)成具體地址 this.mapInstance.reverseGeocoder({ location: { latitude, longitude }, success: res => { this.studentInfo.address = res.result.formatted_addresses.recommend; // 計(jì)算兩個(gè)經(jīng)緯度之間的距離 const distance = calcDistanceLL( this.schoolInfo.latitude, this.schoolInfo.longitude, latitude, longitude, ); this.studentInfo.distance = distance.m; this.loading = false; } }); } }); }, // 重新授權(quán) async reGrantAuth() { ... }, } } </script> <style> ... .button{ height: 60rpx; color: #fff; line-height: 1; background-color: #287DE1; border-radius: 4rpx; font-size: 20rpx; width: 30%; margin: auto; } </style>
至此,本篇文章就寫完啦,撒花撒花。
總結(jié)
到此這篇關(guān)于用Uniapp實(shí)現(xiàn)微信小程序的GPS定位打卡的文章就介紹到這了,更多相關(guān)Uniapp實(shí)現(xiàn)小程序GPS定位打卡內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序?qū)崿F(xiàn)滑動(dòng)切換自定義頁碼的方法分析
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)滑動(dòng)切換自定義頁碼的方法,結(jié)合實(shí)例形式分析了微信小程序頁碼動(dòng)態(tài)切換相關(guān)實(shí)現(xiàn)技巧與注意事項(xiàng),需要的朋友可以參考下2018-12-12JavaScript之DOM_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
由于HTML文檔被瀏覽器解析后就是一棵DOM樹,要改變HTML的結(jié)構(gòu),就需要通過JavaScript來操作DOM。始終記住DOM是一個(gè)樹形結(jié)構(gòu)。2017-07-07JavaScript使用Math.Min返回兩個(gè)數(shù)中較小數(shù)的方法
這篇文章主要介紹了JavaScript使用Math.Min返回兩個(gè)數(shù)中較小數(shù)的方法,涉及javascript中Math.Min方法的使用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04如何將網(wǎng)頁表格內(nèi)容導(dǎo)入excel
這篇文章主要介紹了如何將網(wǎng)頁表格內(nèi)容導(dǎo)入excel,需要的朋友可以參考下2014-02-02javascript下4個(gè)跨瀏覽器必備的函數(shù)
如果你的項(xiàng)目要用到 JavaScript,而你不使用任何 JavaScript 框架,那么對于那些常用且各個(gè)瀏覽器明顯不同的地方就需要用函數(shù)來封裝起來。2010-03-03