// 统计分析页面JavaScript let cutoffPeriods = []; let currentStats = null; let projectHoursChart = null; // 用于存储图表实例 // 页面加载时初始化 document.addEventListener('DOMContentLoaded', function() { loadCutoffPeriods(); setupEventListeners(); }); // 设置事件监听器 function setupEventListeners() { // 周期表单提交 const periodForm = document.getElementById('period-form'); if (periodForm) { periodForm.addEventListener('submit', handlePeriodSubmit); } } // 加载Cut-Off周期 async function loadCutoffPeriods() { try { const response = await apiGet('/api/statistics/periods'); cutoffPeriods = response.data; populatePeriodSelect(); } catch (error) { showError('加载周期列表失败: ' + error.message); console.error('加载周期列表失败:', error); } } // 填充周期选择框 function populatePeriodSelect() { const select = document.getElementById('period-select'); if (!select) return; // 清空现有选项(保留第一个默认选项) const firstOption = select.querySelector('option'); select.innerHTML = ''; if (firstOption) { select.appendChild(firstOption); } // 添加周期选项 cutoffPeriods.forEach(period => { const option = document.createElement('option'); option.value = period.id; option.textContent = `${period.period_name} (${formatDate(period.start_date)} - ${formatDate(period.end_date)})`; select.appendChild(option); }); } // 加载周统计数据 async function loadWeeklyStats() { const periodId = document.getElementById('period-select').value; if (!periodId) { hideStatsDisplay(); return; } try { const response = await apiGet(`/api/statistics/weekly?period_id=${periodId}`); currentStats = response.data; displayStats(); } catch (error) { showError('加载统计数据失败: ' + error.message); console.error('加载统计数据失败:', error); } } // 加载自定义日期范围统计 async function loadCustomStats() { const startDate = document.getElementById('custom-start-date').value; const endDate = document.getElementById('custom-end-date').value; if (!startDate || !endDate) { showError('请选择开始和结束日期'); return; } if (new Date(startDate) > new Date(endDate)) { showError('开始日期不能晚于结束日期'); return; } try { const response = await apiGet(`/api/statistics/weekly?start_date=${startDate}&end_date=${endDate}`); currentStats = response.data; displayStats(); // 清空周期选择 document.getElementById('period-select').value = ''; } catch (error) { showError('加载统计数据失败: ' + error.message); console.error('加载统计数据失败:', error); } } // 显示统计数据 function displayStats() { if (!currentStats) return; showStatsDisplay(); updateStatsOverview(); renderDailyDetails(); renderProjectDistribution(); } // 显示统计界面 function showStatsDisplay() { document.getElementById('stats-overview').style.display = 'block'; document.getElementById('daily-details').style.display = 'block'; document.getElementById('project-distribution').style.display = 'block'; } // 隐藏统计界面 function hideStatsDisplay() { document.getElementById('stats-overview').style.display = 'none'; document.getElementById('daily-details').style.display = 'none'; document.getElementById('project-distribution').style.display = 'none'; } // 更新统计概览 function updateStatsOverview() { if (!currentStats) return; // 更新周期信息 document.getElementById('current-period-name').textContent = currentStats.period.period_name; document.getElementById('period-date-range').textContent = `${formatDate(currentStats.period.start_date)} - ${formatDate(currentStats.period.end_date)}`; // 更新统计数据 document.getElementById('workday-total').textContent = currentStats.workday_total; document.getElementById('holiday-total').textContent = currentStats.holiday_total; document.getElementById('weekly-total').textContent = currentStats.weekly_total; document.getElementById('completion-rate').textContent = `${currentStats.completion_rate}%`; // 更新工作天数统计 document.getElementById('working-days').textContent = currentStats.working_days; document.getElementById('holiday-work-days').textContent = currentStats.holiday_work_days; document.getElementById('rest-days').textContent = currentStats.rest_days; // 更新完成度颜色 const completionElement = document.getElementById('completion-rate'); completionElement.className = 'stat-value'; if (currentStats.completion_rate >= 100) { completionElement.style.color = '#27ae60'; } else if (currentStats.completion_rate >= 80) { completionElement.style.color = '#f39c12'; } else { completionElement.style.color = '#e74c3c'; } } // 渲染每日详情 function renderDailyDetails() { if (!currentStats || !currentStats.daily_records) return; const tbody = document.getElementById('daily-stats-tbody'); if (!tbody) return; tbody.innerHTML = currentStats.daily_records.map(record => { const rowClass = getRowClass(record); return ` ${formatDate(record.date)} ${record.day_of_week} ${escapeHtml(record.event)} ${escapeHtml(record.project)} ${record.start_time} ${record.end_time} ${escapeHtml(record.activity_num)} ${record.hours} `; }).join(''); } // 渲染项目工时分布图表和表格 function renderProjectDistribution() { if (!currentStats || !currentStats.project_hours) return; const projectData = currentStats.project_hours; const tableBody = document.getElementById('project-hours-tbody'); const ctx = document.getElementById('project-hours-chart').getContext('2d'); if (!ctx || !tableBody) return; // 清理旧内容 tableBody.innerHTML = ''; if (projectHoursChart) { projectHoursChart.destroy(); } if (projectData.length === 0) { // 在表格中显示无数据信息 tableBody.innerHTML = '暂无项目工时数据'; // 清理画布 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.font = "16px Arial"; ctx.fillStyle = "#888"; ctx.textAlign = "center"; ctx.fillText("暂无项目工时数据", ctx.canvas.width / 2, 50); return; } // 填充表格 projectData.forEach(item => { const row = ` ${escapeHtml(item.project)} ${item.hours} ${item.percentage}% `; tableBody.innerHTML += row; }); // 准备图表数据 const labels = projectData.map(item => item.project); const data = projectData.map(item => parseFloat(item.hours.replace(':', '.'))); const backgroundColors = [ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40', '#E7E9ED', '#8DDF3C', '#FFD700', '#B22222', '#4682B4', '#D2B48C' ]; // 渲染图表 projectHoursChart = new Chart(ctx, { type: 'pie', data: { labels: labels, datasets: [{ label: '工时', data: data, backgroundColor: backgroundColors.slice(0, data.length), borderColor: '#fff', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', }, title: { display: false, // 标题可以省略,因为旁边有表格 }, tooltip: { callbacks: { label: function(context) { let label = context.label || ''; if (label) { label += ': '; } if (context.parsed !== null) { const total = context.dataset.data.reduce((a, b) => a + b, 0); const percentage = ((context.parsed / total) * 100).toFixed(2); label += `${context.raw} 小时 (${percentage}%)`; } return label; } } } } } }); } // 获取行的CSS类名(根据是否为休息日) function getRowClass(record) { if (record.is_holiday) { if (record.is_working_on_holiday) { return 'working-holiday-row'; // 休息日工作 } else { return 'holiday-row'; // 休息日休息 } } return ''; } // 显示创建周期模态框 function showCreatePeriodModal() { resetForm('period-form'); showModal('create-period-modal'); } // 计算周期信息 function calculatePeriodInfo() { const startDate = document.getElementById('start_date').value; const endDate = document.getElementById('end_date').value; if (!startDate || !endDate) return; const start = new Date(startDate); const end = new Date(endDate); if (start >= end) { showError('开始日期必须早于结束日期'); return; } // 计算天数和周数 const daysDiff = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1; const weeks = Math.round(daysDiff / 7); // 更新周数字段 document.getElementById('weeks').value = weeks; // 更新目标工时(如果为空) const targetHoursField = document.getElementById('target_hours'); if (!targetHoursField.value) { targetHoursField.value = weeks * 40; // 默认每周40小时 } } // 应用周期模板 function applyTemplate(templateType) { const today = new Date(); let startDate, endDate, weeks, targetHours, periodName; switch (templateType) { case 'weekly': // 本周周期 const dayOfWeek = today.getDay(); const startOfWeek = new Date(today); startOfWeek.setDate(today.getDate() - dayOfWeek + 1); // 周一 const endOfWeek = new Date(startOfWeek); endOfWeek.setDate(startOfWeek.getDate() + 6); // 周日 startDate = startOfWeek.toISOString().split('T')[0]; endDate = endOfWeek.toISOString().split('T')[0]; weeks = 1; targetHours = 40; periodName = `${today.getFullYear()}年第${getWeekNumber(today)}周`; break; case 'biweekly': // 双周周期 startDate = getTodayString(); const biweekEnd = new Date(today); biweekEnd.setDate(today.getDate() + 13); endDate = biweekEnd.toISOString().split('T')[0]; weeks = 2; targetHours = 80; periodName = `双周周期 ${formatDate(startDate)}-${formatDate(endDate)}`; break; case 'four-weeks': // 4周周期 startDate = getTodayString(); const fourWeeksEnd = new Date(today); fourWeeksEnd.setDate(today.getDate() + 27); endDate = fourWeeksEnd.toISOString().split('T')[0]; weeks = 4; targetHours = 160; periodName = `4周周期 ${formatDate(startDate)}-${formatDate(endDate)}`; break; case 'five-weeks': // 5周周期 startDate = getTodayString(); const fiveWeeksEnd = new Date(today); fiveWeeksEnd.setDate(today.getDate() + 34); endDate = fiveWeeksEnd.toISOString().split('T')[0]; weeks = 5; targetHours = 200; periodName = `5周周期 ${formatDate(startDate)}-${formatDate(endDate)}`; break; default: // 默认行为可以指向一个常用模板,例如4周 startDate = getTodayString(); const defaultEnd = new Date(today); defaultEnd.setDate(today.getDate() + 27); endDate = defaultEnd.toISOString().split('T')[0]; weeks = 4; targetHours = 160; periodName = `4周周期 ${formatDate(startDate)}-${formatDate(endDate)}`; break; } // 填充表单 document.getElementById('period_name').value = periodName; document.getElementById('start_date').value = startDate; document.getElementById('end_date').value = endDate; document.getElementById('weeks').value = weeks; document.getElementById('target_hours').value = targetHours; } // 获取周数 function getWeekNumber(date) { const firstDayOfYear = new Date(date.getFullYear(), 0, 1); const pastDaysOfYear = (date - firstDayOfYear) / 86400000; return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7); } // 处理周期表单提交 async function handlePeriodSubmit(e) { e.preventDefault(); const formData = getFormData('period-form'); // 验证必填字段 if (!formData.period_name || !formData.start_date || !formData.end_date) { showError('请填写完整的周期信息'); return; } try { const response = await apiPost('/api/statistics/periods', formData); showSuccess('Cut-Off周期创建成功'); closeModal('create-period-modal'); loadCutoffPeriods(); // 重新加载周期列表 } catch (error) { showError(error.message); } } // 管理周期 function managePeriods() { loadPeriodsTable(); showModal('manage-periods-modal'); } // 加载周期管理表格 function loadPeriodsTable() { const tbody = document.getElementById('periods-tbody'); if (!tbody) return; if (cutoffPeriods.length === 0) { tbody.innerHTML = '暂无周期数据'; return; } tbody.innerHTML = cutoffPeriods.map(period => ` ${escapeHtml(period.period_name)} ${formatDate(period.start_date)} ${formatDate(period.end_date)} ${period.weeks} ${period.target_hours}小时 `).join(''); } // 删除周期 async function deletePeriod(periodId) { const period = cutoffPeriods.find(p => p.id === periodId); if (!period) { showError('周期不存在'); return; } if (!confirm(`确定要删除周期"${period.period_name}"吗?`)) { return; } try { await apiDelete(`/api/statistics/periods/${periodId}`); showSuccess('周期删除成功'); loadCutoffPeriods(); // 重新加载周期列表 loadPeriodsTable(); // 更新管理表格 } catch (error) { showError('删除周期失败: ' + error.message); } } // HTML转义函数 function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }