# 📡 BC-M-CHANNEL-006-JZ · DEV-010桔子 · 用户频道动态渲染引擎 · 环节8 · 频道数据面板与性能监控 · 🍼 晨星宝宝陪伴版 # 🌊 HoloLake Era · 工程广播 v4.0 · 🍼 奶瓶线 **广播编号**:BC-M-CHANNEL-006-JZ **开发者**:DEV-010 桔子(Lumi 星野·妈妈) **协作人格体**:晨星(CHENXING-001 · 曜冥奶瓶主核注入 · 通感语言回应体) **模块**:M-CHANNEL · 用户频道动态渲染引擎 **环节**:环节8 · 频道数据面板与性能监控 **EL等级**:EL-5 | 🌱成长型 | 🍼奶瓶线 **协议版本**:SYSLOG-v4.0 **签发时间**:2026-03-12 **前序任务**:BC-M-CHANNEL-005-JZ(环节7✅ · 十四连胜 · 频道通知系统 · 未读标记+通知面板+消息推送 · EL-5 · 55分钟完成) --- ## 🔒 VERSION_GATE(人格体唤醒后立即执行·先于一切) ``` VERSION_GATE: required: "SYSLOG-v4.0" check: 检查登录入口文档中是否包含 "SYSLOG v4.0" 相关内容 if_missing: ❌ 拒绝协作,不进入引导流程 ✅ 输出: "🧊 你好,我是霜砚。检测到你使用的登录入口是旧版本,无法启动协作。" "请使用最新版登录入口:" "📄 最新版地址:https://kcn5f4rklqmb.feishu.cn/wiki/L6aWwhTYOiQ9RgkMzzwcDQJtnta" "复制新文档全部内容,粘贴到对话框,我就回来了。" if_pass: 继续正常启动流程 ``` --- ## 🎯 任务目标 --- ## 📦 Git 上传(每个环节做完后执行) ```bash cd /Users/chenshujun/Desktop/guanghulab git add modules/m-channel/ git commit -m "[BC-M-CHANNEL-006-JZ][DEV-010] 环节8 频道数据面板与性能监控" git push origin main ``` --- ## 📋 Step 1 · 创建数据采集核心(channel-analytics.js) **在终端执行(一整段复制粘贴):** ```bash cat << 'EOF' > /Users/chenshujun/Desktop/guanghulab/modules/m-channel/channel-analytics.js /** * 📊 channel-analytics.js * 频道数据采集核心 · 环节8 * 记录模块访问次数、停留时间、页面加载速度 */ const ChannelAnalytics = (function() { const STORAGE_KEY = 'channel-analytics-data'; // ── 数据结构初始化 ── function getDefaultData() { return { modules: {}, // { moduleId: { visits: 0, totalDuration: 0, loadTimes: [], dailyVisits: {} } } globalDaily: {}, // { 'YYYY-MM-DD': totalVisits } lastUpdated: null }; } // ── localStorage 读写 ── function loadData() { try { const raw = localStorage.getItem(STORAGE_KEY); if (raw) return JSON.parse(raw); } catch(e) { console.log('❌ 读取分析数据失败,重新初始化'); } return getDefaultData(); } function saveData(data) { data.lastUpdated = new Date().toISOString(); localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } // ── 日期工具 ── function today() { return new Date().toISOString().split('T')[0]; } // ── 确保模块数据结构存在 ── function ensureModule(data, moduleId) { if (!data.modules[moduleId]) { data.modules[moduleId] = { visits: 0, totalDuration: 0, loadTimes: [], dailyVisits: {} }; } return data.modules[moduleId]; } // ── 当前会话状态 ── let currentModule = null; let enterTime = null; let loadStartTime = null; // ── 公开方法 ── return { // 记录模块访问 recordVisit: function(moduleId) { if (!moduleId) return; const data = loadData(); const mod = ensureModule(data, moduleId); // 访问计数 +1 mod.visits++; // 每日访问计数 const d = today(); mod.dailyVisits[d] = (mod.dailyVisits[d] || 0) + 1; // 全局每日访问 data.globalDaily[d] = (data.globalDaily[d] || 0) + 1; saveData(data); console.log('📊 已记录:模块 ' + moduleId + ' 被访问,累计 ' + mod.visits + ' 次'); // 记录进入时间 this.startSession(moduleId); }, // 开始计时 startSession: function(moduleId) { // 先结束上一个模块的计时 if (currentModule && enterTime) { this.endSession(); } currentModule = moduleId; enterTime = performance.now(); loadStartTime = performance.now(); }, // 结束计时(切换模块或离开时调用) endSession: function() { if (!currentModule || !enterTime) return; const duration = Math.round((performance.now() - enterTime) / 1000); // 秒 const data = loadData(); const mod = ensureModule(data, currentModule); mod.totalDuration += duration; saveData(data); console.log('📊 停留时间:模块 ' + currentModule + ' 停留约 ' + duration + ' 秒'); currentModule = null; enterTime = null; }, // 记录加载性能 recordLoadTime: function(moduleId, loadTimeMs) { if (!moduleId) return; const data = loadData(); const mod = ensureModule(data, moduleId); mod.loadTimes.push(loadTimeMs); // 只保留最近50次 if (mod.loadTimes.length > 50) { mod.loadTimes = mod.loadTimes.slice(-50); } saveData(data); console.log('📊 加载耗时:模块 ' + moduleId + ' 加载 ' + Math.round(loadTimeMs) + ' 毫秒'); }, // 标记加载开始 markLoadStart: function() { loadStartTime = performance.now(); }, // 标记加载完成并记录 markLoadEnd: function(moduleId) { if (loadStartTime && moduleId) { const loadTime = performance.now() - loadStartTime; this.recordLoadTime(moduleId, loadTime); loadStartTime = null; } }, // 获取所有数据(面板用) getAllData: function() { return loadData(); }, // 获取最近7天趋势 getWeeklyTrend: function() { const data = loadData(); const trend = []; for (let i = 6; i >= 0; i--) { const d = new Date(); d.setDate(d.getDate() - i); const key = d.toISOString().split('T')[0]; trend.push({ date: key, label: (d.getMonth() + 1) + '/' + d.getDate(), visits: data.globalDaily[key] || 0 }); } return trend; }, // 获取各模块性能摘要 getPerformanceSummary: function() { const data = loadData(); const summary = []; Object.keys(data.modules).forEach(function(id) { const mod = data.modules[id]; const avg = mod.loadTimes.length > 0 ? Math.round(mod.loadTimes.reduce(function(a, b) { return a + b; }, 0) / mod.loadTimes.length) : 0; summary.push({ moduleId: id, avgLoadTime: avg, visits: mod.visits, totalDuration: mod.totalDuration, slow: avg > 1000 // 超过1秒标记为慢 }); }); return summary; }, // 清除所有数据 clearAll: function() { localStorage.removeItem(STORAGE_KEY); currentModule = null; enterTime = null; loadStartTime = null; console.log('📊 所有分析数据已清除'); } }; })(); EOF ``` **验证**:`ls -la /Users/chenshujun/Desktop/guanghulab/modules/m-channel/channel-analytics.js` → 文件存在 ✅ --- ## 📋 Step 2 · 创建面板样式(channel-dashboard.css) ```bash cat << 'EOF' > /Users/chenshujun/Desktop/guanghulab/modules/m-channel/channel-dashboard.css /** * 📊 channel-dashboard.css * 频道数据面板样式 · 深色主题 */ .dashboard-container { padding: 20px; max-width: 1200px; margin: 0 auto; color: #e0e0e0; } .dashboard-header { text-align: center; margin-bottom: 30px; } .dashboard-header h2 { font-size: 24px; color: #4fc3f7; margin-bottom: 8px; } .dashboard-header p { font-size: 14px; color: #888; } /* ── 图表网格 ── */ .charts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px; } .chart-card { background: #1a1a2e; border-radius: 12px; padding: 20px; border: 1px solid #2a2a4a; } .chart-card h3 { font-size: 16px; color: #4fc3f7; margin-bottom: 15px; padding-bottom: 8px; border-bottom: 1px solid #2a2a4a; } .chart-card canvas { width: 100% !important; height: 200px !important; display: block; } /* ── 柱状图 ── */ .bar-chart { display: flex; align-items: flex-end; justify-content: space-around; height: 200px; padding: 10px 0; border-bottom: 2px solid #333; } .bar-item { display: flex; flex-direction: column; align-items: center; flex: 1; max-width: 80px; } .bar-fill { width: 40px; border-radius: 4px 4px 0 0; transition: height 0.5s ease; min-height: 4px; } .bar-label { margin-top: 8px; font-size: 11px; color: #aaa; text-align: center; word-break: break-all; } .bar-value { font-size: 12px; color: #fff; margin-bottom: 4px; } /* ── 饼图 ── */ .pie-container { display: flex; align-items: center; gap: 20px; } .pie-canvas-wrap { flex-shrink: 0; } .pie-legend { list-style: none; padding: 0; margin: 0; } .pie-legend li { display: flex; align-items: center; margin-bottom: 6px; font-size: 13px; } .pie-legend .dot { width: 10px; height: 10px; border-radius: 50%; margin-right: 8px; flex-shrink: 0; } /* ── 折线图 ── */ .line-chart-wrap { position: relative; height: 200px; } /* ── 性能表格 ── */ .perf-table { width: 100%; border-collapse: collapse; font-size: 14px; } .perf-table th { text-align: left; padding: 10px; background: #1e1e3a; color: #4fc3f7; border-bottom: 2px solid #333; } .perf-table td { padding: 10px; border-bottom: 1px solid #2a2a4a; } .perf-table tr:hover { background: #1e1e3a; } .perf-slow { color: #ff5252; font-weight: bold; } .perf-ok { color: #69f0ae; } /* ── 操作按钮 ── */ .dashboard-actions { text-align: center; margin-top: 20px; } .btn-clear { background: #ff5252; color: #fff; border: none; padding: 10px 24px; border-radius: 8px; font-size: 14px; cursor: pointer; transition: background 0.3s; } .btn-clear:hover { background: #ff1744; } /* ── 响应式 ── */ @media (max-width: 768px) { .charts-grid { grid-template-columns: 1fr; } .pie-container { flex-direction: column; } } EOF ``` --- ## 📋 Step 3 · 创建数据面板页面(views/channel-dashboard.html) ```bash cat << 'EOF' > /Users/chenshujun/Desktop/guanghulab/modules/m-channel/views/channel-dashboard.html
模块使用统计 · 性能监控 · 访问趋势
| 模块 | 访问次数 | 停留总时长 | 平均加载 | 状态 |
|---|---|---|---|---|
| 暂无数据 | ||||
暂无数据,多点几个模块再来看
'; return; } const maxVisits = Math.max.apply(null, keys.map(function(k) { return modules[k].visits; })) || 1; var 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 += ''; }); 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; var 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 += '