diff --git a/README.md b/README.md index ff3c256..7d816f2 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,104 @@ # FM-OHS -统计复旦微加班工时 +复旦微加班时间统计 —— 一个运行在金蝶 EAS HR 考勤页面上的 [Tampermonkey][tm] 用户脚本,自动计算并显示加班工时。 -## 主要功能: +[tm]: https://www.tampermonkey.net/ -- 查询加班工时 -- 下班时间按下午5:20后计算,月计算周期:上月21号~本月20号。 -- 根据内置的工作日数据库判断是否工作日 -- 查询结果显示在表格上,简单直观 +## 功能 -### 下载安装 +- **加班自动计算**:工作日按 17:20 后计算,非工作日(周末/节假日)按全天计算 +- **内置节假日数据库**:根据工作日数据自动识别调休日、法定节假日 +- **多维度查询**:支持当月、上月、当周等一键查询 +- **结果直显**:加班时数直接写入考勤表格,非工作日绿色高亮 -1. 安装浏览器脚本管理扩展(任选其一) - - [脚本猫](https://docs.scriptcat.org/) - - [tampermonkey](https://www.tampermonkey.net/index.php?locale=zh) +## 快速上手 -2. 脚本已经上传到[Greasy Fork](https://greasyfork.org/zh-CN) 点击安装即可 - - 脚本链接:[Greasy Fork - 复旦微加班时间统计](https://greasyfork.org/zh-CN/scripts/542984-%E5%A4%8D%E6%97%A6%E5%BE%AE%E5%8A%A0%E7%8F%AD%E6%97%B6%E9%97%B4%E7%BB%9F%E8%AE%A1) +### 1. 安装脚本管理器 -### 使用方法: +任选其一: -脚本会在菜单栏新增五个按钮,点击对应的按钮即可实现不同的查询功能。 \ No newline at end of file +- [Tampermonkey](https://www.tampermonkey.net/index.php?locale=zh)(推荐) +- [脚本猫](https://docs.scriptcat.org/) + +### 2. 安装脚本 + +前往 [Greasy Fork - 复旦微加班时间统计][gf] 点击安装。 + +[gf]: https://greasyfork.org/zh-CN/scripts/542984-%E5%A4%8D%E6%97%A6%E5%BE%AE%E5%8A%A0%E7%8F%AD%E6%97%B6%E9%97%B4%E7%BB%9F%E8%AE%A1 + +### 3. 使用 + +打开金蝶 EAS HR 考勤页面,菜单栏会自动出现 6 个按钮: + +| 按钮 | 功能 | +|------|------| +| 统计加班时间 | 直接计算当前表格中的加班工时 | +| 查当月 | 查询当月考勤周期(21 日至次月 20 日) | +| 查上月 | 查询上月考勤周期 | +| 查上上月 | 查询两个月前的考勤周期 | +| 查上上上月 | 查询三个月前的考勤周期 | +| 查当周 | 查询当周(周一至周日) | + +## 每年更新 + +节假日数据每年需要更新一次。API 每天限 5 次调用,因此设计为一年调用一次后持久化。 + +### 前置条件 + +- Python 3.x +- `requests` 库:`pip install requests` + +### 更新步骤 + +```bash +# 1. 抓取新年节假日数据(修改 update.py 中的 year 参数为目标年份) +python update.py + +# 2. 合并到数据库并自动构建 main.js +python comp_json.py + +# 3. 将生成的 main.js 内容更新到 Greasy Fork 或手动安装到 Tampermonkey +``` + +`comp_json.py` 会自动将新数据合并到 `data.json`,然后调用 `build.py` 生成包含完整数据的 `main.js`。 + +## 开发 + +### 项目结构 + +``` +FM-OHS/ +├── main.template.js # 脚本代码模板(~300 行,可读可维护) +├── main.js # 构建产物,由 build.py 生成(提交到 Greasy Fork) +├── data.json # 节假日数据(唯一数据源) +├── build.py # 构建脚本:将 data.json 注入模板 → main.js +├── update.py # 从 API 抓取年度节假日数据 +├── comp_json.py # 合并新数据到 data.json 并自动构建 +└── holidays_2026.json # 2026 年原始 API 响应数据 +``` + +### 修改代码 + +1. 编辑 `main.template.js`(不需要碰 `main.js`) +2. 运行 `python build.py` 生成 `main.js` +3. 在浏览器中加载 `main.js` 验证 + +### 数据格式 + +`data.json` 中的每条记录: + +```json +{ + "year": 2026, + "date": 20260101, + "week": 4, + "workday": 1, + "holiday": 88 +} +``` + +关键字段:`workday`(1 = 工作日,2 = 非工作日),`date`(YYYYMMDD 格式)。 + +## License + +MIT diff --git a/build.py b/build.py new file mode 100644 index 0000000..81681a2 --- /dev/null +++ b/build.py @@ -0,0 +1,21 @@ +import json + +# 读取 data.json +with open("data.json", "r", encoding="utf-8") as f: + data = json.load(f) + +# 读取模板 +with open("main.template.js", "r", encoding="utf-8") as f: + template = f.read() + +# 构造数据块 +data_block = "const workDayData = " + json.dumps(data, ensure_ascii=False, indent=2) + ";" + +# 替换占位符 +output = template.replace("/* WORKDAY_DATA_PLACEHOLDER */", data_block) + +# 写出 main.js +with open("main.js", "w", encoding="utf-8") as f: + f.write(output) + +print(f"main.js 构建完成 ({len(output)} 字符)") diff --git a/comp_json.py b/comp_json.py index 57cb2cc..f4be829 100644 --- a/comp_json.py +++ b/comp_json.py @@ -1,17 +1,33 @@ import json +import subprocess +import sys +from datetime import datetime -# 读取 A 和 B 文件 +# 年份:命令行参数 > 当前年份 +if len(sys.argv) > 1: + year = int(sys.argv[1]) +else: + year = datetime.now().year + +# 读取现有数据 with open("data.json", "r", encoding="utf-8") as f: a = json.load(f) -with open("holidays_2026.json", "r", encoding="utf-8") as f: +# 读取新年份数据 +filename = f"holidays_{year}.json" +with open(filename, "r", encoding="utf-8") as f: b = json.load(f) -# 假设 list 在 data.list 下(根据你之前的接口结构) -a["list"].extend(b["data"]["list"]) +# 去重:跳过 data.json 中已存在的日期 +existing_dates = {item["date"] for item in a["list"]} +new_items = [item for item in b["data"]["list"] if item["date"] not in existing_dates] +a["list"].extend(new_items) -# 保存合并结果 -with open("merged.json", "w", encoding="utf-8") as f: +print(f"合并完成: 新增 {len(new_items)} 条记录,跳过 {len(b['data']['list']) - len(new_items)} 条重复") + +# 保存 data.json +with open("data.json", "w", encoding="utf-8") as f: json.dump(a, f, ensure_ascii=False, indent=2) -print("✅ 合并完成!结果已保存到 merged.json") \ No newline at end of file +# 自动构建 main.js +subprocess.run(["python", "build.py"]) diff --git a/main.js b/main.js index a7d6717..51e131a 100644 --- a/main.js +++ b/main.js @@ -14,7 +14,7 @@ (function() { 'use strict'; // 硬编码工作日数据 - const workDayData = { +const workDayData = { "list": [ { "year": 2024, @@ -21937,7 +21937,7 @@ "holiday_recess": 1 } ] - } +}; const workDayList = workDayData.list; function isWorkDay(weekday, date) { // 将日期格式化为 YYYYMMDD @@ -21945,7 +21945,7 @@ // 查找工作日列表中是否存在该日期 const foundItem = workDayList.find(item => String(item.date) === date); if (!foundItem) { - if (weekday === "周六" || weekday == "周日") { + if (weekday === "周六" || weekday === "周日") { return false; // 周六和周日默认不是工作日 } else{ @@ -21977,7 +21977,8 @@ const validTimes = parsedTimes.filter(time => time !== null); if (validTimes.length === 0) { - return 'No valid times provided'; + console.warn(`calculateTimeDifference: 没有有效的时间数据, timeString="${timeString}"`); + return 0; } // 计算最大值和最小值 @@ -22004,19 +22005,23 @@ } function sumOfSecondColumn(tableId) { - var table = document.getElementById(tableId); - var totalSum = 0; - var rows = table.getElementsByTagName('tr'); + const table = document.getElementById(tableId); + if (!table) { + console.error(`sumOfSecondColumn: 未找到ID为 "${tableId}" 的表格`); + return 0; + } + let totalSum = 0; + const rows = table.getElementsByTagName('tr'); // 遍历每行 从1开始跳过表头 - for (var i = 1; i < rows.length; i++) { - var cells = rows[i].getElementsByTagName('td'); + for (let i = 1; i < rows.length; i++) { + const cells = rows[i].getElementsByTagName('td'); if (cells.length > 1) { // 确保该行至少有两列 - var cellValue = cells[3].textContent; - var weekday = cells[2].textContent; - var date = cells[1].textContent; - var workDay = isWorkDay(weekday, date); - var difference = calculateTimeDifference(cellValue, workDay); + 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; // 修改单元格的值 @@ -22053,10 +22058,18 @@ } 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(); - let day = now.getDate(); + const day = now.getDate(); // 如果今天是21号及以后,当前周期是本月21到下月20 // 否则,周期是上月21到本月20 @@ -22074,13 +22087,6 @@ } const firstDay = new Date(startYear, startMonth, 21); const lastDay = new Date(endYear, endMonth, 20); - const formatLocal = (date) => { - return date.toLocaleDateString('en-CA', { - year: 'numeric', - month: '2-digit', - day: '2-digit' - }).replace(/\//g, '-'); - }; return { firstDay: formatLocal(firstDay), lastDay: formatLocal(lastDay) @@ -22099,13 +22105,6 @@ } const firstDay = new Date(startYear, startMonth, 21); const lastDay = new Date(endYear, endMonth, 20); - const formatLocal = (date) => { - return date.toLocaleDateString('en-CA', { - year: 'numeric', - month: '2-digit', - day: '2-digit' - }).replace(/\//g, '-'); - }; return { firstDay: formatLocal(firstDay), lastDay: formatLocal(lastDay) @@ -22113,6 +22112,8 @@ } } + const QUERY_DELAY_MS = 500; + // 创建菜单按钮 function createMenuButton(text, options = {}) { const button = document.createElement('button'); @@ -22132,90 +22133,88 @@ 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 get_work_time() { - const message = sumOfSecondColumn('grid').toFixed(2); - const table = document.querySelector("#gview_grid > div.ui-state-default.ui-jqgrid-hdiv > div > table > thead > tr") - const rows = table.getElementsByTagName('th'); - // 修改表头 - rows[5].querySelector('#jqgh_grid_fillCardTimeStr').textContent = `加班时间(${message}小时)`; + + // 创建查询按钮(设置日期范围并查询) + 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); + } + }); } - var query_button = document.getElementById("query"); - // 创建菜单按钮 - const button = createMenuButton("统计加班时间", { + // 计算当周(周一至周日)的日期范围 + 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 = createMenuButton('查当月', { - onClick: () => { - const month = getCustomMonthRange(0); - const firstDay = month.firstDay; - const lastDay = month.lastDay; - console.log(`firstDay: ${firstDay}, lastDay ${lastDay}`) - simulateDateInput('beginDate', firstDay); - simulateDateInput('endDate', lastDay); - query_button.click(); - setTimeout(function() { - get_work_time(); - }, 500); - } - }); - const button1 = createMenuButton('查上月', { - onClick: () => { - const month = getCustomMonthRange(1); - const firstDay = month.firstDay; - const lastDay = month.lastDay; - console.log(`firstDay: ${firstDay}, lastDay ${lastDay}`) - simulateDateInput('beginDate', firstDay); - simulateDateInput('endDate', lastDay); - query_button.click(); - setTimeout(function() { - get_work_time(); - }, 500); - } - }); - const button2 = createMenuButton('查上上月', { - onClick: () => { - const month = getCustomMonthRange(2); - const firstDay = month.firstDay; - const lastDay = month.lastDay; - console.log(`firstDay: ${firstDay}, lastDay ${lastDay}`) - simulateDateInput('beginDate', firstDay); - simulateDateInput('endDate', lastDay); - query_button.click(); - setTimeout(function() { - get_work_time(); - }, 500); - } - }); - const button3 = createMenuButton('查上上上月', { - onClick: () => { - const month = getCustomMonthRange(3); - const firstDay = month.firstDay; - const lastDay = month.lastDay; - console.log(`firstDay: ${firstDay}, lastDay ${lastDay}`) - simulateDateInput('beginDate', firstDay); - simulateDateInput('endDate', lastDay); - query_button.click(); - setTimeout(function() { - get_work_time(); - }, 500); } }); + const button0 = createQueryButton('查当月', () => getCustomMonthRange(0)); + const button1 = createQueryButton('查上月', () => getCustomMonthRange(1)); + const button2 = createQueryButton('查上上月', () => getCustomMonthRange(2)); + const button3 = createQueryButton('查上上上月', () => getCustomMonthRange(3)); + const button4 = createQueryButton('查当周', getCurrentWeekRange); // 获取要将按钮添加到其中的容器 - var container = document.getElementById('seclevelmenu'); + let container = document.getElementById('seclevelmenu'); if (!container) { container = document.body; } @@ -22224,6 +22223,7 @@ container.appendChild(button1); container.appendChild(button2); container.appendChild(button3); + container.appendChild(button4); console.log("复旦微加班时间统计脚本已加载"); console.log("点击按钮后会统计加班时间"); })() \ No newline at end of file diff --git a/main.template.js b/main.template.js new file mode 100644 index 0000000..50c1b98 --- /dev/null +++ b/main.template.js @@ -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("点击按钮后会统计加班时间"); +})() \ No newline at end of file diff --git a/update.py b/update.py index 5efea83..fe59bc7 100644 --- a/update.py +++ b/update.py @@ -1,32 +1,38 @@ import requests import json +import sys +from datetime import datetime + +# 年份:命令行参数 > 当前年份 +if len(sys.argv) > 1: + year = int(sys.argv[1]) +else: + year = datetime.now().year -# 接口地址(建议加上你的 api_key,否则可能无法访问) url = "https://api.apihubs.cn/holiday/get" params = { - "year": 2026, + "year": year, "page": 1, "size": 370 - # "api_key": "your_api_key_here" # ⚠️ 如果接口坞要求认证,请取消注释并填入你的 key + # "api_key": "your_api_key_here" # 如果接口要求认证,请取消注释并填入你的 key } try: response = requests.get(url, params=params) - response.raise_for_status() # 检查 HTTP 错误 + response.raise_for_status() - # 解析 JSON 数据 data = response.json() - # 保存到文件 - with open("holidays_2026.json", "w", encoding="utf-8") as f: + filename = f"holidays_{year}.json" + with open(filename, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) - print("✅ 数据已成功保存到 holidays_2026.json") + print(f"数据已成功保存到 {filename}") except requests.exceptions.RequestException as e: - print(f"❌ 网络请求失败: {e}") + print(f"网络请求失败: {e}") except json.JSONDecodeError: - print("❌ 返回内容不是有效的 JSON 格式") + print("返回内容不是有效的 JSON 格式") print("原始响应:", response.text) except Exception as e: - print(f"❌ 发生未知错误: {e}") \ No newline at end of file + print(f"发生未知错误: {e}")