// 主应用类 class WorkListApp { constructor() { this.tasks = []; this.currentEditingTask = null; this.currentFilter = 'all'; this.excludedStatuses = new Set(); this.currentUser = null; this.init(); } // 初始化应用 async init() { // 先检查登录状态 await this.checkAuthStatus(); // 如果已登录,绑定事件并加载任务 if (this.currentUser) { this.bindEvents(); this.loadTasks(); this.setupDatePicker(); } } // 检查认证状态 async checkAuthStatus() { try { const response = await api.checkAuth(); if (response.authenticated) { this.currentUser = response.user; this.showMainApp(); } else { this.showLoginPage(); } } catch (error) { console.log('未登录:', error); this.showLoginPage(); } } // 显示登录页面 showLoginPage() { document.getElementById('loginPage').style.display = 'flex'; document.getElementById('mainApp').style.display = 'none'; // 绑定登录表单事件 const loginForm = document.getElementById('loginForm'); loginForm.addEventListener('submit', async (e) => { e.preventDefault(); await this.handleLogin(); }); } // 显示主应用页面 showMainApp() { document.getElementById('loginPage').style.display = 'none'; document.getElementById('mainApp').style.display = 'block'; // 显示用户名 if (this.currentUser) { document.getElementById('currentUsername').textContent = this.currentUser.username; } } // 处理登录 async handleLogin() { const username = document.getElementById('loginUsername').value; const password = document.getElementById('loginPassword').value; const errorDiv = document.getElementById('loginError'); try { errorDiv.style.display = 'none'; const response = await api.login(username, password); if (response.user) { this.currentUser = response.user; this.showMainApp(); // 绑定事件并加载任务 this.bindEvents(); this.loadTasks(); this.setupDatePicker(); } } catch (error) { errorDiv.textContent = error.message || '登录失败,请检查用户名和密码'; errorDiv.style.display = 'block'; } } // 处理登出 async handleLogout() { try { await api.logout(); this.currentUser = null; this.tasks = []; this.showLoginPage(); } catch (error) { console.error('登出失败:', error); alert('登出失败: ' + error.message); } } // 绑定事件 bindEvents() { // 登出按钮 const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn && !logoutBtn.hasAttribute('data-bound')) { logoutBtn.addEventListener('click', () => { this.handleLogout(); }); logoutBtn.setAttribute('data-bound', 'true'); } // 添加任务按钮 document.getElementById('addTaskBtn').addEventListener('click', () => { this.showTaskModal(); }); // 报表按钮 document.getElementById('reportBtn').addEventListener('click', () => { this.showReportModal(); }); // 时间历史按钮 document.getElementById('timeHistoryBtn').addEventListener('click', () => { this.showTimeHistoryModal(); }); // 模态框关闭 document.querySelectorAll('.close').forEach(closeBtn => { closeBtn.addEventListener('click', (e) => { const modal = e.target.closest('.modal'); this.hideModal(modal); }); }); // 点击模态框外部关闭 document.querySelectorAll('.modal').forEach(modal => { modal.addEventListener('click', (e) => { if (e.target === modal) { this.hideModal(modal); } }); }); // 任务表单 document.getElementById('saveBtn').addEventListener('click', () => { this.saveTask(); }); document.getElementById('cancelBtn').addEventListener('click', () => { this.hideModal(document.getElementById('taskModal')); }); // AI润色按钮 document.getElementById('polishBtn').addEventListener('click', () => { this.polishDescription(); }); // 润色相关按钮 document.getElementById('usePolishedBtn').addEventListener('click', () => { this.usePolishedDescription(); }); document.getElementById('discardPolishedBtn').addEventListener('click', () => { this.discardPolishedDescription(); }); // 报表加载按钮 document.getElementById('loadReportBtn').addEventListener('click', () => { this.loadReport(); }); // 历史记录加载按钮 document.getElementById('loadHistoryBtn').addEventListener('click', () => { this.loadTimeHistory(); }); // 任务历史加载按钮 document.getElementById('loadTaskHistoryBtn').addEventListener('click', () => { this.loadTaskTimeHistory(); }); // 任务筛选按钮 document.querySelectorAll('.filter-btn').forEach(btn => { btn.addEventListener('click', (event) => { const filter = event.currentTarget.dataset.filter; this.setTaskFilter(filter); }); }); // 排除状态按钮 document.querySelectorAll('.exclusion-btn').forEach(btn => { btn.addEventListener('click', (event) => { const status = event.currentTarget.dataset.status; this.toggleExcludeStatus(status); }); }); } // 设置日期选择器 setupDatePicker() { const dateInput = document.getElementById('reportDate'); const today = new Date().toISOString().split('T')[0]; dateInput.value = today; } // 显示加载提示 showLoading() { document.getElementById('loadingOverlay').style.display = 'flex'; } // 隐藏加载提示 hideLoading() { document.getElementById('loadingOverlay').style.display = 'none'; } // 显示模态框 showModal(modalId) { const modal = document.getElementById(modalId); modal.style.display = 'block'; document.body.style.overflow = 'hidden'; } // 隐藏模态框 hideModal(modal) { modal.style.display = 'none'; document.body.style.overflow = 'auto'; } // 显示任务模态框 showTaskModal(task = null) { this.currentEditingTask = task; const modal = document.getElementById('taskModal'); const form = document.getElementById('taskForm'); const polishedDiv = document.getElementById('polishedDescription'); if (task) { // 编辑模式 document.getElementById('modalTitle').textContent = '编辑任务'; document.getElementById('taskTitle').value = task.title; document.getElementById('taskDescription').value = task.description || ''; document.getElementById('taskStatus').value = task.status; // 如果有润色后的描述,显示它 if (task.polished_description) { const polishedText = polishedDiv.querySelector('.polished-text'); polishedText.textContent = task.polished_description; polishedDiv.style.display = 'block'; } else { polishedDiv.style.display = 'none'; } } else { // 新建模式 document.getElementById('modalTitle').textContent = '添加任务'; form.reset(); polishedDiv.style.display = 'none'; } this.showModal('taskModal'); } // 保存任务 async saveTask() { try { this.showLoading(); const formData = { title: document.getElementById('taskTitle').value.trim(), description: document.getElementById('taskDescription').value.trim(), status: document.getElementById('taskStatus').value }; if (!formData.title) { throw new Error('任务标题不能为空'); } let savedTask; if (this.currentEditingTask) { // 更新任务 savedTask = await api.updateTask(this.currentEditingTask.id, formData); } else { // 创建任务 savedTask = await api.createTask(formData); } this.hideModal(document.getElementById('taskModal')); await this.loadTasks(); this.showNotification('任务保存成功', 'success'); } catch (error) { console.error('保存任务失败:', error); this.showNotification(error.message || '保存任务失败', 'error'); } finally { this.hideLoading(); } } // 删除任务 async deleteTask(taskId) { if (!confirm('确定要删除这个任务吗?')) { return; } try { this.showLoading(); await api.deleteTask(taskId); await this.loadTasks(); this.showNotification('任务删除成功', 'success'); } catch (error) { console.error('删除任务失败:', error); this.showNotification(error.message || '删除任务失败', 'error'); } finally { this.hideLoading(); } } // 开始计时 async startTimer(taskId) { try { this.showLoading(); await taskTimer.startTimer(taskId); this.updateTaskStatusLocal(taskId, 'in_progress'); this.renderTasks(); this.updateStats(); this.showNotification('计时开始', 'success'); } catch (error) { console.error('开始计时失败:', error); this.showNotification(error.message || '开始计时失败', 'error'); } finally { this.hideLoading(); } } // 停止计时 async stopTimer(taskId) { try { this.showLoading(); await taskTimer.stopTimer(taskId); this.updateTaskStatusLocal(taskId, 'pending'); this.renderTasks(); this.updateStats(); this.showNotification('计时结束', 'success'); } catch (error) { console.error('停止计时失败:', error); this.showNotification(error.message || '停止计时失败', 'error'); } finally { this.hideLoading(); } } // AI润色描述 async polishDescription() { const description = document.getElementById('taskDescription').value.trim(); if (!description) { this.showNotification('请先输入任务描述', 'warning'); return; } try { this.showLoading(); // 如果有正在编辑的任务,使用其ID;否则提示用户先保存任务 if (!this.currentEditingTask) { this.showNotification('请先保存任务,然后再进行润色', 'warning'); return; } const result = await api.polishTaskDescription(this.currentEditingTask.id); // 显示润色结果 const polishedDiv = document.getElementById('polishedDescription'); const polishedText = polishedDiv.querySelector('.polished-text'); polishedText.textContent = result.polished; polishedDiv.style.display = 'block'; this.showNotification('AI润色完成', 'success'); } catch (error) { console.error('AI润色失败:', error); this.showNotification(error.message || 'AI润色失败', 'error'); } finally { this.hideLoading(); } } // 使用润色版本 async usePolishedDescription() { const polishedText = document.querySelector('.polished-text').textContent; document.getElementById('taskDescription').value = polishedText; document.getElementById('polishedDescription').style.display = 'none'; // 如果是编辑模式,立即保存更新,将润色版本替换原始描述 if (this.currentEditingTask) { try { this.showLoading(); // 更新任务:description = 润色版本, polished_description = null await api.updateTask(this.currentEditingTask.id, { description: polishedText, polished_description: null // 清空润色字段 }); // 关闭模态框 this.hideModal(document.getElementById('taskModal')); // 刷新任务列表 await this.loadTasks(); this.showNotification('已应用润色版本', 'success'); } catch (error) { console.error('应用润色版本失败:', error); this.showNotification(error.message || '应用润色版本失败', 'error'); } finally { this.hideLoading(); } } else { this.showNotification('已使用润色版本', 'success'); } } // 丢弃润色版本 discardPolishedDescription() { document.getElementById('polishedDescription').style.display = 'none'; } // 加载任务列表 async loadTasks() { try { this.tasks = await api.getTasks(); // 恢复未结束的计时器(需要在渲染之前) await taskTimer.restoreTimers(this.tasks); this.renderTasks(); this.updateStats(); } catch (error) { console.error('加载任务失败:', error); this.showNotification('加载任务失败', 'error'); } } // 设置任务状态筛选 setTaskFilter(filter = 'all') { const normalized = filter || 'all'; if (this.currentFilter !== normalized) { this.currentFilter = normalized; } this.renderTasks(); } // 更新筛选按钮状态 updateFilterButtons() { document.querySelectorAll('.filter-btn').forEach(btn => { const isActive = btn.dataset.filter === this.currentFilter; btn.classList.toggle('active', isActive); }); } // 切换排除状态 toggleExcludeStatus(status) { if (this.excludedStatuses.has(status)) { this.excludedStatuses.delete(status); } else { this.excludedStatuses.add(status); } this.renderTasks(); } // 更新排除按钮状态 updateExclusionButtons() { document.querySelectorAll('.exclusion-btn').forEach(btn => { const status = btn.dataset.status; btn.classList.toggle('active', this.excludedStatuses.has(status)); }); } // 获取筛选后的任务列表 getFilteredTasks() { if (this.currentFilter === 'all') { return this.tasks.filter(task => !this.excludedStatuses.has(task.status)); } return this.tasks .filter(task => task.status === this.currentFilter) .filter(task => !this.excludedStatuses.has(task.status)); } // 获取筛选名称 getFilterDisplayName(filter) { const map = { 'all': '全部任务', 'pending': '未开始任务', 'in_progress': '进行中任务', 'completed': '已完成任务' }; return map[filter] || '任务'; } // 渲染任务列表 renderTasks() { const taskList = document.getElementById('taskList'); this.updateFilterButtons(); this.updateExclusionButtons(); const filteredTasks = this.getFilteredTasks(); if (this.tasks.length === 0) { taskList.innerHTML = `

暂无任务

点击"添加任务"开始记录你的工作

`; return; } if (filteredTasks.length === 0) { const filterLabel = this.getFilterDisplayName(this.currentFilter); taskList.innerHTML = `

暂无${filterLabel}

试试切换其他状态,或者新建一条任务

`; return; } taskList.innerHTML = filteredTasks.map(task => this.renderTaskItem(task)).join(''); } // 渲染单个任务项 renderTaskItem(task) { const statusClass = task.status; const statusText = this.getStatusText(task.status); const isRunning = taskTimer.hasActiveTimer(task.id); // 如果正在计时,显示当前会话时长;否则显示总时长 let displayTime; if (isRunning) { const timer = taskTimer.activeTimers.get(task.id); const currentDuration = timer ? timer.currentSessionDuration || 0 : 0; displayTime = this.formatTime(currentDuration); } else { displayTime = this.formatTime(task.total_time || 0); } return `
${this.escapeHtml(task.title)}
${statusText}
${task.description ? `
${this.escapeHtml(task.description)}${task.polished_description ? `
润色版本: ${this.escapeHtml(task.polished_description)}
` : ''}
` : ''}
${displayTime}
总时长: ${this.formatTime(task.total_time || 0)}
创建: ${this.formatDate(task.created_at)}
`; } // 更新本地任务状态 updateTaskStatusLocal(taskId, status) { const task = this.tasks.find(item => item.id === taskId); if (task) { task.status = status; } } // 更新统计信息 updateStats() { const totalTime = this.tasks.reduce((sum, task) => sum + (task.total_time || 0), 0); const completedTasks = this.tasks.filter(task => task.status === 'completed').length; const activeTasks = this.tasks.filter(task => task.status === 'in_progress').length; document.getElementById('totalTime').textContent = this.formatTime(totalTime); document.getElementById('completedTasks').textContent = completedTasks; document.getElementById('activeTasks').textContent = activeTasks; } // 显示报表模态框 showReportModal() { this.showModal('reportModal'); this.loadReport(); } // 显示时间历史模态框 showTimeHistoryModal() { this.showModal('timeHistoryModal'); this.loadTaskFilter(); this.loadTimeHistory(); } // 显示任务时间历史模态框 showTaskTimeHistory(taskId) { this.currentViewingTaskId = taskId; const task = this.tasks.find(t => t.id === taskId); if (task) { document.getElementById('taskHistoryTitle').textContent = `"${task.title}" 的时间历史`; } this.showModal('taskTimeHistoryModal'); this.loadTaskTimeHistory(); } // 加载报表 async loadReport() { try { this.showLoading(); const date = document.getElementById('reportDate').value; const report = await api.getDailyReport(date); this.renderReport(report); } catch (error) { console.error('加载报表失败:', error); this.showNotification('加载报表失败', 'error'); } finally { this.hideLoading(); } } // 渲染报表 renderReport(report) { const reportContent = document.getElementById('reportContent'); if (report.task_stats.length === 0) { reportContent.innerHTML = `

暂无数据

${report.date} 没有记录任何工作时间

`; return; } const totalTime = this.formatTime(report.total_time); reportContent.innerHTML = `

${report.date} - 总工作时长: ${totalTime}

${report.task_stats.map(taskStat => `
${this.escapeHtml(taskStat.task.title)}
${this.formatTime(taskStat.total_duration)}
`).join('')}
`; } // 加载任务筛选器 loadTaskFilter() { const taskFilter = document.getElementById('historyTaskFilter'); taskFilter.innerHTML = ''; this.tasks.forEach(task => { const option = document.createElement('option'); option.value = task.id; option.textContent = task.title; taskFilter.appendChild(option); }); } // 加载时间历史 async loadTimeHistory() { try { this.showLoading(); const days = parseInt(document.getElementById('historyDays').value); const taskId = document.getElementById('historyTaskFilter').value || null; const history = await api.getAllTimeHistory(days, taskId); this.renderTimeHistory(history); } catch (error) { console.error('加载时间历史失败:', error); this.showNotification('加载时间历史失败', 'error'); } finally { this.hideLoading(); } } // 加载任务时间历史 async loadTaskTimeHistory() { if (!this.currentViewingTaskId) return; try { this.showLoading(); const days = parseInt(document.getElementById('taskHistoryDays').value); const history = await api.getTaskTimeHistory(this.currentViewingTaskId, days); this.renderTaskTimeHistory(history); } catch (error) { console.error('加载任务时间历史失败:', error); this.showNotification('加载任务时间历史失败', 'error'); } finally { this.hideLoading(); } } // 渲染时间历史 renderTimeHistory(history) { const content = document.getElementById('timeHistoryContent'); if (history.daily_tasks.length === 0) { content.innerHTML = `

暂无时间记录

在指定时间段内没有找到任何工作时间记录

`; return; } content.innerHTML = history.daily_tasks.map(day => `

${this.formatDate(day.date)} ${this.formatTime(day.total_time)}

${day.tasks.map(task => `
${this.escapeHtml(task.task.title)}
${this.formatTime(task.total_duration)}
${task.segments.map(segment => `
${this.formatDateTime(segment.start_time)} - ${this.formatDateTime(segment.end_time)}
${this.formatTime(segment.duration)}
${this.formatTimePeriod(segment.start_time, segment.end_time)}
`).join('')}
`).join('')}
`).join(''); } // 渲染任务时间历史 renderTaskTimeHistory(history) { const content = document.getElementById('taskTimeHistoryContent'); if (history.daily_segments.length === 0) { content.innerHTML = `

暂无时间记录

该任务在指定时间段内没有工作时间记录

`; return; } content.innerHTML = history.daily_segments.map(day => `

${this.formatDate(day.date)} ${this.formatTime(day.total_duration)}

${day.segments.map(segment => `
${this.formatDateTime(segment.start_time)} - ${this.formatDateTime(segment.end_time)}
${this.formatTime(segment.duration)}
${this.formatTimePeriod(segment.start_time, segment.end_time)}
`).join('')}
`).join(''); } // 工具方法 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')}`; } // 后端时间为UTC(无Z),需要按UTC解析并按上海时区显示 parseUtcISO(dateTimeString) { if (!dateTimeString) return null; // 如果字符串不包含时区信息,则按UTC处理(补上Z) const hasTimezone = /[zZ]|[\+\-]\d{2}:?\d{2}$/.test(dateTimeString); const normalized = hasTimezone ? dateTimeString : `${dateTimeString}Z`; return new Date(normalized); } formatDate(dateString) { const date = this.parseUtcISO(dateString); if (!date) return ''; return date.toLocaleDateString('zh-CN', { timeZone: 'Asia/Shanghai' }); } formatDateTime(dateTimeString) { const date = this.parseUtcISO(dateTimeString); if (!date) return ''; return date.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone: 'Asia/Shanghai' }); } formatTimePeriod(startTime, endTime) { const start = this.parseUtcISO(startTime); const end = this.parseUtcISO(endTime); const duration = Math.floor((end - start) / 1000); if (duration < 60) { return `${duration}秒`; } else if (duration < 3600) { const minutes = Math.floor(duration / 60); const seconds = duration % 60; return `${minutes}分${seconds}秒`; } else { const hours = Math.floor(duration / 3600); const minutes = Math.floor((duration % 3600) / 60); return `${hours}小时${minutes}分钟`; } } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } showNotification(message, type = 'info') { // 简单的通知实现 const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.textContent = message; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 15px 20px; border-radius: 8px; color: white; font-weight: 600; z-index: 3000; animation: slideIn 0.3s ease; max-width: 300px; `; // 根据类型设置背景色 const colors = { 'success': '#48bb78', 'error': '#f56565', 'warning': '#ed8936', 'info': '#4299e1' }; notification.style.backgroundColor = colors[type] || colors.info; document.body.appendChild(notification); // 3秒后自动移除 setTimeout(() => { notification.remove(); }, 3000); } } // 页面加载完成后初始化应用 document.addEventListener('DOMContentLoaded', () => { window.app = new WorkListApp(); });