Ver código fonte

feat(home): 优化打卡逻辑,动态更新按钮文案,添加当前时间定时刷新功能

xieyong 5 dias atrás
pai
commit
18d1a0cee5
1 arquivos alterados com 67 adições e 4 exclusões
  1. 67 4
      src/pages/home/index.vue

+ 67 - 4
src/pages/home/index.vue

@@ -34,7 +34,7 @@
           >
         </view>
       </view>
-      <button class="check-fab press" :class="{ disabled: !canPunch }" @click.stop="onPunch">{{ canPunch ? '立即打卡' : '暂停打卡' }}</button>
+      <button class="check-fab press" :class="{ disabled: !canPunch }" @click.stop="onPunch">{{ punchButtonLabel }}</button>
     </view>
 
     <view class="quick card section">
@@ -107,7 +107,7 @@
 import AppTabBar from "@/components/AppTabBar.vue";
 import ModuleIcon from "@/components/ModuleIcon.vue";
 import { computed, reactive, ref } from "vue";
-import { onShow } from "@dcloudio/uni-app";
+import { onShow, onHide } from "@dcloudio/uni-app";
 import { quickActions, notices } from "@/data/mock";
 import { useCurrentUser } from "@/hooks/useCurrentUser";
 import { getCurrentUser } from "@/utils/storage";
@@ -183,6 +183,22 @@ function extractTime(value) {
   return m ? m[1] : String(value);
 }
 
+// 把 "18:00:00" / "2026-09-14 18:00:00" 这种班次时间解析为"今日该时刻"的 Date
+function parseShiftTime(value) {
+  if (value == null || value === "") return null;
+  const m = String(value).match(/(\d{1,2}):(\d{2})(?::(\d{2}))?/);
+  if (!m) return null;
+  const d = new Date();
+  d.setHours(Number(m[1]), Number(m[2]), Number(m[3] || 0), 0);
+  return d;
+}
+
+// 今日下班时刻,用于判断下班卡是否到点
+const shiftEndDate = computed(() => {
+  const raw = daily.scheduledEnd || shiftInfo.endTime;
+  return parseShiftTime(raw);
+});
+
 const inText = computed(() => daily.actualIn || "--:--");
 const outText = computed(() => daily.actualOut || "--:--");
 
@@ -207,8 +223,30 @@ const buttonState = computed(() => {
   return { cls: "pending", mark: "", label: "打卡" };
 });
 
-// 是否允许打卡:规则未配置 / 已停用 时禁用按钮
-const canPunch = computed(() => ruleStatus.value !== 'empty' && ruleStatus.value !== 'disabled');
+// 是否允许打卡:
+// - 规则未配置 / 已停用:禁用
+// - 已上班未下班,且当前时间未到下班点:禁用(下班卡不能提前打)
+const canPunch = computed(() => {
+  if (ruleStatus.value === 'empty' || ruleStatus.value === 'disabled') return false;
+  if (daily.actualIn && !daily.actualOut) {
+    const end = shiftEndDate.value;
+    if (end && now.value < end) return false;
+  }
+  return true;
+});
+
+// 打卡按钮文案:根据不同禁用原因给出明确提示,而不是统一"暂停打卡"
+const punchButtonLabel = computed(() => {
+  if (ruleStatus.value === 'empty' || ruleStatus.value === 'disabled') return "暂停打卡";
+  if (daily.actualIn && !daily.actualOut) {
+    const raw = daily.scheduledEnd || shiftInfo.endTime;
+    const end = shiftEndDate.value;
+    if (end && now.value < end) {
+      return `${extractTime(raw)} 后下班打卡`;
+    }
+  }
+  return "立即打卡";
+});
 
 async function loadShiftInfo() {
   const result = await queryAttendanceRule();
@@ -238,13 +276,31 @@ async function loadDaily() {
   }
 }
 
+// 每分钟刷新一次"当前时间",让 canPunch / 按钮文案能跨过下班点自动从禁用变可用
+let nowTimer = null;
+function startNowTimer() {
+  if (nowTimer) return;
+  nowTimer = setInterval(() => {
+    now.value = new Date();
+  }, 60 * 1000);
+}
+function stopNowTimer() {
+  if (nowTimer) {
+    clearInterval(nowTimer);
+    nowTimer = null;
+  }
+}
+
 onShow(() => {
   // 进入页面时刷新"当前时间",确保问候语与日期反映最新时段
   now.value = new Date();
+  startNowTimer();
   loadPending();
   loadDaily();
 });
 
+onHide(stopNowTimer);
+
 // 待办角标:与工作台 / 底部导航(AppTabBar)同源,统一走审批中心
 async function loadPending() {
   try {
@@ -267,6 +323,13 @@ async function onPunch() {
     const reason = ruleBlockedReason.value || '不可用';
     return uni.showToast({ title: `考勤规则${reason},暂不能打卡`, icon: "none" });
   }
+  // 兜底:已上班未下班、且当前时间未到下班点时禁用下班卡
+  if (daily.actualIn && !daily.actualOut) {
+    const end = shiftEndDate.value;
+    if (end && now.value < end) {
+      return uni.showToast({ title: `未到下班时间(${extractTime(daily.scheduledEnd || shiftInfo.endTime)}),暂不能打卡`, icon: "none" });
+    }
+  }
   uni.showLoading({ title: "定位中..." });
   try {
     const loc = await getLocationWithFallback();