集成 MyTools:启动拉取配置、动态加班起算时间、保存加班记录

- 启动时从 MyTools 拉取加班起算时间配置(1s超时fallback默认17:20)
- calculateTimeDifference 改用动态 overtimeStart 替代硬编码 17:20
- 计算完成后显示"保存到 MyTools"按钮,POST 记录到 API
- 支持 upsert(同日期覆盖),Toast 提示成功/失败
- MyTools 不可用时所有功能独立正常运行

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 20:04:00 +08:00
parent 99d3ecad6d
commit 2d455ddb20
3 changed files with 268 additions and 12 deletions

View File

@@ -61,15 +61,15 @@
// 计算最大值和最小值
const maxTime = Math.max(...validTimes);
const minTime = Math.min(...validTimes);
// 工作日从下午5点20分17:20:00开始计算加班
const fiveTwentyPM = (17 * 3600 + 20 * 60) * 1000;
// 工作日加班起算时间(从配置获取)
const overtimeStartMs = parseOvertimeStart(overtimeConfig.overtime_start);
let timeDifferenceInHours;
// 计算加班时间
if (workDay) {
// 工作日
if (maxTime >= fiveTwentyPM) {
timeDifferenceInHours = (maxTime - fiveTwentyPM) / (1000 * 60 * 60);
if (maxTime >= overtimeStartMs) {
timeDifferenceInHours = (maxTime - overtimeStartMs) / (1000 * 60 * 60);
} else {
timeDifferenceInHours = 0;
}
@@ -190,6 +190,127 @@
}
const QUERY_DELAY_MS = 500;
const MYTOOLS_BASE = 'https://mytools.fengbohan.com';
// 加班配置(默认值,启动时尝试从 MyTools 拉取)
let overtimeConfig = { overtime_start: '17:20', weekly_target: 18 };
// 上次计算的结果缓存(用于保存到 MyTools
let lastOvertimeRecords = [];
async function initConfig() {
try {
const resp = await fetch(`${MYTOOLS_BASE}/api/overtime-config`, {
signal: AbortSignal.timeout(2000)
});
if (resp.ok) {
const data = await resp.json();
overtimeConfig = data;
console.log('FM-OHS: 已从 MyTools 拉取配置', overtimeConfig);
}
} catch (e) {
console.log('FM-OHS: MyTools 不可用,使用默认配置');
}
}
// 解析加班起算时间为秒数
function parseOvertimeStart(timeStr) {
const [h, m] = timeStr.split(':').map(Number);
return (h * 3600 + m * 60) * 1000;
}
// 收集表格数据(不修改 DOM返回记录数组
function collectOvertimeRecords() {
const table = document.getElementById('grid');
if (!table) return [];
const rows = table.getElementsByTagName('tr');
const records = [];
for (let i = 1; i < rows.length; i++) {
const cells = rows[i].getElementsByTagName('td');
if (cells.length > 1) {
const timeStr = cells[3].textContent;
const weekday = cells[2].textContent;
const date = cells[1].textContent;
const workDay = isWorkDay(weekday, date);
const hours = calculateTimeDifference(timeStr, workDay);
records.push({ date, weekday, hours: parseFloat(hours.toFixed(2)), workday: workDay });
}
}
return records;
}
// Toast 提示
function showToast(message, type) {
const toast = document.createElement('div');
toast.textContent = message;
Object.assign(toast.style, {
position: 'fixed', top: '20px', right: '20px', zIndex: '99999',
padding: '12px 20px', borderRadius: '6px', fontSize: '14px',
fontFamily: '"Microsoft Yahei", sans-serif',
color: '#fff', backgroundColor: type === 'success' ? '#28a745' : '#dc3545',
boxShadow: '0 4px 12px rgba(0,0,0,0.2)', transition: 'opacity 0.3s',
});
document.body.appendChild(toast);
setTimeout(() => { toast.style.opacity = '0'; }, 2500);
setTimeout(() => { toast.remove(); }, 3000);
}
// 保存加班记录到 MyTools
async function saveToMyTools() {
if (lastOvertimeRecords.length === 0) {
showToast('没有可保存的加班记录', 'error');
return;
}
const total = lastOvertimeRecords.reduce((s, r) => s + r.hours, 0);
const weekLabel = (() => {
const d = new Date();
const iso = (d2 => {
const start = new Date(d2.getFullYear(), 0, 1);
const days = Math.floor((d2 - start) / 86400000);
return `${d2.getFullYear()}-W${String(Math.ceil((days + start.getDay() + 1) / 7)).padStart(2, '0')}`;
})(d);
return iso;
})();
try {
const resp = await fetch(`${MYTOOLS_BASE}/api/overtime-records`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ week: weekLabel, records: lastOvertimeRecords, total: parseFloat(total.toFixed(2)) }),
signal: AbortSignal.timeout(3000),
});
if (resp.ok) {
showToast(`已保存 ${lastOvertimeRecords.length} 条记录到 MyTools`, 'success');
} else {
showToast('保存失败: ' + resp.status, 'error');
}
} catch (e) {
showToast('MyTools 不可用,请确认服务已启动', 'error');
}
}
// 创建保存按钮(浮动在表格上方)
function showSaveButton() {
// 移除旧按钮
const old = document.getElementById('fmohs-save-btn');
if (old) old.remove();
const btn = document.createElement('button');
btn.id = 'fmohs-save-btn';
btn.textContent = '保存到 MyTools';
Object.assign(btn.style, {
margin: '8px 4px', padding: '6px 16px', fontSize: '13px',
backgroundColor: '#1f6feb', color: '#fff', border: 'none',
borderRadius: '4px', cursor: 'pointer', fontFamily: '"Microsoft Yahei", sans-serif',
});
btn.addEventListener('click', saveToMyTools);
// 插入到 grid 表格上方
const gridView = document.getElementById('gview_grid');
if (gridView) {
gridView.parentNode.insertBefore(btn, gridView);
}
}
// 创建菜单按钮
function createMenuButton(text, options = {}) {
@@ -275,6 +396,10 @@
return;
}
titleCell.textContent = `加班时间(${totalSum.toFixed(2)}小时)`;
// 收集记录并显示保存按钮
lastOvertimeRecords = collectOvertimeRecords();
showSaveButton();
}
const queryButton = document.getElementById("query");
@@ -303,4 +428,7 @@
container.appendChild(button3);
console.log("复旦微加班时间统计脚本已加载");
console.log("点击按钮后会统计加班时间");
// 启动时拉取配置(非阻塞)
initConfig();
})()