From 611dd8268e40caa4d97e8954b85b5adaaef99afa Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期一, 26 十月 2026 17:45:58 +0800
Subject: [PATCH] feat(hrm): 考勤规则支持地图定位打卡
---
src/views/hrm/attendance/record/modules/clock-panel.vue | 116 ++++++++++++++++++++++++++++++++++++++++++++++------------
1 files changed, 92 insertions(+), 24 deletions(-)
diff --git a/src/views/hrm/attendance/record/modules/clock-panel.vue b/src/views/hrm/attendance/record/modules/clock-panel.vue
index 7f41eed..2190714 100644
--- a/src/views/hrm/attendance/record/modules/clock-panel.vue
+++ b/src/views/hrm/attendance/record/modules/clock-panel.vue
@@ -1,4 +1,6 @@
<script lang="ts" setup>
+import type { AttendanceClockLocation, AttendanceClockResponse } from '#/api/hrm/attendance/record';
+
import { ref } from 'vue';
import { useVbenModal } from '#/packages/effects/common-ui/src';
@@ -9,62 +11,124 @@
const emit = defineEmits(['success']);
-const location = ref('鍏徃鍓嶅彴');
-const loading = ref(false);
+const DEFAULT_LOCATION = '鍏徃鍓嶅彴';
-/** 涓婄彮鎵撳崱 */
-async function handleClockIn() {
+const location = ref(DEFAULT_LOCATION);
+const loading = ref(false);
+const locating = ref(false);
+
+/**
+ * 鑾峰彇娴忚鍣ㄥ畾浣嶅苟缁熶竴涓� WGS84 浜ょ粰鍚庣鏍¢獙銆�
+ * 瀹氫綅涓嶅彲鐢紙涓嶆敮鎸併�佹嫆缁濇巿鏉冦�侀潪 HTTPS锛夋椂杩斿洖 null锛�
+ * 鐢卞悗绔寜鑰冨嫟瑙勫垯鍐冲畾鏄惁鍏佽鏃犲畾浣嶆墦鍗°��
+ */
+function getCurrentLocation(): Promise<AttendanceClockLocation | null> {
+ if (!navigator.geolocation) {
+ return Promise.resolve(null);
+ }
+ return new Promise((resolve) => {
+ navigator.geolocation.getCurrentPosition(
+ (position) =>
+ resolve({
+ longitude: Number(position.coords.longitude.toFixed(7)),
+ latitude: Number(position.coords.latitude.toFixed(7)),
+ accuracy: Number(position.coords.accuracy.toFixed(2)),
+ coordinateSystem: 'WGS84',
+ }),
+ () => resolve(null),
+ { enableHighAccuracy: true, maximumAge: 0, timeout: 15_000 },
+ );
+ });
+}
+
+/** 鏈惎鐢ㄥ畾浣嶈寖鍥存牎楠屾椂鍚庣涓嶈繑鍥炶窛绂伙紝鐩存帴鎻愮ず鎴愬姛 */
+function buildSuccessMessage(
+ action: string,
+ result?: AttendanceClockResponse,
+) {
+ if (result?.distanceMeters === null || result?.distanceMeters === undefined) {
+ return `${action}鎴愬姛`;
+ }
+ const place = result.locationName ? `璺�${result.locationName}` : '璺濇墦鍗″湴鐐�';
+ return `${action}鎴愬姛锛�${place} ${Number(result.distanceMeters).toFixed(1)} 绫砢;
+}
+
+async function submitClock(action: 'in' | 'out') {
+ if (loading.value) {
+ return;
+ }
loading.value = true;
try {
- await clockIn({ location: location.value });
- message.success('涓婄彮鎵撳崱鎴愬姛');
+ locating.value = true;
+ const position = await getCurrentLocation();
+ locating.value = false;
+ if (!position) {
+ message.warning(
+ '鏈幏鍙栧埌瀹氫綅锛屽皢鎸夋墦鍗″湴鐐规枃鏈彁浜わ紱鑻ヨ�冨嫟瑙勫垯瑕佹眰瀹氫綅锛岃寮�鍚畾浣嶆潈闄愬悗閲嶈瘯锛堥潪 HTTPS 鐜娴忚鍣ㄤ細绂佺敤瀹氫綅锛�',
+ );
+ }
+ const locationText = location.value.trim() || undefined;
+ const result =
+ action === 'in'
+ ? await clockIn(position, locationText)
+ : await clockOut(position, locationText);
+ message.success(
+ buildSuccessMessage(action === 'in' ? '涓婄彮鎵撳崱' : '涓嬬彮鎵撳崱', result),
+ );
emit('success');
- } catch (e: any) {
- message.error(e.message || '鎵撳崱澶辫触');
+ } catch (error: any) {
+ // 鎺ュ彛閿欒宸茬敱璇锋眰鎷︽埅鍣ㄧ粺涓�鎻愮ず锛岃繖閲屽彧鍏滃簳鏈湴寮傚父锛岄伩鍏嶉噸澶嶅脊鍑�
+ const handledByInterceptor = Boolean(
+ error?.response || error?.data || error?.isAxiosError || error?.code,
+ );
+ if (!handledByInterceptor) {
+ message.error(error?.message || '鎵撳崱澶辫触');
+ }
} finally {
+ locating.value = false;
loading.value = false;
}
+}
+
+/** 涓婄彮鎵撳崱 */
+function handleClockIn() {
+ return submitClock('in');
}
/** 涓嬬彮鎵撳崱 */
-async function handleClockOut() {
- loading.value = true;
- try {
- await clockOut({ location: location.value });
- message.success('涓嬬彮鎵撳崱鎴愬姛');
- emit('success');
- } catch (e: any) {
- message.error(e.message || '鎵撳崱澶辫触');
- } finally {
- loading.value = false;
- }
+function handleClockOut() {
+ return submitClock('out');
}
-const [Modal, modalApi] = useVbenModal({
+const [Modal] = useVbenModal({
onOpenChange(isOpen: boolean) {
if (!isOpen) {
- location.value = '鍏徃鍓嶅彴';
+ location.value = DEFAULT_LOCATION;
}
},
});
</script>
<template>
- <Modal title="鎵撳崱" class="w-[400px]">
+ <Modal title="鎵撳崱" class="w-[420px]">
<div class="p-4">
<div class="mb-4">
<label class="mr-2">鎵撳崱鍦扮偣锛�</label>
<a-input
v-model:value="location"
placeholder="璇疯緭鍏ユ墦鍗″湴鐐�"
- style="width: 200px"
+ style="width: 220px"
/>
+ </div>
+ <div class="mb-4 text-xs text-gray-500">
+ 鐐瑰嚮鎵撳崱鍚庝細鑷姩鑾峰彇褰撳墠 GPS 浣嶇疆锛屽苟鎸夎�冨嫟瑙勫垯涓殑鍦扮偣涓庡厑璁稿崐寰勬牎楠岋紱娴忚鍣ㄥ畾浣嶄笉鍙敤鏃讹紝灏嗘寜涓婃柟鍦扮偣鏂囨湰鎻愪氦銆�
</div>
<div class="flex gap-4">
<a-button
type="primary"
size="large"
:loading="loading"
+ :disabled="loading"
@click="handleClockIn"
>
涓婄彮鎵撳崱
@@ -72,11 +136,15 @@
<a-button
size="large"
:loading="loading"
+ :disabled="loading"
@click="handleClockOut"
>
涓嬬彮鎵撳崱
</a-button>
</div>
+ <div v-if="locating" class="mt-2 text-xs text-gray-400">
+ 姝e湪鑾峰彇瀹氫綅鈥�
+ </div>
</div>
</Modal>
-</template>
\ No newline at end of file
+</template>
--
Gitblit v1.9.3