🦊 小贝尔的考勤本
2026年2月
2月10日
今天也要加油鸭 💪
🏢 上班时间
--:--
🏡 下班时间
--:--
📉 迟到 / 📈 加班
0.00h / 0.00h
💰 今日加班费
¥0
📊 本月累计
净加班时长
0.00h
累计加班费
¥0
config: { start: '08:30', end: '17:30', rate: 50 }, currentDate: new Date(), selectedDate: '', init() { const d = localStorage.getItem('fox_attend_data'); if(d) this.data = JSON.parse(d); const c = localStorage.getItem('fox_attend_cfg'); if(c) { const saved = JSON.parse(c); // 强制覆盖时间设置,但保留时薪 this.config.rate = saved.rate || 50; } // 强制默认时间 this.config.start = '08:30'; this.config.end = '17:30'; this.selectToday(); this.render(); }, // ... (中间代码不变) ... calcDaily(tIn, tOut) { const toH = (t) => { const [h, m] = t.split(':').map(Number); return h + m/60; }; const stdIn = toH(this.config.start); const stdOut = toH(this.config.end); // 允许单边打卡计算 // 如果没有上班卡,迟到算0(或者算缺勤?按用户需求是“不打上班也可以打下班”,意味着可能只算加班费) // 这里的逻辑:只计算存在的项 let late = 0; if(tIn) { const actIn = toH(tIn); if(actIn > stdIn) late = actIn - stdIn; } let ot = 0; if(tOut) { const actOut = toH(tOut); if(actOut > stdOut) { ot = actOut - stdOut; // 新增规则:超过 20:30 (20.5) 下班,扣除 0.5 小时吃饭时间 if(actOut > 20.5) { ot = Math.max(0, ot - 0.5); } } } // 加班费 = (加班 - 迟到) * 时薪 // 注意:如果不打上班卡,late=0,意味着加班费全拿(符合“不打上班也可以”的语境) const netHours = ot - late; const money = netHours * parseFloat(this.config.rate); return { late, ot, money }; }, // 渲染详情:解绑按钮逻辑 renderDetail() { const r = this.data[this.selectedDate] || {}; // ... (日期显示代码不变) ... document.getElementById('inTime').innerText = r.in || '--:--'; document.getElementById('outTime').innerText = r.out || '--:--'; // 只要有任意一个打卡,就计算 let lateH = 0, otH = 0, money = 0; let lateTag = '', otTag = ''; let statusText = '今天也要加油鸭 💪'; if(r.in || r.out) { const res = this.calcDaily(r.in, r.out); lateH = res.late; otH = res.ot; money = res.money; if(lateH > 0) lateTag = `迟到 ${lateH.toFixed(2)}h`; if(otH > 0) otTag = `加班 ${otH.toFixed(2)}h`; statusText = money > 0 ? '💰 哇!赚到奶茶钱了!' : (money < 0 ? '🥺 呜呜...' : '✅ 下班啦!'); } // ... (显示代码不变) ... // 按钮逻辑:随时可点,互不影响 const btnIn = document.getElementById('btnIn'); // 假设之前获取了 // 这里直接用 onclick 绑定,不需要改 DOM 属性,除了文字 }, // ... // 设置弹窗:移除时间设置,只留时薪 openSettings() { // 隐藏时间输入框(通过 CSS 或直接移除 DOM,这里简单起见,仅赋值) // 更好的做法是修改 HTML,但我这里只改 JS 逻辑,让它不可编辑 const startInput = document.getElementById('cfgStart'); const endInput = document.getElementById('cfgEnd'); startInput.value = this.config.start; endInput.value = this.config.end; startInput.disabled = true; // 禁用 endInput.disabled = true; // 禁用 document.getElementById('cfgRate').value = this.config.rate; document.getElementById('settingsModal').style.display = 'flex'; }, saveSettings() { this.config.start = document.getElementById('cfgStart').value; this.config.end = document.getElementById('cfgEnd').value; this.config.rate = document.getElementById('cfgRate').value; localStorage.setItem('fox_attend_cfg', JSON.stringify(this.config)); this.closeModals(); this.render(); }, openEdit() { const r = this.data[this.selectedDate] || {}; document.getElementById('editIn').value = r.in || ''; document.getElementById('editOut').value = r.out || ''; document.getElementById('editModal').style.display = 'flex'; }, saveEdit() { const i = document.getElementById('editIn').value; const o = document.getElementById('editOut').value; if(i || o) { if(!this.data[this.selectedDate]) this.data[this.selectedDate] = {}; if(i) this.data[this.selectedDate].in = i; if(o) this.data[this.selectedDate].out = o; this.save(); } this.closeModals(); this.render(); }, clearDay() { if(confirm('清除记录?')) { delete this.data[this.selectedDate]; this.save(); this.closeModals(); this.render(); } }, closeModals() { document.querySelectorAll('.modal-mask').forEach(e => e.style.display='none'); }, save() { localStorage.setItem('fox_attend_data', JSON.stringify(this.data)); }, nowStr() { const d = new Date(); return `${pad(d.getHours())}:${pad(d.getMinutes())}`; }, fmtDate(d) { return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`; } }; function pad(n) { return n<10?'0'+n:n; } app.init();