新增查当周按钮,代码重构与数据分离

- 新增"查当周"按钮,计算当周周一至周日的加班总工时
- 新增 createQueryButton 工厂函数,消除月按钮重复代码
- 修复 calculateTimeDifference 返回字符串导致 NaN 的 bug
- 修复 sumOfSecondColumn/get_work_time 缺少空值保护的 bug
- 修复 isWorkDay 中 == 改为 ===
- var 统一为 const/let,提取 QUERY_DELAY_MS 常量
- 提取 getCustomMonthRange 中重复的 formatLocal
- 按钮增加 cursor:pointer 样式
- 数据与代码分离:main.template.js(~300行)+ build.py 自动组装 main.js
- update.py/comp_json.py 支持命令行年份参数,默认当年
- comp_json.py 增加去重逻辑
- 更新 README 为 GitHub 开源项目风格,含快速上手和每年更新章节

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 19:11:13 +08:00
parent 6943e17c12
commit b74978d2dc
6 changed files with 559 additions and 129 deletions

306
main.template.js Normal file
View File

@@ -0,0 +1,306 @@
// ==UserScript==
// @name 复旦微加班时间统计
// @namespace http://start.fengbohan.com/
// @version 2026-01-28
// @description 更新2026年的节假日数据
// @author bhfeng
// @match http://192.168.36.67:7888/shr/dynamic.do?uipk=com.kingdee.eas.hr.ats.app.WorkCalendarItem*
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAdVBMVEX///////7+/v4kVZ729/lafbO2xd3x8/f5+vz8/Pzd4+4tW6GSp8xnh7nP2OgxXqLK1OZ1kb/s7/UgUZytvdhKcKw+aKgaTZlhgrbU3eqar9Dm6fFVd7CEnsZwjb4VSZfAy+BCbKq3xd58l8OXrM6nudaMo8kyjkGlAAABfklEQVQokT1Si5arIBCLDIqCiogP8FHtWvv/n3iH7vZyDjDMQAIJwLdlpVIlz0L8T0FAxDaTy5JBrWn52ZhxoO6tQ+ZyjT7fJSqRpYJAJte8qbDPmxKPY4vgXOqdhramwzp6B1GTKRJahjjVVdlQI3FauiTahkOmFfHyJ9qJduC2lHf4o69weiZYyLyAYvLWFe3O1apHa80AsZOpGfdhZrLHDfSDwpumFuVO5HqI8xot3xvrM5Qy+DGiqo3fTgbvteRRT36AHmlk2J/R26vlbHmuiMVrWtOlpoGPX2a2wV3BMjQiNSXakezVA50zfvbHyGTMQlcFnRNtRQXEl3u4lklWVRi6FNRuyTRD/5E7sXehvIlCBM6c3zBed3E/dNKqCdlg/bRIlENj/XHMxztphdUGxNxTKNhAPbz3/edjlEC0uRTLc6b81syu5K8fPOhxKtAvm5/NMw/vNRNfy1U9hUKpbnFNsyh8v0Pa0A/O1SyE+LrxV6mSdjH2gsPf/D//7BjTQny+YwAAAABJRU5ErkJggg==
// @grant none
// @run-at document-idle
// @license MI
// ==/UserScript==
(function() {
'use strict';
// 硬编码工作日数据
/* WORKDAY_DATA_PLACEHOLDER */
const workDayList = workDayData.list;
function isWorkDay(weekday, date) {
// 将日期格式化为 YYYYMMDD
date = date.replace(/-/g, '');
// 查找工作日列表中是否存在该日期
const foundItem = workDayList.find(item => String(item.date) === date);
if (!foundItem) {
if (weekday === "周六" || weekday === "周日") {
return false; // 周六和周日默认不是工作日
}
else{
return true; // 默认工作日是周一到周五
}
} else if (foundItem.workday === 1) {
return true; // 情况1工作日
} else {
return false; // 情况2非工作日
}
}
function calculateTimeDifference(timeString, workDay) {
// 判断字符串是否为 "--"
if (timeString === '--') {
return 0;
}
// 按逗号分割字符串
const times = timeString.split(',');
// 解析时间并转换为毫秒
const parsedTimes = times.map(timeStr => {
const [hours, minutes, seconds] = timeStr.trim().split(':').map(Number);
return isNaN(hours) || isNaN(minutes) || isNaN(seconds) ? null : (hours * 3600 + minutes * 60 + seconds) * 1000;
});
// 过滤掉无效的时间
const validTimes = parsedTimes.filter(time => time !== null);
if (validTimes.length === 0) {
console.warn(`calculateTimeDifference: 没有有效的时间数据, timeString="${timeString}"`);
return 0;
}
// 计算最大值和最小值
const maxTime = Math.max(...validTimes);
const minTime = Math.min(...validTimes);
// 工作日从下午5点20分17:20:00开始计算加班
const fiveTwentyPM = (17 * 3600 + 20 * 60) * 1000;
let timeDifferenceInHours;
// 计算加班时间
if (workDay) {
// 工作日
if (maxTime >= fiveTwentyPM) {
timeDifferenceInHours = (maxTime - fiveTwentyPM) / (1000 * 60 * 60);
} else {
timeDifferenceInHours = 0;
}
}
else {
// 非工作日
timeDifferenceInHours = (maxTime - minTime) / (1000 * 60 * 60);
}
return timeDifferenceInHours;
}
function sumOfSecondColumn(tableId) {
const table = document.getElementById(tableId);
if (!table) {
console.error(`sumOfSecondColumn: 未找到ID为 "${tableId}" 的表格`);
return 0;
}
let totalSum = 0;
const rows = table.getElementsByTagName('tr');
// 遍历每行 从1开始跳过表头
for (let i = 1; i < rows.length; i++) {
const cells = rows[i].getElementsByTagName('td');
if (cells.length > 1) { // 确保该行至少有两列
const cellValue = cells[3].textContent;
const weekday = cells[2].textContent;
const date = cells[1].textContent;
const workDay = isWorkDay(weekday, date);
const difference = calculateTimeDifference(cellValue, workDay);
totalSum += difference;
// 修改单元格的值
cells[5].title = "";
cells[5].textContent = `${difference.toFixed(2)}H`;
if (!workDay){
cells[5].style.backgroundColor = "#90EE90";
}
}
}
return totalSum;
}
// 方便快速查询前一月、前二月、前三月
function simulateDateInput(elementId, dateValue) {
const inputElement = document.getElementById(elementId);
if (!inputElement) {
console.error(`未找到ID为 ${elementId} 的输入框`);
return;
}
inputElement.focus();
inputElement.value = dateValue;
inputElement.dispatchEvent(new Event('input', {
bubbles: true,
cancelable: true
}));
inputElement.dispatchEvent(new Event('change', {
bubbles: true,
cancelable: true
}));
inputElement.blur();
}
function getCustomMonthRange(monthsAgo) {
const formatLocal = (date) => {
return date.toLocaleDateString('en-CA', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).replace(/\//g, '-');
};
const now = new Date();
let year = now.getFullYear();
let month = now.getMonth();
const day = now.getDate();
// 如果今天是21号及以后当前周期是本月21到下月20
// 否则周期是上月21到本月20
if (day >= 21) {
month = month - monthsAgo;
let startYear = year, startMonth = month;
while (startMonth < 0) {
startMonth += 12;
startYear--;
}
let endYear = startYear, endMonth = startMonth + 1;
if (endMonth > 11) {
endMonth -= 12;
endYear++;
}
const firstDay = new Date(startYear, startMonth, 21);
const lastDay = new Date(endYear, endMonth, 20);
return {
firstDay: formatLocal(firstDay),
lastDay: formatLocal(lastDay)
};
} else {
month = month - monthsAgo - 1;
let startYear = year, startMonth = month;
while (startMonth < 0) {
startMonth += 12;
startYear--;
}
let endYear = startYear, endMonth = startMonth + 1;
if (endMonth > 11) {
endMonth -= 12;
endYear++;
}
const firstDay = new Date(startYear, startMonth, 21);
const lastDay = new Date(endYear, endMonth, 20);
return {
firstDay: formatLocal(firstDay),
lastDay: formatLocal(lastDay)
};
}
}
const QUERY_DELAY_MS = 500;
// 创建菜单按钮
function createMenuButton(text, options = {}) {
const button = document.createElement('button');
button.textContent = text;
const copiedStyles = {
webkitTapHighlightColor: "rgba(0, 0, 0, 0)",
fontFamily: '"Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", "Hiragino Sans GB", "Heiti SC", "WenQuanYi Micro Hei", sans-serif',
fontSize: "14px",
listStyle: "none",
textAlign: "center",
boxSizing: "border-box",
webkitTextSizeAdjust: "100%",
backgroundColor: "transparent",
display: "block",
lineHeight: "60px",
height: "60px",
padding: "0px 10px",
textDecoration: "none",
color: "#e5e5e5",
cursor: "pointer",
};
Object.assign(button.style, copiedStyles);
// 处理点击事件回调
if (typeof options.onClick === 'function') {
button.addEventListener('click', options.onClick);
}
return button;
}
// 创建查询按钮(设置日期范围并查询)
function createQueryButton(text, rangeGetter) {
return createMenuButton(text, {
onClick: () => {
const { firstDay, lastDay } = rangeGetter();
console.log(`firstDay: ${firstDay}, lastDay: ${lastDay}`);
simulateDateInput('beginDate', firstDay);
simulateDateInput('endDate', lastDay);
queryButton.click();
setTimeout(() => {
get_work_time();
}, QUERY_DELAY_MS);
}
});
}
// 计算当周(周一至周日)的日期范围
function getCurrentWeekRange() {
const now = new Date();
const dayOfWeek = now.getDay(); // 0=周日, 1=周一, ..., 6=周六
const mondayOffset = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
const monday = new Date(now);
monday.setDate(now.getDate() + mondayOffset);
const sunday = new Date(monday);
sunday.setDate(monday.getDate() + 6);
const formatLocal = (date) => {
return date.toLocaleDateString('en-CA', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).replace(/\//g, '-');
};
return {
firstDay: formatLocal(monday),
lastDay: formatLocal(sunday)
};
}
// 查询工作时间
function get_work_time() {
const totalSum = sumOfSecondColumn('grid');
const headerTable = document.querySelector("#gview_grid > div.ui-state-default.ui-jqgrid-hdiv > div > table > thead > tr");
if (!headerTable) {
console.error('get_work_time: 未找到表头元素');
return;
}
const rows = headerTable.getElementsByTagName('th');
const titleCell = rows[5].querySelector('#jqgh_grid_fillCardTimeStr');
if (!titleCell) {
console.error('get_work_time: 未找到表头加班列元素');
return;
}
titleCell.textContent = `加班时间(${totalSum.toFixed(2)}小时)`;
}
const queryButton = document.getElementById("query");
const button = createMenuButton("统计加班时间", {
onClick: () => {
get_work_time();
}
});
const button0 = createQueryButton('查当月', () => getCustomMonthRange(0));
const button1 = createQueryButton('查上月', () => getCustomMonthRange(1));
const button2 = createQueryButton('查上上月', () => getCustomMonthRange(2));
const button3 = createQueryButton('查上上上月', () => getCustomMonthRange(3));
const button4 = createQueryButton('查当周', getCurrentWeekRange);
// 获取要将按钮添加到其中的容器
let container = document.getElementById('seclevelmenu');
if (!container) {
container = document.body;
}
container.appendChild(button);
container.appendChild(button0);
container.appendChild(button1);
container.appendChild(button2);
container.appendChild(button3);
container.appendChild(button4);
console.log("复旦微加班时间统计脚本已加载");
console.log("点击按钮后会统计加班时间");
})()