// Admin Overview Statistics Module (ESM) // Depends on globals: axios, Chart, window.API_BASE, window.authToken let trendChartInstance = null; function getStatsRangeParams() { const s = document.getElementById('statsStartDate')?.value || ''; const e = document.getElementById('statsEndDate')?.value || ''; const params = new URLSearchParams(); if (s) params.set('startDate', s); if (e) params.set('endDate', e); const qs = params.toString(); return qs ? `?${qs}` : ''; } function updateStatsRangeHint() { const s = document.getElementById('statsStartDate')?.value || ''; const e = document.getElementById('statsEndDate')?.value || ''; const el = document.getElementById('statsRangeHint'); if (!el) return; if (!s && !e) { el.textContent = '当前筛选:全部'; return; } if (s && e) { el.textContent = `当前筛选:${s} - ${e}`; } else if (s) { el.textContent = `当前筛选:自 ${s} 起`; } else { el.textContent = `当前筛选:截至 ${e}`; } } async function loadDetailedStats() { try { const range = getStatsRangeParams(); const response = await axios.get(`${window.API_BASE}/admin/stats/detailed${range}`, { headers: { Authorization: `Bearer ${window.authToken}` } }); const stats = response.data; if (document.getElementById('documentsThisWeek')) { document.getElementById('documentsThisWeek').textContent = stats.basic.documentsThisWeek || '-'; } if (document.getElementById('documentsThisMonth')) { document.getElementById('documentsThisMonth').textContent = stats.basic.documentsThisMonth || '-'; } if (document.getElementById('totalStorageMB')) { document.getElementById('totalStorageMB').textContent = (stats.basic.totalStorageMB || 0) + ' MB'; } displayDocumentsByStatus(stats.documentsByStatus || []); displayTopUsers(stats.topUsers || []); updateStatsRangeHint(); } catch (error) { console.error('Failed to load detailed stats:', error); } } function displayDocumentsByStatus(statusData) { const container = document.getElementById('documentsByStatus'); if (!container) return; const statusColors = { 'PENDING': 'bg-gray-100 text-gray-800', 'PROCESSING': 'bg-blue-100 text-blue-800', 'OCR_COMPLETED': 'bg-yellow-100 text-yellow-800', 'TRANSLATION_COMPLETED': 'bg-purple-100 text-purple-800', 'COMPLETED': 'bg-green-100 text-green-800', 'FAILED': 'bg-red-100 text-red-800' }; const statusNames = { 'PENDING': '待处理', 'PROCESSING': '处理中', 'OCR_COMPLETED': 'OCR完成', 'TRANSLATION_COMPLETED': '翻译完成', 'COMPLETED': '已完成', 'FAILED': '失败' }; container.innerHTML = statusData.map(item => `
${statusNames[item.status] || item.status}
${item.count}
`).join(''); } function displayTopUsers(topUsers) { const tbody = document.getElementById('topUsersList'); if (!tbody) return; if (topUsers.length === 0) { tbody.innerHTML = ` 暂无数据 `; return; } const escapeHtml = (text) => { if (!text) return '-'; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }; tbody.innerHTML = topUsers.map((user, index) => ` ${index + 1} ${escapeHtml(user.name || '-')} ${escapeHtml(user.email)} ${user.documentCount} `).join(''); } async function loadTrendsChart() { try { const s = document.getElementById('statsStartDate')?.value; const e = document.getElementById('statsEndDate')?.value; let url = `${window.API_BASE}/admin/stats/trends?days=30`; if (s || e) { const params = new URLSearchParams(); if (s) params.set('startDate', s); if (e) params.set('endDate', e); url = `${window.API_BASE}/admin/stats/trends?${params.toString()}`; } const response = await axios.get(url, { headers: { Authorization: `Bearer ${window.authToken}` } }); const trends = response.data; const ctx = document.getElementById('trendChart'); if (!ctx) return; if (trendChartInstance) trendChartInstance.destroy(); trendChartInstance = new Chart(ctx, { type: 'line', data: { labels: trends.map(t => { const date = new Date(t.date); return `${date.getMonth() + 1}/${date.getDate()}`; }), datasets: [ { label: '总处理量', data: trends.map(t => t.total), borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', tension: 0.4 }, { label: '成功', data: trends.map(t => t.completed), borderColor: 'rgb(34, 197, 94)', backgroundColor: 'rgba(34, 197, 94, 0.1)', tension: 0.4 }, { label: '失败', data: trends.map(t => t.failed), borderColor: 'rgb(239, 68, 68)', backgroundColor: 'rgba(239, 68, 68, 0.1)', tension: 0.4 }, ] }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { position: 'top' }, tooltip: { mode: 'index', intersect: false } }, scales: { y: { beginAtZero: true, ticks: { precision: 0 } } } } }); } catch (error) { console.error('Failed to load trends chart:', error); } } async function applyStatsRange() { await loadDetailedStats(); await loadTrendsChart(); } async function clearStatsRange() { const s = document.getElementById('statsStartDate'); const e = document.getElementById('statsEndDate'); if (s) s.value = ''; if (e) e.value = ''; await applyStatsRange(); } export async function initStats() { // 挂到 window 以兼容现有 onclick window.applyStatsRange = applyStatsRange; window.clearStatsRange = clearStatsRange; await loadDetailedStats(); await loadTrendsChart(); }