集成 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:
@@ -5419,8 +5419,8 @@
|
||||
"holiday": 44,
|
||||
"holiday_or": 45,
|
||||
"holiday_overtime": 10,
|
||||
"holiday_today": 2,
|
||||
"holiday_legal": 2,
|
||||
"holiday_today": 1,
|
||||
"holiday_legal": 1,
|
||||
"holiday_recess": 1
|
||||
},
|
||||
{
|
||||
@@ -5439,8 +5439,8 @@
|
||||
"holiday": 44,
|
||||
"holiday_or": 44,
|
||||
"holiday_overtime": 10,
|
||||
"holiday_today": 1,
|
||||
"holiday_legal": 1,
|
||||
"holiday_today": 2,
|
||||
"holiday_legal": 2,
|
||||
"holiday_recess": 1
|
||||
},
|
||||
{
|
||||
|
||||
136
main.js
136
main.js
@@ -21984,15 +21984,15 @@ const workDayData = {
|
||||
// 计算最大值和最小值
|
||||
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;
|
||||
}
|
||||
@@ -22113,6 +22113,127 @@ const workDayData = {
|
||||
}
|
||||
|
||||
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 = {}) {
|
||||
@@ -22198,6 +22319,10 @@ const workDayData = {
|
||||
return;
|
||||
}
|
||||
titleCell.textContent = `加班时间(${totalSum.toFixed(2)}小时)`;
|
||||
|
||||
// 收集记录并显示保存按钮
|
||||
lastOvertimeRecords = collectOvertimeRecords();
|
||||
showSaveButton();
|
||||
}
|
||||
|
||||
const queryButton = document.getElementById("query");
|
||||
@@ -22226,4 +22351,7 @@ const workDayData = {
|
||||
container.appendChild(button3);
|
||||
console.log("复旦微加班时间统计脚本已加载");
|
||||
console.log("点击按钮后会统计加班时间");
|
||||
|
||||
// 启动时拉取配置(非阻塞)
|
||||
initConfig();
|
||||
})()
|
||||
136
main.template.js
136
main.template.js
@@ -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();
|
||||
})()
|
||||
Reference in New Issue
Block a user