// ==UserScript== // @name 华为路由器增强 HUAWEI-Stat_Max // @name:en Bro-Stat_HUAWEI // @namespace ucxn // @version 5.9.7 // @description 哥哥科技 QQ群 680464365 // @description:en https://github.com/ucxn/Bro-Stat // @author 哥哥科技 space.bilibili.com/501430041 // @noframes // @tag 路由器 华为 网络 监控 统计 数据 可视化 极客 增强 UI HA 智能 定时 后台 // @icon https://scriptcat.org/api/v2/resource/image/PD6xhxddlUESIwAV // @include /^https?:\/\/10(\.[0-9]{1,3}){3}(:\d+)?\/.*$/ // @include http://192.168.*.* // @include http://172.16.* // @include https://192.168.*.* // @include https://172.16.* // @exclude *://*/cgi-bin/luci* // @grant GM_setValue // @grant GM_getValue // @storageName GBNPA_Storage // @license AGPL-3.0-or-later // @run-at document-start // ==/UserScript== (function () { 'use strict'; console.log("🚀 哥哥科技 V5.9.9 引擎已装载..."); const ESC_MAP = { '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }; function escapeHTML(str) { return str ? String(str).replace(/[&<>'"]/g, m => ESC_MAP[m]) : ''; } // ======== [0] 用户极客环境变量配置区 ======== const CONFIG = { readSaveData: 1, // 【历史记录】 1: 从路由器后台读档 | 0: 新局模式 | 2: 从本地长期历史读档 uiLayout: 1, // 【面板拓扑结构】 0: 经典版 | 1: 详细紧凑版(驾驶舱美学) | 2: 详细平铺版(报表流美学) injectMode: 1, // 【UI注入模式】 0: 原生侧边栏(1min)| 1: 优先,10秒悬浮舱(D)| 2: 联动模式| 3:强制模式 calcMode: 1, // 1: 上行/下行倍数模式, 0: 上行占总和比例模式 lanPortMode: 1, // 【物理网口】 0: 关闭 | 1: 底部追加显示 | 2: WAN高速接管主线 portInterval: 1, // 物理网口刷新频率(秒) ratioExtremeUp: 10, // 极端上传判定阈值 (> 1000%) ratioWarnUp: 0.07, // 重度上传警告阈值 (> 7%) ratioExtremeDown: 0.01, // 极端下载判定阈值 (< 1%) ratioThreshold: 7, // (仅calcMode=0时有效) 上传占比报警阈值(%) lanRefreshInterval: 2, // LAN口刷新时间(秒),用于精准补偿0到唤醒时的瞬时流量 wanRefreshInterval: 2, // 【新增】WAN口刷新时间(秒),用于精准补偿0到唤醒时的瞬时流量 宽带最大外网上行速率: 3e8, 宽带最大外网下行速率: 24e8, // 配置外网最大上传|下载比特(bit/bps)速率,请略微大于真实值;500兆为5e8,一千兆1e9 portMap: { "eth1": "网口 1", "eth2": "网口 2", "eth3": "网口 3", "eth4": "网口 4", "wl0": "2.4G", "wl1": "5.2G", "wl2": "5.8G" } }; const S = { wInstUp: 0, wInstDn: 0, wTotUp: 0, wTotDn: 0, cls: {}, isPinned: !0, w2U: 0, w2D: 0, w2TotUp: 0, w2TotDn: 0, w2LT: undefined, hasW2: !1, is5G_149: null, fI: 0, _domRebuilt: !1, _lastPanelState: null, oDC: null }; async function gWT() { try { let r = await fetch('/api/ntwk/wan?type=active&_=' + Date.now()); if (r.ok) return await r.text(); } catch(e) {console.warn(e)} return ""; } const Phys = { p: Object.create(null), wU: undefined, wD: undefined, tU: 0, tD: 0, lT: undefined, _pM: null, _wID: null }; let isF = !1, lCxt = null; function fB(bps) { if (bps > 1e9) return `${Math.round(bps * 1e-6)} Mbit/s`; if (bps > 1e6) return `${(bps * 1e-6).toFixed(2)} Mbps`; if (bps > 1e3) return `${Math.round(bps * 1e-3)} Kbps`; return `${Math.round(bps)} bps`; } function fBy(bps) { return bps === 0 ? '0 B' : ((bps * 0.000125) > 1023.9 ? `${(bps * 1.220703125e-7).toFixed(2)} MiB/s` : `${(bps * 0.000125) | 0} KB/s`); } function fV(bits) { if (bits > 83886080000) return `${(bits / 8589934592).toFixed(4)} GiB`; if (bits > 8388608000) return `${(bits / 8388608).toFixed(1)} MiB`; if (bits > 8388608) return `${(bits / 8388608).toFixed(4)} MiB`; if (bits > 8192) return `${(bits / 8192).toFixed(2)} KiB`; return `${Math.round(bits / 8)} B`; } function fVD(bitsIntegral, bitsOfficial) { if (bitsIntegral > 8796093022208) return `${(bitsOfficial / 8796093022208).toFixed(4)} | ${(bitsIntegral / 8796093022208).toFixed(4)} TiB`; if (bitsIntegral >= 8589934592) return `${(bitsOfficial / 8589934592).toPrecision(5)} | ${(bitsIntegral / 8589934592).toPrecision(5)} GiB`; if (bitsIntegral > 8388608) return `${(bitsOfficial / 8388608).toFixed(3)} | ${(bitsIntegral / 8388608).toFixed(3)} MiB`; if (bitsIntegral > 8192) return `${(bitsOfficial / 8192).toFixed(2)} | ${(bitsIntegral / 8192).toFixed(2)} KiB`; return `${Math.round(bitsOfficial / 8)} | ${Math.round(bitsIntegral / 8)} B`;} function fSV(bits) { if (bits >= 84607500288) return `${(bits / 8589934592).toPrecision(4)}G`; if (bits > 8388608000) return `${Math.round(bits / 8388608)}M`; if (bits > 8388608) return `${(bits / 8388608).toPrecision(4)}M`; if (bits >= 8192) return `${(bits / 8192).toFixed(1)}K`; return `${Math.round(bits / 8)}B`;} function fOT(totalSec) { totalSec = totalSec | 0; if (totalSec < 0) return ""; const d = (totalSec / 86400) | 0; let r = totalSec - d * 86400; const h = (r / 3600) | 0; r = r - h * 3600; const m = (r / 60) | 0; const s = r - m * 60; return d > 0 ? `${d}天${h}时${m}分${s}秒` : `${h}小时${m}分${s}秒`;} function nM(m) { return m ? m.trim().toLowerCase().replaceAll('-', ':') : ''; } const st = document.createElement('style'); st.innerHTML = `.config-item{ clear:both;}.config-item-box{display:flex!important; align-items:stretch!important;padding-bottom: 12px!important;}.config-item .logo{width:33%!important; float:none!important;display:flex!important;flex-direction:row;}.config-item .dev-intro{flex:1;display:flex!important;flex-direction:column;justify-content:flex-start;min-height:100px;padding-bottom:0!important;margin-bottom:0!important;}.config-item .info{width:27%!important;float:none!important;display:flex!important;flex-direction:column;justify-content:flex-start;padding:0 10px!important;border-right:1px solid #eee;}.config-item .speed{width:40%!important;float:none!important;display:flex!important;flex-direction:column;justify-content:center;padding:0 10px!important;}.geek-row{display:flex;justify-content:space-between;align-items:center;white-space:nowrap;height:20px;} .geek-label{width:110px;color:#333;font-weight:bold;}.geek-val-box{flex:1;display:flex;gap:15px;margin-left:10px;}.geek-fixed-width{display:inline-block;width:120px;}.geek-right-box{text-align:right;min-width:220px;font-weight:bold;}.c-up{color:#ff4c00;}.c-down{color:#0059fa;}.gege-up-box,.gege-down-box{margin-top:auto!important;margin-bottom:0!important;width:95%;}.gege-ratio-box{margin-top:10px;width:95%;margin-bottom:5px;}.t-row{font-size:12px;font-weight:bold;margin-bottom:2px;display:flex;justify-content:space-between;font-family:Consolas;}.zte-thin-bar{width:100%;height:3px;background:rgba(0,0,0,0.05);border-radius:1.5px;overflow:hidden;}.zte-thin-bar-inner{height:100%;transition:width 0.5s ease-out;}.zte-thin-bar-inner.up{background:#ff4c00;}.zte-thin-bar-inner.down{background:#0059fa;}.gege-ratio-top{display:flex;justify-content:space-between;font-size:12px;font-weight:bold;margin-bottom:2px;}.gege-ratio-bar{width:100%;height:4px;background:#0059fa;border-radius:2px;overflow:hidden;}.gege-ratio-bar-inner{height:100%;background:#ff4c00;transition:width 0.5s ease-out;}.zte-enhance-speed{display:flex;flex-direction:column;gap:6px;width:100%;font-family:Consolas;} .zte-bar-wrap{position:relative;width:100%;border-radius:4px;border:1px solid;font-size:13px;font-weight:bold;overflow:hidden;padding:3px 8px;display:flex;justify-content:space-between;align-items:center;z-index:1;box-sizing:border-box;}.zte-bar-wrap span{font-size:inherit;font-weight:inherit;}.zte-bar-up{color:#ff4c00;border-color:rgba(255,76,0,0.3);}.zte-bar-down{color:#0059fa;border-color:rgba(0,89,250,0.3);}.zte-bar-up::before{content:'';position:absolute;left:0;top:0;bottom:0;z-index:-1;background:rgba(255,76,0,0.12);width:var(--p-up,0%);transition:width 0.5s;}.zte-bar-down::before{content:'';position:absolute;left:0;top:0;bottom:0;z-index:-1;background:rgba(0,89,250,0.12);width:var(--p-down,0%);transition:width 0.5s;}#config-list.gege-list-container{contain:content!important;background-color:#ffffff!important;border-radius:8px!important;border:1px solid #e0e0e0!important;padding:20px 30px!important;box-shadow:0 2px 10px rgba(0,0,0,0.02)!important;margin-top:10px!important;}.gege-section{margin-bottom:10px;} .gege-section:last-child{margin-bottom:0;}.gege-list-container .config-title{font-size:16px!important;font-weight:bold!important;color:#333!important;margin:15px 0 10px 0!important;padding-bottom:5px!important;}.gege-list-container .gege-section:first-child .config-title{margin-top:0!important;}.gege-empty-state{color:#999!important;font-size:14px!important;padding:0 0 15px 5px!important;border-bottom:1px solid #f0f0f0!important;margin-bottom:5px!important;}.gege-list-item{background-color:transparent!important;border-bottom:1px solid #f0f0f0!important;padding:15px 10px!important;margin-bottom:0!important;border-radius:0!important;} .gege-list-item:last-child{border-bottom:none!important;}#zte-geek-board{contain:content;background-color:transparent!important;border-left:4px solid #0059fa!important;border-radius:0!important;padding:5px 0 5px 15px!important;margin:10px 0 15px 0!important;box-shadow:none!important;border-bottom:1px solid #f0f0f0!important;font-size:14px;display:flex;flex-direction:column;gap:6px;padding-bottom:15px!important;}#gege-global-overlay #zte-geek-board.geek-frozen-pane{position:sticky!important;top:0px!important;z-index:100!important;background-color:#f3f4f5!important;margin-top:0!important;padding-top:15px!important;box-shadow:0 10px 15px -3px rgba(0,0,0,0.05)!important;border-radius:0 0 8px 8px!important;}.gege-pin{cursor:pointer;font-size:11px;filter:grayscale(100%);opacity:0.5;transition:transform 0.2s;margin-left:2px;} .gege-pin.active{filter:none;opacity:1;transform:scale(1.1);}#gege-global-overlay{position:fixed;top:7.5%;right:0;bottom:0;background:#f3f4f5;z-index:9999;overflow-y:auto;padding-bottom:50px;left:0!important;border-radius:16px 16px 0 0;box-shadow:0 -5px 25px rgba(0,0,0,0.15);transition:top 0.3s ease;}@media (max-width: 768px){.geek-right-box:has(#gb-wan-zero-up),.geek-right-box:has(#gb-cur-up-vol){display:none!important}.gege-list-item{padding:12px 10px!important}.config-item-box{position:relative!important;flex-direction:column!important;padding-bottom:0!important}.config-item .info,.config-item .logo,.config-item .speed{width:100%!important;border:none!important;padding:0!important;position:static!important}.config-item .dev-intro{min-height:auto!important;justify-content:center!important;padding-right:90px!important}.config-item .logo{padding-bottom:4px!important}.config-item .info{flex-direction:column!important;margin:0 0 6px 0!important;gap:2px!important}.dev-ip{position:absolute!important;top:0!important;right:0!important;font-size:11px!important;background:rgba(0,89,250,0.08);color:#0059fa!important;padding:2px 6px!important;border-radius:4px;font-weight:bold;line-height:1.2;z-index:10;width:auto!important}.dev-number{width:auto!important;margin:0!important;font-size:11px!important}.gege-ratio-box{width:100%!important;margin-top:2px!important;margin-bottom:0!important}.gege-down-box{width:100%!important;margin-top:2px!important}#zte-geek-board{padding:8px!important;gap:0!important;font-size:11.5px!important}.geek-row{height:auto!important;flex-wrap:wrap!important;margin-bottom:4px!important;justify-content:flex-start!important;gap:2px 6px!important;line-height:1.3!important}.geek-label{width:auto!important;min-width:60px!important;font-size:11.5px!important;flex:0 0 auto!important}.geek-val-box{width:auto!important;flex:1 1 0%!important;display:flex!important;flex-wrap:wrap!important;margin-left:0!important;gap:2px 6px!important}.geek-fixed-width{width:auto!important}.geek-right-box{width:100%!important;flex:0 0 100%!important;text-align:left!important;font-size:11.5px!important;margin-top:2px!important;margin-left:0!important}.gege-list-container{padding:8px!important}.zte-enhance-speed{gap:4px!important}}`; document. head. appendChild(st); window.gegeRenderedMacs = new Set(); async function rSD(pWT = null, sT = null) { if (isF && pWT === null) return; isF = !0; let n, wT = ""; try { if (pWT !== null) { wT = pWT; n = sT || performance.now(); } else { wT = await gWT(); n = performance.now(); } window.__gLWT = wT; window.__gLWT_t = n; // 保障解耦模式全局缓存不丢失 const wI = wT ? (JSON.parse(wT) || {}) : {}; S.hasW2 = !1; let cWU = (+wI.UpBandwidth || 0) * 8000, cWD = (+wI.DownBandwidth || 0) * 8000, cI = Object.create(null); let cSU = 0, cSD = 0; (lCxt ? (JSON.parse(lCxt) || []) : []).forEach(d => { if (d.MACAddress) { if (!d.Active) return; // 华为特性:防死设备 let m = nM(d.MACAddress), u = (+d.UpRate || 0) * 8000, dn = (+d.DownRate || 0) * 8000, uT = (+d.TxKBytes || 0) * 8000, dT = (+d.RxKBytes || 0) * 8000; let bN = d.ActualName || d.HostName || "未知设备"; let ifc = d.InterfaceType || ""; if (ifc !== '5GHz' && ifc !== '2.4GHz' && ifc !== 'DC') ifc = d.Layer2Interface || ifc; cI[m] = { upRate: u, dnRate: dn, iface: ifc, // [修复] 将计算好的 ifc 真正赋给字典 offUp: uT, offDn: dT, aRec: d.Active ? d.AccessRecord : null, name: bN, ip: d.IPAddress || "", // [优化] 不再盲目 new Date() rssi: d.rssi || 0, vendor: d.VendorClassID || "", rate: d.rate || 0 }; cSU += u; cSD += dn; } }); let ol = document.getElementById('gege-global-overlay'), cM = Object.keys( cI), iD = window.gegeForceUIRedraw || (cM.length !== window.gegeRenderedMacs.size); if (!iD && cM.length > 0) { for (let i = 0; i < cM.length; i++) { if (!window.gegeRenderedMacs.has(cM[i])) { iD = !0; break; } } } if (iD) { for (let m in S.cls) if (!cI[m]) { S.cls[m].intUp += S.cls[m].upR * (n - S.cls[m].lUT) * 0.0005; S.cls[m].intDn += S.cls[m].dnR * (n - S.cls[m].lUT) * 0.0005; S.cls[m].upR = S.cls[m].dnR = 0; } } if (ol && ol.style.display === 'block' && (iD || !ol.querySelector('.gege-list-item'))) { bVD(ol, cI); // [解耦] 将洗净去重后的 cI 字典传给画板 window.gegeRenderedMacs = new Set( cM); window.gegeForceUIRedraw = !1; } if (S.wLT === undefined) { S.wLT = n; } else if (cWU !== S.wInstUp || cWD !== S.wInstDn) { let wDt = n - S.wLT; if (S.wInstUp > 0) { S.wTotUp += (S.wInstUp + cWU) * wDt * 0.0005; } else if (cWU > 0) { let wEU = cWU * 0.5 * CONFIG.wanRefreshInterval; S.wTotUp += wEU; S.wZEU = (S.wZEU || 0) + wEU; S.wZEUC = (S.wZEUC || 0) + 1; } if (S.wInstDn > 0) { S.wTotDn += (S.wInstDn + cWD) * wDt * 0.0005; } else if (cWD > 0) { let wED = cWD * 0.5 * CONFIG.wanRefreshInterval; S.wTotDn += wED; S.wZED = (S.wZED || 0) + wED; S.wZEDC = (S.wZEDC || 0) + 1; } S.wLT = n; } if (CONFIG.readSaveData === 2 && !S.snapLoaded) { try { let sp = typeof GM_getValue !== 'undefined' ? GM_getValue('ha_snapshot') : null; S.snap = sp || {}; if(sp && sp.global) { S.wTotUp = S.wTotUp === 0 ? sp.global.wan_up || 0 : S.wTotUp; S.wTotDn = S.wTotDn === 0 ? sp.global.wan_down || 0 : S.wTotDn; } } catch(e){console.warn(e)} S.snapLoaded = !0; } for (const [m, cC] of Object.entries(cI)) { let spD = (CONFIG.readSaveData === 2 && S.snap && S.snap.devices && S.snap.devices[m]) || null; S.cls[m] ??= { upR: cC.upRate, dnR: cC.dnRate, lUT: n, intUp: spD ? (spD.integral_up || 0) : 0, intDn: spD ? (spD.integral_down || 0) : 0, uB: CONFIG.readSaveData === 1 ? 0 : (spD ? cC.offUp - (spD.up || 0) : cC.offUp), dB: CONFIG.readSaveData === 1 ? 0 : (spD ? cC.offDn - (spD.down || 0) : cC.offDn), oU: cC.offUp, oD: cC.offDn, hU: new Float64Array(32), hD: new Float64Array(32), hIdx: 0 }; let cS = S.cls[m], dU = cC.offUp - cS.lU, dD = cC.offDn - cS.lD; if (dU < 0 || dD < 0) { if (dU < 0) { cS.uB += dU; cS.oU += dU; cS.dpU = cS.lU; } if (dD < 0) { cS.dB += dD; cS.oD += dD; cS.dpD = cS.lD; } cS.aR = 3;} else if (cS.aR === 3) { if (dU > 0 || dD > 0) { if (cS.dpU && dU > cS.dpU * 0.975 && cS.dpD && dD > cS.dpD * 0.975) { if (dU <= cS.dpU * 1.1 || dU <= cS.dpU + CONFIG.宽带最大外网上行速率 * CONFIG.lanRefreshInterval) { cS.uB += cS.dpU; cS.oU += cS.dpU; } else { cS.uB += dU; cS.oU += dU;} if (dD <= cS.dpD * 1.1 || dD <= cS.dpD + CONFIG.宽带最大外网下行速率 * CONFIG.lanRefreshInterval) { cS.dB += cS.dpD; cS.oD += cS.dpD; } else {cS.dB += dD; cS.oD += dD; } cS.aR = 2; } else { cS.aR = 0; } cS.dpU = 0; cS.dpD = 0; } } else if (cS.aR > 0) { cS.aR--; } if (cS.aR === 2 || (cS.aR == 1 && cC.upRate > CONFIG.宽带最大外网上行速率 * 1.3) || cC.upRate > 6e8) { cSU -= cC.upRate; cC.upRate = 0; } if (cS.aR === 2 || (cS.aR == 1 && cC.dnRate > CONFIG.宽带最大外网下行速率 * 1.3) || cC.dnRate > 24e8) { cSD -= cC.dnRate; cC.dnRate = 0; } if (cC.upRate !== cS.upR || cC.dnRate !== cS.dnR || cC.offUp !== cS.lU || cC.offDn !== cS.lD) { cS.onS = cC.aRec ? Math.max(0, (Date.now() - new Date(cC.aRec.split('#')[0].replace(/-/g, '/')).getTime()) / 1000) : 0; if (cC.upRate !== cS.upR || cC.dnRate !== cS.dnR) { let ms = n - cS.lUT; if (cS.upR > 0) { cS.intUp += (cS.upR + cC.upRate) * ms * 0.0005; } else if (cC.upRate > 0) { let eU = cC.upRate * CONFIG.lanRefreshInterval * 0.5; cS.intUp += eU; cS.zEU = (cS.zEU || 0) + eU; cS.zUC = (cS.zUC || 0) + 1; } if (cS.dnR > 0) { cS.intDn += (cS.dnR + cC.dnRate) * ms * 0.0005; } else if (cC.dnRate > 0) { let eD = cC.dnRate * CONFIG.lanRefreshInterval * 0.5; cS.intDn += eD; cS.zED = (cS.zED || 0) + eD; cS.zDC = (cS.zDC || 0) + 1; } cS.upR = cC.upRate; cS.dnR = cC.dnRate; cS.lUT = n; } } cS.lU = cC.offUp; cS.lD = cC.offDn; } S.wInstUp = cWU; S.wInstDn = cWD; rUI(cWU, cWD, cSU, cSD, cI); } catch (e) { console.error("[哥哥科技] 周期采样中断:", e); } finally { isF = !1; } } const calcStageRatio = (W, L_int, L_hp) => { if (W === 0) return 1.0; let L_max = Math.max(L_int, L_hp); let L_min = Math.min(L_int, L_hp); let Gap = Math.abs(L_int - L_hp); if (L_int > 0.84 * W && L_hp > 0.75 * W && (L_max < 1.5 * W || Gap < 0.6 * W)) { return ((L_int + L_hp) / (2 * W)); } else if (L_min < W && W < L_max && L_max < 1.5 * W) { return L_max / W; } else { return (Math.abs(L_int - W) < Math.abs(L_hp - W) ? L_int : L_hp) / W; } }; const SPRK = [' ', '▂', '▃', '▄', '▅', '▆', '▇', '█']; function getSpark(ringArr, headIdx, maxVal) { let s = ""; for (let i = 15; i >= 0; i--) { let v = ringArr[(headIdx - i) & 31]; s += SPRK[v <= 0 ? 0 : Math.min(7, Math.max(1, ((v / maxVal) * 7) | 0))]; } return s; } const getIconSvg = (r, isW) => { if (isW) return ``; let c = r >= -23 ? '#4caf50' : r >= -34 ? '#9c27b0' : '#0059fa'; if (r >= -40) return ``; if (r >= -45) return ``; if (r >= -49) return ``; if (r >= -55) return ``; if (r >= -60) return ``; if (r >= -67) return ``; if (r >= -70) return ``; if (r >= -74) return ``; if (r >= -77) return ``; if (r >= -84) return ``; return ``; }; function rUI(wU, wD, sU, sD, cI) { let tOD = 0, LUp = 0, LDn = 0, hpU = 0, hpD = 0, abU = 0, abD = 0, curHpU = 0, curHpD = 0, tot_cU = 0; for (let k in S.cls) { let s = S.cls[k], cC = cI[k]; let cU = Math.max(0, (s.lU || 0) - (s.uB || 0)); let cD = Math.max(0, (s.lD || 0) - (s.dB || 0)); let sessU = Math.max(0, (s.lU || 0) - (s.oU || 0)); let sessD = Math.max(0, (s.lD || 0) - (s.oD || 0)); tot_cU += cU; LUp += s.intUp || 0; LDn += s.intDn || 0; hpU += (CONFIG.readSaveData === 2 ? cU : sessU); hpD += (CONFIG.readSaveData === 2 ? cD : sessD); if (cC) { curHpU += (CONFIG.readSaveData === 2 ? cU : sessU); curHpD += (CONFIG.readSaveData === 2 ? cD : sessD); tOD += cC.offDn || 0; } abU += CONFIG.readSaveData === 2 ? sessU : (cC ? (cC.offUp || 0) : (s.lU || 0)); abD += CONFIG.readSaveData === 2 ? sessD : (cC ? (cC.offDn || 0) : (s.lD || 0)); s.hIdx = (s.hIdx + 1) & 31; s.hU[s.hIdx] = cC ? cC.upRate : 0; s.hD[s.hIdx] = cC ? cC.dnRate : 0; } if (typeof GM_setValue !== 'undefined' && S.rTick === 1) { let cln = {}; for (let k in S.cls) { let s = S.cls[k], cC = cI[k]; cln[k] = { up: Math.max(0, (s.lU || 0) - (s.uB || 0)), down: Math.max(0, (s.lD || 0) - (s.dB || 0)), integral_up: s.intUp || 0, integral_down: s.intDn || 0, status: s.aR ? "off" : (CONFIG.portMap[cC?.iface] || cC?.iface || "未知接口"), name: cC?.name || k, ip: cC?.ip || "", raw_up: cC?.offUp || 0, raw_down: cC?.offDn || 0 };} try { GM_setValue('ha_snapshot', { timestamp: Date.now(), global: { wan_up: S.wTotUp, wan_down: S.wTotDn, lan_integral_up: LUp, lan_integral_down: LDn, lan_high_up: hpU, lan_high_down: hpD, lan_off_up: abU, lan_off_down: abD }, devices: cln }); } catch(e) {console.warn(e);} } S.rTick = ((S.rTick || 0) + 1) & 15; if (S.rTick === 1 || !S.cRT) { S.aWu = (S.wTotUp - (S.lwTU || S.wTotUp)) / (CONFIG.wanRefreshInterval << 4); S.lwTU = S.wTotUp; S.aWd = (S.wTotDn - (S.lwTD || S.wTotDn)) / (CONFIG.wanRefreshInterval << 4); S.lwTD = S.wTotDn; if (S.hasW2) { let rU = S.w2TotUp > 0 ? (S.wTotUp / S.w2TotUp) : (S.wTotUp > 0 ? Infinity : 0), rD = S.w2TotDn > 0 ? (S.wTotDn / S.w2TotDn) : (S.wTotDn > 0 ? Infinity : 0); let fR = (r) => r === Infinity ? '∞' : (r > 1 ? r.toFixed(2) + 'x' : (r * 100).toPrecision(3) + '%'); S.cRT = `${fR(rU)},${fR(rD)}`; } else { let rUp = calcStageRatio((Phys.tU + S.wTotUp) / ((Phys.tU > 0) + (S.wTotUp > 0)) || 0, LUp, hpU), rDn = calcStageRatio((Phys.tD + S.wTotDn) / ((Phys.tD > 0) + (S.wTotDn > 0)) || 0, LDn, hpD); S.cRT = `${(rUp * 100).toFixed(2)}%,${(rDn * 100).toFixed(2)}%`; if (document.getElementById('gb-ratio-display')) document.getElementById('gb-ratio-display').innerHTML = S.cRT; } } let bd = document.getElementById('zte-geek-board'); if (!bd) { bd = document.createElement('div'); bd.id = 'zte-geek-board'; let layoutHtml = ''; if (CONFIG.uiLayout === 1) { // 紧凑版 (驾驶舱) layoutHtml = `