在这个老哥的自定义头https://www.nodeseek.com/post-516869-1 Komari应用哪吒v1主题的流量进度条代码实现,基础进行修改。
增加了右下角显示访问者IP 服务商 浏览器等信息,并能根据页面亮色,暗色主题变换
供小白使用 设置 站点 自定义头 “De鸡窝”及个人配置请自己修改。
留个备份
<script>(function() { // 配置 var CONFIG = { interval: 60000, apiUrl: '/api/rpc2', trafficTolerance: 0.10 }; // 注入样式 var style = document.createElement('style'); style.textContent = ` .server-footer-name>div:first-child{visibility:hidden!important} .server-footer-theme{display:none!important} /* 自定义描述样式 - 绿色70%透明度 */ .custom-desc { color: rgba(34, 197, 94, 0.7) !important; } /* 访客信息面板样式 - 自动跟随页面主题 */ #visitor-info-panel { position: fixed; bottom: 20px; right: 20px; border-radius: 12px; padding: 16px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; line-height: 1.4; z-index: 9999; backdrop-filter: blur(10px); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); min-width: 220px; max-width: 280px; transition: all 0.3s ease; opacity: 0.9; border: 1px solid rgba(226, 232, 240, 0.8); background: rgba(255, 255, 255, 0.95); color: #1e293b; } /* 暗色主题样式 */ .dark-theme #visitor-info-panel, body.dark #visitor-info-panel, html[data-theme="dark"] #visitor-info-panel, html.dark #visitor-info-panel, .dark #visitor-info-panel, [data-mode="dark"] #visitor-info-panel, [class*="dark"] #visitor-info-panel, [class*="dark-mode"] #visitor-info-panel, #visitor-info-panel.dark-mode { background: rgba(15, 23, 42, 0.95); border-color: rgba(148, 163, 184, 0.3); color: #f1f5f9; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); } /* 亮色主题样式 - 确保优先级 */ .light-theme #visitor-info-panel, body.light #visitor-info-panel, html[data-theme="light"] #visitor-info-panel, html.light #visitor-info-panel, .light #visitor-info-panel, [data-mode="light"] #visitor-info-panel, [class*="light"] #visitor-info-panel, [class*="light-mode"] #visitor-info-panel, #visitor-info-panel.light-mode { background: rgba(255, 255, 255, 0.95) !important; border-color: rgba(226, 232, 240, 0.8) !important; color: #1e293b !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15) !important; } /* 跟随系统主题的媒体查询 */ @media (prefers-color-scheme: dark) { :root:not(.light-theme):not(.light) #visitor-info-panel, body:not(.light-theme):not(.light) #visitor-info-panel, html:not(.light-theme):not(.light) #visitor-info-panel, :not([data-theme="light"]) #visitor-info-panel { background: rgba(15, 23, 42, 0.95); border-color: rgba(148, 163, 184, 0.3); color: #f1f5f9; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); } } #visitor-info-panel:hover { opacity: 1; transform: translateY(-2px); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2); } .dark-theme #visitor-info-panel:hover, body.dark #visitor-info-panel:hover, html[data-theme="dark"] #visitor-info-panel:hover, html.dark #visitor-info-panel:hover { box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4); } .visitor-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid rgba(226, 232, 240, 0.6); } .dark-theme .visitor-header, body.dark .visitor-header, html[data-theme="dark"] .visitor-header, html.dark .visitor-header { border-bottom-color: rgba(148, 163, 184, 0.3); } .visitor-title { font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 6px; color: #3b82f6; } .dark-theme .visitor-title, body.dark .visitor-title, html[data-theme="dark"] .visitor-title, html.dark .visitor-title { color: #60a5fa; } .visitor-title i { font-size: 16px; } .visitor-toggle { background: none; border: none; cursor: pointer; font-size: 18px; padding: 2px 6px; border-radius: 4px; transition: all 0.2s; color: #64748b; } .dark-theme .visitor-toggle, body.dark .visitor-toggle, html[data-theme="dark"] .visitor-toggle, html.dark .visitor-toggle { color: #94a3b8; } .visitor-toggle:hover { background: rgba(59, 130, 246, 0.1); color: #3b82f6; } .dark-theme .visitor-toggle:hover, body.dark .visitor-toggle:hover, html[data-theme="dark"] .visitor-toggle:hover, html.dark .visitor-toggle:hover { background: rgba(96, 165, 250, 0.1); color: #60a5fa; } .visitor-content { display: block; } .visitor-content.hidden { display: none; } .visitor-row { display: flex; justify-content: space-between; margin-bottom: 8px; align-items: flex-start; } .visitor-label { min-width: 60px; font-weight: 500; color: #64748b; } .dark-theme .visitor-label, body.dark .visitor-label, html[data-theme="dark"] .visitor-label, html.dark .visitor-label { color: #94a3b8; } .visitor-value { text-align: right; flex: 1; word-break: break-word; max-width: 150px; color: #334155; } .dark-theme .visitor-value, body.dark .visitor-value, html[data-theme="dark"] .visitor-value, html.dark .visitor-value { color: #e2e8f0; } /* 特定值的颜色 */ .visitor-ip { font-weight: 600; font-family: 'Monaco', 'Consolas', monospace; font-size: 11px; color: #059669; } .dark-theme .visitor-ip, body.dark .visitor-ip, html[data-theme="dark"] .visitor-ip, html.dark .visitor-ip { color: #34d399; } .visitor-isp { color: #d97706; } .dark-theme .visitor-isp, body.dark .visitor-isp, html[data-theme="dark"] .visitor-isp, html.dark .visitor-isp { color: #fbbf24; } .visitor-location { color: #7c3aed; } .dark-theme .visitor-location, body.dark .visitor-location, html[data-theme="dark"] .visitor-location, html.dark .visitor-location { color: #a78bfa; } .visitor-browser { color: #db2777; } .dark-theme .visitor-browser, body.dark .visitor-browser, html[data-theme="dark"] .visitor-browser, html.dark .visitor-browser { color: #f472b6; } .visitor-os { color: #1d4ed8; } .dark-theme .visitor-os, body.dark .visitor-os, html[data-theme="dark"] .visitor-os, html.dark .visitor-os { color: #60a5fa; } .visitor-refresh-btn { margin-top: 12px; padding: 6px 12px; background: linear-gradient(135deg, #3b82f6, #8b5cf6); color: white; border: none; border-radius: 6px; font-size: 11px; font-weight: 500; cursor: pointer; width: 100%; transition: all 0.2s; } .visitor-refresh-btn:hover { background: linear-gradient(135deg, #2563eb, #7c3aed); transform: translateY(-1px); } .visitor-refresh-btn:active { transform: translateY(0); } .visitor-copy-btn { background: rgba(100, 116, 139, 0.15); border: none; cursor: pointer; padding: 2px 6px; border-radius: 4px; margin-left: 4px; font-size: 10px; transition: all 0.2s; color: #64748b; } .dark-theme .visitor-copy-btn, body.dark .visitor-copy-btn, html[data-theme="dark"] .visitor-copy-btn, html.dark .visitor-copy-btn { background: rgba(148, 163, 184, 0.2); color: #94a3b8; } .visitor-copy-btn:hover { background: rgba(59, 130, 246, 0.2); color: #3b82f6; } .dark-theme .visitor-copy-btn:hover, body.dark .visitor-copy-btn:hover, html[data-theme="dark"] .visitor-copy-btn:hover, html.dark .visitor-copy-btn:hover { background: rgba(96, 165, 250, 0.2); color: #60a5fa; } `; document.head.appendChild(style); // 全局配置 window.CustomDesc = "De 鸡窝"; window.ShowNetTransfer = true; window.DisableAnimatedMan = true; window.FixedTopServerName = true; // 工具函数 var UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']; var UNIT_MAP = { KIB: 1024, MIB: 1048576, GIB: 1073741824, TIB: 1099511627776 }; var CYCLE_MAP = { 30: '月', 92: '季', 184: '半年', 365: '年', 730: '二年', 1095: '三年', 1825: '五年' }; function formatBytes(bytes) { if (!bytes) return { value: '0', unit: 'B' }; var i = Math.floor(Math.log(bytes) / Math.log(1024)); return { value: (bytes / Math.pow(1024, i)).toFixed(2), unit: UNITS[i] }; } function formatTime(s) { if (!s) return 'N/A'; try { return new Date(s).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour: '2-digit', minute: '2-digit', second: '2-digit' }); } catch (e) { return 'N/A'; } } function getCycleText(c) { if (!c || c <= 0) return c === -1 || c === 0 ? '一次性' : '年'; for (var days in CYCLE_MAP) { if (Math.abs(c - days) <= 3) return CYCLE_MAP[days]; } var y = Math.round(c / 365); return y >= 1 && y <= 10 ? y + '年' : '年'; } function calcResetDays(expiredAt) { if (!expiredAt) return 'N/A'; try { var day = new Date(expiredAt).getDate(), now = new Date(); var reset = new Date(now.getFullYear(), now.getMonth(), day); if (reset <= now) reset.setMonth(reset.getMonth() + 1); return Math.ceil((reset - now) / 86400000) + '日'; } catch (e) { return 'N/A'; } } function parseTraffic(text) { var m = text.match(/([\d.]+)\s*(GiB|MiB|TiB|KiB)/i); return m ? parseFloat(m[1]) * (UNIT_MAP[m[2].toUpperCase()] || 1) : null; } function normName(n) { return n.toUpperCase().replace(/[^A-Z0-9-]/g, ''); } function getColor(p) { return 'hsl(' + ((100 - p) * 1.4) + ',70%,50%)'; } // 渲染器 var barCache = new Map(); function createBar(d) { var uf = formatBytes(d.used), tf = formatBytes(d.limit); var pc = Math.min(100, d.used / d.limit * 100).toFixed(2); var div = document.createElement('div'); div.className = 'space-y-1.5 traffic-bar'; div.dataset.uuid = d.uuid; div.style.width = '100%'; div.innerHTML = '<div class="flex items-center justify-between">' + '<div class="flex items-baseline gap-1">' + '<span class="text-[10px] font-medium text-neutral-800 dark:text-neutral-200 used-val">' + uf.value + '</span>' + '<span class="text-[10px] font-medium text-neutral-800 dark:text-neutral-200 used-unit">' + uf.unit + '</span>' + '<span class="text-[10px] text-neutral-500 dark:text-neutral-400">/ ' + tf.value + ' ' + tf.unit + '</span>' + '</div>' + '<div class="text-[10px] font-medium text-neutral-600 dark:text-neutral-300 percent-val">' + pc + '%</div>' + '</div>' + '<div class="relative h-1.5" style="width:100%">' + '<div class="absolute inset-0 bg-neutral-100 dark:bg-neutral-800 rounded-full"></div>' + '<div class="absolute inset-0 rounded-full transition-all duration-300 progress-bar" style="width:' + pc + '%;background-color:' + getColor(parseFloat(pc)) + '"></div>' + '</div>' + '<div class="flex items-center justify-between">' + '<div class="text-[10px] text-neutral-500 dark:text-neutral-400 update-time">更新于: ' + formatTime(d.next_update) + '</div>' + '<div class="text-[10px] text-neutral-500 dark:text-neutral-400 reset-date">距离流量重置: ' + d.reset_date + '</div>' + '</div>'; return div; } function updateBar(el, d) { var uf = formatBytes(d.used); var pc = Math.min(100, d.used / d.limit * 100).toFixed(2); el.querySelector('.used-val').textContent = uf.value; el.querySelector('.used-unit').textContent = uf.unit; el.querySelector('.percent-val').textContent = pc + '%'; el.querySelector('.update-time').textContent = '更新于: ' + formatTime(d.next_update); el.querySelector('.reset-date').textContent = '距离流量重置: ' + d.reset_date; var bar = el.querySelector('.progress-bar'); bar.style.width = pc + '%'; bar.style.backgroundColor = getColor(parseFloat(pc)); } function fixPrice(container, d) { if (!d.price || d.billing_cycle == null) return; var text = '价格: ' + (d.currency || '$') + d.price + '/' + getCycleText(d.billing_cycle); var ps = container.getElementsByTagName('p'); for (var i = 0; i < ps.length; i++) { if (ps[i].textContent.indexOf('价格:') !== -1) ps[i].textContent = text; } } function getCardTraffic(container) { var divs = container.querySelectorAll('.inline-flex'); var up = null, down = null; for (var i = 0; i < divs.length; i++) { var t = divs[i].textContent; if (t.indexOf('上传') !== -1) up = parseTraffic(t); else if (t.indexOf('下载') !== -1) down = parseTraffic(t); } return up && down ? { up: up, down: down } : null; } function matchCard(candidates, d) { if (candidates.length === 1) return candidates[0]; var best = null, bestDiff = Infinity; for (var i = 0; i < candidates.length; i++) { var traffic = getCardTraffic(candidates[i].closest('div')); if (!traffic) continue; var upDiff = Math.abs(traffic.up - d.net_total_up) / Math.max(d.net_total_up, 1); var downDiff = Math.abs(traffic.down - d.net_total_down) / Math.max(d.net_total_down, 1); if (upDiff < CONFIG.trafficTolerance && downDiff < CONFIG.trafficTolerance) { var avg = (upDiff + downDiff) / 2; if (avg < bestDiff) { best = candidates[i]; bestDiff = avg; } } } return best || candidates[0]; } // 应用自定义描述样式 function applyCustomDescStyle() { var elements = document.querySelectorAll('*'); for (var i = 0; i < elements.length; i++) { var el = elements[i]; if (el.textContent && el.textContent.trim() === 'De 鸡窝') { el.classList.add('custom-desc'); } } } function render(list) { var sections = document.querySelectorAll('section.grid.items-center.gap-2'); var used = new Set(); // 应用自定义描述样式 applyCustomDescStyle(); for (var i = 0; i < list.length; i++) { var d = list[i]; if (!d.limit || !d.used) continue; var norm = normName(d.name); var candidates = []; for (var j = 0; j < sections.length; j++) { if (used.has(sections[j])) continue; var nameEl = sections[j].querySelector('p'); if (nameEl && normName(nameEl.textContent.trim()) === norm) candidates.push(sections[j]); } if (!candidates.length) continue; var target = matchCard(candidates, d); used.add(target); var container = target.closest('div'); fixPrice(container, d); // 找到上传下载的 section 作为插入点 var uploadDownloadSec = null; var allSections = container.querySelectorAll('section.flex.items-center.w-full.justify-between.gap-1'); for (var k = 0; k < allSections.length; k++) { if (allSections[k].textContent.indexOf('上传:') !== -1 && allSections[k].textContent.indexOf('下载:') !== -1) { uploadDownloadSec = allSections[k]; break; } } if (!uploadDownloadSec) continue; // 检查是否已存在进度条(防止重复) var existingBar = container.querySelector('.traffic-bar[data-uuid="' + d.uuid + '"]'); if (existingBar) { // 已存在,只更新数据 updateBar(existingBar, d); } else { // 不存在,创建新的 var bar = createBar(d); uploadDownloadSec.parentNode.insertBefore(bar, uploadDownloadSec.nextSibling); barCache.set(d.uuid, bar); } } } // 数据管理 var dataCache = null, loading = false; function rpc(method, params) { return fetch(CONFIG.apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ id: Date.now(), method: method, params: params || {}, jsonrpc: '2.0' }) }).then(function(r) { return r.json(); }); } function calcUsed(up, down, type) { if (type === 'max') return Math.max(up, down); if (type === 'min') return Math.min(up, down); if (type === 'up') return up; if (type === 'down') return down; return up + down; } function fetchData(cb) { var now = Date.now(); if (dataCache && now - dataCache.time < CONFIG.interval) { cb(dataCache.data); return; } if (loading) return; loading = true; rpc('common:getNodes').then(function(res) { var nodes = Object.values(res.result || res.data || {}); return Promise.all(nodes.map(function(n) { return rpc('common:getNodeRecentStatus', { uuid: n.uuid, limit: 1 }).then(function(sr) { var rec = ((sr.result || sr.data || {}).records || [])[0] || {}; var up = rec.net_total_up || 0, down = rec.net_total_down || 0; return { name: n.name, uuid: n.uuid, limit: n.traffic_limit || 0, used: calcUsed(up, down, n.traffic_limit_type || 'sum'), next_update: rec.time, reset_date: calcResetDays(n.expired_at), price: n.price, currency: n.currency, billing_cycle: n.billing_cycle, net_total_up: up, net_total_down: down }; }).catch(function() { return { name: n.name, uuid: n.uuid, limit: n.traffic_limit || 0, used: 0, next_update: null, reset_date: calcResetDays(n.expired_at), price: n.price, currency: n.currency, billing_cycle: n.billing_cycle, net_total_up: 0, net_total_down: 0 }; }); })); }).then(function(data) { dataCache = { time: now, data: data }; cb(data); }).finally(function() { loading = false; }); } // 访客信息功能 var visitorData = null; function detectBrowser() { var ua = navigator.userAgent; var browser = "未知浏览器"; if (ua.indexOf("Edg") > -1) browser = "Microsoft Edge"; else if (ua.indexOf("Chrome") > -1) browser = "Google Chrome"; else if (ua.indexOf("Firefox") > -1) browser = "Mozilla Firefox"; else if (ua.indexOf("Safari") > -1) browser = "Apple Safari"; else if (ua.indexOf("Opera") > -1) browser = "Opera"; else if (ua.indexOf("Trident") > -1 || ua.indexOf("MSIE") > -1) browser = "Internet Explorer"; return browser; } function detectOS() { var ua = navigator.userAgent; var os = "未知系统"; if (ua.indexOf("Win") > -1) { if (ua.indexOf("Windows NT 10.0") > -1) os = "Windows 10/11"; else if (ua.indexOf("Windows NT 6.3") > -1) os = "Windows 8.1"; else if (ua.indexOf("Windows NT 6.2") > -1) os = "Windows 8"; else if (ua.indexOf("Windows NT 6.1") > -1) os = "Windows 7"; else if (ua.indexOf("Windows NT 6.0") > -1) os = "Windows Vista"; else os = "Windows"; } else if (ua.indexOf("Mac") > -1) { os = "macOS"; } else if (ua.indexOf("Linux") > -1) { os = "Linux"; } else if (ua.indexOf("Android") > -1) { os = "Android"; } else if (ua.indexOf("iOS") > -1 || ua.indexOf("iPhone") > -1 || ua.indexOf("iPad") > -1) { os = "iOS"; } return os; } function detectScreen() { return window.screen.width + "×" + window.screen.height; } function getLocalTime() { return new Date().toLocaleString('zh-CN'); } function copyToClipboard(text) { navigator.clipboard.writeText(text).then(function() { var originalText = event.target.textContent; event.target.textContent = '已复制'; setTimeout(function() { event.target.textContent = originalText; }, 2000); }).catch(function(err) { console.error('复制失败:', err); }); } async function fetchVisitorInfo() { try { // 尝试从多个IP API获取信息 const ipApis = [ 'https://api.ipify.org?format=json', 'https://api64.ipify.org?format=json', 'https://ipapi.co/json/' ]; let ipData = null; for (const api of ipApis) { try { const response = await fetch(api); if (response.ok) { ipData = await response.json(); break; } } catch (e) { continue; } } // 如果获取失败,使用备用方案 if (!ipData || !ipData.ip) { return { ip: '获取中...', isp: '未知', city: '未知', region: '未知', country: '未知', timezone: '未知' }; } // 使用ipapi.co获取详细信息 if (ipData && ipData.ip) { try { const detailResponse = await fetch(`https://ipapi.co/${ipData.ip}/json/`); if (detailResponse.ok) { const details = await detailResponse.json(); return { ip: ipData.ip, isp: details.org || details.asn || '未知', city: details.city || '未知', region: details.region || '未知', country: details.country_name || '未知', timezone: details.timezone || '未知' }; } } catch (e) { // 如果详细API失败,至少返回IP } } return { ip: ipData.ip || '获取中...', isp: '未知', city: '未知', region: '未知', country: '未知', timezone: '未知' }; } catch (error) { console.error('获取访客信息失败:', error); return { ip: '获取失败', isp: '未知', city: '未知', region: '未知', country: '未知', timezone: '未知' }; } } // 检测当前页面主题 function detectPageTheme() { // 常见主题类名检测 var root = document.documentElement; var body = document.body; // 检查常见暗色主题类名 var darkClasses = ['dark', 'dark-mode', 'dark-theme', 'darkMode', 'theme-dark']; var lightClasses = ['light', 'light-mode', 'light-theme', 'lightMode', 'theme-light']; // 检查 body 和 html 的类名 for (var i = 0; i < darkClasses.length; i++) { if (body.classList.contains(darkClasses[i]) || root.classList.contains(darkClasses[i])) { return 'dark'; } if (body.classList.contains(lightClasses[i]) || root.classList.contains(lightClasses[i])) { return 'light'; } } // 检查 data-theme 属性 var rootTheme = root.getAttribute('data-theme'); var bodyTheme = body.getAttribute('data-theme'); if (rootTheme === 'dark' || bodyTheme === 'dark') return 'dark'; if (rootTheme === 'light' || bodyTheme === 'light') return 'light'; // 检查 data-mode 属性 var rootMode = root.getAttribute('data-mode'); var bodyMode = body.getAttribute('data-mode'); if (rootMode === 'dark' || bodyMode === 'dark') return 'dark'; if (rootMode === 'light' || bodyMode === 'light') return 'light'; // 检查是否有 dark 相关的类名(部分匹配) var allClasses = body.className + ' ' + root.className; if (allClasses.includes('dark') && !allClasses.includes('light')) return 'dark'; if (allClasses.includes('light') && !allClasses.includes('dark')) return 'light'; // 检查系统主题偏好 if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark'; } return 'light'; // 默认为亮色主题 } // 观察页面主题变化 function observeThemeChanges() { var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'attributes' && (mutation.attributeName === 'class' || mutation.attributeName === 'data-theme' || mutation.attributeName === 'data-mode')) { // 主题可能已更改,但我们依赖CSS选择器自动处理 } }); }); // 观察html和body元素 observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme', 'data-mode'] }); observer.observe(document.body, { attributes: true, attributeFilter: ['class', 'data-theme', 'data-mode'] }); // 监听系统主题变化 if (window.matchMedia) { var darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); darkModeMediaQuery.addListener(function(e) { // 系统主题变化时,如果页面没有明确设置主题,则跟随系统 if (!document.documentElement.classList.contains('dark') && !document.documentElement.classList.contains('light') && !document.body.classList.contains('dark') && !document.body.classList.contains('light') && !document.documentElement.getAttribute('data-theme') && !document.body.getAttribute('data-theme')) { // 我们的CSS媒体查询会自动处理 } }); } } function createVisitorPanel() { var panel = document.createElement('div'); panel.id = 'visitor-info-panel'; panel.innerHTML = ` <div class="visitor-header"> <div class="visitor-title"> <span>🌐 访客信息</span> </div> <button class="visitor-toggle" title="隐藏/显示">−</button> </div> <div class="visitor-content"> <div class="visitor-row"> <span class="visitor-label">IP地址:</span> <span class="visitor-value visitor-ip">获取中...</span> <button class="visitor-copy-btn" onclick="copyToClipboard(this.previousElementSibling.textContent)">复制</button> </div> <div class="visitor-row"> <span class="visitor-label">网络服务商:</span> <span class="visitor-value visitor-isp">获取中...</span> </div> <div class="visitor-row"> <span class="visitor-label">位置:</span> <span class="visitor-value visitor-location">获取中...</span> </div> <div class="visitor-row"> <span class="visitor-label">浏览器:</span> <span class="visitor-value visitor-browser">${detectBrowser()}</span> </div> <div class="visitor-row"> <span class="visitor-label">操作系统:</span> <span class="visitor-value visitor-os">${detectOS()}</span> </div> <div class="visitor-row"> <span class="visitor-label">屏幕分辨率:</span> <span class="visitor-value">${detectScreen()}</span> </div> <div class="visitor-row"> <span class="visitor-label">本地时间:</span> <span class="visitor-value">${getLocalTime()}</span> </div> <button class="visitor-refresh-btn" onclick="refreshVisitorInfo()"> 🔄 刷新信息 </button> </div> `; document.body.appendChild(panel); // 添加显示/隐藏事件 panel.querySelector('.visitor-toggle').addEventListener('click', function() { var content = panel.querySelector('.visitor-content'); content.classList.toggle('hidden'); this.textContent = content.classList.contains('hidden') ? '+' : '−'; }); // 添加拖动功能 var isDragging = false; var offsetX, offsetY; panel.addEventListener('mousedown', function(e) { if (e.target.closest('.visitor-toggle') || e.target.closest('.visitor-copy-btn') || e.target.closest('.visitor-refresh-btn')) { return; } isDragging = true; offsetX = e.clientX - panel.getBoundingClientRect().left; offsetY = e.clientY - panel.getBoundingClientRect().top; panel.style.cursor = 'grabbing'; }); document.addEventListener('mousemove', function(e) { if (!isDragging) return; e.preventDefault(); var x = e.clientX - offsetX; var y = e.clientY - offsetY; // 限制在窗口范围内 var maxX = window.innerWidth - panel.offsetWidth; var maxY = window.innerHeight - panel.offsetHeight; x = Math.max(0, Math.min(x, maxX)); y = Math.max(0, Math.min(y, maxY)); panel.style.left = x + 'px'; panel.style.right = 'auto'; panel.style.bottom = y + 'px'; panel.style.top = 'auto'; }); document.addEventListener('mouseup', function() { isDragging = false; panel.style.cursor = 'default'; }); // 开始观察主题变化 observeThemeChanges(); // 更新访客信息 refreshVisitorInfo(); } async function refreshVisitorInfo() { var panel = document.getElementById('visitor-info-panel'); if (!panel) return; // 显示加载状态 var ipElement = panel.querySelector('.visitor-ip'); ipElement.textContent = '获取中...'; panel.querySelector('.visitor-isp').textContent = '获取中...'; panel.querySelector('.visitor-location').textContent = '获取中...'; try { const visitorInfo = await fetchVisitorInfo(); visitorData = visitorInfo; // 更新显示 panel.querySelector('.visitor-ip').textContent = visitorInfo.ip; panel.querySelector('.visitor-isp').textContent = visitorInfo.isp; panel.querySelector('.visitor-location').textContent = visitorInfo.city + ', ' + visitorInfo.region; // 修正信息显示错误:操作系统和屏幕分辨率显示反了 panel.querySelectorAll('.visitor-row')[4].querySelector('.visitor-value').textContent = detectOS(); panel.querySelectorAll('.visitor-row')[5].querySelector('.visitor-value').textContent = detectScreen(); } catch (error) { console.error('刷新访客信息失败:', error); panel.querySelector('.visitor-ip').textContent = '获取失败'; panel.querySelector('.visitor-isp').textContent = '网络异常'; panel.querySelector('.visitor-location').textContent = '请检查网络连接'; } } // 将函数暴露到全局作用域 window.refreshVisitorInfo = refreshVisitorInfo; window.copyToClipboard = copyToClipboard; // 观察器 - 优化防止频繁触发 var observer = null, timer = null, renderPending = false; function update() { fetchData(render); } function scheduleRender() { if (renderPending) return; renderPending = true; setTimeout(function() { renderPending = false; update(); }, 300); } function init() { if (observer) return; observer = new MutationObserver(scheduleRender); observer.observe(document.body, { childList: true, subtree: true }); update(); timer = setInterval(update, CONFIG.interval); // 创建访客信息面板 setTimeout(createVisitorPanel, 1000); // 每分钟更新一次本地时间 setInterval(function() { var panel = document.getElementById('visitor-info-panel'); if (panel && !panel.querySelector('.visitor-content').classList.contains('hidden')) { var timeElement = panel.querySelectorAll('.visitor-row')[6].querySelector('.visitor-value'); if (timeElement) { timeElement.textContent = getLocalTime(); } } }, 60000); window.addEventListener('beforeunload', function() { if (observer) observer.disconnect(); if (timer) clearInterval(timer); barCache.clear(); }, { once: true }); } // 启动 function tryInit() { if (document.querySelector('section.grid[class*="grid-cols-"]')) { requestAnimationFrame(init); } else { setTimeout(tryInit, 250); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', tryInit, { once: true }); } else { tryInit(); }})();</script>
评论 (0)