// 计时器模块 class TaskTimer { constructor() { this.activeTimers = new Map(); // 存储活跃的计时器 this.timerIntervals = new Map(); // 存储定时器ID } // 开始计时 async startTimer(taskId) { try { // 检查是否已有活跃计时器 if (this.activeTimers.has(taskId)) { throw new Error('该任务已在计时中'); } // 调用API开始计时 const timeRecord = await api.startTimer(taskId); // 创建本地计时器 const timer = { taskId: taskId, startTime: new Date(), currentSessionDuration: 0, // 当前会话时长(从0开始) isRunning: true }; this.activeTimers.set(taskId, timer); // 启动定时器更新 const intervalId = setInterval(() => { this.updateTimer(taskId); }, 1000); this.timerIntervals.set(taskId, intervalId); // 更新UI this.updateTimerDisplay(taskId); this.updateTaskStatus(taskId, 'in_progress'); return timer; } catch (error) { console.error('开始计时失败:', error); throw error; } } // 停止计时 async stopTimer(taskId) { try { const timer = this.activeTimers.get(taskId); if (!timer) { throw new Error('没有找到活跃的计时器'); } // 调用API停止计时 await api.stopTimer(taskId); // 清除本地计时器 this.clearTimer(taskId); // 更新UI this.updateTaskStatus(taskId, 'pending'); this.updateTimerDisplay(taskId); // 刷新任务列表以显示总时长 if (window.app) { window.app.loadTasks(); } } catch (error) { console.error('停止计时失败:', error); throw error; } } // 清除计时器 clearTimer(taskId) { const intervalId = this.timerIntervals.get(taskId); if (intervalId) { clearInterval(intervalId); this.timerIntervals.delete(taskId); } this.activeTimers.delete(taskId); } // 更新计时器 updateTimer(taskId) { const timer = this.activeTimers.get(taskId); if (!timer) return; const now = new Date(); // 计算当前会话的时长(从开始计时到现在) timer.currentSessionDuration = Math.floor((now - timer.startTime) / 1000); this.updateTimerDisplay(taskId); } // 更新计时器显示 updateTimerDisplay(taskId) { const timer = this.activeTimers.get(taskId); const displayElement = document.querySelector(`[data-task-id="${taskId}"] .timer-display`); if (!displayElement) return; if (timer && timer.isRunning) { // 显示当前会话的时长(从0开始) const duration = timer.currentSessionDuration || 0; const hours = Math.floor(duration / 3600); const minutes = Math.floor((duration % 3600) / 60); const seconds = duration % 60; displayElement.textContent = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; displayElement.style.color = '#48bb78'; } else { // 显示总时长 this.updateTotalTimeDisplay(taskId); } } // 更新总时长显示 async updateTotalTimeDisplay(taskId) { try { const tasks = await api.getTasks(); const task = tasks.find(t => t.id === taskId); if (task) { const displayElement = document.querySelector(`[data-task-id="${taskId}"] .timer-display`); if (displayElement) { const totalSeconds = task.total_time || 0; const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; displayElement.textContent = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; displayElement.style.color = '#4a5568'; } } } catch (error) { console.error('更新总时长显示失败:', error); } } // 更新任务状态 updateTaskStatus(taskId, status) { const taskElement = document.querySelector(`[data-task-id="${taskId}"]`); if (!taskElement) return; // 更新状态类 taskElement.className = taskElement.className.replace(/in-progress|completed|pending/g, ''); taskElement.classList.add(status); // 更新状态显示 const statusElement = taskElement.querySelector('.task-status'); if (statusElement) { statusElement.textContent = this.getStatusText(status); statusElement.className = `task-status ${status}`; } // 更新按钮状态 this.updateTimerButtons(taskId, status); } // 更新计时器按钮 updateTimerButtons(taskId, status) { const startBtn = document.querySelector(`[data-task-id="${taskId}"] .start-timer-btn`); const stopBtn = document.querySelector(`[data-task-id="${taskId}"] .stop-timer-btn`); if (startBtn && stopBtn) { if (status === 'in_progress') { startBtn.style.display = 'none'; stopBtn.style.display = 'inline-flex'; } else { startBtn.style.display = 'inline-flex'; stopBtn.style.display = 'none'; } } } // 获取状态文本 getStatusText(status) { const statusMap = { 'pending': '待开始', 'in_progress': '进行中', 'completed': '已完成' }; return statusMap[status] || status; } // 格式化时间 formatTime(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } // 检查是否有活跃计时器 hasActiveTimer(taskId) { return this.activeTimers.has(taskId); } // 获取所有活跃计时器 getActiveTimers() { return Array.from(this.activeTimers.values()); } // 停止所有计时器 async stopAllTimers() { const promises = Array.from(this.activeTimers.keys()).map(taskId => this.stopTimer(taskId).catch(error => { console.error(`停止任务${taskId}计时失败:`, error); }) ); await Promise.all(promises); } // 恢复未结束的计时器(用于页面重新加载后) async restoreTimers(tasks) { try { const taskIds = tasks.map(task => task.id); if (!taskIds.length) { return; } const statuses = await api.getTimerStatuses(taskIds); for (const task of tasks) { const timerStatus = statuses?.[task.id]; if (timerStatus && timerStatus.is_running) { // 使用服务器返回的开始时间(从ISO格式字符串创建Date对象) // 如果字符串不包含时区信息,则按UTC处理(补上Z) const timeString = timerStatus.start_time; const hasTimezone = /[zZ]|[\+\-]\d{2}:?\d{2}$/.test(timeString); const normalized = hasTimezone ? timeString : `${timeString}Z`; const startTime = new Date(normalized); const now = new Date(); // 恢复计时器 const timer = { taskId: task.id, startTime: startTime, currentSessionDuration: Math.floor((now - startTime) / 1000), isRunning: true }; this.activeTimers.set(task.id, timer); // 启动定时器更新 const intervalId = setInterval(() => { this.updateTimer(task.id); }, 1000); this.timerIntervals.set(task.id, intervalId); // 更新UI this.updateTimerDisplay(task.id); this.updateTaskStatus(task.id, 'in_progress'); console.log(`恢复了任务 ${task.id} 的计时器`); } } } catch (error) { console.error('恢复计时器失败:', error); } } } // 创建全局计时器实例 const taskTimer = new TaskTimer();