|
|
@@ -421,19 +421,136 @@ export function normalizeAttendanceRule(rule) {
|
|
|
}
|
|
|
|
|
|
// ============ 定位 ============
|
|
|
-export function getLocation(options) {
|
|
|
- const timeout = (options && options.timeout) || 10000;
|
|
|
+// 后端「打卡范围 / 围栏」用的是 GCJ02(高德/腾讯地图上选的点),
|
|
|
+// 因此各端最终提交的坐标都统一到 GCJ02。
|
|
|
+const GCJ02_A = 6378245.0; // 克拉索夫斯基椭球长半轴
|
|
|
+const GCJ02_EE = 0.00669342162296594323; // 偏心率平方
|
|
|
+const PI = Math.PI;
|
|
|
+
|
|
|
+function outOfChina(lat, lng) {
|
|
|
+ return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
|
|
|
+}
|
|
|
|
|
|
- // #ifdef H5
|
|
|
+function transformLat(x, y) {
|
|
|
+ let ret =
|
|
|
+ -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
|
|
|
+ ret += ((20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0) / 3.0;
|
|
|
+ ret += ((20.0 * Math.sin(y * PI) + 40.0 * Math.sin((y / 3.0) * PI)) * 2.0) / 3.0;
|
|
|
+ ret += ((160.0 * Math.sin((y / 12.0) * PI) + 320 * Math.sin((y * PI) / 30.0)) * 2.0) / 3.0;
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+function transformLng(x, y) {
|
|
|
+ let ret =
|
|
|
+ 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
|
|
|
+ ret += ((20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0) / 3.0;
|
|
|
+ ret += ((20.0 * Math.sin(x * PI) + 40.0 * Math.sin((x / 3.0) * PI)) * 2.0) / 3.0;
|
|
|
+ ret += ((150.0 * Math.sin((x / 12.0) * PI) + 300.0 * Math.sin((x / 30.0) * PI)) * 2.0) / 3.0;
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * WGS84(gps 原始坐标)→ GCJ02(国测局坐标)
|
|
|
+ * App 端只集成系统定位时只能拿到 wgs84(系统定位仅支持 wgs84),
|
|
|
+ * 由前端转换成 gcj02,保证与后端围栏、后台地图是同一坐标系。
|
|
|
+ * 境外坐标不做转换。
|
|
|
+ */
|
|
|
+export function wgs84ToGcj02(latitude, longitude) {
|
|
|
+ const lat = Number(latitude);
|
|
|
+ const lng = Number(longitude);
|
|
|
+ if (!isFinite(lat) || !isFinite(lng)) return null;
|
|
|
+ if (outOfChina(lat, lng)) return { latitude: lat, longitude: lng };
|
|
|
+ let dLat = transformLat(lng - 105.0, lat - 35.0);
|
|
|
+ let dLng = transformLng(lng - 105.0, lat - 35.0);
|
|
|
+ const radLat = (lat / 180.0) * PI;
|
|
|
+ let magic = Math.sin(radLat);
|
|
|
+ magic = 1 - GCJ02_EE * magic * magic;
|
|
|
+ const sqrtMagic = Math.sqrt(magic);
|
|
|
+ dLat = (dLat * 180.0) / (((GCJ02_A * (1 - GCJ02_EE)) / (magic * sqrtMagic)) * PI);
|
|
|
+ dLng = (dLng * 180.0) / ((GCJ02_A / sqrtMagic) * Math.cos(radLat) * PI);
|
|
|
+ return { latitude: lat + dLat, longitude: lng + dLng };
|
|
|
+}
|
|
|
+
|
|
|
+/** uni.getLocation 包装,统一返回 { latitude, longitude, accuracy, address } */
|
|
|
+function requestUniLocation(type) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ uni.getLocation({
|
|
|
+ type,
|
|
|
+ geocode: false,
|
|
|
+ success: (res) =>
|
|
|
+ resolve({
|
|
|
+ latitude: res.latitude,
|
|
|
+ longitude: res.longitude,
|
|
|
+ accuracy: res.accuracy,
|
|
|
+ address: res.address,
|
|
|
+ }),
|
|
|
+ fail: (err) => reject(err),
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+/** 把平台原始 errMsg 翻译成人话 */
|
|
|
+function formatLocationError(err) {
|
|
|
+ const raw = String((err && (err.errMsg || err.message)) || "");
|
|
|
+ if (/not support gcj02/i.test(raw)) return "当前安装包不支持 gcj02 定位,请更新后重试";
|
|
|
+ if (/auth deny|auth denied|authorize|permission denied/i.test(raw))
|
|
|
+ return "未获得定位权限,请在手机设置中允许定位后重试";
|
|
|
+ if (/timeout/i.test(raw)) return "定位超时,请到开阔区域后重试";
|
|
|
+ if (/location switch off|system permission|ERROR_NOCELL/i.test(raw))
|
|
|
+ return "手机定位服务未开启,请打开系统定位后重试";
|
|
|
+ console.warn("[考勤] 定位失败:", raw);
|
|
|
+ return "获取位置失败,请稍后重试";
|
|
|
+}
|
|
|
+
|
|
|
+/** 是否「当前安装包没有 gcj02 能力」:App 未集成三方定位 SDK / 小程序未认证备案 */
|
|
|
+function isGcj02Unsupported(err) {
|
|
|
+ const raw = String((err && (err.errMsg || err.message)) || "");
|
|
|
+ const code = err && (err.errCode != null ? err.errCode : err.code);
|
|
|
+ return /not support gcj02/i.test(raw) || Number(code) === 18;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * App / 小程序端定位:
|
|
|
+ * 1. 优先取 gcj02(微信小程序原生支持;App 端需要集成高德/腾讯定位模块,
|
|
|
+ * 正式包若只勾了「系统定位」会返回 getLocation:fail not support gcj02);
|
|
|
+ * 2. 当前包不支持 gcj02 时,回退取 wgs84 原始坐标,再在前端转成 gcj02 提交。
|
|
|
+ */
|
|
|
+async function getNativeLocation() {
|
|
|
+ try {
|
|
|
+ return await requestUniLocation("gcj02");
|
|
|
+ } catch (err) {
|
|
|
+ if (!isGcj02Unsupported(err)) throw new Error(formatLocationError(err));
|
|
|
+ let raw = null;
|
|
|
+ try {
|
|
|
+ raw = await requestUniLocation("wgs84");
|
|
|
+ } catch (fallbackErr) {
|
|
|
+ throw new Error(formatLocationError(fallbackErr));
|
|
|
+ }
|
|
|
+ const converted = wgs84ToGcj02(raw.latitude, raw.longitude);
|
|
|
+ console.warn("[考勤] 当前安装包不支持 gcj02,已回退 wgs84 并转换为 gcj02");
|
|
|
+ if (!converted) return raw;
|
|
|
+ return {
|
|
|
+ latitude: converted.latitude,
|
|
|
+ longitude: converted.longitude,
|
|
|
+ accuracy: raw.accuracy,
|
|
|
+ address: raw.address,
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** H5 端:浏览器定位给的是 wgs84,转成 gcj02 与后端围栏保持一致 */
|
|
|
+function getBrowserLocation(timeout) {
|
|
|
if (typeof navigator !== "undefined" && navigator.geolocation) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
navigator.geolocation.getCurrentPosition(
|
|
|
- (pos) =>
|
|
|
+ (pos) => {
|
|
|
+ const converted = wgs84ToGcj02(pos.coords.latitude, pos.coords.longitude);
|
|
|
resolve({
|
|
|
- latitude: pos.coords.latitude,
|
|
|
- longitude: pos.coords.longitude,
|
|
|
+ latitude: converted ? converted.latitude : pos.coords.latitude,
|
|
|
+ longitude: converted ? converted.longitude : pos.coords.longitude,
|
|
|
accuracy: pos.coords.accuracy,
|
|
|
- }),
|
|
|
+ });
|
|
|
+ },
|
|
|
(err) => {
|
|
|
const msg =
|
|
|
err && err.code === 1
|
|
|
@@ -446,21 +563,16 @@ export function getLocation(options) {
|
|
|
});
|
|
|
}
|
|
|
return Promise.reject(new Error("当前环境不支持定位"));
|
|
|
+}
|
|
|
+
|
|
|
+export function getLocation(options) {
|
|
|
+ const timeout = (options && options.timeout) || 10000;
|
|
|
+
|
|
|
+ // #ifdef H5
|
|
|
+ return getBrowserLocation(timeout);
|
|
|
// #endif
|
|
|
|
|
|
- // #ifdef MP-WEIXIN || APP-PLUS
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- uni.getLocation({
|
|
|
- type: "gcj02",
|
|
|
- geocode: false,
|
|
|
- success: (res) =>
|
|
|
- resolve({
|
|
|
- latitude: res.latitude,
|
|
|
- longitude: res.longitude,
|
|
|
- accuracy: res.accuracy,
|
|
|
- }),
|
|
|
- fail: (err) => reject(new Error((err && err.errMsg) || "获取位置失败")),
|
|
|
- });
|
|
|
- });
|
|
|
+ // #ifndef H5
|
|
|
+ return getNativeLocation();
|
|
|
// #endif
|
|
|
}
|