源码来源
微信小程序地图获取地点信息(打卡签到功能为例)-2020-7-26
十分感谢作者能将代码分享出来让我们学习参考,如有侵权联系删除
准备工作
前提是要了解腾讯位置服务获取自己的key
并且配置在项目中,可以看这篇文章来进行配置
【微信小程序开发】微信小程序集成腾讯位置项目配置
js源码解析
wxml
与wxss
的代码这里暂且就不做解析了,主要是功能实现的解析
一、require分析
这里作者一共在Page
前require
了这些数据,其中
util
常量是作者自己写的一些工具方法,主要是对时间的处理app
常量是获取app.js
中的数据,为了让qqmapsdk
来获取app.js
中数据来进行核心业务逻辑的实现urlList
是作者用工具类配置向后台请求数据的部分代码qqmapsdk
是实例化API
的核心类
// location_check_in/location_check_in.js
const util = require('../../utils/util')
// getAPP()相当于获得app.js中的数据
const app = getApp()
// 发送wx.request请求
const urlList = require("../../utils/api.js") // 根据实际项目自己配置
// 实例化API核心类
// 相当于从app.js 中拿到 globalData 中的 qqmapsdk
const qqmapsdk = app.globalData.qqmapsdk
二、Page页面逻辑分析
这里每一个方法上都给出了注释,我这里就不一一赘述了,主要是每个方法的具体实现
1.data
正常代码阅读逻辑应该是先从生命周期函数onLoad
读起,碰到变量后再去data
中查看对应的意思,但是对于写文章来说会逻辑过于混乱,所以我们这里直接吧data
中的每个变量的含义进行解析
一共有这些数据,下面我们来一一剖析一下
markers
,这个变量主要是用于我们后续getAddress
中获取当前地址信息的作用,其中的每个数据我都给出了注释解释poi
,就是单纯地存储getAddress
中的经纬度信息addressName
,是获取当前位置的名称time
,是获取当前时间,用于前端显示,只显示时分秒
timer
,适用于存储每秒调用setINterval
获取时间这个方法的返回值,用于程序结束后停止运行这个方法,以免浪费空间timer2
,与timer
同理,不过这个是每20秒
重新获取地址信息的setINterval
canClick
,主要用于防止用户多次点击签到,让用户只能签到一次
data: {
// markers: 也就是getAddress中的msk
// 获取当前地点的
// 地名 title
// 经度 longitude
// 维度 latitude
// 当前所在位置显示的图标路径 iconPath
// 图片的宽高? width height
markers: '',
// poi: 也就是位置的经纬度(没有给定位置默认为当前位置)
poi: {
latitude: '',
longitude: ''
},
// 当前地点的地点名
addressName: '',
// time是获取当前时间 只取时分秒
time: '',
// 是setInterval的返回值
// 返回的值是当前setInterval的id
// 为什么要返回这个值呢?因为有的时候要涉及到clearInterval,clearInterval(id)就能删除相应的setInterval了
// 每秒获取时间的setInterval的id
timer: '',
// 同上,只不过是每20秒获取一次当前的定位信息的setInterval的id
timer2: '', // 用来每个一段时间自动刷新一次定位
// 允许用户点击,防止多次提交
// 在checkIn函数中,代表签到只能进行一次
canClick: true
},
2.onLoad
我们可以看到,页面打开主要干了三件事
- 调用了
getTime
方法 - 调用了
getAddress
方法 - 将
canClick
参数变为ture
,代表用户可以点击签到按钮,并且每20秒重新调用一次getAddress
方法,来对当前定位进行刷新
// 页面打开时发生
onLoad: function (options) {
var that = this
that.getTime()
that.getAddress()
that.setData({
canClick: true, // 允许用户点击,防止多次提交
timer2: setInterval(function () {
that.getAddress()
}, 20000) // 每20秒刷新一次定位
})
},
3.getTime方法
既然页面第一个加载就是getTime方法,我们就来看一下获取时间这个方法具体是干什么,如何实现的
每一步都给出了比较详细的解释,这里需要说的一共有两个点
- 调用了util工具类中的
formatDate
方法 time: time.substr(-8)
,这个主要是formatDate
方法获得的是年月日时分秒
,我们只需要时分秒
,相当于字符串前八位
是年月日
我们省略了
// 获取时间
getTime: function () {
let that = this
// 设置一个临时变量time等于data内的time
let time = that.data.time
that.setData({
// setInterval是一个实现定时调用的函数,可按照指定的周期(以毫秒计)来调用函数或计算表达式
// 本函数1000毫秒(1秒)调用一次
timer: setInterval(function () {
// 获取当前时间
// 格式为
// 2023/05/02 15:18:41
time = util.formatTime(new Date())
that.setData({
// 相当于只取时分秒,不取年月日
// 格式为
// 15:24:01
time: time.substr(-8)
});
if (time == 0) {
// 页面跳转后,要把定时器清空掉,免得浪费性能
// clearInterval()函数是在JavaScript中用于取消setInterval()函数设定的定时执行操作。
// 相当于把每秒调用的这个函数关掉
clearInterval(that.data.timer)
}
}, 1000)
})
},
(1)util工具类中的formatDate方法
这里没什么理解难点
- 注意一下
ES6
语法的箭头表达式 - 还有最后
return
中的返回形式,在注释中也有比较详细的解答 return
中还调用了一个formatNumber
// 这里用到了ES6特性的箭头函数
// 具体可看https://blog.csdn.net/weixin_45709829/article/details/123931582?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168300966016800222859768%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=168300966016800222859768&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-123931582-null-null.142^v86^insert_down28,239^v2^insert_chatgpt&utm_term=es6%20%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0&spm=1018.2226.3001.4187
const formatTime = date => {
// const只能被定义赋值一次,初始化结束后就不能被重新定义或赋值
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
// [year,month,day] = [2023,5,2]
// [year, month, day].map(formatNumber) = ["2023", "05", "02"]
// [year, month, day].map(formatNumber).join('/') = 2023/05/02
// [year, month, day].map(formatNumber).join('/') + ' ' = 2023/05/02 (多了一个空格)
// [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second] =
//2023/05/02 15,17,28
// [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber) =
// 2023/05/02 15,18,18
// [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') =
// 2023/05/02 15:18:41
// 也就是说join相当于把数组中间插入一个特定字符后再变成字符串
// map相当于补零到两位数
return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}
(2)util工具类中的formatNumber
注释中给出了解释
// 用于map,将不是字符串的转化为字符串
// 再将一位的时间补0
// 比如17:3:2 =>17:03:02
const formatNumber = n => {
n = n.toString()
return n[1] ? n : '0' + n
}
4.getAddress方法
这里是比较长的,但是基本上都是官网给出的模板,主要用到了我们开头常量中qqmapsdk
的逆地址解析(坐标位置描述)函数
其中官方文档为逆地址解析(坐标位置描述)
// 获取地址
getAddress(e) {
var that = this;
// reverseGeocoder 逆地址解析(坐标位置描述)
// 详见官方文档https://lbs.qq.com/miniProgram/jsSdk/jsSdkGuide/methodReverseGeocoder
qqmapsdk.reverseGeocoder({
//位置坐标,默认获取当前位置,非必须参数
/**
*
location: {
latitude: 39.984060,
longitude: 116.307520
},
*/
// 成功后的回调
success: function(res) {
// console.log(res);
that.setData({
addressName: res.result.address
})
var res = res.result;
var mks = [];
//当get_poi为0时或者为不填默认值时,检索目标位置,按需使用
mks.push({ // 获取返回结果,放到mks数组中
title: res.address,
id: 0,
// 维度
latitude: res.location.lat,
// 地名
longitude: res.location.lng,
// 当前所在位置显示的图标路径
iconPath: '../../images/zcxj/myPosition.png', // 图标路径
// 图标的像素?
width: 21,
height: 28,
// callout: { //在markers上展示地址名称,根据需求是否需要
// content: res.address,
// color: '#000',
// display: 'ALWAYS'
// }
});
that.setData({ // 设置markers属性和地图位置poi,将结果在地图展示
markers: mks,
poi: {
latitude: res.location.lat,
longitude: res.location.lng
}
});
},
fail: function(error) {
console.error(error);
},
complete: function(res) {
console.log(res);
}
})
},
5.rePosition方法
用户在前端主动点击了重新定位,我们这里要调用getAddress
重新获取定位
// 用户在前端点击重新定位的按钮,重新请求位置
rePosition: function () {
console.log('用户点了重新定位')
this.getAddress()
},
6.onUnload方法
在用户退出时触发,主要用于将两个Interval关闭,以免浪费资源
// onunload 事件在用户退出页面时发生
onUnload: function () {
// 根据timer和timer2存储的id来
// 清除两个不断循环的setInterval
clearInterval(this.data.timer)
clearInterval(this.data.timer2)
console.log("定时器已被清除")
},
7.checkIn方法
用户在前端点击了签到按钮,我们先把canClick
设置为FALSE
,代表用户点了一次除非签到失败是不能点第二次的,然后获取当前时间以及地点名称,并返回给前端的用户,提示用户检查当前签到时间和地点是否有误,如果有误则将canClick
改回true
,代表本次签到未成功,用户还可以点击一次签到事件,如果成功,则唤起业务逻辑
// 用户在前端点击了签到按钮
checkIn: function () {
// 这里canClick代表签到按钮只能点一次
this.setData({
canClick: false
})
console.log('用户点击了签到')
var that = this
// 重新获得当前时间
var nowTime = util.formatTime(new Date())
// 展示弹窗
wx.showModal({
title: '请确认打卡信息',
// content: '请确认待整改项已整改完毕!',
content: `地点:${this.data.addressName}\n时间:${nowTime}`, // 开发者工具上没有换行,真机调试时会有的
confirmText: '确认',
success (res) {
if (res.confirm) {
console.log('用户点击确定')
// 调起签到接口
that.realyCheckIn()
} else if (res.cancel) {
console.log('用户点击取消')
that.setData({
canClick: true
})
}
}
})
},
8.realyCheckIn方法
因为作者本身逻辑代码的隐私要求以及本人实在没看懂下面的代码,这里就只说到request
向后台发送请求的部分,欢迎其他读者进行补充
首先我们要向后端传输数据,需要传递经纬度
,用户信息
,签到事件
等信息,所以我们将所有信息封装成表单
来向后端进行发送
// 签到业务逻辑实现
realyCheckIn: function() {
var that = this
// 其他需要一并提交过去的业务数据
var patrolForm = app.globalData.patrolForm
console.log(app.globalData)
// debugger
// 要在这里给 patrolForm 补充其他的参数
// 补充了当前地点的名称以及当前的时间
patrolForm.checkaddress = this.data.addressName
patrolForm.searchtime = util.formatTime(new Date())
// 应该先判断用户有没有登录,没登录就授权登录
patrolForm.searchuser = app.globalData.user ? app.globalData.user.UserName : app.globalData.userInfo.nickName
console.log("传给后台的 searchuser:", patrolForm.searchuser)
// 拼接:"经度,纬度"
patrolForm.latandlon = this.data.poi.longitude + "," + this.data.poi.latitude
console.log(patrolForm)
console.log("↑ 签到提交的post参数")
var tmpNumber = 0
// 向后端发送请求
wx.request({
url: urlList.submitCheckInInfo,
data: patrolForm,
method: "POST",
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: function (res) {
console.log(res)
// 如果请求成功了
if(res.data.IsSuccess) {
// 这里typeof是返回当前 res.data.IsSuccess 的数据类型,比如String Int
console.log(res.data.IsSuccess, typeof(res.data.IsSuccess))
console.log("请求成功")
var patrolId = res.data.ReturnData[0].id
// // 看怎么取到返回的id
// debugger
if (patrolForm.img_arr1.length > 0) {
for (var i = 0; i < patrolForm.img_arr1.length; i++){
tmpNumber = i
// 上传图片
wx.uploadFile({
// 图片上传的接口地址
url: urlList.submitCheckInPhoto + "?patrolid=" + patrolId,
filePath: patrolForm.img_arr1[i],
name: 'content',
// formData: {
// // 这里面可以携带一些参数一并传过去
// patrolId: patrolId
// },
// header: {
// Authorization: token
// },
success: function (res) {
console.log(res)
},
fail: function (res) {
that.setData({
canClick: true
})
},
complete: function () {
// 因为上传图片是异步操作,所以会导致这里的 i 会取不到,故需要用个作用域更大点的变量来标识,否则 if 里面的代码不会执行
if(tmpNumber === patrolForm.img_arr1.length - 1) {
// 有图片就等图片上传完了再返回首页
wx.showToast({
title: '巡查签到成功!',
icon: 'success',
duration: 2000,
complete: function(){
wx.navigateBack({
delta: 2 // 回退两层页面
})
}
})
}
}
})
}
} else{
wx.showToast({
title: '巡查签到成功!',
icon: 'success',
duration: 2000,
complete: function(){
wx.navigateBack({
delta: 2
})
}
})
}
}
},
fail: function(res) {
that.setData({
canClick: true
})
}
})
},