哪吒面板V1前台+后台美化代码分享

万毒不侵-无想的一方净土

参考文章:哪吒美化之在卡面添加流量统计进度条 哪吒美化之自定义仪表盘美化 站长部署V1面板也有一段时间了,早在在V0面板时期站长就早早安排上了哪吒面板的美化设置,但由于当时的站长比较懒惰,代码基本是Ctrl+C和Ctrl+V过来的,后来哪吒面板更新到了V1,各种美化已不再适用,站长只好暂时使用默认主题默认外观,美化进度暂时搁置。 不过,很快啊,本后端小码农也是被公司安排上了前后端分离人不分离的工作套餐,也是学上了前端,于是寻思着自己动手丰衣足食,恰好看到V1面板也拥有了dalao开发的美化,不过仅限于仪表板,仪表板作为后台只能独乐乐,要众乐乐,自然得把前台一起动了,依靠本人一点点刚get的前端知识,花费了一上午的时间完成了前台展示页面+后台仪表板的美化代码,现分享如下: 前台效果:自定义背景及图标,流量统计融入服务器页签,鼠标点击碎玻璃特效 仪表板效果:自定义背景和个人头像,标题栏部分模糊,鼠标点击碎玻璃特效 前台部分: 哪吒探针 + 鼠标碎玻璃特效+服务器页签流量显示 /* 设置全局字体为苹方,增加文字白色阴影增强可读性 */ * { font-family: ‘PingFang SC’, ‘Helvetica Neue’, Helvetica, Arial, sans-serif !important; font-size: 16px; text-shadow: 0px 0px 10px rgba(0, 0, 0, 0.9);-webkit-text-fill-color: white; } /* 设置页面背景为一张图片,全屏覆盖 */ html, body { height: 100% !important; background: url("替换为你想要自定义的图片地址") no-repeat center center fixed !important; background-size: cover !important; margin: 0; } #root{ background-color: transparent !important; } /* 卡片透明并带模糊效果 */ .rounded-lg{ backdrop-filter: blur(4px)!important; background-color: transparent !important; box-shadow: none !important; } .bg-white{ backdrop-filter: blur(4px)!important; background-color: transparent !important; box-shadow: none !important; } .flex { background-color: transparent !important; } .bg-card{ background-color: transparent !important; } /* 替换左上角 Logo 图标 */ img[src*="/apple-touch-icon.png"] { content: url("替换为你想要使用的图标") !important; } /* 去除页面底部页脚 */ footer { display: none !important; } /* 主内容容器设置为半透明背景 */ .el-container { background-color: rgba(255, 255, 255, 0.7) !important; } /* 卡片、弹窗等组件设置更高透明度的背景 */ .el-card, .el-message-box, .el-dialog { background-color: rgba(255, 255, 255, 0.9) !important; } /* 深色文字设置白色阴影增强对比度 */ .el-text, .el-button, .el-table, .el-form-item__label, .el-form-item__content, .el-menu-item, .el-submenu__title { color: #333 !important; text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.8); } /* 浅色文字设置黑色阴影提升可视性 */ .el-text-light, .el-button--text, .el-link { color: #fff !important; text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); } /* 碎玻璃特效碎片样式 */ .fragment { position: absolute; width: 10px; height: 10px; background-color: rgba(255, 255, 255, 0.8); clip-path: polygon(0 0, 100% 0, 100% 100%); transform-origin: center; animation: shatter 1.5s ease-out forwards; pointer-events: none; } /* 碎玻璃动画关键帧 */ @keyframes shatter { 0% { transform: translate(0, 0) scale(1); opacity: 1; } 100% { transform: translate(var(--dx), var(--dy)) rotate(var(--angle)) scale(0.5); opacity: 0; } } /* 返回顶部按钮样式 */ #backToTop { position: fixed; bottom: 30px; right: 30px; z-index: 1000; background-color: transparent; color: white; border: none; border-radius: 50%; width: 60px; height: 60px; font-size: 24px; cursor: pointer; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); transition: opacity 0.3s, transform 0.3s; display: none; } //覆写内容宽度 .w-full { max-width: 75rem !important; } #backToTop:hover { transform: scale(1.1); } #backToTop::before { content: "🚀"; font-size: 24px; display: block; line-height: 60px; text-align: center; } .mt-4.w-full.mx-auto div { display: none; } //禁用动画小人 window.DisableAnimatedMan = true; document.addEventListener('click', function(e) { createShatterEffect(e.pageX, e.pageY); }); //点击碎片特效 function createShatterEffect(x, y) { const fragmentCount = 20; for (let i = 0; i fragment.remove(), 1500); } } //隐藏数据 const observer = new MutationObserver(mutations = { document.querySelectorAll('span[data-current-value]').forEach(el = { if(!el.dataset.processed) { el.style.opacity = "0"; el.dataset.processed = true; } }); }); observer.observe(document.body, { childList: true, subtree: true }); //卡面添加流量统计 window.ShowNetTransfer = "true"; ; (function () { let trafficTimer = null; let trafficCache = null; const config = { showTrafficStats: true, insertPosition: 'replace', // 可选:'after', 'before', 'replace' interval: 60000, // 60秒刷新周期 style: 1 }; function formatFileSize(bytes) { if (bytes === 0) return { value: '0', unit: 'B' }; const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; let unitIndex = 0; let size = bytes; while (size = 1024 && unitIndex 1e15 || total 1e15) { used /= 1e10; total /= 1e10; } return (used / total * 100).toFixed(1); } function formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }); } function safeSetTextContent(parent, selector, text) { const el = parent.querySelector(selector); if (el) { el.textContent = text; } } // 返回进度条颜色:绿色(0%)到红色(100%) function getGradientColor(percentage) { const clamp = (val, min, max) = Math.min(Math.max(val, min), max); const lerp = (start, end, t) = Math.round(start + (end - start) * t); const p = clamp(Number(percentage), 0, 100) / 100; const startColor = { r: 16, g: 185, b: 129 }; // #10b981 const endColor = { r: 239, g: 68, b: 68 }; // #ef4444 const r = lerp(startColor.r, endColor.r, p); const g = lerp(startColor.g, endColor.g, p); const b = lerp(startColor.b, endColor.b, p); return `rgb(${r}, ${g}, ${b})`; } function renderTrafficStats(trafficData) { const serverMap = new Map(); for (const cycleId in trafficData) { const cycle = trafficData[cycleId]; if (!cycle.server_name || !cycle.transfer) continue; for (const serverId in cycle.server_name) { const serverName = cycle.server_name[serverId]; const transfer = cycle.transfer[serverId]; const max = cycle.max; const from = cycle.from; const to = cycle.to; const next_update = cycle.next_update[serverId]; if (serverName && transfer !== undefined && max && from && to) { serverMap.set(serverName, { id: serverId, transfer: transfer, max: max, name: cycle.name, from: from, to: to, next_update: next_update }); } } } serverMap.forEach((serverData, serverName) = { const targetElement = Array.from(document.querySelectorAll('section.grid.items-center.gap-2')) .find(section = { const firstText = section.querySelector('p.break-all.font-bold.tracking-tight.text-xs')?.textContent.trim(); return firstText === serverName; }); if (!targetElement) { //console.warn(`[renderTrafficStats] 未找到服务器 "${serverName}" (ID: ${serverData.id}) 的元素`); return; } const usedFormatted = formatFileSize(serverData.transfer); const totalFormatted = formatFileSize(serverData.max); const percentage = calculatePercentage(serverData.transfer, serverData.max); const fromFormatted = formatDate(serverData.from); const toFormatted = formatDate(serverData.to); const next_update = new Date(serverData.next_update).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }); const uniqueClassName = 'traffic-stats-for-server-' + serverData.id; const progressColor = getGradientColor(percentage); let insertPosition = config.insertPosition; const containerDiv = targetElement.closest('div'); if (!containerDiv) return; const existing = Array.from(containerDiv.querySelectorAll('.new-inserted-element')).find(el = el.classList.contains(uniqueClassName) ); if (!config.showTrafficStats) { if (existing) existing.remove(); return; } if (existing) { safeSetTextContent(existing, '.used-traffic', usedFormatted.value); safeSetTextContent(existing, '.used-unit', usedFormatted.unit); safeSetTextContent(existing, '.total-traffic', totalFormatted.value); safeSetTextContent(existing, '.total-unit', totalFormatted.unit); safeSetTextContent(existing, '.from-date', fromFormatted); safeSetTextContent(existing, '.to-date', toFormatted); safeSetTextContent(existing, '.percentage-value', percentage + '%'); safeSetTextContent(existing, '.next-update', `next update: ${next_update}`); const progressBar = existing.querySelector('.progress-bar'); if (progressBar) { progressBar.style.width = percentage + '%'; progressBar.style.backgroundColor = progressColor; } console.log(`[renderTrafficStats] 更新已存在的流量条目: ${serverName}`); } else { let oldSection = containerDiv.querySelector('section.flex.items-center.w-full.justify-between.gap-1'); if (!oldSection) { oldSection = containerDiv.querySelector('section.grid.items-center.gap-3'); insertPosition = 'after'; } if (!oldSection) return; const newElement = document.createElement('div'); newElement.classList.add('spa…

Read more · Journal