This commit is contained in:
2025-12-30 09:03:29 +00:00
commit 32294ebec1
19 changed files with 3146 additions and 0 deletions

268
frontend/js/timer.js Normal file
View File

@@ -0,0 +1,268 @@
// 计时器模块
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对象
const startTime = new Date(timerStatus.start_time);
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();