哪吒面板终极进化之VPS橱窗

78

源地址

直接进入正题,继续了哪吒面板的终极进化之路:VPS橱窗

步骤

特性

  • 支持购买同款按钮

  • 分组支持数量显示

  • 支持价格付款周期展示

  • 支持多种联系方式展示

  • 支持到期日期进度条展示

  • 支持剩余价值展示,支持年付半年付季付月付,参考下面特殊说明cycle字段,仅供参考,计算方式取自@sunfei大佬的帖子,感谢 @sunfei 大佬分享。

  • 支持ServerStatus主题

  • 支持自动续费配置

特殊说明

  • 不限定所属分类,需要为每个小鸡指定shop字段,其值对应affLinks中的key

  • affLinks中填写不同商家的邀请链接地址

  • extraData中的数字key要换成自己机器的id

  • extraData中的购买价格price支持单位:$、¥、P、€

  • extraData中的付款周期cycle支持填写:年付、半年付、季付、月付、年、半、季、月、Year、Half、Quarterly、Month、Y、H、Q、M、year、half、quarterly、month

  • extraData中的自动续费autoPay支持填写:是、否

  • contacts中填写你需要的联系方式

  • contactsmain标识需要显示在购买同款按钮旁边的联系方式

  • contactsanimatedType标识剩余价值的动画方式,留空为横向移动,可选值为:verticalfade。参考地址:https://semantic-ui.com/elements/button.html#animated

  • contacts中的icon图标在这里查找:https://semantic-ui.com/elements/icon.html

  • 进度条的颜色progressType可用值参考:https://semantic-ui.com/modules/progress.html

  • ServerStatus主题的修改默认隐藏掉了系统在线时间负载三列

  • 其它效果大家可自行发挥

<script>
// 默认分组模式
if(!localStorage.getItem("showGroup")){
    localStorage.setItem("showGroup", true);
}
// 默认暗黑模式
if(!localStorage.getItem("theme")){
    localStorage.setItem("theme", 'dark');
}
window.onload = function(){
const affLinks = {
  crunchbits: 'https://get.crunchbits.com/order/lblk-yearly-kvm-ssd-vps/84',
  rainyun: 'https://www.rainyun.com/MjYzMTk=_',
}
const contacts = {
  main: 'telegram',
  animatedType: 'vertical',
  telegram: {
    label: 'TG',
    icon: 'telegram plane',
    url: '你的tg网址'
  },
  qq: {
    label: 'QQ',
    icon: 'qq',
    url: 'https://wpa.qq.com/msgrd?V=3&Uin=你的qq&Site=你的域名&menu=yes'
  },
  email: {
    label: 'Email',
    icon: 'mail',
    url: '你的邮箱'
  },
}
const extraData = {
  2: {
    pid: 0,
    shop: 'crunchbits',
    price: '$27.38',
    cycle: 'Year',
    start: '08/13/2023',
    expire: '08/13/2024',
    autoPay: '否'
  },
  14: {
    pid: 0,
    shop: 'rainyun',
    price: 'P5500',
    cycle: 'Month',
    start: '12/27/2023',
    expire: '∞',
    autoPay: '是'
  },
}
const cycleNames = {
  '年付': 'year',
  '半年付': 'half',
  '季付': 'quarterly',
  '月付': 'month',
  '年': 'year',
  '半': 'half',
  '季': 'quarterly',
  '月': 'month',
  'Year': 'year',
  'Half': 'half',
  'Quarterly': 'quarterly',
  'Month': 'month',
  'Y': 'year',
  'H': 'half',
  'Q': 'quarterly',
  'M': 'month',
  'year': 'year',
  'half': 'half',
  'quarterly': 'quarterly',
  'month': 'month',
}
const cycleValues = {
  year: 365,
  half: 180,
  quarterly: 90,
  month: 30,
}
// 判断当前主题
const cookie = document.cookie;
let preferredTheme = document.body.innerHTML.match(/(?<=defaultTemplate: ")(default|server-status)(?=")/g) ? document.body.innerHTML.match(/(?<=defaultTemplate: ")(default|server-status)(?=")/g)[0] : 'default';
preferredTheme = document.cookie.match(/(server-status|default)/g) ? document.cookie.match(/(server-status|default)/g)[0] : preferredTheme;
// 默认主题
if(preferredTheme === 'default'){
  const cats = document.querySelectorAll('.ui.accordion');
  cats.forEach((e, i)=>{
    let $catsTitle = e.querySelector('.title');
    let ct = $catsTitle.innerText;
    ct = ct.trim();	
    
    let $itemCard = e.querySelectorAll('.ui.card');
    let uiCardCount = $itemCard.length;
    $catsTitle.innerHTML = $catsTitle.innerHTML.replace(ct, ct+ ' ('+ uiCardCount +')');
    $itemCard.forEach((ee, ii)=>{
      let $content = ee.querySelector('.content');
      let $descriptionGrid = ee.querySelector('.description .ui.grid');
      let $itemTitle = $content.querySelector('.header');
      let id = ee.getAttribute('id');
      if(extraData[id]){
        let pid = extraData[id].pid;
        pid = parseInt(pid);
        let shop = extraData[id].shop;
        let price = extraData[id].price;
        let start = extraData[id].start;
        let expire = extraData[id].expire;
        let cycle = extraData[id].cycle;
        let autoPay = extraData[id].autoPay;
        let cycleName = cycleNames[cycle];
        let cycleValue = cycleValues[cycleName];
        let nowTime = parseInt(new Date().getTime() / 1000);
        let beginTime = parseInt(new Date(start).getTime() / 1000);
        if(autoPay && autoPay=='是'){
          let beginDate = new Date(start);
          let nowDate = new Date();
          let mSteps = {
            year: 12,
            half: 6,
            quarterly: 3,
            month: 1,
          }
          expire = beginDate.setMonth(beginDate.getMonth() + mSteps[cycleName]);
          expire = new Date(expire);
          while(expire.getTime() < nowDate.getTime()){
            expire = expire.setMonth(expire.getMonth() + mSteps[cycleName]);
            expire = new Date(expire);
          }
          expire = expire.toLocaleDateString();
        }
        let endTime = parseInt(new Date(expire).getTime() / 1000);
        
        // 购买同款按钮
        let $aButtonsBox = document.createElement('div');
        $aButtonsBox.setAttribute('style', 'margin-bottom: .5em;text-align:right;');
        let $aLinkButtons = document.createElement('div');
        $aLinkButtons.setAttribute('class', 'ui buttons mini');
        let $aLinkBuy = document.createElement('a');
        $aLinkBuy.setAttribute('class', 'ui button positive');
        $aLinkBuy.setAttribute('target', '_blank');
        $aLinkBuy.innerHTML = '购买同款';
        $aLinkBuy.href = affLinks[shop];
        if(pid){
          $aLinkBuy.href += '&pid='+ pid;
        }
        if(price){
          // 购买价格行
          let $priceL = document.createElement('div');
          $priceL.setAttribute('class', 'three wide column');
          $priceL.innerHTML = '价格';
          $descriptionGrid.insertBefore($priceL, $descriptionGrid.childNodes[$descriptionGrid.childNodes.length-3]);
          let $priceR = document.createElement('div');
          $priceR.setAttribute('class', 'thirteen wide column');
          $priceR.innerHTML = '<div class="ui red label"><i class="money bill alternate yellow icon"></i>'+ price +'<a class="detail">'+ cycle +'</a></div>';
          $descriptionGrid.insertBefore($priceR, $descriptionGrid.childNodes[$descriptionGrid.childNodes.length-3])
        }
        if(expire){
          // 到期日期行
          let $expireL = document.createElement('div');
          $expireL.setAttribute('class', 'three wide column');
          $expireL.innerHTML = '到期';
          $descriptionGrid.insertBefore($expireL, $descriptionGrid.childNodes[$descriptionGrid.childNodes.length-3])
          // 到期日期行右侧进度条
          let $expireR = document.createElement('div');
          let aTime = (nowTime-beginTime);
          let bTime = (endTime-beginTime);
          let cTime = parseInt(aTime / bTime * 100);
          let progressType = 'red';
          let textStyle = 'text-shadow: 0px 0px 5px #db2828;';
          if(expire === '∞'){
            progressType = 'success';
            textStyle = '';
          }
          $expireR.setAttribute('class', 'thirteen wide column');
          $expireR.innerHTML = '<div class="ui progress '+ progressType +'"><div class="bar" style="transition-duration: 300ms; min-width: unset; width: '+ cTime +'% !important;padding-left: 0.4em;"><small style="'+ textStyle +'">'+ expire +'</small></div></div>';
          $descriptionGrid.insertBefore($expireR, $descriptionGrid.childNodes[$descriptionGrid.childNodes.length-3])
        }
        $aLinkButtons.append($aLinkBuy);
        let $aLinkOr = document.createElement('div');
        $aLinkOr.innerHTML = '<div class="or" data-text="or"></div>';
        $aLinkButtons.append($aLinkOr);
        // 购买同款按钮右侧联系方式
        let remainingAnimatedType = contacts['animatedType'];
        let priceValue = price.replace(/[$¥P€]/g, '');
        let priceUnit = price.match(/[$¥P€]/g)[0];
        let remainingDays = Math.floor((endTime - nowTime) / (24 * 60 * 60));
        let remainingPrice = parseFloat(priceValue) / cycleValue  * remainingDays;
        if(!remainingPrice){
          remainingPrice = 0;
        }
        remainingPrice = remainingPrice.toFixed(2);
        let mainContact = contacts['main'];
        // 购买同款按钮右侧主要联系方式显示剩余价值
        let $aLinkContactMain = document.createElement('a');
        $aLinkContactMain.setAttribute('class', 'ui button '+ remainingAnimatedType +' animated blue');
        $aLinkContactMain.setAttribute('target', '_blank');
        $aLinkContactMain.innerHTML = '<div class="hidden content" style="padding:0;" title="剩余价值">'+ contacts[mainContact].label +'议价</div><div class="visible content" style="padding:0;" title="剩余价值"><i class="'+ contacts[mainContact].icon +' icon" style="color:white;"></i>'+ priceUnit + remainingPrice +'</div>';
        $aLinkContactMain.href = contacts[mainContact].url;
        $aLinkButtons.append($aLinkContactMain);
        $aButtonsBox.append($aLinkButtons);
        // 购买同款按钮右侧其它联系方式
        for(let key in contacts){
          if(key!='main' && key!='animatedType' && key!=contacts['main']){
            let icon = contacts[key].icon;
            let $aLinkContact = document.createElement('a');
            $aLinkContact.setAttribute('class', 'ui circular '+ icon +' mini icon button');
            $aLinkContact.setAttribute('target', '_blank');
            $aLinkContact.setAttribute('style', 'margin-left:.5em;');
            $aLinkContact.innerHTML = '<i class="'+ icon +' icon"></i>';
            $aLinkContact.href = contacts[key].url;
            $aButtonsBox.append($aLinkContact);
          }
        }
        $content.append($aButtonsBox);
      }
    });
  });
}
// ServerStatus主题
if(preferredTheme === 'server-status'){
  let $toggleView = document.querySelector('aside.toolbox .toggleView');
  $toggleView.addEventListener('click', ()=>{
    setTimeout(function(){
      location.reload();
    }, 0);
  });
  let $tables = document.querySelectorAll('.table.table-condensed');
  $tables.forEach(e=>{
    $tableTh = e.querySelector('.node-group-tag th');
    $list = e.querySelectorAll('tr.accordion-toggle');
    $tableTh && ($tableTh.innerText += '('+ $list.length +')');
    
    let $head = e.querySelector('table.table-condensed thead').lastChild;
    let $servers = e.querySelector('#servers');
    // 隐藏三列:系统、在线天数、负载
    $head.querySelector('th.os').style.display = 'none';
    $head.querySelector('th.uptime').style.display = 'none';
    $head.querySelector('th.load').style.display = 'none';
    // 添加三列
    let $expireTh = document.createElement('th');
    $expireTh.innerText = '到期 / 剩余价值';
    $expireTh.setAttribute('class', 'node-cell expire center');
    $head.insertBefore($expireTh, $head.childNodes[3]);
    
    let $buyTh = document.createElement('th');
    $buyTh.innerText = '购买同款';
    $buyTh.setAttribute('class', 'node-cell expire center');
    $head.append($buyTh);
    
    let $contactTh = document.createElement('th');
    $contactTh.innerText = '议价';
    $contactTh.setAttribute('class', 'node-cell expire center');
    $head.append($contactTh);
    
    $servers.querySelectorAll('tr.accordion-toggle').forEach(ee=>{
      ee.querySelector('td.os').style.display = 'none';
      ee.querySelector('td.uptime').style.display = 'none';
      ee.querySelector('td.load').style.display = 'none';
      
      let id = ee.getAttribute('id');
      id = id.replace('r', '');
      if(extraData[id]){
        let pid = extraData[id].pid;
        pid = parseInt(pid);
        let shop = extraData[id].shop;
        let price = extraData[id].price;
        let start = extraData[id].start;
        let expire = extraData[id].expire;
        let cycle = extraData[id].cycle;
        let autoPay = extraData[id].autoPay;
        let cycleName = cycleNames[cycle];
        let cycleValue = cycleValues[cycleName];
        let nowTime = parseInt(new Date().getTime() / 1000);
        let beginTime = parseInt(new Date(start).getTime() / 1000);
        if(autoPay && autoPay=='是'){
          let beginDate = new Date(start);
          let nowDate = new Date();
          let mSteps = {
            year: 12,
            half: 6,
            quarterly: 3,
            month: 1,
          }
          expire = beginDate.setMonth(beginDate.getMonth() + mSteps[cycleName]);
          expire = new Date(expire);
          while(expire.getTime() < nowDate.getTime()){
            expire = expire.setMonth(expire.getMonth() + mSteps[cycleName]);
            expire = new Date(expire);
          }
          expire = expire.toLocaleDateString();
        }
        let endTime = parseInt(new Date(expire).getTime() / 1000);
        
        if(expire || price){
          // 到期时间、剩余价值列
          let $expireTd = document.createElement('td');
          $expireTd.setAttribute('class', 'node-cell expire');
          let aTime = (nowTime-beginTime);
          let bTime = (endTime-beginTime);
          let cTime = parseInt(aTime / bTime * 100);
          let progressType = 'warning';
          if(expire === '∞'){
            progressType = 'success';
          }
          
          let priceValue = price.replace(/[$¥P€]/g, '');
          let priceUnit = price.match(/[$¥P€]/g)[0];
          let remainingDays = Math.floor((endTime - nowTime) / (24 * 60 * 60));
          let remainingPrice = parseFloat(priceValue) / cycleValue  * remainingDays;
          if(!remainingPrice){
            remainingPrice = 0;
          }
          remainingPrice = remainingPrice.toFixed(2);
          $expireTd.innerHTML = '<div class="progress progress-expire"><div class="progress-bar progress-bar-'+ progressType +'" style="width: '+ cTime +'%;padding-right:5px;"><small style="white-space: nowrap;">'+ expire +' / '+ priceUnit + remainingPrice +'</small></div></div>';
          ee.insertBefore($expireTd, ee.childNodes[3]);
        }
        // 购买同款列
        let $buyTd = document.createElement('td');
        $buyTd.setAttribute('class', 'node-cell buy');
        $buyTd.setAttribute('style', 'text-align:center;');
        let $buyTdBtn = document.createElement('div');
        $buyTdBtn.setAttribute('class', 'ui left labeled button');
        let $buyTdBtnLabel = document.createElement('div');
        $buyTdBtnLabel.setAttribute('class', 'ui basic label mini');
        $buyTdBtnLabel.setAttribute('style', 'min-height: 20px;padding:0 .5em;height: 20px;font-weight:normal;line-height: 20px;font-size:.78571429rem;');
        $buyTdBtnLabel.innerHTML = price +' / '+ cycle;
        $buyTdBtn.append($buyTdBtnLabel);
        let $buyTdBtnLabelIcon = document.createElement('a');
        $buyTdBtnLabelIcon.setAttribute('class', 'ui icon button mini green');
        $buyTdBtnLabelIcon.setAttribute('style', 'min-height: 20px;padding:0 .5em;height: 20px;line-height: 20px;');
        $buyTdBtnLabelIcon.setAttribute('target', '_blank');
        $buyTdBtnLabelIcon.addEventListener('click', (e)=>{e.stopPropagation()});
        $buyTdBtnLabelIcon.innerHTML = '<i class="shopping cart icon"></i>';
        $buyTdBtnLabelIcon.href = affLinks[shop];
        if(pid){
          $buyTdBtnLabelIcon.href += '&pid='+ pid;
        }
        $buyTdBtn.append($buyTdBtnLabelIcon);
        $buyTd.append($buyTdBtn);
        ee.append($buyTd);
        // 联系方式列
        let $contactTd = document.createElement('td');
        $contactTd.setAttribute('class', 'node-cell contact');
        $contactTd.setAttribute('style', 'text-align:center;white-space:nowrap;');
        for(let key in contacts){
          if(key!='main' && key!='animatedType'){
            let $contactTdContactBtn = document.createElement('a');
            let contactIcon = contacts[key].icon;
            $contactTdContactBtn.setAttribute('class', 'ui circular '+ contactIcon +' icon button mini blue');
            $contactTdContactBtn.setAttribute('style', 'min-height: 20px;padding:0 .5em;height: 20px;line-height: 20px;');
            $contactTdContactBtn.setAttribute('target', '_blank');
            $contactTdContactBtn.addEventListener('click', (e)=>{e.stopPropagation()});
            $contactTdContactBtn.innerHTML = '<i class="'+ contactIcon +' icon"></i>';
            $contactTdContactBtn.href = contacts[key].url;
            $contactTd.append($contactTdContactBtn);
          }
        }
        ee.append($contactTd);
      } else {
        // 无附加信息时显示占位符
        let $expireTd = document.createElement('td');
        $expireTd.setAttribute('class', 'node-cell expire');
        $expireTd.setAttribute('style', 'text-align:center;');
        $expireTd.innerHTML = '-';
        ee.insertBefore($expireTd, ee.childNodes[3]);
        let $buyTd = document.createElement('td');
        $buyTd.setAttribute('class', 'node-cell buy');
        $buyTd.setAttribute('style', 'text-align:center;');
        $buyTd.innerHTML = '-';
        ee.append($buyTd);
        let $contactTd = document.createElement('td');
        $contactTd.setAttribute('class', 'node-cell contact');
        $contactTd.setAttribute('style', 'text-align:center;');
        $contactTd.innerHTML = '-';
        ee.append($contactTd);
      }
    });
  });
}
}
</script>

效果图

photo_2024-05-31_19-24-37.jpg

更新日志

2024.5.27
增加默认分组和默认暗黑模式;

2024.5.21
增加自动续费配置

2024.5.17
修复默认主题设置ServerStatus脚本失效问题;

2024.5.16
支持ServerStatus主题;

2024.5.15
继续优化剩余价值展示效果;
优化进度条代码、更新介绍文档;