瀏覽代碼

feat(attendance): 增强异常日状态处理逻辑,优化考勤数据统计与显示

xieyong 1 月之前
父節點
當前提交
dfaa50807c
共有 1 個文件被更改,包括 53 次插入 和 17 次删除
  1. 53 17
      src/pages/attendance/index.vue

+ 53 - 17
src/pages/attendance/index.vue

@@ -132,6 +132,35 @@ useAuthGuard();
 const currentUser = useCurrentUser();
 const weeks = ["一", "二", "三", "四", "五", "六", "日"];
 
+// 异常状态枚举:服务端 daily / month 接口均会标这些,对应迟到/早退/缺勤。
+// 仅靠 exceptionCount 判断会漏掉「status 标了异常但 exceptionCount 没回写」的场景,
+// 所以统一从状态字段里再识别一次,保证红点/红格稳定出现。
+const ABNORMAL_STATUSES = ["LATE", "EARLY", "ABSENT"];
+
+// 把后端的 status 字段统一成数组形态:兼容 string、array,也兼容 attendanceStatusList / statusList 两个字段名。
+function getDayStatuses(data) {
+  if (!data) return [];
+  const out = [];
+  for (const v of [data.attendanceStatusList, data.statusList]) {
+    if (!v) continue;
+    if (typeof v === "string") out.push(v);
+    else if (Array.isArray(v)) out.push(...v);
+  }
+  return out;
+}
+
+// 是否异常日:
+//   1) exceptionCount > 0(服务端显式计数)
+//   2) lateMinutes / earlyMinutes > 0(兜底:服务端可能不写 exceptionCount,但分钟数已填)
+//   3) 状态字段包含 LATE / EARLY / ABSENT
+// 任一条命中即视为异常,保证日历红点/红格与 summary 的「异常」计数同源。
+function isAbnormalDay(data) {
+  if (!data) return false;
+  if ((data.exceptionCount || 0) > 0) return true;
+  if ((data.lateMinutes || 0) > 0 || (data.earlyMinutes || 0) > 0) return true;
+  return getDayStatuses(data).some((s) => ABNORMAL_STATUSES.includes(s));
+}
+
 const currentMonth = ref(formatMonth(new Date()));
 const monthList = ref([]); // ESS 月度返回数组
 const selectedDate = ref(formatDate(new Date()));
@@ -167,14 +196,18 @@ const summary = computed(() => {
   let abnormalCount = 0;
   const schedule = weeklySchedule.value;
   for (const item of list) {
-    const status = item.attendanceStatusList
-    // 休息日判定:API 标 REST 或 周班次表里是休息日
-    // 后者兜底,避免后端漏标(比如月底补算没及时回写)让工作日误算成"出勤"
-    if (status === "REST" || isRestDay(item.attendanceDate, schedule)) continue;
-    workDays++
-    if (status === "NORMAL") attendanceDays++
-    if ((item.lateMinutes || 0) > 0) lateCount++
-    if ((item.exceptionCount || 0) > 0) abnormalCount++
+    const statuses = getDayStatuses(item);
+    // 休息日跳过:与日历「休息日无点」保持一致;
+    // 优先看状态 REST,缺失时再按周班次表兜底(月底补算没及时回写等场景)。
+    const isRest = statuses.includes("REST") || isRestDay(item.attendanceDate, schedule);
+    if (isRest) continue;
+    workDays++;
+    // 出勤天数 = NORMAL:与日历「绿点」同源
+    if (statuses.includes("NORMAL")) attendanceDays++;
+    // 异常数:与日历「红点」同源,覆盖 LATE/EARLY/ABSENT 三类
+    if (isAbnormalDay(item)) abnormalCount++;
+    // 迟到:异常里的一个子集,按 lateMinutes 或 LATE 状态识别(数组形态兼容)
+    if ((item.lateMinutes || 0) > 0 || statuses.includes("LATE")) lateCount++;
   }
   const rate = workDays > 0 ? `${((attendanceDays / workDays) * 100).toFixed(1)}%` : "0%"
   return { attendanceDays, lateCount, abnormalCount, attendanceRate: rate }
@@ -222,36 +255,39 @@ function computeDot(data, isSelected, scheduleInfo) {
   // 休息日且无打卡数据:不显示绿点(避免周末出现误导性的"已打卡"圆点)
   if (scheduleInfo?.isRestDay && !data) return isSelected;
   if (!data) return isSelected;
-  const status = data.attendanceStatusList;
   // 选中态:始终显示圆点(蓝底配白点)
   if (isSelected) return true;
-  if ((data.exceptionCount || 0) > 0) return true;
-  if (status === "NORMAL") return true;
+  if (isAbnormalDay(data)) return true;
+  const statuses = getDayStatuses(data);
+  // 正常 / 请假 显示圆点;其他未识别状态保持静默(避免给漏标的数据乱上色)
+  if (statuses.includes("NORMAL") || statuses.includes("LEAVE")) return true;
   return false;
 }
 
 function computeDotCls(data, isSelected) {
-  // 选中态用白点(跟蓝底搭),其他日期按状态色显示(绿/红)
+  // 选中态用白点(跟蓝底搭),其他日期按状态色显示(绿/红/黄)
   if (!data) return isSelected ? "today" : "";
   if (isSelected) return "today";
-  if ((data.exceptionCount || 0) > 0) return "abnormal";
+  if (isAbnormalDay(data)) return "abnormal";
+  const statuses = getDayStatuses(data);
+  if (statuses.includes("LEAVE")) return "leave";
   return "normal";
 }
 
 function cellClass(cell) {
   if (cell.blank) return "blank";
-  const status = cell.data?.attendanceStatusList;
+  const statuses = getDayStatuses(cell.data);
   // 休息日兜底:API 标 REST 或 周班次表里是休息日 都按休息日样式
   // (后端漏标或接口延迟时,日历格依然能正确显示为灰色)
-  const isRest = status === "REST" || cell.scheduleInfo?.isRestDay;
+  const isRest = statuses.includes("REST") || cell.scheduleInfo?.isRestDay;
   const cls = [];
   // 选中态优先:被选中的日期复用原本 today 的蓝色背景(白字 + 白点)
   if (cell.isSelected) cls.push("today");
   // 今天的标记:仅在今天不是当前选中时显示,避免选中其他日期后"今天"完全消失
   if (cell.isToday && !cell.isSelected) cls.push("is-today-mark");
   if (isRest) cls.push("rest");
-  else if (status === "LEAVE") cls.push("leave");
-  else if ((cell.data?.exceptionCount || 0) > 0) cls.push("abnormal");
+  else if (statuses.includes("LEAVE")) cls.push("leave");
+  else if (isAbnormalDay(cell.data)) cls.push("abnormal");
   return cls.join(" ");
 }