您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
收集H3C内部考勤日历页面的打卡数据并计算剩余工时
// ==UserScript== // @name H3C剩余工时计算 // @namespace H3C剩余工时计算 // @version 2025-01-02 // @description 收集H3C内部考勤日历页面的打卡数据并计算剩余工时 // @author H3Cer // @match https://eip.h3c.com/myCenter/kaoqin // @grant none // ==/UserScript== // 说明: // 1、 使用前请将上下班时间、午休时间、是否是弹性工作制等等内容修改为自己的班次内容(下方两行/****/中间的内容) // 2、 为了适应2025年开始实施的新考勤制度,特意将非工作日的打卡也统计在内(但是当天有加班记录的不统计) // 3、 工作日只统计早晚剩余工时(工作日包括正常工作日和调休后上班的周末),非工作日全天统计(但是不包括午休时间) // 4、 脚本考虑到的各种异常情况可能不太全面,如果发生统计错误或者无法统计的情况,请谅解(尤其是存在考勤异常时,暂时忘了考勤异常时是如何显示的了) (function() { 'use strict'; let isInsertDiv = false; // 累计总分钟数 let totalMinutesWorkOvertime = 0; // 辅助函数:将时间字符串转换为分钟数 const timeToMinutes = (timeStr) => { const [hours, minutes] = timeStr.split(':').map(Number); return isNaN(hours) || isNaN(minutes) ? 0 : hours * 60 + minutes; // 防止出现 NaN }; /************************************************************************************************/ const delayInterval = 5000; // 延时运行时间,打开考勤日历页面后延时一段时间再进行统计(毫秒),避免页面加载未完成导致显示块插入位置不正确 const scanInterval = 1000; // 扫描更新间隔,开始统计之后,以此时间为间隔,持续进行扫描、统计(毫秒) const morningLimit = timeToMinutes('09:30'); //上班时间(有弹性工作制的,此处请填写弹性上班时间) const eveningLimit = timeToMinutes('18:00'); //下班时间 const flexibleTime = 60; //弹性工作制时间(分钟),非弹性工作制的写0 const siestaLimit1 = timeToMinutes('12:00'); //午休开始时间 const siestaLimit2 = timeToMinutes('13:30'); //午休结束时间 /************************************************************************************************/ // 在页面顶部创建一个显示总分钟数和总小时数的 div// 在页面顶部插入显示总分钟数和总小时数的 div,插入在 .ant-fullcalendar-fullscreen 上方 const createDisplayElement = () => { // 查找 .ant-fullcalendar-fullscreen 元素 const fullscreenElement = document.querySelector('.ant-fullcalendar-fullscreen'); // 如果找到了该元素,则创建并插入新的显示 div if (fullscreenElement) { const displayDiv = document.createElement('div'); displayDiv.id = 'total-time-display'; displayDiv.style.backgroundColor = '#f0f0f0'; displayDiv.style.padding = '16px'; displayDiv.style.textAlign = 'left'; displayDiv.style.zIndex = '1000'; displayDiv.style.fontSize = '16px'; displayDiv.style.fontWeight = 'bold'; displayDiv.style.color = '#333'; // 初始化文字为 "剩余工时: " displayDiv.textContent = "剩余工时: "; // 将新元素插入到 .ant-fullcalendar-fullscreen 上方 fullscreenElement.parentNode.insertBefore(displayDiv, fullscreenElement); } }; // 更新显示的总分钟数和总小时数 const updateDisplay = (totalMinutesWorkOvertime, totalHours) => { const displayDiv = document.getElementById('total-time-display'); console.log(`displayDiv: ${displayDiv}`); if (displayDiv) { displayDiv.textContent = `剩余工时: ${totalHours} 小时( ${totalMinutesWorkOvertime} 分钟 )`; } else { console.log("无法找到指定displayDiv"); } }; const scanElements = () => { try { console.clear(); // 清空控制台 totalMinutesWorkOvertime = 0; // 查找 .ant-calendar-picker 元素(用于获取当前月份,排除非本月的日期) const element1 = document.querySelector('.ant-calendar-picker'); if (element1) { const textContent = element1.textContent.trim(); // console.log('找到的文字内容:', textContent); // 查找所有 .ant-fullcalendar-cell 元素(此元素就是每一天的信息) const elements2 = document.querySelectorAll('.ant-fullcalendar-cell'); console.log(`共找到 ${elements2.length} 个元素:`); elements2.forEach((element2, index) => { if (element2.title.includes(textContent)) { let currentDate = 0; // 查找 element2 内部的 class="ant-fullcalendar-value" 元素(确定当天是哪一天(1~31的天数序号)) const currentDateElements = element2.querySelector('.ant-fullcalendar-value'); if (currentDateElements) { const tmpDate = currentDateElements.textContent.trim() if (!isNaN(currentDate)) { currentDate = tmpDate; } } // 检查 element2 的子元素是否包含 "加班" const childElements = element2.querySelectorAll('*'); const containsOvertime = Array.from(childElements).some(child => child.textContent.trim().includes('加班') ); if (containsOvertime) { console.log(`本月 ${currentDate} 日内容包含 "加班",不统计`); return; // 跳过当前元素的处理 } // 查找 element2 内部的 class="absolute" 的子元素(打卡时间,格式是:上班打开时间(时:分) - 下班打卡时间(时:分)) const absoluteElement = element2.querySelector('.absolute'); if (absoluteElement) { const timeRange = absoluteElement.textContent.trim(); // 如果 .absolute 元素为空,则跳过 if (!timeRange) { //console.log(`本月 ${currentDate} 日内容为空,不统计`); return; } //console.log(`第 ${index + 1} 个元素中的 .absolute 值: ${timeRange}`); let minutesWorkOvertimeBefore = 0; let minutesWorkOvertimeAfter = 0; // 检查是否是节假日 const isHoliday = element2.querySelector('.ant-fullcalendar-holiday'); // const isHoliday = false //debug使用 if (isHoliday) { //如果是节假日,则全天统计,并且不考虑弹性工作制(但是要减去午休时间); const [startTimeTmp, endTimeTmp] = timeRange.split(' - ').map(timeToMinutes); const startTime = isNaN(startTimeTmp) ? siestaLimit1 : startTimeTmp; // 防止出现 NaN const endTime = isNaN(endTimeTmp) ? siestaLimit2 : endTimeTmp; // 防止出现 NaN if (siestaLimit1 > endTime) { // 如果整个工作时间段都在上午 // 计算上午的剩余工时 minutesWorkOvertimeBefore = endTime - startTime; // 计算下午的剩余工时 minutesWorkOvertimeAfter = 0; } else if (startTime > siestaLimit2) { // 如果整个工作时间段都在下午 // 计算上午的剩余工时 minutesWorkOvertimeBefore = 0; // 计算下午的剩余工时 minutesWorkOvertimeAfter = endTime - startTime; } else { // 计算上午的剩余工时 minutesWorkOvertimeBefore = Math.max(0, siestaLimit1 - startTime); // 计算下午的剩余工时 minutesWorkOvertimeAfter = Math.max(0, endTime - siestaLimit2); } } else { const [startTimeTmp, endTimeTmp] = timeRange.split(' - ').map(timeToMinutes); const startTime = isNaN(startTimeTmp) ? morningLimit : startTimeTmp; // 防止出现 NaN const endTime = isNaN(endTimeTmp) ? eveningLimit : endTimeTmp; // 防止出现 NaN // 计算上午的剩余工时 minutesWorkOvertimeBefore = Math.max(0, morningLimit - startTime) - flexibleTime; // 计算下午的剩余工时 minutesWorkOvertimeAfter = Math.max(0, endTime - eveningLimit); } //console.log(`本月 ${currentDate} 日早于 09:30 的分钟数: ${minutesWorkOvertimeBefore}`); //console.log(`本月 ${currentDate} 日晚于 18:00 的分钟数: ${minutesWorkOvertimeAfter}`); const minutesCurrentDate = minutesWorkOvertimeBefore + minutesWorkOvertimeAfter; console.log(`本月 ${currentDate} 日剩余工时: ${minutesCurrentDate}`); // 求和 totalMinutesWorkOvertime += minutesWorkOvertimeBefore; totalMinutesWorkOvertime += minutesWorkOvertimeAfter; } } }); // 输出累计结果 console.log(`总分钟数: ${totalMinutesWorkOvertime}`); // 计算小时数 const totalHours = ((totalMinutesWorkOvertime) / 60).toFixed(2); console.log(`总小时数: ${totalHours}`); // 确认显示元素是否已经增加 console.log(`isInsertDiv: ${isInsertDiv}`); if (!isInsertDiv) { // 初始化创建显示元素 createDisplayElement(); isInsertDiv = true; } // 更新显示的总分钟数和总小时数 updateDisplay(totalMinutesWorkOvertime, totalHours); } else { // console.log('未找到 .ant-calendar-picker 元素'); } } catch (error) { // 什么都不做,直接忽略异常 } }; //延时运行 setTimeout(() => { scanElements(); //最开始运行一次 setInterval(scanElements, scanInterval); // 设置定时器,循环运行 }, delayInterval); })();