| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578 |
- // HR 考勤工具集
- // 由 kd-aiot-frontend-hr-H5 的 src/utils/attendance-time.ts + src/utils/location.ts
- // 以及 src/api/attendance.ts 中的纯函数部分迁移而来(Vue2 / JS 写法)
- // ============ 枚举 ============
- export const HOLIDAY_DAY_TYPE = {
- LEGAL_HOLIDAY: "LEGAL_HOLIDAY",
- COMPENSATORY_WORKDAY: "COMPENSATORY_WORKDAY",
- COMPENSATORY_RESTDAY: "COMPENSATORY_RESTDAY",
- };
- export const HOLIDAY_SOURCE_TYPE = {
- SYSTEM: "SYSTEM",
- EXTERNAL: "EXTERNAL",
- MANUAL: "MANUAL",
- };
- const WEEKDAY_KEYWORDS = {
- 周一: 1, 周二: 2, 周三: 3, 周四: 4, 周五: 5, 周六: 6, 周日: 7,
- 星期一: 1, 星期二: 2, 星期三: 3, 星期四: 4, 星期五: 5, 星期六: 6, 星期日: 7,
- Monday: 1, Tuesday: 2, Wednesday: 3, Thursday: 4,
- Friday: 5, Saturday: 6, Sunday: 7,
- Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6, Sun: 7,
- };
- const WEEKDAY_LABELS = {
- 1: "周一", 2: "周二", 3: "周三", 4: "周四", 5: "周五", 6: "周六", 7: "周日",
- };
- const pad2 = (n) => String(n).padStart(2, "0");
- // ============ 日期格式化 ============
- /** yyyy-MM */
- export function formatMonth(date) {
- const d = date instanceof Date ? date : new Date(date);
- return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}`;
- }
- /**
- * 健壮的 yyyy-MM-dd。
- * 兼容 Date / 时间戳 / "yyyy-MM-dd" / "yyyy-MM-dd HH:mm:ss" / ISO 串,
- * 非法值返回空字符串(调用方据此跳过该条数据)。
- */
- export function formatDateKey(value) {
- if (value === null || value === undefined || value === "") return "";
- let d;
- if (value instanceof Date) {
- d = value;
- } else if (typeof value === "number") {
- d = new Date(value);
- } else {
- const raw = String(value);
- if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) return raw;
- d = new Date(/[T ]/.test(raw) ? raw : raw.replace(/-/g, "/"));
- }
- if (!d || isNaN(d.getTime())) return "";
- return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
- }
- /** 解析打卡时间:支持 Date / 时间戳 / "HH:mm" / "HH:mm:ss" / 日期串 */
- export function parsePunchTime(value) {
- if (!value) return null;
- if (value instanceof Date) return isNaN(value.getTime()) ? null : value;
- if (typeof value === "number") {
- const d = new Date(value);
- return isNaN(d.getTime()) ? null : d;
- }
- const raw = String(value);
- if (/[T ]/.test(raw)) {
- const d = new Date(raw);
- return isNaN(d.getTime()) ? null : d;
- }
- const m = raw.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
- if (m) {
- const now = new Date();
- return new Date(
- now.getFullYear(), now.getMonth(), now.getDate(),
- Number(m[1]), Number(m[2]), Number(m[3] || 0)
- );
- }
- const d = new Date(raw.replace(/-/g, "/"));
- return isNaN(d.getTime()) ? null : d;
- }
- /** 解析成 yyyy-MM-dd 键;失败返回 null */
- export function punchDateKey(value) {
- const d = parsePunchTime(value);
- if (!d) return null;
- return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
- }
- /** end - start 分钟数,负数截断为 0 */
- export function minutesBetween(start, end) {
- const s = parsePunchTime(start);
- const e = parsePunchTime(end);
- if (!s || !e) return 0;
- const diff = Math.floor((e.getTime() - s.getTime()) / 60000);
- return diff > 0 ? diff : 0;
- }
- /**
- * 本地补偿迟到 / 早退 / 工时(后端 daily 接口常不下发达这仨事实字段)。
- * 跨日补卡(actualOut 日期 ≠ 当天)按当天 23:59 截断工时,且不算早退。
- */
- export function computeDailyMinutes(params) {
- const { actualIn, actualOut, scheduledStart, scheduledEnd, dateStr } = params || {};
- let lateMinutes = 0;
- let earlyMinutes = 0;
- let actualWorkMinutes = 0;
- let isCrossDayRepair = false;
- const outDateKey = punchDateKey(actualOut);
- if (actualOut && dateStr && outDateKey && outDateKey !== dateStr) {
- isCrossDayRepair = true;
- }
- if (actualIn && scheduledStart) {
- const inDate = parsePunchTime(actualIn);
- if (inDate) {
- const parts = String(scheduledStart).split(":");
- const scheduled = new Date(
- inDate.getFullYear(), inDate.getMonth(), inDate.getDate(),
- Number(parts[0]), Number(parts[1])
- );
- const diff = Math.floor((inDate.getTime() - scheduled.getTime()) / 60000);
- if (diff > 0) lateMinutes = diff;
- }
- }
- if (actualOut && scheduledEnd && !isCrossDayRepair) {
- const outDate = parsePunchTime(actualOut);
- if (outDate) {
- const parts = String(scheduledEnd).split(":");
- const scheduled = new Date(
- outDate.getFullYear(), outDate.getMonth(), outDate.getDate(),
- Number(parts[0]), Number(parts[1])
- );
- const diff = Math.floor((scheduled.getTime() - outDate.getTime()) / 60000);
- if (isFinite(diff) && diff > 0) earlyMinutes = diff;
- }
- }
- if (actualIn && actualOut) {
- const s = parsePunchTime(actualIn);
- const e = parsePunchTime(actualOut);
- if (s && e && e > s) {
- if (isCrossDayRepair) {
- const dayEnd = new Date(s.getFullYear(), s.getMonth(), s.getDate(), 23, 59, 59);
- actualWorkMinutes = Math.floor((dayEnd.getTime() - s.getTime()) / 60000);
- } else {
- actualWorkMinutes = Math.floor((e.getTime() - s.getTime()) / 60000);
- }
- }
- }
- return { lateMinutes, earlyMinutes, actualWorkMinutes, isCrossDayRepair };
- }
- /** 跨日补卡的下班时间显示成 "次日 HH:mm" */
- export function formatPunchTimeWithDate(value, baseDateStr) {
- if (!value) return "";
- const t = punchDateKey(value);
- const base = baseDateStr || "";
- const m = String(value).match(/(\d{1,2}:\d{2})/);
- const hm = m ? m[1] : String(value);
- if (!t || !base || t === base) return hm;
- return `次日 ${hm}`;
- }
- // ============ 打卡类型(上班卡 / 下班卡)判定 ============
- /**
- * 时间串 → 当天分钟数(0~1439)。
- * 兼容 "HH:mm" / "HH:mm:ss" / "yyyy-MM-dd HH:mm:ss" / ISO(取第一段 HH:mm);
- * 非法值返回 null。
- */
- export function minutesOfDay(value) {
- if (!value) return null;
- const m = String(value).match(/(\d{1,2}):(\d{2})/);
- if (!m) return null;
- const h = Number(m[1]);
- const mi = Number(m[2]);
- if (!isFinite(h) || !isFinite(mi) || h > 23 || mi > 59) return null;
- return h * 60 + mi;
- }
- /** 分钟数 → "HH:mm" */
- export function formatMinutesOfDay(minutes) {
- if (minutes === null || minutes === undefined || !isFinite(minutes)) return "";
- const m = ((Math.round(minutes) % 1440) + 1440) % 1440;
- return `${String(Math.floor(m / 60)).padStart(2, "0")}:${String(m % 60).padStart(2, "0")}`;
- }
- /** 拿不到班次时间时的默认「上午结束」分界 */
- export const DEFAULT_HALF_DAY_END_MINUTES = 12 * 60;
- /**
- * 「上班卡」的有效截止分界(分钟数)。
- * 过了这个点还没打上班卡 → 视为忘打卡,之后点击打卡按「下班卡」处理(上班卡需走补卡流程)。
- * 分界规则(2026-10-23 用户口径):规则里配置了上午下班时间用配置值,否则固定 12:00。
- * shiftStart / shiftEnd 参数保留兼容旧调用,不再参与计算。
- */
- export function resolvePunchInDeadline(shiftStart, shiftEnd, halfDayEnd) {
- const explicit = minutesOfDay(halfDayEnd);
- if (explicit !== null) return explicit;
- return DEFAULT_HALF_DAY_END_MINUTES;
- }
- /**
- * 判定本次该打「上班卡」还是「下班卡」。
- *
- * - 上下班都打过 → "DONE"
- * - 已打上班未打下班 → "OUT"
- * - 都没打:已过上班卡截止分界(规则配置的上午下班时间,未配置则 12:00)→ "OUT",否则 "IN"
- * (2026-10-23 用户口径:12 点之前是上班打卡,之后是下班打卡,不再按班次中点算)
- *
- * @param {object} params
- * @param {string} [params.actualIn] 已记录的上班打卡时间
- * @param {string} [params.actualOut] 已记录的下班打卡时间
- * @param {string} [params.shiftStart] 班次上班时间(保留兼容,不参与分界计算)
- * @param {string} [params.shiftEnd] 班次下班时间(保留兼容,不参与分界计算)
- * @param {string} [params.halfDayEnd] 规则里配置的上午下班时间,优先级最高
- * @param {Date} [params.now] 当前时间,默认 new Date()
- * @returns {"IN"|"OUT"|"DONE"}
- */
- export function resolvePunchType(params = {}) {
- const { actualIn, actualOut } = params;
- if (actualIn && actualOut) return "DONE";
- if (actualIn) return "OUT";
- const now = params.now instanceof Date ? params.now : new Date();
- const nowMinutes = now.getHours() * 60 + now.getMinutes();
- const explicit = minutesOfDay(params.halfDayEnd);
- return nowMinutes >= (explicit !== null ? explicit : DEFAULT_HALF_DAY_END_MINUTES)
- ? "OUT"
- : "IN";
- }
- /**
- * 判断当前是否属于「提前打下班卡」(已打上班卡、未打下班卡、还没到班次下班时间)。
- * 这种情况不拦截打卡,但要提示「记为早退」并二次确认。
- *
- * @param {object} params
- * @param {string} [params.actualIn] 已记录的上班打卡时间(必须已打上班卡才可能早退)
- * @param {string} [params.actualOut] 已记录的下班打卡时间
- * @param {string} [params.shiftEnd] 班次下班时间(HH:mm 或含时间的任意串)
- * @param {Date} [params.now] 当前时间,默认 new Date()
- * @returns {{early: boolean, endTime: string}} early 是否提前打下班卡;endTime 班次下班时间(HH:mm)
- */
- export function resolveEarlyOut(params = {}) {
- const { actualIn, actualOut, shiftEnd } = params;
- if (!actualIn || actualOut) return { early: false, endTime: "" };
- const endMinutes = minutesOfDay(shiftEnd);
- if (endMinutes === null) return { early: false, endTime: "" };
- const d = params.now instanceof Date ? params.now : new Date();
- const nowMinutes = d.getHours() * 60 + d.getMinutes();
- return {
- early: nowMinutes < endMinutes,
- endTime: formatMinutesOfDay(endMinutes),
- };
- }
- // ============ 周班次表解析 ============
- const SHIFT_RANGE_RE = /(?:(.+?)\s*)?[((]\s*(\d{1,2}:\d{2}(?::\d{2})?)\s*[-~到至]\s*(\d{1,2}:\d{2}(?::\d{2})?)\s*[))]/;
- const TIME_RANGE_ONLY_RE = /(\d{1,2}:\d{2}(?::\d{2})?)\s*[-~到至]\s*(\d{1,2}:\d{2}(?::\d{2})?)/;
- function buildRestStub(i) {
- return {
- weekday: i,
- weekdayLabel: WEEKDAY_LABELS[i],
- isRestDay: true,
- shiftName: "",
- segments: [],
- };
- }
- /**
- * 解析后端 shiftTimeSummary(如 "周一:行政班(08:30-17:30);周六:休息")
- * 为 7 项(周一→周日)班次数组。
- */
- export function parseShiftWeeklySummary(summary) {
- if (!summary || typeof summary !== "string") {
- return [1, 2, 3, 4, 5, 6, 7].map(buildRestStub);
- }
- const byWeekday = {};
- summary
- .split(/[;;]/)
- .map((s) => s.trim())
- .filter(Boolean)
- .forEach((part) => {
- const m = part.match(/^([^::]+)[::]\s*(.*)$/);
- if (!m) return;
- const weekday = WEEKDAY_KEYWORDS[m[1].trim()];
- if (!weekday) return;
- const content = m[2].trim();
- if (/^休(息|息日)?$/.test(content)) {
- byWeekday[weekday] = buildRestStub(weekday);
- return;
- }
- const withName = content.match(SHIFT_RANGE_RE);
- if (withName) {
- byWeekday[weekday] = {
- weekday,
- weekdayLabel: WEEKDAY_LABELS[weekday],
- isRestDay: false,
- shiftName: (withName[1] || "").trim(),
- segments: [{ start: withName[2].slice(0, 5), end: withName[3].slice(0, 5), raw: content }],
- };
- return;
- }
- const bare = content.match(TIME_RANGE_ONLY_RE);
- if (bare) {
- byWeekday[weekday] = {
- weekday,
- weekdayLabel: WEEKDAY_LABELS[weekday],
- isRestDay: false,
- shiftName: "",
- segments: [{ start: bare[1].slice(0, 5), end: bare[2].slice(0, 5), raw: content }],
- };
- }
- });
- const result = [];
- for (let i = 1; i <= 7; i++) result.push(byWeekday[i] || buildRestStub(i));
- return result;
- }
- /** 按日期取周班次表中的那一天(周日 → 索引 6) */
- export function getShiftForDate(dateStr, schedule) {
- if (!dateStr || !Array.isArray(schedule) || schedule.length === 0) return null;
- const d = new Date(String(dateStr).replace(/-/g, "/"));
- if (isNaN(d.getTime())) return null;
- const iso = d.getDay() === 0 ? 7 : d.getDay();
- return schedule[iso - 1] || null;
- }
- /** 后端 holidayDates → Map<yyyy-MM-dd, HolidayInfo> */
- export function buildHolidayMap(holidayDates) {
- const map = new Map();
- if (!Array.isArray(holidayDates)) return map;
- holidayDates.forEach((item) => {
- if (!item || item.calendarDate === null || item.calendarDate === undefined) return;
- const dateStr = formatDateKey(item.calendarDate);
- if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return;
- map.set(dateStr, {
- calendarDate: dateStr,
- changeReason: item.changeReason || "",
- dayType: item.dayType || "",
- sourceType: item.sourceType || "",
- });
- });
- return map;
- }
- export function getHolidayInfo(dateStr, holidayMap) {
- if (!dateStr || !holidayMap || typeof holidayMap.get !== "function") return null;
- return holidayMap.get(dateStr) || null;
- }
- /** 综合节假日 / 调休 + 周班次表判断是否休息日 */
- export function isRestDay(dateStr, schedule, holidayMap) {
- if (dateStr && holidayMap && typeof holidayMap.get === "function") {
- const holiday = holidayMap.get(dateStr);
- if (holiday) {
- if (holiday.dayType === HOLIDAY_DAY_TYPE.COMPENSATORY_WORKDAY) return false;
- if (
- holiday.dayType === HOLIDAY_DAY_TYPE.LEGAL_HOLIDAY ||
- holiday.dayType === HOLIDAY_DAY_TYPE.COMPENSATORY_RESTDAY
- ) {
- return true;
- }
- }
- }
- const info = getShiftForDate(dateStr, schedule);
- return info ? Boolean(info.isRestDay) : false;
- }
- function parseShiftSummary(summary) {
- if (!summary || typeof summary !== "string") return [];
- const segments = [];
- summary
- .split(/[;;]/)
- .map((s) => s.trim())
- .filter(Boolean)
- .forEach((part) => {
- let m = part.match(/\((\d{1,2}:\d{2}(?::\d{2})?)\s*[-~]\s*(\d{1,2}:\d{2}(?::\d{2})?)\)/);
- if (!m) m = part.match(/(\d{1,2}:\d{2}(?::\d{2})?)\s*[-~到至]\s*(\d{1,2}:\d{2}(?::\d{2})?)/);
- if (m) segments.push({ start: m[1].slice(0, 5), end: m[2].slice(0, 5), raw: part });
- });
- return segments;
- }
- /**
- * 归一化打卡规则:补出 shiftSegments / weeklySchedule / shiftStartTime /
- * shiftEndTime / holidayMap,供页面直接使用。
- */
- export function normalizeAttendanceRule(rule) {
- if (!rule) return null;
- const segments = parseShiftSummary(rule.shiftTimeSummary);
- const weeklySchedule = parseShiftWeeklySummary(rule.shiftTimeSummary);
- const holidayMap = buildHolidayMap(rule.holidayDates);
- const workDay = weeklySchedule.find((d) => !d.isRestDay && d.segments.length > 0);
- let shiftStartTime = "";
- let shiftEndTime = "";
- if (workDay) {
- const segs = workDay.segments;
- shiftStartTime = segs[0].start;
- shiftEndTime = segs[segs.length - 1].end;
- } else if (segments.length) {
- shiftStartTime = segments[0].start;
- shiftEndTime = segments[segments.length - 1].end;
- }
- return Object.assign({}, rule, {
- shiftSegments: segments,
- weeklySchedule,
- shiftStartTime,
- shiftEndTime,
- holidayMap,
- });
- }
- // ============ 定位 ============
- // 后端「打卡范围 / 围栏」用的是 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;
- }
- 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) => {
- const converted = wgs84ToGcj02(pos.coords.latitude, pos.coords.longitude);
- resolve({
- 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
- ? "定位权限被拒绝,请在浏览器中允许定位后重试"
- : "获取位置失败,请稍后重试";
- reject(new Error(msg));
- },
- { enableHighAccuracy: true, timeout, maximumAge: 0 }
- );
- });
- }
- return Promise.reject(new Error("当前环境不支持定位"));
- }
- export function getLocation(options) {
- const timeout = (options && options.timeout) || 10000;
- // #ifdef H5
- return getBrowserLocation(timeout);
- // #endif
- // #ifndef H5
- return getNativeLocation();
- // #endif
- }
|