init
This commit is contained in:
316
static/js/statistics.js
Normal file
316
static/js/statistics.js
Normal file
@@ -0,0 +1,316 @@
|
||||
let myChart, trendChart;
|
||||
let isAdmin = false;
|
||||
|
||||
$(document).ready(function() {
|
||||
// 设置默认日期范围(最近30天)
|
||||
applyQuickSelect('30days');
|
||||
|
||||
// 加载统计数据
|
||||
loadStatistics();
|
||||
});
|
||||
|
||||
// 快捷时间选择
|
||||
function applyQuickSelect(value) {
|
||||
if (!value) {
|
||||
value = $('#quick-select').val();
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
let dateFrom, dateTo;
|
||||
|
||||
switch(value) {
|
||||
case 'today':
|
||||
dateFrom = dateTo = today;
|
||||
break;
|
||||
case 'week':
|
||||
dateFrom = new Date(today);
|
||||
dateFrom.setDate(today.getDate() - today.getDay());
|
||||
dateTo = today;
|
||||
break;
|
||||
case 'month':
|
||||
dateFrom = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
dateTo = today;
|
||||
break;
|
||||
case '30days':
|
||||
dateFrom = new Date(today);
|
||||
dateFrom.setDate(today.getDate() - 30);
|
||||
dateTo = today;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
$('#date-from').val(dateFrom.toISOString().split('T')[0]);
|
||||
$('#date-to').val(dateTo.toISOString().split('T')[0]);
|
||||
$('#quick-select').val(value);
|
||||
}
|
||||
|
||||
// 加载统计数据
|
||||
function loadStatistics() {
|
||||
loadOverview();
|
||||
loadUserStatistics();
|
||||
loadLeaderboard();
|
||||
}
|
||||
|
||||
// 加载概览数据
|
||||
function loadOverview() {
|
||||
const dateFrom = $('#date-from').val();
|
||||
const dateTo = $('#date-to').val();
|
||||
|
||||
$.ajax({
|
||||
url: '/api/statistics/overview',
|
||||
method: 'GET',
|
||||
data: {
|
||||
group_id: 1,
|
||||
date_from: dateFrom,
|
||||
date_to: dateTo
|
||||
},
|
||||
success: function(data) {
|
||||
isAdmin = data.is_admin;
|
||||
|
||||
$('#today-count').text(data.today_completed);
|
||||
$('#month-count').text(data.month_completed);
|
||||
$('#pending-count').text(data.status_counts.pending || 0);
|
||||
$('#claimed-count').text(data.status_counts.claimed || 0);
|
||||
|
||||
// 更新标题和统计数据
|
||||
if (isAdmin) {
|
||||
$('#stats-title').text('全局完成统计');
|
||||
$('#trend-title').text('全局完成趋势');
|
||||
// 管理员使用全局数据
|
||||
$('#my-total').text(data.total_completed);
|
||||
$('#my-pending').text(data.claimed_pending);
|
||||
// 渲染管理员图表
|
||||
renderMyChart(data.total_completed, data.claimed_pending);
|
||||
} else {
|
||||
$('#stats-title').text('我的完成统计');
|
||||
$('#trend-title').text('我的完成趋势');
|
||||
}
|
||||
|
||||
// 渲染趋势图
|
||||
renderTrendChart(data.trend);
|
||||
},
|
||||
error: handleAjaxError
|
||||
});
|
||||
}
|
||||
|
||||
// 加载个人统计
|
||||
function loadUserStatistics() {
|
||||
const userStr = localStorage.getItem('current_user');
|
||||
if (isAdmin) {
|
||||
return;
|
||||
}
|
||||
if (!userStr) {
|
||||
console.error('用户信息不存在');
|
||||
$('#my-total').text('0');
|
||||
$('#my-pending').text('0');
|
||||
return;
|
||||
}
|
||||
|
||||
let user;
|
||||
try {
|
||||
user = JSON.parse(userStr);
|
||||
} catch (e) {
|
||||
console.error('用户信息格式错误', e);
|
||||
$('#my-total').text('0');
|
||||
$('#my-pending').text('0');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user || !user.id) {
|
||||
console.error('用户ID不存在');
|
||||
$('#my-total').text('0');
|
||||
$('#my-pending').text('0');
|
||||
return;
|
||||
}
|
||||
|
||||
const dateFrom = $('#date-from').val();
|
||||
const dateTo = $('#date-to').val();
|
||||
|
||||
$.ajax({
|
||||
url: `/api/statistics/user/${user.id}`,
|
||||
method: 'GET',
|
||||
data: {
|
||||
group_id: 1,
|
||||
date_from: dateFrom,
|
||||
date_to: dateTo
|
||||
},
|
||||
success: function(data) {
|
||||
$('#my-total').text(data.total_completed);
|
||||
$('#my-pending').text(data.claimed_pending);
|
||||
|
||||
// 渲染个人图表
|
||||
renderMyChart(data.total_completed, data.claimed_pending);
|
||||
},
|
||||
error: handleAjaxError
|
||||
});
|
||||
}
|
||||
|
||||
// 加载排行榜
|
||||
function loadLeaderboard() {
|
||||
$.ajax({
|
||||
url: '/api/statistics/leaderboard',
|
||||
method: 'GET',
|
||||
data: {
|
||||
group_id: 1,
|
||||
period: 'monthly'
|
||||
},
|
||||
success: function(data) {
|
||||
renderLeaderboard(data.leaderboard);
|
||||
},
|
||||
error: handleAjaxError
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染个人图表
|
||||
function renderMyChart(totalCompleted, claimedPending) {
|
||||
const ctx = document.getElementById('myChart');
|
||||
|
||||
if (myChart) {
|
||||
myChart.destroy();
|
||||
}
|
||||
|
||||
myChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['总完成数', '待完成数'],
|
||||
datasets: [{
|
||||
label: '任务数量',
|
||||
data: [totalCompleted, claimedPending],
|
||||
backgroundColor: [
|
||||
'rgba(75, 192, 192, 0.5)',
|
||||
'rgba(255, 159, 64, 0.5)'
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(75, 192, 192, 1)',
|
||||
'rgba(255, 159, 64, 1)'
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染趋势图
|
||||
function renderTrendChart(trend) {
|
||||
const ctx = document.getElementById('trendChart');
|
||||
|
||||
if (trendChart) {
|
||||
trendChart.destroy();
|
||||
}
|
||||
|
||||
const labels = trend.map(t => t.date);
|
||||
const data = trend.map(t => t.count);
|
||||
|
||||
trendChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: '完成数',
|
||||
data: data,
|
||||
fill: true,
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
tension: 0.4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染排行榜
|
||||
function renderLeaderboard(leaderboard) {
|
||||
const tbody = $('#leaderboard');
|
||||
tbody.empty();
|
||||
|
||||
if (leaderboard.length === 0) {
|
||||
tbody.append('<tr><td colspan="3" class="text-center">暂无数据</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentUser = JSON.parse(localStorage.getItem('current_user'));
|
||||
|
||||
leaderboard.forEach(function(row) {
|
||||
const isCurrentUser = row.user_id === currentUser.id;
|
||||
const rowClass = isCurrentUser ? 'table-primary' : '';
|
||||
|
||||
let rankBadge = '';
|
||||
if (row.rank === 1) {
|
||||
rankBadge = '<i class="bi bi-trophy-fill text-warning"></i> ';
|
||||
} else if (row.rank === 2) {
|
||||
rankBadge = '<i class="bi bi-trophy-fill text-secondary"></i> ';
|
||||
} else if (row.rank === 3) {
|
||||
rankBadge = '<i class="bi bi-trophy-fill text-danger"></i> ';
|
||||
}
|
||||
|
||||
tbody.append(`
|
||||
<tr class="${rowClass}">
|
||||
<td>${rankBadge}${row.rank}</td>
|
||||
<td>${row.username}${isCurrentUser ? ' <span class="badge bg-primary">我</span>' : ''}</td>
|
||||
<td><strong>${row.completed_count}</strong></td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
function exportData() {
|
||||
const dateFrom = $('#date-from').val();
|
||||
const dateTo = $('#date-to').val();
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
let url = '/api/statistics/export?group_id=1';
|
||||
if (dateFrom) url += `&date_from=${dateFrom}`;
|
||||
if (dateTo) url += `&date_to=${dateTo}`;
|
||||
|
||||
// 创建隐藏的下载链接
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `statistics_${new Date().toISOString().split('T')[0]}.csv`;
|
||||
|
||||
// 添加认证头(通过fetch实现)
|
||||
fetch(url, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token
|
||||
}
|
||||
})
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `statistics_${new Date().toISOString().split('T')[0]}.csv`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
showSuccess('数据导出成功!');
|
||||
})
|
||||
.catch(error => {
|
||||
showError('导出失败:' + error.message);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user