init
This commit is contained in:
268
frontend/js/timer.js
Normal file
268
frontend/js/timer.js
Normal 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();
|
||||
Reference in New Issue
Block a user