页面效果展示
集成腾讯地图SDK
腾讯位置服务为微信小程序提供了基础的标点能力、线和圆的绘制接口等地图组件和位置展示、地图选点等地图API位置服务能力支持,使得开发者可以自由地实现自己的微信小程序产品。 在此基础上,腾讯位置服务微信小程序JavaScript SDK是专为小程序开发者提供的LBS数据服务工具包,可以在小程序中调用腾讯位置服务的POI检索、关键词输入提示、地址解析、逆地址解析、行政区划和距离计算等数据服务,让您的小程序更强大!
文档地址:微信小程序JavaScript SDK
使用步骤说明:
1.申请开发者密钥(key):申请密钥
2.开通webserviceAPI服务
控制台 ->应用管理 -> 我的应用 ->添加key-> 勾选WebServiceAPI -> 保存
(小程序SDK需要用到webserviceAPI的部分服务,所以使用该功能的KEY需要具备相应的权限)
3.下载微信小程序JavaScriptSDK
微信小程序JavaScriptSDK v1.1 JavaScriptSDK v1.2 ,这里推荐下载1.2版本,将下载好的SDK放在对应文件夹中,去引用它(即 qqmap-wx-jssdk.min.js 文件)引用到你小程序项目中。
4.安全域名设置
在小程序管理后台 -> 开发 -> 开发管理 -> 开发设置 -> “服务器域名” 中设置request合法域名,添加https://apis.map.qq.com
这个操作需要小程序管理员进到后台去配置 本地环境开发只需设置 勾上 不校验合法域名 即可
5.小程序核心代码示例
// 引入SDK核心类,js文件根据自己业务,位置可自行放置 var QQMapWX = require('../../libs/qqmap-wx-jssdk.js'); var qqmapsdk; Page({
onLoad: function () {
// 实例化API核心类
qqmapsdk = new QQMapWX({
key: '申请的key'
});
},
onShow: function () {
// 调用接口
qqmapsdk.search({
keyword: 'DreamCoders',
success: function (res) {
console.log(res);
},
fail: function (res) {
console.log(res);
},
complete: function (res) {
console.log(res);
}
});
微信小程序代码 wx.getLocation(Object object)
以 Promise 风格 调用:支持
用户授权:需要 scope.userLocation
小程序插件:支持,需要小程序基础库版本不低于 1.9.6
微信 Windows 版:支持
微信 Mac 版:支持
功能描述 获取当前的地理位置、速度。当用户离开小程序后,此接口无法调用。开启高精度定位,接口耗时会增加,可指定 highAccuracyExpireTime 作为超时时间。地图相关使用的坐标格式应为 gcj02。 高频率调用会导致耗电,如有需要可使用持续定位接口 wx.onLocationChange。 基础库 2.17.0 版本起 wx.getLocation 增加调用频率限制,相关公告。
使用方法 自 2022 年 7 月 14 日后发布的小程序,若使用该接口,需要在 app.json 中进行声明,否则将无法正常使用该接口,2022年7月14日前发布的小程序不受影响。具体规则见公告
申请开通 暂只针对如下类目的小程序开放,需要先通过类目审核,再在小程序管理后台,「开发」-「开发管理」-「接口设置」中自助开通该接口权限。 接口权限申请入口将于2022年3月11日开始内测,于3月31日全量上线。并从4月18日开始,在代码审核环节将检测该接口是否已完成开通,如未开通,将在代码提审环节进行拦截。
微信小程序获取定位关键方法 getLocation。因此需要在 app.json 中进行声明,后期小程序上线还需要单独申请getLocation 接口权限。
app.json 部分关键代码
{
"pages": [
"pages/index/index",
"pages/sign/sign"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "Weixin",
"navigationBarTextStyle": "black"
},
"tabBar": {
"custom": false,
"backgroundColor": "#fefefe",
"color": "#999999",
"selectedColor": "#1C9D9D",
"list": [{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "/images/home.png",
"selectedIconPath": "/images/home_cur.png"
},
{
"pagePath": "pages/sign/sign",
"text": "打卡",
"iconPath": "/images/day.png",
"selectedIconPath": "/images/day_cur.png"
},
{
"pagePath": "pages/index/index",
"text": "我的",
"iconPath": "/images/my.png",
"selectedIconPath": "/images/my_cur.png"
}
]
},
"permission": {
"scope.userLocation": {
"desc": "您的位置信息将用于小程序考勤签到功能"
}
},
"requiredPrivateInfos":[
"getLocation"
],
"style": "v2",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents"
}
sign.wxml代码
<!-- 部分UI代码参考苏苏就是小苏苏 -->
<view class="index">
<!-- 用户信息 -->
<view class="head ">
<view class="head_box flex-row" style="justify-content:left">
<view class="user_ava">
<open-data type="userAvatarUrl"></open-data>
</view>
<view>
<view class="user_name">DreamCoders <text>{{tip}}</text></view>
<view class="user_add">新的一天开始了,加油哦~</view>
</view>
<view class="mealBtn" bindtap="ToMealTap">
<image src="/images/meal.png"></image>
<view class="mealText">{{is_meal==2 ? '已订':'订餐'}}</view>
</view>
</view>
</view>
<view class="contentBox">
<!-- 打卡记录 -->
<view class="signRecord">
<view class="signInfo">上班打卡
<text class="text-green">{{record[0].times ? record[0].times : '未打卡'}}</text>
<view class="sign_address">
<view class="">{{record[0].address ? record[0].address : '暂无打卡地址'}}</view>
</view>
</view>
<view class="signInfo">下班打卡
<text class="text-green">{{record[1].times ? record[1].times : '未打卡'}}</text>
<view class="sign_address">
<view class="">{{record[1].address ? record[1].address : '暂无打卡地址'}}</view>
</view>
</view>
</view>
<view class="dateInfo ">
<text>{{nowDate}} {{nowDay}}</text>
</view>
<!-- 打卡按钮 -->
<view class="c_clock flex-column">
<view class="clock_time flex-column j_c {{status==1?'c1':''}} {{is_out==2 ? 'outArea' : ''}}" catchtap="signTap">
<text>{{signType>0 ? "下班打卡" : "上班打卡"}}</text>
<text>{{now_time}}</text>
</view>
<view class="clock_time_over flex-column j_c {{status==1?'c2':''}}" catchtap="clockInStart">
<text>已打卡</text>
<text>{{now_time_stop}}</text>
</view>
</view>
<!-- 打卡地址 -->
<view class="clock_address ">
<image src="/images/add0.png" class="add_icon" />
<text>{{current_address}}</text>
</view>
<view class="refresh" catchtap="refreshAdd">刷新位置</view>
</view>
</view>
sign.js代码
具体业务逻辑根据实际情况改写
let qqMapSdk= require("../../utils/qqmap.js");
let util = require('../../utils/util.js')
Page({
/**
* 页面的初始数据
*/
data: {
signType:0,//0上班打卡 1下班打卡
is_out:2,//1办公地点打卡 2外勤打卡
is_meal:1,//1未定餐 2已订餐
now_time: '',//当前时间
nowDate:'',//当前年月日
nowDay:'',//星期几
tip:'',//提示 上午好、下午好
current_address: '',//当前定位地址
status: 0, //0未打卡 1已打卡
latlng:[],//经纬度
now_time_stop: '', //已打卡时间
area:{},//考勤点多个
record:[],//打卡记录
},
onLoad: function (options) {
this.getCurrentTime();
this.setData({
now_time: this.getTime(),
nowDate: util.formatTime(new Date()),
nowDay: util.formatDay(new Date()),
tip: util.formatSole(),
})
},
onShow: function () {
this.getLocation();
this.setData({
status:0,
current_address:'',
})
},
signTap() {
var that = this;
if (!that.data.current_address) {
return wx.showToast({
title: '未获取当前定位',
icon: 'error'
})
}
var list = that.data.record.concat({'times':that.data.now_time,'address':that.data.current_address});
wx.vibrateLong();//手机震动提示
that.getSignRecord();
that.setData({
status: 1, //已打卡
record:list,
now_time_stop: that.data.now_time,
})
console.log(list);
console.log(that.data.record);
wx.showToast({
title: '打卡成功',
icon: 'none'
})
},
getCurrentTime: function () {
var time = setInterval(() => {
this.setData({
now_time: this.getTime()
})
}, 1000)
},
getTime() {
let dateTime = '';
let hh = new Date().getHours()
let mf = new Date().getMinutes() < 10 ? '0' + new Date().getMinutes() :
new Date().getMinutes()
let ss = new Date().getSeconds() < 10 ? '0' + new Date().getSeconds() :
new Date().getSeconds()
dateTime = hh + ':' + mf + ':' + ss;
return dateTime;
},
// 请求获取定位授权
getUserAuth: function () {
return new Promise((resolve, reject) => {
wx.authorize({
scope: 'scope.userLocation'
}).then(() => {
resolve()
}).catch(() => {
let that = this;
wx.getSetting({
success: (res) => {
if (res.authSetting['scope.userLocation'] != undefined && res.authSetting['scope.userLocation'] != true) {
wx.showModal({
title: '请求授权当前位置',
content: '需要获取您的地理位置,请确认授权',
success: function (res) {
if (res.cancel) {
wx.showToast({
title: '拒绝授权',
icon: 'none',
duration: 1000
})
} else if (res.confirm) {
wx.openSetting({
success: function (dataAu) {
if (dataAu.authSetting["scope.userLocation"] == true) {
//再次授权,调用wx.getLocation的API
that.getLocation();
} else {
wx.showToast({
title: '授权失败',
icon: 'none',
duration: 1000
})
}
}
})
}
}
})
} else if (res.authSetting['scope.userLocation'] == undefined) {
that.getLocation();
} else {
that.getLocation();
}
}
})
})
})
},
getLocation: function () {
const that = this
// 实例化腾讯地图API核心类
const QQMapWX = new qqMapSdk({
key: '你申请的KEY'// KEY必填
});
//获取当前位置
wx.getLocation({
type: 'gcj02',
success: function(res) {
that.latitude = res.latitude
that.longitude = res.longitude
QQMapWX.reverseGeocoder({
location: {
latitude: res.latitude,
longitude: res.longitude
},
success: function(res) {
let address = res.result.address + res.result.formatted_addresses.recommend;
that.getSignRecord();
that.setData({
current_address:address,
latlng:[res.result.location.lat,res.result.location.lng]
})
},
fail: function(res) {
this.getUserAuth()
wx.showToast({
title: '获取定位失败,请打开定位,重新进入!',
icon: 'none'
});
}
})
},
})
},
// 刷新定位
refreshAdd() {
this.getLocation(),
this.getSignRecord()
},
//处理打卡记录及判断打卡位置是否办公地点打卡
getSignRecord: function () {
var that = this;
console.log(that.data.latlng);
var distance = that.getDistance(that.data.latlng[0],that.data.latlng[1],31.370450,121.228252);
if(distance < 200000000000000){
that.setData({
is_out:1,
})
}
},
//经纬度距离计算
getDistance:function (lat1, lng1, lat2, lng2, unit = false) {
var radLat1 = lat1 * Math.PI / 180.0
var radLat2 = lat2 * Math.PI / 180.0
var a = radLat1 - radLat2
var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0
var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)))
s = s * 6378.137 // EARTH_RADIUS;
s = Math.round(s * 10000) / 10000 //输出为公里
if (0) { //是否返回带单位
if (s < 1) { //如果距离小于1km返回m
s = s.toFixed(3)
s = s * 1000 + "m"
} else {
s = s.toFixed(2)
s = s + "km"
}
} else {
s = s.toFixed(3)
s = s * 1000
}
return s
},
//订餐操作
ToMealTap:function (e) {
wx.showToast({
title: '订餐成功',
icon: 'none'
})
this.setData({
is_meal: 2,
})
}
})
sign.wxss 代码
page {
height: calc(100% - 10px)
}
.index {
margin-top: 10px;
background: #fff;
min-height: 100%;
}
.head {
padding-bottom: 10rpx;
border-bottom: 2rpx solid #E5E5E5;
}
.head_box {
padding: 26rpx 28rpx 8px;
width: 750rpx;
box-sizing: border-box;
}
.user_ava {
width: 116rpx;
height: 116rpx;
overflow: hidden;
border-radius: 25%;
margin-right: 32rpx;
}
.user_name {
font-size: 32rpx;
font-weight: 600;
color: #333333;
margin-bottom: 18rpx;
}
.user_name text {
font-size: 24rpx;
color: #999999;
font-weight: 400;
margin-left: 40rpx;
}
.user_add {
font-size: 28rpx;
color: #3380F3;
}
.contentBox {
padding: 44rpx 28rpx;
}
.signRecord{
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
margin-top: 15px;
}
.dateInfo{
text-align: center;
position: relative;
top: 50px;
font-size: 35rpx;
}
.c_title {
font-size: 28rpx;
color: #666666;
margin-bottom: 26rpx;
}
.c_section .c_item {
position: relative;
font-size: 30rpx;
font-weight: 600;
color: #333333;
padding-left: 40rpx;
margin-bottom: 110rpx;
}
.c_section text {
color: #307CED;
text-overflow: ellipsis;
overflow: hidden;
width: 80%;
white-space: nowrap;
}
.c_section .c_item::before {
content: '';
position: absolute;
width: 18rpx;
height: 18rpx;
border: 2rpx solid #999999;
left: 0;
top: 50%;
margin-top: -9rpx;
border-radius: 50%;
}
.c_section {
position: relative;
}
.c_section .c_item::after {
content: '';
position: absolute;
width: 2rpx;
height: 178rpx;
background: #E6E6E6;
left: 10rpx;
top: 34rpx;
}
.c_section view:last-child::after {
display: none;
}
.start_lo {
position: absolute;
top: 30px;
left: -5px;
}
.start_end {
position: absolute;
bottom: -108px;
left: 20px;
}
.c_clock {
margin: 180rpx auto 0;
width: 350rpx;
height: 380rpx;
perspective: 1500;
-webkit-perspective: 1500;
-moz-perspective: 1500;
}
.clock_time {
width: 350rpx;
height: 350rpx;
margin-bottom: 30rpx;
position: absolute;
transition: all 1s;
backface-visibility: hidden;
}
.clock_time::after {
content: '';
top: 0;
left: 0;
width: 350rpx;
height: 350rpx;
border-radius: 50%;
position: absolute;
z-index: 9;
background: rgba(48, 124, 237, 0.08);
animation: scale 1s infinite alternate-reverse;
}
/* 已打卡 */
.clock_time_over {
width: 350rpx;
height: 350rpx;
margin-bottom: 30rpx;
border-radius: 50%;
background: rgba(48, 124, 237, 0.08);
position: absolute;
transition: all 1s;
backface-visibility: hidden;
transform: rotateY(-180deg);
}
.clock_time_over::after {
position: absolute;
z-index: 11;
content: '';
width: 320rpx;
height: 320rpx;
background: #C6CED9;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.clock_time_over text {
position: relative;
z-index: 13;
color: #FFFFFF;
}
.clock_time_over text:first-child {
font-size: 36rpx;
margin-bottom: 14rpx;
}
.clock_time_over text:last-child {
font-size: 28rpx;
}
@keyframes scale {
0% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
.clock_time::before {
position: absolute;
z-index: 11;
content: '';
width: 320rpx;
height: 320rpx;
background: rgb(48, 124, 237, 0.79);
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.clock_time text {
position: relative;
z-index: 13;
color: #FFFFFF;
}
.clock_time text:first-child {
font-size: 36rpx;
margin-bottom: 14rpx;
}
.clock_time text:last-child {
font-size: 45rpx;
}
.clock_address {
text-align: center;
font-size: 30rpx;
color: #333333;
width: 80%;
margin: 20px auto;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
.clock_address text {
vertical-align: middle;
}
.add_icon {
width: 28rpx;
height: 36rpx;
margin-right: 16rpx;
vertical-align: middle;
}
.refresh {
margin-top: 25px;
color: #307CED;
display: flex;
align-items: center;
justify-content: center;
}
.now_location {
font-size: 24rpx;
color: #333333 !important;
}
.upload_box {
width: 260rpx;
height: 180rpx;
background: #F5F5F8;
border-radius: 5rpx;
}
.upload_box text {
font-size: 20rpx;
color: #999 !important;
font-weight: 100;
}
.camera_icon {
width: 42rpx;
height: 44rpx;
margin-bottom: 10rpx;
}
.clock_img {
width: 100%;
height: 100%;
}
.del_icon {
width: 32rpx;
height: 32rpx;
position: absolute;
right: -4px;
top: -11rpx;
}
.ative::before {
background: #307cedc9;
border: 2rpx solid #307cedc9 !important;
}
.c1 {
transform: rotateY(180deg)
}
.c1::after {
animation: none !important;
}
.c2 {
transform: rotateY(0deg)
}
.mealBtn{
position: absolute;
right: 15px;
}
.mealBtn image{
width: 27px;
height: 27px;
}
.mealText{
font-size: 12px;
color: #999999;
}
.outArea::before{
background: #f44336 !important;
}
.signInfo{
width: 48%;
height: 65px;
background: #f1f1f1;
padding: 10px;
border-radius: 5px;
}
.signInfo text{
float: inline-end;
}
.sign_address{
display: flex;
margin-top: 5px;
}
.sign_address view{
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: 14px;
margin-top: 1px !important;
color: #5f5a5a;
}
.text-green{
color: green;
}
util.js 代码
function formatTime(date) {
var year = date.getFullYear()
var month = date.getMonth() + 1
var day = date.getDate()
return year + "年" + month + "月" + day + "日";
}
const formatDay = dates => {
let _day = new Array('星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六');
let date = new Date(dates);
date.setDate(date.getDate());
let day = date.getDay();
return _day[day];
}
const formatSole = () => {
let timeNow = new Date();
let hours = timeNow.getHours();
let text = ``;
if (hours >= 0 && hours <= 6) {
text = `深夜了,不要熬夜太久哟`;
} else if (hours > 6 && hours <= 8) {
text = `早上好`;
} else if (hours > 8 && hours <= 10) {
text = `上午好`;
} else if (hours > 10 && hours <= 13) {
text = `中午好`;
} else if (hours > 13 && hours <= 17) {
text = `下午好`;
} else if (hours > 17 && hours <= 23) {
text = `晚上好`;
}
return text;
}
module.exports = {
formatTime: formatTime,
formatDay: formatDay,
formatSole: formatSole
}