| | |
| | | <view class="checkin-button-container"> |
| | | <view class="checkin-button-wrapper"> |
| | | <view class="checkin-button" |
| | | :class="{ 'disabled': todayRecord.workEndAt || noNeedCheckIn }" |
| | | :class="{ 'disabled': todayRecord.workEndAt || noNeedCheckIn || locating || submitting }" |
| | | @click="handleCheckInOut"> |
| | | <text class="checkin-button-text">{{ checkInOutText }}</text> |
| | | <text class="checkin-button-text">{{ locating ? '定位中...' : (submitting ? '提交中...' : checkInOutText) }}</text> |
| | | <text class="checkin-time">{{ nowTime.split(' ')[1] }}</text> |
| | | </view> |
| | | </view> |
| | |
| | | |
| | | // 打卡冷却时间(毫秒) |
| | | const CHECKIN_COOLDOWN = 5000; |
| | | const locating = ref(false); |
| | | const submitting = ref(false); |
| | | // 返回上一页 |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | |
| | | }); |
| | | }; |
| | | |
| | | // 获取位置权限 |
| | | const getLocationPermission = () => { |
| | | return new Promise((resolve, reject) => { |
| | | // #ifdef APP-PLUS |
| | | uni.getAppAuthorizeSetting({ |
| | | success: res => { |
| | | if (res.authSetting["scope.userLocation"]) { |
| | | resolve(true); |
| | | } else { |
| | | uni.requestAppAuthorize({ |
| | | scope: "scope.userLocation", |
| | | success: res => { |
| | | resolve(res.authSetting["scope.userLocation"]); |
| | | }, |
| | | fail: err => { |
| | | reject(err); |
| | | }, |
| | | }); |
| | | } |
| | | }, |
| | | fail: err => { |
| | | reject(err); |
| | | }, |
| | | }); |
| | | // #else |
| | | // 非APP环境直接返回成功 |
| | | resolve(true); |
| | | // #endif |
| | | }); |
| | | }; |
| | | const form = ref({ |
| | | longitude: "", |
| | | latitude: "", |
| | | }); |
| | | |
| | | const getCurrentLocation = () => { |
| | | uni.showLoading({ title: "获取位置中..." }); |
| | | |
| | | uni.getLocation({ |
| | | type: "gcj02", |
| | | const showLocationError = err => { |
| | | const errorMessage = err?.errMsg || err?.message || "未知错误"; |
| | | console.error("获取位置失败:", err); |
| | | uni.showModal({ |
| | | title: "无法获取位置", |
| | | content: `请开启手机定位服务,并允许应用使用精确位置后重试。\n\n${errorMessage}`, |
| | | confirmText: "去设置", |
| | | cancelText: "取消", |
| | | success: res => { |
| | | uni.hideLoading(); |
| | | form.value.latitude = res.latitude; |
| | | form.value.longitude = res.longitude; |
| | | }, |
| | | fail: err => { |
| | | uni.hideLoading(); |
| | | console.error("获取位置失败:", err); |
| | | |
| | | // 显示错误提示并引导用户检查权限 |
| | | showToast("获取位置失败,请检查定位权限"); |
| | | |
| | | // 引导用户检查权限设置 |
| | | uni.showModal({ |
| | | title: "位置权限提示", |
| | | content: |
| | | "获取位置失败,可能是因为位置权限未开启,请在设备设置中检查并开启位置权限。", |
| | | confirmText: "知道了", |
| | | cancelText: "取消", |
| | | success: res => { |
| | | if (res.confirm) { |
| | | // 可以尝试打开设置页面(如果支持) |
| | | if (uni.openSetting) { |
| | | uni.openSetting({ |
| | | success: settingRes => { |
| | | console.log("设置结果:", settingRes); |
| | | }, |
| | | }); |
| | | } |
| | | } |
| | | if (!res.confirm) return; |
| | | // #ifdef APP-PLUS |
| | | uni.openAppAuthorizeSetting({ |
| | | fail: settingError => { |
| | | console.error("打开应用权限设置失败:", settingError); |
| | | }, |
| | | }); |
| | | |
| | | // 失败时显示错误信息 |
| | | form.value.visitAddress = "位置获取失败"; |
| | | // #endif |
| | | // #ifndef APP-PLUS |
| | | if (uni.openSetting) uni.openSetting({}); |
| | | // #endif |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | const getCurrentLocation = (showError = true) => { |
| | | if (locating.value) { |
| | | return Promise.reject(new Error("正在获取位置,请稍候")); |
| | | } |
| | | locating.value = true; |
| | | form.value.latitude = ""; |
| | | form.value.longitude = ""; |
| | | |
| | | return new Promise((resolve, reject) => { |
| | | uni.getLocation({ |
| | | // 使用系统 WGS84 定位,正式包不依赖高德 Android Key、包名和签名 SHA1。 |
| | | type: "wgs84", |
| | | isHighAccuracy: true, |
| | | highAccuracyExpireTime: 10000, |
| | | success: res => { |
| | | const latitude = Number(res.latitude); |
| | | const longitude = Number(res.longitude); |
| | | if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) { |
| | | reject(new Error("定位结果中的经纬度无效")); |
| | | return; |
| | | } |
| | | form.value.latitude = latitude; |
| | | form.value.longitude = longitude; |
| | | resolve({ latitude, longitude }); |
| | | }, |
| | | fail: reject, |
| | | complete: () => { |
| | | locating.value = false; |
| | | }, |
| | | }); |
| | | }).catch(err => { |
| | | if (showError) showLocationError(err); |
| | | throw err; |
| | | }); |
| | | }; |
| | | |
| | |
| | | |
| | | // 打卡逻辑 |
| | | const handleCheckInOut = async () => { |
| | | if (locating.value || submitting.value) return; |
| | | |
| | | if (noNeedCheckIn.value) { |
| | | uni.showToast({ |
| | | title: "今日无需打卡", |
| | |
| | | return; |
| | | } |
| | | |
| | | // 调用打卡API |
| | | createPersonalAttendanceRecord({ |
| | | ...form.value, |
| | | }) |
| | | .then(res => { |
| | | // 记录打卡时间 |
| | | lastCheckInTime.value = Date.now(); |
| | | |
| | | try { |
| | | // 每次打卡都重新定位,避免提交进入页面时缓存的旧位置。 |
| | | await getCurrentLocation(); |
| | | if (form.value.latitude === "" || form.value.longitude === "") { |
| | | throw new Error("未获取到有效位置"); |
| | | } |
| | | submitting.value = true; |
| | | await createPersonalAttendanceRecord({ ...form.value }); |
| | | lastCheckInTime.value = Date.now(); |
| | | uni.showToast({ |
| | | title: "打卡成功!", |
| | | icon: "success", |
| | | }); |
| | | fetchTodayData(); |
| | | } catch (error) { |
| | | // 定位失败已由 getCurrentLocation 给出权限引导,这里只提示接口错误。 |
| | | if (form.value.latitude !== "" && form.value.longitude !== "") { |
| | | uni.showToast({ |
| | | title: "打卡成功!", |
| | | icon: "success", |
| | | }); |
| | | // 重新获取今日打卡信息 |
| | | fetchTodayData(); |
| | | }) |
| | | .catch(error => { |
| | | uni.showToast({ |
| | | title: error.msg || "打卡失败,请重试", |
| | | title: error.msg || error.message || "打卡失败,请重试", |
| | | icon: "none", |
| | | }); |
| | | }); |
| | | } |
| | | } finally { |
| | | submitting.value = false; |
| | | } |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | |
| | | timer = setInterval(updateNowTime, 1000); |
| | | getWorkTimeDict(); |
| | | |
| | | // 获取位置权限并检查位置 |
| | | try { |
| | | // await getLocationPermission(); |
| | | await getCurrentLocation(); |
| | | } catch (error) { |
| | | console.error("位置权限获取失败:", error); |
| | | } |
| | | // 进入页面时预定位;真正打卡前还会再次获取,预定位失败不打扰用户。 |
| | | getCurrentLocation(false).catch(error => { |
| | | console.error("页面预定位失败:", error); |
| | | }); |
| | | }); |
| | | |
| | | onBeforeUnmount(() => { |
| | |
| | | .attendance-records { |
| | | animation-delay: 0.2s; |
| | | } |
| | | </style> |
| | | </style> |