checkinLocation.js
3.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import Taro from '@tarojs/taro'
const toRadians = degree => (degree * Math.PI) / 180
/**
* @description 计算两个经纬度之间的距离(米)
* @param {Object} start
* @param {number|string} start.lng
* @param {number|string} start.lat
* @param {Object} end
* @param {number|string} end.lng
* @param {number|string} end.lat
* @returns {number}
*/
export const calculateDistanceMeters = (start, end) => {
const startLng = Number(start?.lng)
const startLat = Number(start?.lat)
const endLng = Number(end?.lng)
const endLat = Number(end?.lat)
if (![startLng, startLat, endLng, endLat].every(Number.isFinite)) {
return NaN
}
const earthRadius = 6371000
const deltaLat = toRadians(endLat - startLat)
const deltaLng = toRadians(endLng - startLng)
const a =
Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
Math.cos(toRadians(startLat)) *
Math.cos(toRadians(endLat)) *
Math.sin(deltaLng / 2) *
Math.sin(deltaLng / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
return earthRadius * c
}
/**
* @description 判断当前位置是否在扫码打卡允许范围内
* @param {Object} params
* @param {boolean} params.geoEnabled
* @param {number|string} params.userLng
* @param {number|string} params.userLat
* @param {number|string} params.centerLng
* @param {number|string} params.centerLat
* @param {number|string} params.radiusMeters
* @returns {{allowed:boolean,distance:number,reason:string}}
*/
export const checkCheckinRange = (params = {}) => {
const { geoEnabled, userLng, userLat, centerLng, centerLat, radiusMeters } = params
if (geoEnabled !== true) {
return {
allowed: true,
distance: 0,
reason: 'geo_disabled',
}
}
const distance = calculateDistanceMeters(
{ lng: userLng, lat: userLat },
{ lng: centerLng, lat: centerLat }
)
if (!Number.isFinite(distance)) {
return {
allowed: false,
distance: NaN,
reason: 'invalid_location',
}
}
const rangeLimit = Number(radiusMeters)
if (!Number.isFinite(rangeLimit) || rangeLimit < 0) {
return {
allowed: false,
distance,
reason: 'invalid_radius',
}
}
return {
allowed: distance <= rangeLimit,
distance,
reason: distance <= rangeLimit ? 'within_range' : 'out_of_range',
}
}
/**
* @description 静默获取当前位置并校验是否在扫码打卡范围内
* @param {Object} params
* @param {boolean} params.geoEnabled
* @param {number|string} params.centerLng
* @param {number|string} params.centerLat
* @param {number|string} params.radiusMeters
* @returns {Promise<{allowed:boolean,distance:number,reason:string,location?:{lng:number,lat:number}}>}
*/
export const verifyCheckinRangeWithCurrentLocation = async (params = {}) => {
const { geoEnabled, centerLng, centerLat, radiusMeters } = params
// 未开启地理围栏时直接放行,避免页面层重复写同样的短路判断。
if (geoEnabled !== true) {
return {
allowed: true,
distance: 0,
reason: 'geo_disabled',
}
}
try {
// 重新拉取当前位置而不是复用缓存,确保扫码瞬间的位置判断更接近真实场景。
const location = await Taro.getLocation({
type: 'gcj02',
altitude: false,
isHighAccuracy: true,
highAccuracyExpireTime: 4000,
})
const normalizedLocation = {
lng: location.longitude,
lat: location.latitude,
}
const rangeResult = checkCheckinRange({
geoEnabled,
userLng: normalizedLocation.lng,
userLat: normalizedLocation.lat,
centerLng,
centerLat,
radiusMeters,
})
return {
...rangeResult,
location: normalizedLocation,
}
} catch (error) {
console.error('获取扫码打卡位置失败:', error)
return {
allowed: false,
distance: NaN,
reason: 'location_fetch_failed',
}
}
}