// ==UserScript== // @name SuperGit - Github国内加速助手 // @namespace https://hsya.top/ // @version 1.8.0 // @description 专为国内开发者打造。自动在 GitHub 页面注入「镜像下载」按钮,覆盖 Release、Raw、Clone ZIP 等 8 大场景,一键选择最优镜像节点,下载速度提升数十倍。 // @author 划水鸭 // @homepage https://hsya.top/supergit.html // @match *://github.com/* // @match *://gist.github.com/* // @match *://*.github.com/* // @connect github.com // @connect githubusercontent.com // @connect codeload.github.com // @connect bgithub.xyz // @connect raw.githubusercontent.com // @connect raw.bgithub.xyz // @connect api.akams.cn // @connect * // @icon https://hsya.top/static/img/supergit.png // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_openInTab // @grant GM_addStyle // @grant GM_info // @grant GM_registerMenuCommand // @run-at document-idle // ==/UserScript== (function() { 'use strict'; if (window.__supergit_injected) return; window.__supergit_injected = true; const VERSION = 'v' + GM_info.script.version; // 图标:GitHub CSP 限制外域 img-src,需通过 GM_xmlhttpRequest 预加载转 base64 const ICON_URL = 'https://hsya.top/static/img/supergit.png'; let ICON_URI = 'data:image/svg+xml,' + encodeURIComponent( '' ); function preloadIcon() { return new Promise((resolve) => { GM_xmlhttpRequest({ method: 'GET', url: ICON_URL, responseType: 'blob', timeout: 10000, onload: (resp) => { try { const reader = new FileReader(); reader.onloadend = () => { if (reader.result) ICON_URI = reader.result; resolve(); }; reader.onerror = () => resolve(); reader.readAsDataURL(resp.response); } catch { resolve(); } }, onerror: () => resolve(), ontimeout: () => resolve(), }); }); } // 下载小图标 const DL_SVG = ''; // GitHub 常见可下载位置配置 const INJECT_SCENARIOS = { 'release-zip': { label: 'Release ZIP/Tar', desc: 'Release 页面的源码压缩包', defaultOn: true }, 'release-asset': { label: 'Release 附件', desc: 'Release 页面的上传附件', defaultOn: true }, 'raw-file': { label: 'Raw 原始文件', desc: '代码文件查看页的原始文件', defaultOn: true }, 'clone-zip': { label: 'Clone 弹框 ZIP', desc: '仓库主页 Clone 弹框的 ZIP 下载', defaultOn: true }, 'repo-archive': { label: '仓库 Code 按钮', desc: '仓库主页 Code 按钮旁', defaultOn: true }, 'lfs-file': { label: 'LFS 大文件', desc: 'Git LFS 管理的大文件', defaultOn: true }, 'gist-raw': { label: 'Gist 原始文件', desc: 'Gist 代码片段的原始文件', defaultOn: true }, 'compare-diff': { label: 'Compare/Diff 补丁', desc: '代码比较页面的 Patch/Diff', defaultOn: true }, }; // ===== 状态 ===== let allNodes = []; let visibleKeys = []; let panelOpen = false; let injectConfig = {}; // ===== Storage 封装 (GM API) ===== function gmGet(keys) { const result = {}; if (typeof keys === 'string') keys = [keys]; for (const k of keys) { try { result[k] = GM_getValue(k); } catch { result[k] = undefined; } } return result; } function gmSet(obj) { for (const [k, v] of Object.entries(obj)) { try { GM_setValue(k, v); } catch (e) { console.error('[SuperGit] GM_setValue error:', e); } } } // ===== 跨域请求 (GM_xmlhttpRequest) ===== function gmFetch(url, options = {}) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: options.method || 'GET', url: url, headers: options.headers || {}, responseType: options.responseType || 'json', timeout: options.timeout || 30000, onload: (resp) => { if (resp.status >= 200 && resp.status < 400) { resolve(resp); } else { reject(new Error(`HTTP ${resp.status}`)); } }, onerror: (err) => reject(err), ontimeout: () => reject(new Error('Timeout')), }); }); } // ===== 加载镜像节点 (原 background.js loadMirrorNodes) ===== async function loadMirrorNodes() { try { console.log('[SuperGit] 正在加载镜像节点...'); const resp = await gmFetch('https://api.akams.cn/github'); if (resp.status >= 200 && resp.status < 400) { const data = typeof resp.response === 'string' ? JSON.parse(resp.response) : resp.response; if (data.code === 200 && data.data && Array.isArray(data.data)) { allNodes = data.data .filter(node => node.latency > 0 && node.latency < 1500) .sort((a, b) => a.latency - b.latency); console.log(`[SuperGit] 加载了 ${allNodes.length} 个镜像节点`); gmSet({ mirrorNodes: allNodes, sg_nodes_update_time: Date.now() }); return; } else { throw new Error('API返回数据格式错误'); } } else { throw new Error(`HTTP错误`); } } catch (error) { console.error('[SuperGit] 加载镜像节点失败:', error); allNodes = [{ url: 'https://bgithub.xyz', name: '默认镜像', latency: 100 }]; gmSet({ mirrorNodes: allNodes }); } } // ===== 初始化 ===== async function init() { // 优先从 GM storage 加载缓存 const store = gmGet(['mirrorNodes', 'sg_visible_keys', 'sg_inject_config', 'sg_nodes_update_time']); if (store.mirrorNodes && Array.isArray(store.mirrorNodes) && store.mirrorNodes.length > 0) { allNodes = store.mirrorNodes; console.log('[SuperGit] 从本地存储加载镜像节点:', allNodes.length); } visibleKeys = store.sg_visible_keys || []; if (!visibleKeys.length && allNodes.length) { visibleKeys = allNodes.slice(0, 10).map(n => nodeKey(n)); saveVisibleKeys(); } // 加载注入配置 injectConfig = store.sg_inject_config || {}; Object.keys(INJECT_SCENARIOS).forEach(k => { if (injectConfig[k] === undefined) { injectConfig[k] = INJECT_SCENARIOS[k].defaultOn; } }); saveInjectConfig(); // 缓存过期则刷新 const lastUpdate = store.sg_nodes_update_time || 0; if (Date.now() - lastUpdate > 10 * 60 * 1000 || !allNodes.length) { console.log('[SuperGit] 节点数据已过期或为空,立即刷新'); await loadMirrorNodes(); } // 定期刷新 (每10分钟) setInterval(loadMirrorNodes, 10 * 60 * 1000); // 预加载图标(绕过 GitHub CSP) await preloadIcon(); injectUI(); bindEvents(); refreshNodeList(); setupInject(); // 兜底:节点为空时自动刷新一次 if (!allNodes.length) autoRefreshNodes(); // 注册油猴菜单命令 GM_registerMenuCommand('打开节点管理面板', () => { if (!panelOpen) togglePanel(); }); } // ===== 工具函数 ===== function nodeKey(node) { return node.url || ''; } function shortDomain(url) { try { const h = new URL(url).hostname.replace('www.', ''); const parts = h.split('.'); return parts.length > 2 ? parts.slice(-2).join('.') : h; } catch { return url; } } function delayClass(ms) { return ms < 300 ? 'sg-delay-fast' : ms < 800 ? 'sg-delay-medium' : 'sg-delay-slow'; } function dmDelayClass(ms) { return ms < 300 ? 'sg-dm-fast' : ms < 800 ? 'sg-dm-medium' : 'sg-dm-slow'; } function delayPercent(ms) { return Math.min(100, (ms / 1500) * 100); } function saveVisibleKeys() { gmSet({ sg_visible_keys: visibleKeys }); } function saveInjectConfig() { gmSet({ sg_inject_config: injectConfig }); } // 生成镜像链接 function mirrorUrl(githubUrl, nodeUrl) { let url = githubUrl; if (url.includes('bgithub.xyz')) { url = url.replace('bgithub.xyz', 'github.com').replace('raw.bgithub.xyz', 'raw.githubusercontent.com'); } if (nodeUrl.endsWith('/')) nodeUrl = nodeUrl.slice(0, -1); return `${nodeUrl}/${url}`; } // 兜底:节点为空时自动刷新一次 let _autoRefreshing = false; function autoRefreshNodes(onDone) { if (_autoRefreshing || allNodes.length > 0) { onDone?.(); return; } _autoRefreshing = true; console.log('[SuperGit] 节点列表为空,自动刷新...'); loadMirrorNodes().then(() => { _autoRefreshing = false; if (allNodes.length) { visibleKeys = visibleKeys.filter(k => allNodes.some(n => nodeKey(n) === k)); if (!visibleKeys.length) visibleKeys = allNodes.slice(0, 10).map(n => nodeKey(n)); saveVisibleKeys(); refreshNodeList(); showToast(`自动刷新成功,${allNodes.length} 个节点`); } else { showToast('自动刷新失败,请手动刷新'); } onDone?.(); }).catch(() => { _autoRefreshing = false; showToast('自动刷新失败,请手动刷新'); onDone?.(); }); } function showToast(msg) { const t = document.createElement('div'); t.className = 'sg-toast'; t.textContent = msg; document.body.appendChild(t); requestAnimationFrame(() => t.classList.add('sg-show')); setTimeout(() => { t.classList.remove('sg-show'); setTimeout(() => t.remove(), 300); }, 2000); } // ===== 注入 CSS ===== GM_addStyle(` /* SuperGit 右下角浮动按钮 */ #supergit-fab { position: fixed; bottom: 24px; right: 24px; z-index: 2147483647; width: 48px; height: 48px; border-radius: 50%; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; background: #2da44e; color: #fff; font-size: 22px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3); transition: all 0.2s ease; line-height: 1; } #supergit-fab:hover { transform: scale(1.1); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4); } #supergit-fab:active { transform: scale(0.95); } #supergit-fab svg { width: 24px; height: 24px; fill: currentColor; } /* 脉冲动画 - 有节点时绿色脉冲 */ @keyframes sg-pulse { 0% { box-shadow: 0 0 0 0 rgba(45, 164, 78, 0.5); } 70% { box-shadow: 0 0 0 10px rgba(45, 164, 78, 0); } 100% { box-shadow: 0 0 0 0 rgba(45, 164, 78, 0); } } #supergit-fab.sg-pulse { animation: sg-pulse 2s infinite; } /* 面板遮罩 */ #supergit-overlay { position: fixed; inset: 0; z-index: 2147483646; background: rgba(0, 0, 0, 0.5); opacity: 0; transition: opacity 0.2s ease; pointer-events: none; } #supergit-overlay.sg-visible { opacity: 1; pointer-events: auto; } /* 管理面板 */ #supergit-panel { position: fixed; bottom: 84px; right: 24px; z-index: 2147483647; width: 380px; max-height: 520px; background: #0d1117; border: 1px solid #30363d; border-radius: 12px; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5); color: #e6edf3; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; font-size: 14px; overflow: hidden; transform: translateY(20px) scale(0.95); opacity: 0; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: none; display: flex; flex-direction: column; } #supergit-panel.sg-visible { transform: translateY(0) scale(1); opacity: 1; pointer-events: auto; } /* 面板头部 */ .sg-panel-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px 12px; border-bottom: 1px solid #21262d; flex-shrink: 0; } .sg-panel-header h3 { margin: 0; font-size: 15px; font-weight: 600; display: flex; align-items: center; gap: 8px; color: #e6edf3; } .sg-panel-header h3 svg { width: 20px; height: 20px; fill: #2da44e; } .sg-close-btn { width: 32px; height: 32px; border: none; border-radius: 6px; background: transparent; color: #8b949e; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.15s; } .sg-close-btn:hover { background: #21262d; color: #e6edf3; } /* 状态栏 */ .sg-status-bar { display: flex; align-items: center; gap: 12px; padding: 10px 20px; font-size: 12px; color: #8b949e; border-bottom: 1px solid #21262d; } .sg-status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } .sg-status-dot.sg-online { background: #2da44e; } .sg-status-dot.sg-offline { background: #6e7681; } .sg-status-bar .sg-last-update { margin-left: auto; } /* 工具栏 */ .sg-toolbar { display: flex; gap: 8px; padding: 12px 20px; border-bottom: 1px solid #21262d; } .sg-toolbar-btn { padding: 6px 12px; border: 1px solid #30363d; border-radius: 6px; background: #21262d; color: #e6edf3; font-size: 12px; cursor: pointer; display: flex; align-items: center; gap: 4px; transition: all 0.15s; white-space: nowrap; } .sg-toolbar-btn:hover { background: #30363d; border-color: #8b949e; } .sg-toolbar-btn svg { width: 14px; height: 14px; fill: currentColor; } .sg-toolbar-btn.sg-loading { opacity: 0.6; pointer-events: none; } .sg-toolbar-btn svg.sg-spin { animation: sg-spin 1s linear infinite; } @keyframes sg-spin { to { transform: rotate(360deg); } } /* 节点列表 */ .sg-node-list { max-height: 280px; overflow-y: auto; padding: 8px 0; flex: 1; } .sg-node-list::-webkit-scrollbar { width: 6px; } .sg-node-list::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; } .sg-node-item { display: flex; align-items: center; gap: 12px; padding: 8px 20px; transition: background 0.15s; } .sg-node-item:hover { background: #161b22; } /* 自定义 Checkbox */ .sg-checkbox { position: relative; width: 16px; height: 16px; flex-shrink: 0; cursor: pointer; } .sg-checkbox input { position: absolute; opacity: 0; width: 100%; height: 100%; cursor: pointer; margin: 0; } .sg-checkbox .sg-checkmark { width: 16px; height: 16px; border: 1.5px solid #30363d; border-radius: 4px; display: flex; align-items: center; justify-content: center; transition: all 0.15s; background: #0d1117; } .sg-checkbox input:checked + .sg-checkmark { background: #2da44e; border-color: #2da44e; } .sg-checkbox input:checked + .sg-checkmark::after { content: ''; width: 4px; height: 8px; border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg) translate(-0.5px, -0.5px); } .sg-node-info { flex: 1; min-width: 0; } .sg-node-domain { font-size: 13px; font-weight: 500; color: #e6edf3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .sg-node-delay { display: flex; align-items: center; gap: 6px; margin-top: 2px; } .sg-delay-bar { height: 3px; border-radius: 2px; background: #21262d; flex: 1; max-width: 60px; overflow: hidden; } .sg-delay-bar-fill { height: 100%; border-radius: 2px; transition: width 0.3s ease; } .sg-delay-text { font-size: 11px; color: #8b949e; } /* 延迟颜色等级 */ .sg-delay-fast { color: #2da44e; } .sg-delay-fast .sg-delay-bar-fill { background: #2da44e; } .sg-delay-medium { color: #d29922; } .sg-delay-medium .sg-delay-bar-fill { background: #d29922; } .sg-delay-slow { color: #f85149; } .sg-delay-slow .sg-delay-bar-fill { background: #f85149; } .sg-node-actions { flex-shrink: 0; } .sg-test-btn { padding: 4px 8px; border: 1px solid #30363d; border-radius: 4px; background: transparent; color: #8b949e; font-size: 11px; cursor: pointer; transition: all 0.15s; } .sg-test-btn:hover { background: #21262d; color: #e6edf3; border-color: #8b949e; } /* 空状态 */ .sg-empty { padding: 32px 20px; text-align: center; color: #8b949e; font-size: 13px; } /* 提示消息 */ .sg-toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(20px); z-index: 2147483648; background: #1f6feb; color: #fff; padding: 8px 20px; border-radius: 6px; font-size: 13px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); opacity: 0; transition: all 0.25s ease; pointer-events: none; white-space: nowrap; } .sg-toast.sg-show { transform: translateX(-50%) translateY(0); opacity: 1; } /* ===== 注入下载按钮(绿色文字按钮) ===== */ .sg-dl-btn { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border: 1px solid #2da44e; border-radius: 5px; background: transparent; color: #2da44e; font-size: 12px; font-weight: 500; cursor: pointer; vertical-align: middle; margin-left: 6px; transition: all 0.15s; flex-shrink: 0; white-space: nowrap; line-height: 1.4; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; } .sg-dl-btn:hover { background: #2da44e; color: #fff; } .sg-dl-btn svg { width: 12px; height: 12px; fill: currentColor; flex-shrink: 0; } /* ===== 下载弹窗 ===== */ #sg-download-modal { position: fixed; inset: 0; z-index: 2147483648; display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transition: opacity 0.2s ease; } #sg-download-modal.sg-visible { opacity: 1; pointer-events: auto; } #sg-dm-overlay { position: absolute; inset: 0; background: rgba(0, 0, 0, 0.6); } #sg-dm-card { position: relative; width: 400px; max-height: 480px; background: #0d1117; border: 1px solid #30363d; border-radius: 12px; box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5); display: flex; flex-direction: column; transform: translateY(12px) scale(0.97); transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; } #sg-download-modal.sg-visible #sg-dm-card { transform: translateY(0) scale(1); } /* 弹窗头部 */ .sg-dm-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid #21262d; flex-shrink: 0; } .sg-dm-header h4 { margin: 0; font-size: 14px; font-weight: 600; color: #ffffff; display: flex; align-items: center; gap: 8px; } .sg-dm-header h4 img, .sg-dm-header h4 .sg-dm-icon { width: 16px; height: 16px; } .sg-dm-refresh-btn { width: 28px; height: 28px; border: none; border-radius: 6px; background: transparent; color: #8b949e; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.15s; } .sg-dm-refresh-btn:hover { background: #21262d; color: #e6edf3; } .sg-dm-refresh-btn svg { width: 14px; height: 14px; fill: currentColor; transition: transform 0.3s; } .sg-dm-refresh-btn.sg-spin svg { animation: sg-spin 1s linear infinite; } /* 弹窗文件信息 */ .sg-dm-fileinfo { padding: 10px 18px; background: #161b22; border-bottom: 1px solid #21262d; font-size: 12px; color: #8b949e; flex-shrink: 0; } .sg-dm-fileinfo .sg-dm-filename { color: #e6edf3; font-weight: 500; font-size: 13px; word-break: break-all; } /* 弹窗节点列表 */ .sg-dm-nodelist { flex: 1; overflow-y: auto; padding: 6px 0; } .sg-dm-nodelist::-webkit-scrollbar { width: 6px; } .sg-dm-nodelist::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; } .sg-dm-node-item { display: flex; align-items: center; gap: 10px; padding: 8px 18px; transition: background 0.12s; } .sg-dm-node-item:hover { background: #161b22; } .sg-dm-domain { flex: 1; font-size: 13px; color: #e6edf3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .sg-dm-node-delay { font-size: 11px; color: #8b949e; white-space: nowrap; min-width: 40px; text-align: right; } .sg-dm-node-delay.sg-dm-fast { color: #2da44e; } .sg-dm-node-delay.sg-dm-medium { color: #d29922; } .sg-dm-node-delay.sg-dm-slow { color: #f85149; } .sg-dm-download-btn { padding: 4px 12px; border: 1px solid #2da44e; border-radius: 5px; background: transparent; color: #2da44e; font-size: 12px; font-weight: 500; cursor: pointer; transition: all 0.15s; white-space: nowrap; } .sg-dm-download-btn:hover { background: #2da44e; color: #fff; } /* ===== 管理面板 Tab ===== */ .sg-tabs { display: flex; border-bottom: 1px solid #21262d; flex-shrink: 0; } .sg-tab { flex: 1; padding: 10px 0; text-align: center; font-size: 13px; color: #8b949e; cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.15s; background: none; border-top: none; border-left: none; border-right: none; } .sg-tab:hover { color: #e6edf3; background: #161b22; } .sg-tab.sg-active { color: #e6edf3; border-bottom-color: #2da44e; } .sg-tab-content { display: none; overflow-y: auto; flex: 1; } .sg-tab-content.sg-active { display: flex; flex-direction: column; } /* 注入设置区域 */ .sg-inject-setting { display: flex; align-items: center; justify-content: space-between; padding: 12px 20px; border-bottom: 1px solid #21262d; font-size: 13px; } .sg-inject-setting:last-child { border-bottom: none; } .sg-inject-label { display: flex; flex-direction: column; gap: 2px; } .sg-inject-label .sg-label-title { color: #e6edf3; font-weight: 500; } .sg-inject-label .sg-label-desc { color: #6e7681; font-size: 11px; } /* ===== 开关(增强样式) ===== */ .sg-toggle { position: relative; width: 40px; height: 22px; cursor: pointer; display: inline-block; flex-shrink: 0; } .sg-toggle input { position: absolute; opacity: 0; width: 100%; height: 100%; cursor: pointer; margin: 0; z-index: 1; } .sg-toggle .sg-toggle-track { width: 40px; height: 22px; border-radius: 11px; background: #30363d; transition: background 0.25s; position: relative; box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3); } .sg-toggle input:checked + .sg-toggle-track { background: #2da44e; box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2), 0 0 0 2px rgba(45, 164, 78, 0.2); } .sg-toggle .sg-toggle-track::after { content: ''; position: absolute; width: 16px; height: 16px; border-radius: 50%; background: #fff; top: 3px; left: 3px; transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); } .sg-toggle input:checked + .sg-toggle-track::after { transform: translateX(18px); } /* 开关状态文字 */ .sg-toggle-status { font-size: 11px; margin-left: 6px; flex-shrink: 0; } .sg-toggle-status.sg-on { color: #2da44e; } .sg-toggle-status.sg-off { color: #6e7681; } /* ===== 底部 footer ===== */ .sg-footer { padding: 8px 16px; border-top: 1px solid #21262d; display: flex; align-items: center; justify-content: space-between; font-size: 11px; color: #484f58; flex-shrink: 0; background: #010409; } .sg-footer a { color: #8b949e; text-decoration: none; transition: color 0.15s; } .sg-footer a:hover { color: #58a6ff; } `); // ===== 注入 UI ===== function injectUI() { // 浮动按钮 const fab = document.createElement('button'); fab.id = 'supergit-fab'; fab.title = 'SuperGit镜像节点管理'; const fabImg = document.createElement('img'); fabImg.src = ICON_URI; fabImg.alt = 'SuperGit'; fabImg.style.cssText = 'width:32px;height:32px;'; fab.appendChild(fabImg); document.body.appendChild(fab); // 遮罩 const overlay = document.createElement('div'); overlay.id = 'supergit-overlay'; document.body.appendChild(overlay); // 管理面板 const panel = document.createElement('div'); panel.id = 'supergit-panel'; panel.innerHTML = buildPanelHTML(); document.body.appendChild(panel); if (allNodes.length > 0) fab.classList.add('sg-pulse'); // 下载弹窗 const modal = document.createElement('div'); modal.id = 'sg-download-modal'; modal.innerHTML = buildModalHTML(); document.body.appendChild(modal); } function buildPanelHTML() { const scenariosHTML = Object.entries(INJECT_SCENARIOS).map(([key, cfg]) => { const checked = injectConfig[key] ? 'checked' : ''; const statusClass = injectConfig[key] ? 'sg-on' : 'sg-off'; const statusText = injectConfig[key] ? '已开启' : '已关闭'; return `