/** * channel-dashboard.js * 频道数据面板逻辑·图表渲染 */ const ChannelDashboard = (function() { // 配色方案 const COLORS = ['#4fc3f7', '#ffb74d', '#ff8a80', '#aed581', '#ba68c8', '#4dd0e1', '#ffd54f']; // 柱状图渲染 function renderBarChart() { const container = document.getElementById('visitBarChart'); if (!container) return; const data = ChannelAnalytics.getAllData(); const modules = data.modules; const keys = Object.keys(modules); if (keys.length === 0) { container.innerHTML = '

暂无数据,多点几个模块再来看

'; return; } const maxVisits = Math.max.apply(null, keys.map(function(k) { return modules[k].visits; })) || 1; let html = ''; keys.forEach(function(id, i) { const mod = modules[id]; const height = Math.max(4, (mod.visits / maxVisits) * 180); const color = COLORS[i % COLORS.length]; html += '
' + '' + mod.visits + '' + '
' + '' + id.replace('m-', '') + '' + '
'; }); container.innerHTML = html; } // 饼图渲染 function renderPieChart() { const canvas = document.getElementById('pieCanvas'); const legendEl = document.getElementById('pieLegend'); if (!canvas || !legendEl) return; const ctx = canvas.getContext('2d'); const data = ChannelAnalytics.getAllData(); const modules = data.modules; const keys = Object.keys(modules); const total = keys.reduce(function(sum, k) { return sum + (modules[k].totalDuration || 0); }, 0); // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); if (total === 0 || keys.length === 0) { ctx.fillStyle = '#333'; ctx.beginPath(); ctx.arc(80, 80, 70, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#666'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('暂无数据', 80, 84); legendEl.innerHTML = ''; return; } let startAngle = -Math.PI / 2; let legendHtml = ''; keys.forEach(function(id, i) { const mod = modules[id]; const pct = mod.totalDuration / total; const sweep = pct * Math.PI * 2; const color = COLORS[i % COLORS.length]; ctx.beginPath(); ctx.moveTo(80, 80); ctx.arc(80, 80, 70, startAngle, startAngle + sweep); ctx.closePath(); ctx.fillStyle = color; ctx.fill(); startAngle += sweep; const minutes = Math.round(mod.totalDuration / 60); const pctStr = Math.round(pct * 100); legendHtml += '
  • ' + id.replace('m-', '') + ' ' + pctStr + '% (' + minutes + '分钟)
  • '; }); legendEl.innerHTML = legendHtml; } // 折线图渲染 function renderLineChart() { const canvas = document.getElementById('lineCanvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const trend = ChannelAnalytics.getWeeklyTrend(); const w = canvas.width; const h = canvas.height; ctx.clearRect(0, 0, w, h); const maxVal = Math.max.apply(null, trend.map(function(t) { return t.visits; })) || 1; const padLeft = 40; const padBottom = 30; const padTop = 10; const chartW = w - padLeft - 20; const chartH = h - padBottom - padTop; const stepX = chartW / (trend.length - 1 || 1); // 网格线 ctx.strokeStyle = '#2a2a4a'; ctx.lineWidth = 1; for (let g = 0; g <= 4; g++) { let gy = padTop + (chartH / 4) * g; ctx.beginPath(); ctx.moveTo(padLeft, gy); ctx.lineTo(w - 20, gy); ctx.stroke(); } // 折线 ctx.strokeStyle = '#4fc3f7'; ctx.lineWidth = 2; ctx.beginPath(); trend.forEach(function(t, i) { let x = padLeft + stepX * i; let y = padTop + chartH - (t.visits / maxVal) * chartH; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); }); ctx.stroke(); // 数据点 trend.forEach(function(t, i) { let x = padLeft + stepX * i; let y = padTop + chartH - (t.visits / maxVal) * chartH; ctx.beginPath(); ctx.arc(x, y, 4, 0, Math.PI * 2); ctx.fillStyle = '#4fc3f7'; ctx.fill(); }); // 标签 ctx.fillStyle = '#aaa'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center'; trend.forEach(function(t, i) { let x = padLeft + stepX * i; let y = padTop + chartH + 15; ctx.fillText(t.date.slice(5), x, y); }); } // 性能表格渲染 function renderPerfTable() { const tbody = document.getElementById('perfTableBody'); if (!tbody) return; const data = ChannelAnalytics.getAllData(); const modules = data.modules; const keys = Object.keys(modules); if (keys.length === 0) { tbody.innerHTML = '暂无数据'; return; } let html = ''; keys.forEach(function(id) { const mod = modules[id]; const avgLoad = mod.loadTimes.length ? Math.round(mod.loadTimes.reduce((a, b) => a + b, 0) / mod.loadTimes.length) : 0; const minutes = Math.round(mod.totalDuration / 60); const statusClass = avgLoad > 300 ? 'slow' : ''; const statusText = avgLoad > 300 ? '⚠️ 慢' : '✅ 正常'; html += '' + '' + id.replace('m-', '') + '' + '' + mod.visits + '' + '' + minutes + '分钟' + '' + avgLoad + 'ms' + '' + statusText + '' + ''; }); tbody.innerHTML = html; } // 公开方法 return { render: function() { renderBarChart(); renderPieChart(); renderLineChart(); renderPerfTable(); }, clearData: function() { if (confirm('确定清除所有统计数据吗?')) { ChannelAnalytics.clearAll(); this.render(); console.log('🗑️ 数据已清除,图表已重置'); } } }; })();