| | |
| | | <u-icon name="calendar" |
| | | color="#348fe2" |
| | | size="16"></u-icon> |
| | | <text class="shift-text">ç½ç: 09:00-18:00</text> |
| | | <text class="shift-text">ç½ç: {{ workTimeDict.startAt }}-{{ workTimeDict.endAt }}</text> |
| | | </view> |
| | | </view> |
| | | <!-- æå¡æé® --> |
| | | <view class="checkin-button-container"> |
| | | <view class="checkin-button-wrapper"> |
| | | <view class="checkin-button" |
| | | :class="{ 'disabled': checkInOutText === 'å·²æå¡' }" |
| | | :class="{ 'disabled': todayRecord.workEndAt }" |
| | | @click="handleCheckInOut"> |
| | | <text class="checkin-button-text">{{ checkInOutText }}</text> |
| | | <text class="checkin-time">{{ nowTime.split(' ')[1] }}</text> |
| | |
| | | <view class="employee-info"> |
| | | <view class="info-item"> |
| | | <text class="info-label">é¨é¨</text> |
| | | <text class="info-value">{{ currentUser.dept }}</text> |
| | | <text class="info-value">{{ todayRecord.deptName || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="info-label">å§å</text> |
| | | <text class="info-value">{{ currentUser.name }}</text> |
| | | <text class="info-value">{{ todayRecord.staffName || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="info-label">å·¥å·</text> |
| | | <text class="info-value">{{ currentUser.no }}</text> |
| | | <text class="info-value">{{ todayRecord.staffNo || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | <!-- 仿¥èå¤ç¶æ --> |
| | | <view class="today-status"> |
| | | <u-icon :name="todayRecord ? 'checkmark-circle' : 'close-circle'" |
| | | :color="todayRecord ? '#4cd964' : '#ff3b30'" |
| | | <u-icon :name="todayRecord.id ? 'checkmark-circle' : 'close-circle'" |
| | | :color="todayRecord.id ? '#4cd964' : '#ff3b30'" |
| | | size="16"></u-icon> |
| | | <text class="status-text"> |
| | | {{ todayRecord ? `仿¥èå¤: ä¸ç ${todayRecord.checkInTime}` : '仿¥æªæå¡' }} |
| | | {{ todayRecord.id ? `仿¥èå¤: ä¸ç ${todayRecord.workStartAt || '-'}` : '仿¥æªæå¡' }} |
| | | </text> |
| | | </view> |
| | | <!-- ä¸çèå¤ç¶æ --> |
| | | <view v-if="todayRecord && todayRecord.checkOutTime" |
| | | <view v-if="todayRecord.id && todayRecord.workEndAt" |
| | | class="today-status"> |
| | | <u-icon :name="todayRecord ? 'checkmark-circle' : 'close-circle'" |
| | | :color="todayRecord ? '#4cd964' : '#ff3b30'" |
| | | <u-icon name="checkmark-circle" |
| | | color="#4cd964" |
| | | size="16"></u-icon> |
| | | <text class="status-text"> |
| | | {{ `仿¥èå¤: ä¸ç ${todayRecord.checkOutTime}` }} |
| | | {{ `仿¥èå¤: ä¸ç ${todayRecord.workEndAt}` }} |
| | | </text> |
| | | </view> |
| | | <!-- æå¡ç¶æ --> |
| | | <view v-if="todayRecord" |
| | | class="today-status"> |
| | | <u-icon :name="todayRecord.status === 'normal' ? 'checkmark-circle' : 'clock'" |
| | | :color="todayRecord.status === 'normal' ? '#4cd964' : '#ff3b30'" |
| | | <view class="today-status"> |
| | | <u-icon :name="todayRecord.id ? (todayRecord.status === 0 ? 'checkmark-circle' : 'clock') : 'clock'" |
| | | :color="todayRecord.id ? (todayRecord.status === 0 ? '#4cd964' : '#ff3b30') : '#ff3b30'" |
| | | size="16"></u-icon> |
| | | <text class="status-text"> |
| | | {{ `æå¡ç¶æ: ${todayRecord.statusText}` }} |
| | | {{ `æå¡ç¶æ: ${todayStatusText}` }} |
| | | </text> |
| | | </view> |
| | | <view v-else |
| | | <!-- å·¥æ¶ä¿¡æ¯ --> |
| | | <view v-if="todayRecord.id && todayRecord.workHours" |
| | | class="today-status"> |
| | | <u-icon name="clock" |
| | | color="#ff3b30" |
| | | color="#348fe2" |
| | | size="16"></u-icon> |
| | | <text class="status-text"> |
| | | æå¡ç¶æ: ç¼ºå¡ |
| | | {{ `å·¥æ¶(å°æ¶): ${todayRecord.workHours}` }} |
| | | </text> |
| | | </view> |
| | | </view> |
| | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted, onBeforeUnmount } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | // 模æå½åç»å½åå·¥ |
| | | const currentUser = reactive({ |
| | | id: 1, |
| | | name: "å¼ ä¸", |
| | | no: "E10001", |
| | | dept: "ç产ä¸é¨", |
| | | }); |
| | | import { getDicts } from "@/api/system/dict/data"; |
| | | import { |
| | | createPersonalAttendanceRecord, |
| | | findTodayPersonalAttendanceRecord, |
| | | } from "@/api/personnelManagement/attendance.js"; |
| | | // 仿¥æå¡è®°å½ |
| | | const todayRecord = ref({}); |
| | | |
| | | // 模æèå¤åå§æ°æ® |
| | | const rawAttendance = ref([ |
| | | { |
| | | id: 2, |
| | | date: "2026-02-08", |
| | | userId: 1, |
| | | name: "å¼ ä¸", |
| | | no: "E10001", |
| | | dept: "ç产ä¸é¨", |
| | | checkInTime: "09:15", |
| | | checkOutTime: "18:05", |
| | | workHours: 8.8, |
| | | status: "late", |
| | | statusText: "è¿å°", |
| | | remark: "å äº¤éæ¥å µè¿å°", |
| | | }, |
| | | { |
| | | id: 3, |
| | | date: "2026-02-07", |
| | | userId: 1, |
| | | name: "å¼ ä¸", |
| | | no: "E10001", |
| | | dept: "ç产ä¸é¨", |
| | | checkInTime: "08:45", |
| | | checkOutTime: "18:30", |
| | | workHours: 9.7, |
| | | status: "normal", |
| | | statusText: "æ£å¸¸", |
| | | remark: "å ç0.5å°æ¶", |
| | | }, |
| | | ]); |
| | | // çæ¬¡ä¿¡æ¯ |
| | | const workTimeDict = ref({ |
| | | startAt: "09:00", |
| | | endAt: "18:00", |
| | | }); |
| | | |
| | | // å½åæ¶é´å±ç¤º |
| | | const nowTime = ref(""); |
| | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | // æ¥è¯¢ä»æ¥æå¡ä¿¡æ¯ |
| | | const fetchTodayData = () => { |
| | | findTodayPersonalAttendanceRecord({}).then(res => { |
| | | todayRecord.value = res.data; |
| | | }); |
| | | }; |
| | | |
| | | // æå¡èå´ç¶æ |
| | |
| | | // 仿¥æ¥æ |
| | | const todayStr = computed(() => nowTime.value.slice(0, 10)); |
| | | |
| | | // 彿¥å½ååå·¥èå¤è®°å½ |
| | | const todayRecord = computed(() => |
| | | rawAttendance.value.find( |
| | | item => item.userId === currentUser.id && item.date === todayStr.value |
| | | ) |
| | | ); |
| | | |
| | | // æå¡æé®ææ¬ |
| | | const checkInOutText = computed(() => { |
| | | if (!todayRecord.value || !todayRecord.value.checkInTime) { |
| | | if (!todayRecord.value || !todayRecord.value.workStartAt) { |
| | | return "ä¸çæå¡"; |
| | | } |
| | | if (!todayRecord.value.checkOutTime) { |
| | | if (!todayRecord.value.workEndAt) { |
| | | return "ä¸çæå¡"; |
| | | } |
| | | return "å·²æå¡"; |
| | | }); |
| | | |
| | | // 仿¥ç¶ææ ç¾ç±»å |
| | | const todayStatusTag = computed(() => { |
| | | if (!todayRecord.value.id) return "info"; |
| | | if (todayRecord.value.status === 0) return "success"; |
| | | return "danger"; |
| | | }); |
| | | |
| | | // 仿¥ç¶æææ¬ |
| | | const todayStatusText = computed(() => { |
| | | if (!todayRecord.value.id) return "æªæå¡"; |
| | | switch (todayRecord.value.status) { |
| | | case 0: |
| | | return "æ£å¸¸"; |
| | | case 1: |
| | | return "è¿å°"; |
| | | case 2: |
| | | return "æ©é"; |
| | | case 3: |
| | | return "è¿å°ãæ©é"; |
| | | case 4: |
| | | return "缺å¤"; |
| | | } |
| | | }); |
| | | |
| | | // 导èªå°è¯¦ç»æ¥åé¡µé¢ |
| | |
| | | }); |
| | | }; |
| | | |
| | | // æå¡é»è¾ï¼ä»
å端模æï¼ |
| | | // è·åçæ¬¡åå
¸æ°æ® |
| | | const getWorkTimeDict = () => { |
| | | getDicts("sys_work_time") |
| | | .then(res => { |
| | | if (res.data && res.data.length > 0) { |
| | | const dictData = res.data; |
| | | workTimeDict.value = { |
| | | startAt: dictData[0].dictValue || "-", |
| | | endAt: dictData[1].dictValue || "-", |
| | | }; |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("è·åçæ¬¡åå
¸å¤±è´¥:", error); |
| | | }); |
| | | }; |
| | | |
| | | // æå¡é»è¾ |
| | | const handleCheckInOut = async () => { |
| | | if (todayRecord.value.workEndAt) { |
| | | uni.showToast({ |
| | | title: "æ¨å·²ç»æè¿å¡äº,æ éé夿å¡!!!", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | // æ£æ¥æ¯å¦å¨æå¡èå´å
|
| | | if (!inCheckRange.value) { |
| | | uni.showToast({ |
| | |
| | | return; |
| | | } |
| | | |
| | | const [dateStr, timeStr] = nowTime.value.split(" "); |
| | | if (!dateStr || !timeStr) return; |
| | | |
| | | // ä¸çæå¡ |
| | | if (!todayRecord.value) { |
| | | const newId = rawAttendance.value.length |
| | | ? Math.max(...rawAttendance.value.map(i => i.id)) + 1 |
| | | : 1; |
| | | const status = timeStr > "09:00:00" ? "late" : "normal"; |
| | | const statusText = status === "late" ? "è¿å°" : "æ£å¸¸"; |
| | | rawAttendance.value.push({ |
| | | id: newId, |
| | | date: dateStr, |
| | | userId: currentUser.id, |
| | | name: currentUser.name, |
| | | no: currentUser.no, |
| | | dept: currentUser.dept, |
| | | checkInTime: timeStr.slice(0, 5), |
| | | checkOutTime: "", |
| | | workHours: null, |
| | | status, |
| | | statusText, |
| | | remark: "", |
| | | // è°ç¨æå¡API |
| | | createPersonalAttendanceRecord({}) |
| | | .then(res => { |
| | | uni.showToast({ |
| | | title: "æå¡æåï¼", |
| | | icon: "success", |
| | | }); |
| | | // éæ°è·å仿¥æå¡ä¿¡æ¯ |
| | | fetchTodayData(); |
| | | }) |
| | | .catch(error => { |
| | | console.error("æå¡å¤±è´¥:", error); |
| | | uni.showToast({ |
| | | title: error.msg || "æå¡å¤±è´¥ï¼è¯·éè¯", |
| | | icon: "none", |
| | | }); |
| | | }); |
| | | uni.showToast({ |
| | | title: "ä¸çæå¡æå", |
| | | icon: "success", |
| | | }); |
| | | } else if (!todayRecord.value.checkOutTime) { |
| | | // ä¸çæå¡ |
| | | todayRecord.value.checkOutTime = timeStr.slice(0, 5); |
| | | // ç®åæ 9:00-18:00 计ç®å·¥æ¶ |
| | | const start = todayRecord.value.checkInTime || "09:00"; |
| | | const [sh, sm] = start.split(":").map(v => parseInt(v, 10)); |
| | | const [eh, em] = todayRecord.value.checkOutTime |
| | | .split(":") |
| | | .map(v => parseInt(v, 10)); |
| | | const diff = (eh * 60 + em - (sh * 60 + sm)) / 60; |
| | | todayRecord.value.workHours = Number(Math.max(diff, 0).toFixed(1)); |
| | | |
| | | // æ©é夿ï¼18:00 å离å¼è§ä¸ºæ©éï¼åªç¤ºæï¼ |
| | | if (timeStr < "18:00:00") { |
| | | if (todayRecord.value.status === "late") { |
| | | // æ¢è¿å°åæ©é |
| | | todayRecord.value.status = "late-early"; |
| | | todayRecord.value.statusText = "è¿å° + æ©é"; |
| | | } else { |
| | | // ä»
æ©é |
| | | todayRecord.value.status = "early"; |
| | | todayRecord.value.statusText = "æ©é"; |
| | | } |
| | | } else if (todayRecord.value.status === "normal") { |
| | | todayRecord.value.statusText = "æ£å¸¸"; |
| | | } |
| | | uni.showToast({ |
| | | title: "ä¸çæå¡æå", |
| | | icon: "success", |
| | | }); |
| | | } else { |
| | | uni.showToast({ |
| | | title: "仿¥å·²å®æä¸ä¸çæå¡", |
| | | icon: "none", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | fetchTodayData(); |
| | | updateNowTime(); |
| | | timer = setInterval(updateNowTime, 1000); |
| | | getWorkTimeDict(); |
| | | |
| | | // è·åä½ç½®æéå¹¶æ£æ¥ä½ç½® |
| | | try { |