|
@@ -132,6 +132,35 @@ useAuthGuard();
|
|
|
const currentUser = useCurrentUser();
|
|
const currentUser = useCurrentUser();
|
|
|
const weeks = ["一", "二", "三", "四", "五", "六", "日"];
|
|
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 currentMonth = ref(formatMonth(new Date()));
|
|
|
const monthList = ref([]); // ESS 月度返回数组
|
|
const monthList = ref([]); // ESS 月度返回数组
|
|
|
const selectedDate = ref(formatDate(new Date()));
|
|
const selectedDate = ref(formatDate(new Date()));
|
|
@@ -167,14 +196,18 @@ const summary = computed(() => {
|
|
|
let abnormalCount = 0;
|
|
let abnormalCount = 0;
|
|
|
const schedule = weeklySchedule.value;
|
|
const schedule = weeklySchedule.value;
|
|
|
for (const item of list) {
|
|
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%"
|
|
const rate = workDays > 0 ? `${((attendanceDays / workDays) * 100).toFixed(1)}%` : "0%"
|
|
|
return { attendanceDays, lateCount, abnormalCount, attendanceRate: rate }
|
|
return { attendanceDays, lateCount, abnormalCount, attendanceRate: rate }
|
|
@@ -222,36 +255,39 @@ function computeDot(data, isSelected, scheduleInfo) {
|
|
|
// 休息日且无打卡数据:不显示绿点(避免周末出现误导性的"已打卡"圆点)
|
|
// 休息日且无打卡数据:不显示绿点(避免周末出现误导性的"已打卡"圆点)
|
|
|
if (scheduleInfo?.isRestDay && !data) return isSelected;
|
|
if (scheduleInfo?.isRestDay && !data) return isSelected;
|
|
|
if (!data) return isSelected;
|
|
if (!data) return isSelected;
|
|
|
- const status = data.attendanceStatusList;
|
|
|
|
|
// 选中态:始终显示圆点(蓝底配白点)
|
|
// 选中态:始终显示圆点(蓝底配白点)
|
|
|
if (isSelected) return true;
|
|
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;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function computeDotCls(data, isSelected) {
|
|
function computeDotCls(data, isSelected) {
|
|
|
- // 选中态用白点(跟蓝底搭),其他日期按状态色显示(绿/红)
|
|
|
|
|
|
|
+ // 选中态用白点(跟蓝底搭),其他日期按状态色显示(绿/红/黄)
|
|
|
if (!data) return isSelected ? "today" : "";
|
|
if (!data) return isSelected ? "today" : "";
|
|
|
if (isSelected) return "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";
|
|
return "normal";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function cellClass(cell) {
|
|
function cellClass(cell) {
|
|
|
if (cell.blank) return "blank";
|
|
if (cell.blank) return "blank";
|
|
|
- const status = cell.data?.attendanceStatusList;
|
|
|
|
|
|
|
+ const statuses = getDayStatuses(cell.data);
|
|
|
// 休息日兜底:API 标 REST 或 周班次表里是休息日 都按休息日样式
|
|
// 休息日兜底:API 标 REST 或 周班次表里是休息日 都按休息日样式
|
|
|
// (后端漏标或接口延迟时,日历格依然能正确显示为灰色)
|
|
// (后端漏标或接口延迟时,日历格依然能正确显示为灰色)
|
|
|
- const isRest = status === "REST" || cell.scheduleInfo?.isRestDay;
|
|
|
|
|
|
|
+ const isRest = statuses.includes("REST") || cell.scheduleInfo?.isRestDay;
|
|
|
const cls = [];
|
|
const cls = [];
|
|
|
// 选中态优先:被选中的日期复用原本 today 的蓝色背景(白字 + 白点)
|
|
// 选中态优先:被选中的日期复用原本 today 的蓝色背景(白字 + 白点)
|
|
|
if (cell.isSelected) cls.push("today");
|
|
if (cell.isSelected) cls.push("today");
|
|
|
// 今天的标记:仅在今天不是当前选中时显示,避免选中其他日期后"今天"完全消失
|
|
// 今天的标记:仅在今天不是当前选中时显示,避免选中其他日期后"今天"完全消失
|
|
|
if (cell.isToday && !cell.isSelected) cls.push("is-today-mark");
|
|
if (cell.isToday && !cell.isSelected) cls.push("is-today-mark");
|
|
|
if (isRest) cls.push("rest");
|
|
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(" ");
|
|
return cls.join(" ");
|
|
|
}
|
|
}
|
|
|
|
|
|