基于DateTime选择的考勤计算代码问题修复需求
考勤动态表格问题解决方案
以下是修复后的完整代码,解决了你提到的三个问题:
完整代码
HTML
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>考勤计算表格</title> <style> table { width: 100%; border-collapse: collapse; margin: 20px 0; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } .error { color: red; font-size: 12px; margin-top: 5px; } button { padding: 8px 16px; margin: 10px 0; cursor: pointer; } </style> </head> <body> <h2>考勤记录</h2> <button onclick="addRow()">添加行</button> <table id="attendanceTable"> <thead> <tr> <th>序号</th> <th>Start Time</th> <th>End Time</th> <th>Time Taken</th> <th>Total Time Taken</th> <th>操作</th> </tr> </thead> <tbody> <tr> <td>1</td> <td><input type="datetime-local" class="start-time"></td> <td><input type="datetime-local" class="end-time"></td> <td class="time-taken">-</td> <td class="total-time">-</td> <td><button onclick="deleteRow(this)">删除</button></td> </tr> </tbody> </table> <div id="errorMessage" class="error"></div> </body> </html>
JavaScript
// 初始化绑定事件 document.addEventListener('DOMContentLoaded', function() { bindRowEvents(document.querySelectorAll('#attendanceTable tbody tr')); }); // 给行内输入框绑定事件 function bindRowEvents(rows) { rows.forEach(row => { const startTimeInput = row.querySelector('.start-time'); const endTimeInput = row.querySelector('.end-time'); startTimeInput.addEventListener('input', handleRowChange); endTimeInput.addEventListener('input', handleRowChange); }); } // 行内容变化时触发计算和验证 function handleRowChange(e) { const row = e.target.closest('tr'); const startTime = row.querySelector('.start-time').value; const endTime = row.querySelector('.end-time').value; const errorDiv = document.getElementById('errorMessage'); // 清空之前的错误提示 errorDiv.textContent = ''; // 验证时间连续性(非第一行) const rowIndex = Array.from(row.parentElement.children).indexOf(row); if (rowIndex > 0) { const prevRow = row.parentElement.children[rowIndex - 1]; const prevEndTime = prevRow.querySelector('.end-time').value; if (prevEndTime && startTime && startTime !== prevEndTime) { errorDiv.textContent = `第${rowIndex + 1}行的开始时间必须等于第${rowIndex}行的结束时间`; } } // 计算当前行的Time Taken if (startTime && endTime) { const start = new Date(startTime); const end = new Date(endTime); const diffMs = end - start; if (diffMs < 0) { errorDiv.textContent = `第${rowIndex + 1}行的结束时间不能早于开始时间`; row.querySelector('.time-taken').textContent = '-'; } else { const timeTaken = formatDuration(diffMs); row.querySelector('.time-taken').textContent = timeTaken; } } else { row.querySelector('.time-taken').textContent = '-'; } // 更新所有行的Total Time Taken updateCumulativeTotals(); } // 格式化毫秒数为天、小时、分钟格式 function formatDuration(ms) { const minutes = Math.floor(ms / 60000); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); const remainingHours = hours % 24; const remainingMinutes = minutes % 60; let parts = []; if (days > 0) parts.push(`${days} days`); if (remainingHours > 0) parts.push(`${remainingHours} hrs`); if (remainingMinutes > 0 || parts.length === 0) parts.push(`${remainingMinutes} mins`); return parts.join(' '); } // 计算并更新累计总时长 function updateCumulativeTotals() { const rows = document.querySelectorAll('#attendanceTable tbody tr'); let totalMs = 0; rows.forEach((row, index) => { const timeTakenText = row.querySelector('.time-taken').textContent; if (timeTakenText !== '-') { // 解析Time Taken文本为毫秒数 const parts = timeTakenText.split(' '); let ms = 0; for (let i = 0; i < parts.length; i += 2) { const value = parseInt(parts[i]); const unit = parts[i+1]; if (unit.includes('day')) ms += value * 24 * 60 * 60 * 1000; if (unit.includes('hr')) ms += value * 60 * 60 * 1000; if (unit.includes('min')) ms += value * 60 * 1000; } totalMs += ms; } // 更新当前行的累计总时长 row.querySelector('.total-time').textContent = totalMs > 0 ? formatDuration(totalMs) : '-'; }); } // 添加新行 function addRow() { const tableBody = document.getElementById('attendanceTable').querySelector('tbody'); const lastRow = tableBody.lastElementChild; const newRow = lastRow.cloneNode(true); // 重置新行的输入值和文本 newRow.querySelector('.start-time').value = ''; newRow.querySelector('.end-time').value = ''; newRow.querySelector('.time-taken').textContent = '-'; newRow.querySelector('.total-time').textContent = '-'; // 更新序号 const newIndex = Array.from(tableBody.children).length + 1; newRow.querySelector('td:first-child').textContent = newIndex; tableBody.appendChild(newRow); // 给新行绑定事件 bindRowEvents([newRow]); } // 删除行 function deleteRow(btn) { const row = btn.closest('tr'); const tableBody = row.parentElement; if (tableBody.children.length > 1) { row.remove(); // 更新序号 Array.from(tableBody.children).forEach((row, index) => { row.querySelector('td:first-child').textContent = index + 1; }); // 更新累计总时长 updateCumulativeTotals(); } else { alert('至少保留一行记录'); } }
问题修复说明
1. 正确计算累计总时长
- 新增
formatDuration函数,将毫秒数转换为指定格式的时长文本; updateCumulativeTotals函数遍历所有行,解析每行的Time Taken为毫秒数累加,再转换为格式化文本更新到Total Time Taken列;- 每次行内时间输入变化时自动触发累计计算,确保实时更新。
2. 时间连续性验证
- 在
handleRowChange中,判断当前行不是第一行时,获取上一行的结束时间与当前行开始时间对比; - 不匹配时在页面底部显示错误提示;
- 额外添加了结束时间不能早于开始时间的验证逻辑。
3. 实时获取字段值
- 将JS逻辑改为事件监听模式,通过
addEventListener监听输入框的input事件,每次输入变化时触发计算和验证; - 初始化时给现有行绑定事件,新增行时也会绑定事件,确保所有行都能实时响应输入变化;
- 彻底解决了原代码仅在页面加载时运行一次的问题。
内容的提问来源于stack exchange,提问作者Ahamed Zulfan
相关产品推荐
相关产品推荐

