You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 18:58:11