哪吒面板终极进化之VPS橱窗
直接进入正题,继续了哪吒面板的终极进化之路:VPS橱窗
步骤
在
哪吒面板
、设置界面
、自定义代码(包括 style 和 script)
中添加下面这段js
代码;刷新即可看到效果
演示,哪吒版本:
0.16.22
配合油猴脚本:哪吒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
中填写你需要的联系方式contacts
中main
标识需要显示在购买同款按钮旁边的联系方式contacts
中animatedType
标识剩余价值
的动画方式,留空为横向移动
,可选值为:vertical
、fade
。参考地址:https://semantic-ui.com/elements/button.html#animatedcontacts
中的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>
效果图
更新日志
2024.5.27
增加默认分组和默认暗黑模式;
2024.5.21
增加自动续费配置
2024.5.17
修复默认主题设置ServerStatus脚本失效问题;
2024.5.16
支持ServerStatus主题;
2024.5.15
继续优化剩余价值展示效果;
优化进度条代码、更新介绍文档;