点击进入原帖

处理了进度条一柱擎天情况

<style>
  /* 不显示自带流量卡片 */
  .mt-4.w-full.mx-auto > div {
    display: none;
  }
</style>

<script>
  /* 卡片显示上下行流量 */
  //window.ShowNetTransfer = "true";
</script>

<script>
  ; (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 < units.length - 1) {
        size /= 1024;
        unitIndex++;
      }
      return {
        value: size.toFixed(unitIndex === 0 ? 0 : 2),
        unit: units[unitIndex]
      };
    }

    function calculatePercentage(used, total) {
      used = Number(used);
      total = Number(total);
      if (used > 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;
      }
    }

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
        });
      }
    }
  }

  const isInline = !!document.querySelector('section.server-inline-list');
  console.log(`[调试] 当前布局是 ${isInline ? 'inline' : 'standard'}`);
  serverMap.forEach((serverData, serverName) => {
    const targetElement = Array.from(document.querySelectorAll('section.grid.items-center.gap-2'))
      .find(el => el.textContent.trim().includes(serverName));
    if (!targetElement) {
      //console.warn(`[renderTrafficStats] 未找到服务器 "${serverName}" (ID: ${serverData.id}) 的元素`);
      return;
    }

    const usedFormatted = formatFileSize(serverData.transfer);
    const totalFormatted = formatFileSize(serverData.max);
    let 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;
    let insertPosition = config.insertPosition;

    const containerDiv = targetElement.closest('div');
    let oldSection = containerDiv.querySelector('section.flex.items-center.w-full.justify-between.gap-1');
    if (!oldSection) {
      oldSection = containerDiv.querySelector('section.grid.grid-cols-5.items-center.gap-3');
      insertPosition = 'after';
    }
    if (isInline) 
    {
      oldSection = containerDiv.querySelector('section.grid.grid-cols-9.items-center.gap-3');
      insertPosition = 'after';
    }
    const existing = containerDiv.querySelector('.' + uniqueClassName);

    // 处理超过100%的情况
    const displayPercentage = percentage > 100 ? 100 : percentage;
    const progressWidth = percentage > 100 ? '100%' : `${percentage}%`;
    const progressColorClass = percentage > 100 ? 'bg-red-500' : 'bg-emerald-500';

    if (config.showTrafficStats) {
      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', displayPercentage + '%');
        safeSetTextContent(existing, '.next-update', `next update: ${next_update}`);
        const progressBar = existing.querySelector('.progress-bar');
        if (progressBar) {
          progressBar.style.width = progressWidth;
          progressBar.classList.remove('bg-emerald-500', 'bg-red-500');
          progressBar.classList.add(progressColorClass);
        }
        console.log(`[renderTrafficStats] 更新已存在的流量条目: ${serverName}`);
      } else if (oldSection) {
        const newElement = document.createElement('div');
        newElement.classList.add('space-y-1.5', 'new-inserted-element', uniqueClassName);
        newElement.style.width = '100%';
        if (config.style === 1) {
          newElement.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-traffic">${usedFormatted.value}</span>
                <span class="text-[10px] font-medium text-neutral-800 dark:text-neutral-200 used-unit">${usedFormatted.unit}</span>
                <span class="text-[10px] text-neutral-500 dark:text-neutral-400">/ </span>
                <span class="text-[10px] text-neutral-500 dark:text-neutral-400 total-traffic">${totalFormatted.value}</span>
                <span class="text-[10px] text-neutral-500 dark:text-neutral-400 total-unit">${totalFormatted.unit}</span>
              </div>
              <div class="text-[10px] font-medium text-neutral-600 dark:text-neutral-300">
                <span class="from-date">${fromFormatted}</span>
                <span class="text-neutral-500 dark:text-neutral-400">-</span>
                <span class="to-date">${toFormatted}</span>
              </div>
            </div>
            <div class="relative h-1.5">
              <div class="absolute inset-0 bg-neutral-100 dark:bg-neutral-800 rounded-full"></div>
              <div class="absolute inset-0 ${progressColorClass} rounded-full transition-all duration-300 progress-bar" style="width: ${progressWidth};"></div>
            </div>
          `;
        } else if (config.style === 2) {
          newElement.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-traffic">${usedFormatted.value}</span>
                <span class="text-[10px] font-medium text-neutral-800 dark:text-neutral-200 used-unit">${usedFormatted.unit}</span>
                <span class="text-[10px] text-neutral-500 dark:text-neutral-400">/ </span>
                <span class="text-[10px] text-neutral-500 dark:text-neutral-400 total-traffic">${totalFormatted.value}</span>
                <span class="text-[10px] text-neutral-500 dark:text-neutral-400 total-unit">${totalFormatted.unit}</span>
              </div>
              <span class="text-[10px] text-neutral-500 dark:text-neutral-400 percentage-value">${displayPercentage}%</span>
            </div>
            <div class="relative h-1.5">
              <div class="absolute inset-0 bg-neutral-100 dark:bg-neutral-800 rounded-full"></div>
              <div class="absolute inset-0 ${progressColorClass} rounded-full transition-all duration-300 progress-bar" style="width: ${progressWidth};"></div>
            </div>
            <div class="flex items-center justify-between">
              <div class="text-[10px] text-neutral-500 dark:text-neutral-400">
                <span class="from-date">${fromFormatted}</span>
                <span class="text-neutral-500 dark:text-neutral-400">-</span>
                <span class="to-date">${toFormatted}</span>
              </div>
              <span class="text-[10px] text-neutral-500 dark:text-neutral-400">next update: ${next_update}</span>
            </div>
          `;
        }
        if (insertPosition === 'before') oldSection.before(newElement);
        else if (insertPosition === 'replace') oldSection.replaceWith(newElement);
        else oldSection.after(newElement);
        console.log(`[renderTrafficStats] 插入新流量条目: ${serverName},插入方式: ${insertPosition}`);
      }
    } else {
      if (existing) {
        existing.remove();
        console.log(`[renderTrafficStats] 已隐藏流量条目: ${serverName}`);
      }
    }
  });
}


    function updateTrafficStats(force = false) {
      const now = Date.now();
      if (!force && trafficCache && (now - trafficCache.timestamp < config.interval)) {
        console.log('[updateTrafficStats] 使用缓存数据');
        renderTrafficStats(trafficCache.data);
        return;
      }

      console.log('[updateTrafficStats] 正在请求新数据...');
      fetch('/api/v1/service')
        .then(res => res.json())
        .then(data => {
          if (!data.success) {
            console.warn('[updateTrafficStats] 请求成功但返回数据异常');
            return;
          }
          console.log('[updateTrafficStats] 成功获取新数据');
          const trafficData = data.data.cycle_transfer_stats;
          trafficCache = {
            timestamp: now,
            data: trafficData
          };
          renderTrafficStats(trafficData);
        })
        .catch(err => console.error('[updateTrafficStats] 获取失败:', err));
    }

    function startPeriodicRefresh() {
      if (!trafficTimer) {
        console.log('[startPeriodicRefresh] 启动周期刷新任务');
        trafficTimer = setInterval(() => {
          updateTrafficStats();
        }, config.interval);
      }
    }

    function onDomChildListChange() {
      console.log('[onDomChildListChange] 检测到DOM变化, 立即刷新');
      updateTrafficStats();
      if (!trafficTimer) {
        console.log('[onDomChildListChange] 启动定时刷新');
        startPeriodicRefresh();
      }
    }

    const observer = new MutationObserver(mutations => {
      for (const mutation of mutations) {
        if (mutation.type === 'childList') {
          const nodes = [...mutation.addedNodes, ...mutation.removedNodes];
          const matched = nodes.some(node => {
            if (node.nodeType !== 1 || !node.querySelectorAll) return false;
            return Array.from(node.querySelectorAll('span.text-muted-foreground'))
              .some(el => Array.from(el.classList).some(cls => cls.includes('text-[')));
          });

          if (matched) {
            onDomChildListChange();
            break;
          } 
        }
      }
    });

    const targetNode = document.querySelector('main') || document.body;

    observer.observe(targetNode, {
      childList: true,
      subtree: true
    });

    updateTrafficStats(true);  // 初始强制刷新
    startPeriodicRefresh();

    window.addEventListener('beforeunload', () => {
      if (trafficTimer) clearInterval(trafficTimer);
      observer.disconnect();
    });
  })();
</script>