// 统计分析页面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;
}