哪吒面板终极进化之VPS橱窗
2024-09-03 00:42:00 # 燕坊清作

简介

为了方便,为了好看,继续了哪吒面板的终极进化之路:VPS橱窗。我觉得可以叫做VPS小商城了,貌似该有的都有了,没有的以后可能还会有。。

更新日志

  • 2024年09月03日
    • 更新后台脚本修复可能无法获取到所需数据的bug
  • 2024年08月30日
    • 更新后台脚本支持商家信息管理
    • 更新后台脚本商家支持下拉选择
  • 2024年08月28日
    • 修复shop为空时按钮链接的小bug
    • 优化默认主题进度条过短时到期日期显示问题
  • 2024年08月27日
    • 增加默认启用半透明效果代码
  • 2024年08月19日
    • 更新前台脚本过期时间进度条样式,尽量贴合原框架风格
  • 2024年08月17日
    • 支持0.18.14+版本的后台自定义代码,请查看后台脚本
    • 优化支持最新版的serverstatus主题,请查看前台脚本

步骤

  • 哪吒面板设置界面自定义代码(包括 style 和 script)中添加下面前台脚本代码;
  • 刷新即可看到效果
  • 演示,哪吒版本:0.19.5
  • 后台脚本仅支持哪吒面板v0.18.14+,低于此版本的需要使用油猴脚本:哪吒VPS橱窗后台脚本食用,油猴脚本不在更新,推荐安装哪吒面板最新版并使用下方后台脚本
  • 哪吒面板设置界面Custom Codes for Dashboard中添加下面后台脚本代码;

特性

  • 支持购买同款按钮
  • 分组支持数量显示
  • 支持价格付款周期展示
  • 支持多种联系方式展示
  • 支持到期日期进度条展示
  • 支持剩余价值展示,支持年付半年付季付月付,参考下面特殊说明cycle字段,仅供参考,计算方式取自@sunfei大佬的帖子,感谢分享。
  • 支持ServerStatus主题
  • 支持自动续费配置
  • 默认显示分组,不需要可删除相关代码
  • 默认显示暗黑模式,不需要可删除相关代码
  • 默认启用半透明效果,不需要可删除相关代码
  • 后台脚本仅支持哪吒面板0.18.14+版本,低于此版本的请继续使用油猴版VPS橱窗后台脚本,油猴脚本不再更新
  • 后台脚本支持商家信息管理VPS额外信息管理,将尽可能保证支持界面可视化操作,减少编辑脚本操作

特殊说明

  • 不限定所属分类,需要为每个小鸡指定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主题的修改默认隐藏掉了系统在线时间负载三列
  • 对增加的魔改元素增加css类名,方便有需要的同学添加个性化样式,具体可以使用浏览器工具检查元素查看
  • 其它效果大家可自行发挥

前台脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
<script>
// 默认分组模式
if(!localStorage.getItem("showGroup")){
localStorage.setItem("showGroup", true);
}
// 默认暗黑模式
if(!localStorage.getItem("theme")){
localStorage.setItem("theme", 'dark');
}
// 默认半透明效果
if(!localStorage.getItem("semiTransparent")){
localStorage.setItem("semiTransparent", 'true');
}
window.onload = function(){
const affLinks = {
crunchbits: 'https://get.crunchbits.com/order/lblk-yearly-kvm-ssd-vps/84',
rainyun: 'https://www.rainyun.com/MjYzMTk=_',
colocrossing: 'https://cloud.colocrossing.com/aff.php?aff=316',
racknerd: 'https://my.racknerd.com/aff.php?aff=9727',
serv00: 'https://www.serv00.com/offer'
}
const contacts = {
main: 'telegram',
animatedType: 'vertical',
telegram: {
label: 'TG',
icon: 'telegram plane',
url: 'https://t.me/bmqyChatBot'
},
qq: {
label: 'QQ',
icon: 'qq',
url: 'https://wpa.qq.com/msgrd?V=3&Uin=88268459&Site=nezha.887776.xyz&menu=yes'
},
email: {
label: 'Email',
icon: 'mail',
url: 'mailto:notice@bmqy.net'
},
}
const extraData = {
2: {
pid: 0,
shop: 'crunchbits',
price: '$27.38',
cycle: 'Year',
start: '08/13/2023',
expire: '08/13/2024',
autoPay: '否'
},
15: {
pid: 0,
shop: 'crunchbits',
price: '$22.69',
cycle: 'Year',
start: '04/08/2024',
expire: '04/08/2025',
autoPay: '否'
},
10: {
pid: 358,
shop: 'racknerd',
price: '$10.98',
cycle: 'Year',
start: '11/14/2023',
expire: '11/14/2024',
autoPay: '否'
},
12: {
pid: 23,
shop: 'colocrossing',
price: '$10.00',
cycle: 'Year',
start: '02/13/2024',
expire: '02/13/2025',
autoPay: '否'
},
13: {
pid: 0,
shop: 'rainyun',
price: '¥245',
cycle: 'Year',
start: '07/03/2023',
expire: '08/09/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', 'btn-buy-box ui buttons mini');
let $aLinkBuy = document.createElement('a');
$aLinkBuy.setAttribute('class', 'ui btn-buy button positive');
$aLinkBuy.setAttribute('target', shop ? '_blank' : '');
$aLinkBuy.innerHTML = '购买同款';
$aLinkBuy.href = shop ? affLinks[shop] : 'javascript:void(0);';
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', 'price-box 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;left:.5em;text-align: right;';
if(expire === '∞'){
progressType = 'success';
textStyle = '';
}
$expireR.setAttribute('class', 'expire-box thirteen wide column');
$expireR.innerHTML = '<div class="ui indicating progress '+ progressType +' active"><div class="bar" style="line-height: 1em !important;transition-duration: 300ms; min-width: unset; width: '+ cTime +'% !important;padding-left: 0.4em;"><div class="progress" style="'+ textStyle +'">'+ expire +'</div></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', 'contact-main 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', 'contact-btn 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主题
function bindToggleViewClick(){
let $toggleView = document.querySelector('aside.toolbox .toggleView')
$toggleView && $toggleView.addEventListener('click', ()=>{
setTimeout(function(){
location.reload();
}, 0);
});
}
if(preferredTheme === 'server-status'){
bindToggleViewClick();
let $biThreeDots = document.querySelector('aside.toolbox .bi-three-dots');
$biThreeDots && $biThreeDots.addEventListener('click', ()=>{
bindToggleViewClick();
});

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 = 'danger';
if(expire === '∞'){
progressType = 'war';
}

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 +' progress-bar-striped active" style="width: '+ cTime +'%;padding-right:5px;background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);text-align:right !important;"><small class="progress" style="white-space: nowrap;background-color: transparent;background-image: unset;">'+ 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', shop ? '_blank' : '');
$buyTdBtnLabelIcon.addEventListener('click', (e)=>{e.stopPropagation()});
$buyTdBtnLabelIcon.innerHTML = '<i class="shopping cart icon"></i>';
$buyTdBtnLabelIcon.href = shop ? affLinks[shop] : 'javascript:void(0);';
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>

后台脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
<script>
window.onload = function(){
const storagePreKey = 'nzVpsChuChuangData.';
const storageSet = function(key, value){
key = `${storagePreKey}${key}`;
localStorage.setItem(key, JSON.stringify(value));
}
const storageGet = function(key, defaultValue){
key = `${storagePreKey}${key}`;
return JSON.parse(localStorage.getItem(key)) || defaultValue;
}
const extraDataKeyName = {
shop:"商家名称",
pid: '产品ID',
price:"购买价格",
cycle: "付款周期",
start: "购买日期",
expire: "过期时间",
autoPay: '自动续费',
}
const cycleOptions = [
'年付',
'半年付',
'季付',
'月付',
'年',
'半',
'季',
'月',
'Year',
'Half',
'Quarterly',
'Month',
'Y',
'H',
'Q',
'M',
'year',
'half',
'quarterly',
'month',
];
const autoPayOptions = [
'否',
'是'
];
let timmer = null;
let changer = false;
const pathname = location.pathname;
const $footer = document.querySelector('.footer');
if(!$footer || $footer.innerText.indexOf('Powered by 哪吒监控')==-1) return false;
let raw = storageGet('raw', '');
let affLinks = storageGet('affLinks', null);
let extra = storageGet('extra', null);
let isFirst = false;
if(affLinks == null && extra== null){
isFirst = true;
}
if(pathname === '/setting'){
const $settingForm = document.forms.settingForm;
let settingData = new FormData($settingForm);
settingData = new URLSearchParams(settingData).toString();
storageSet('raw', settingData);
const CustomCode = document.querySelector('textarea[name=CustomCode]').value;
let CustomCodeValueAffLinks =CustomCode.match(/(?<=affLinks = )[\s\S]+(?=\n([\s\t]+)?const contacts)/g);
let CustomCodeValueExtra =CustomCode.match(/(?<=extraData = )[\s\S]+(?=\n([\s\t]+)?const cycleNames)/g);
if(!CustomCodeValueAffLinks || !CustomCodeValueExtra){
$.suiAlert({
title: '',
description: '请检查是否已经添加《哪吒面板VPS橱窗脚本》,脚本可去:https://www.bmqy.net/2665.html,获取',
type: 'error',
time: '3',
position: 'top-center',
});
return false;
}
CustomCodeValueAffLinks = CustomCodeValueAffLinks ? CustomCodeValueAffLinks[0] : '{}';
CustomCodeValueAffLinks = CustomCodeValueAffLinks.replace(/([0-9a-zA-Z]+):\s?'/g, '"$1":"').replace(/'/g, '"').replace(/[\r\n]+/g, '').replace(/,}/g, '}');
affLinks = JSON.parse(CustomCodeValueAffLinks);
storageSet('affLinks', affLinks);
CustomCodeValueExtra = CustomCodeValueExtra ? CustomCodeValueExtra[0] : '{}';
CustomCodeValueExtra = CustomCodeValueExtra.replace(/([0-9a-zA-Z]+):/g, '"$1":').replace(/},\n}/g, '}\n}').replace(/'/g, '"').replace(/[\r\n]+/g, '').replace(/,}/g, '}');
extra = JSON.parse(CustomCodeValueExtra);
storageSet('extra', extra);
if(isFirst){
$.suiAlert({
title: '',
description: 'VPS橱窗后台脚本数据获取成功,可以正常使用了。',
type: 'success',
time: '3',
position: 'top-center',
});
} else {
$.suiAlert({
title: '',
description: 'VPS橱窗后台脚本已重新获取数据。',
type: 'success',
time: '3',
position: 'top-center',
});
}
} else {
if(pathname !='/' && pathname !='/login' && (affLinks == null || extra == null)){
$.suiAlert({
title: '',
description: '请先进入【设置】页面获取脚本所需数据!!!',
type: 'warning',
time: '3',
position: 'top-center',
});
return false;
}
if(pathname === '/server'){
createShopFormModal(affLinks);
const $table = document.querySelector('table.table');
const $tableTr = $table.querySelectorAll('tbody tr');
$tableTr.forEach(e=>{
let $tds = e.querySelectorAll('td');
let id = $tds[1].innerText;
id = id.replace(/\(\d+\)/g, '');
let $nameTd = $tds[2];
let $extraDataBox = document.createElement('div');
$extraDataBox.id = id;
$extraDataBox.setAttribute('class', 'extra-box');
$extraDataBox.setAttribute('style', 'margin-top:10px;');
for(let key in extraDataKeyName){
let extraData = extra[id];
let $inputLabel = document.createElement('div');
$inputLabel.setAttribute('style', 'white-space: nowrap;padding-bottom:3px;');
let requiredTag = '';
if(['price', 'cycle', 'start'].indexOf(key) > -1){
requiredTag = '*';
}
$inputLabel.innerHTML = '<span style="display:inline-block;width:70px;">'+ requiredTag + extraDataKeyName[key] +':</span>';
let $input = document.createElement('input');
if(key === 'pid'){
$input.placeholder = '商家所售产品ID';
} else if(['start', 'expire'].indexOf(key) > -1){
$input.placeholder = '月/日/年';
} else if(key === 'price'){
$input.placeholder = '支持:$、¥、€、P';
}
if(['shop', 'cycle', 'autoPay'].indexOf(key) > -1){
$input = document.createElement('select');
if(key === 'shop'){
$input.options.add(new Option('请选择', ''));
for(let key in affLinks){
$input.options.add(new Option(key, key));
}
}
if(key === 'cycle'){
for(let key in cycleOptions){
$input.options.add(new Option(cycleOptions[key], cycleOptions[key]));
}
}
if(key === 'autoPay'){
for(let key in autoPayOptions){
$input.options.add(new Option(autoPayOptions[key], autoPayOptions[key]));
}
}
}
$input.name = key;
if(extraData){
$input.value = extraData[key] ? extraData[key] : '';
}
$input.addEventListener('change', ()=>{
changer = true;
if(timmer) return false;
console.log('1s 后提交');
timmer = setTimeout(function(){
updateExtraData();
}, 1500);
});
$input.addEventListener('focus', ()=>{
if(timmer){
console.log('终断提交');
clearTimeout(timmer);
timmer = null;
}
});
$input.addEventListener('blur', ()=>{
if(timmer) return false;
if(changer){
console.log('1s 后提交');
timmer = setTimeout(function(){
updateExtraData();
}, 1500);
}
});
$inputLabel.append($input);
if(key === 'shop'){
let $addShopBtn = document.createElement('button');
$addShopBtn.title = '管理商家信息';
$addShopBtn.setAttribute('class', 'ui icon button mini');
$addShopBtn.setAttribute('style', 'margin-left:5px;');
$addShopBtn.innerHTML = '<i class="icon setting"></i>';
$addShopBtn.addEventListener('click', managerShoopFormModal);
$inputLabel.append($addShopBtn);
}
$extraDataBox.append($inputLabel);
}
$nameTd.append($extraDataBox);
})
}
}

function updateExtraData(){
let paramsRaw = new URLSearchParams(raw);
let customCodeOld = paramsRaw.get('CustomCode');
let $extraBox = document.querySelectorAll('table.table .extra-box');
let extraNew = {};
$extraBox.forEach(e=>{
let shop = e.querySelector('select[name=shop]').value,
pid = e.querySelector('input[name=pid]').value,
price = e.querySelector('input[name=price]').value,
cycle = e.querySelector('select[name=cycle]').value,
start = e.querySelector('input[name=start]').value,
expire = e.querySelector('input[name=expire]').value,
autoPay = e.querySelector('select[name=autoPay]').value;
if(price && cycle && start){
extraNew[e.id] = {
shop: shop,
pid: pid,
price: price,
cycle: cycle,
start: start,
expire: expire,
autoPay: autoPay,
}
}
});
storageSet('extra', extraNew);
let customCodeNew = customCodeOld.replace(/(?<=extraData = )[\s\S]+(?=\n[\s\t]*const cycleNames)/g, JSON.stringify(extraNew));
paramsRaw.set('CustomCode', customCodeNew);
$.post('/api/setting', paramsRaw.toString()).then(res=>{
if(res.code == 200){
$.suiAlert({
title: '',
description: 'VPS橱窗前台脚本更新成功。',
type: 'success',
time: '3',
position: 'top-center',
});
} else {
$.suiAlert({
title: '',
description: responses.responseText,
type: 'error',
time: '3',
position: 'top-center',
});
}
}).catch(err=>{
$.suiAlert({
title: '',
description: JSON.stringify(err),
type: 'error',
time: '3',
position: 'top-center',
});
}).always(()=>{
clearTimeout(timmer);
timmer = null;
changer = false;
})
}

function managerShoopFormModal(){
showOnSubmitFormModal(".shopForm.modal", "#shopForm", "/api/setting", getShopData);
}

function getShopData(){
let paramsRaw = new URLSearchParams(raw);
let customCodeOld = paramsRaw.get('CustomCode');
let affLinks = storageGet('affLinks', null);
let customCodeNew = customCodeOld.replace(/(?<=affLinks = )[\s\S]+(?=\n[\s\t]*const contacts)/g, JSON.stringify(affLinks));
paramsRaw.set('CustomCode', customCodeNew);
return paramsRaw.toString();
}

function updateShopData(){
let $shopForm = document.querySelector('#shopForm');
let data = {};
let emptyCount = 0;
for(let i=0; i<$shopForm.elements.length; i+=2){
let $name = $shopForm.elements[i];
let $url = $shopForm.elements[i+1];
if($name.value && $url.value){
data[$name.value] = $url.value;
storageSet('affLinks', data);
if(i == $shopForm.elements.length-2){
createShopFormModal(data);
}
} else {
emptyCount += 1;
}
}
if(emptyCount > 1){
createShopFormModal(data);
}
}

function createInput(name, value){
let $input = document.createElement('input');
$input.type = 'text';
$input.name = name;
$input.setAttribute('value', value || '');
$input.placeholder = name==='name' ? '名称' : '邀请链接';
$input.addEventListener('blur', updateShopData);
return $input;
}
function createShopFormModal(affLinks){
let $shopFormModal = document.querySelector('#shopFormModal');
let isFirst = true;
if(!$shopFormModal){
$shopFormModal = document.createElement('div');
$shopFormModal.id = 'shopFormModal';
$shopFormModal.setAttribute('class', 'ui tiny shopForm modal transition hidden');
} else {
isFirst = false;
$shopFormModal.innerHTML = '';
}
let $shopForm = document.createElement('form');
$shopForm.id = 'shopForm';
$shopForm.setAttribute('class', 'ui form');
for(let key in affLinks){
let $shopFormField = document.createElement('div');
let $shopFormGrid = document.createElement('div');
$shopFormGrid.setAttribute('class', 'ui grid');
let $shopFormGridItem = document.createElement('div');
$shopFormGridItem.setAttribute('class', 'four wide column');
$shopFormGridItem.append(createInput('name', key));
$shopFormGrid.append($shopFormGridItem);
$shopFormGridItem = document.createElement('div');
$shopFormGridItem.setAttribute('class', 'twelve wide column');
$shopFormGridItem.append(createInput('url', affLinks[key]));
$shopFormGrid.append($shopFormGridItem);
$shopFormField.append($shopFormGrid);
$shopForm.append($shopFormField);
}
let $shopFormField = document.createElement('div');
let $shopFormGrid = document.createElement('div');
$shopFormGrid.setAttribute('class', 'ui grid');
let $shopFormGridItem = document.createElement('div');
$shopFormGridItem = document.createElement('div');
$shopFormGridItem.setAttribute('class', 'four wide column');
$shopFormGridItem.append(createInput('name', ''));
$shopFormGrid.append($shopFormGridItem);
$shopFormGridItem = document.createElement('div');
$shopFormGridItem.setAttribute('class', 'twelve wide column');
$shopFormGridItem.append(createInput('url', ''));
$shopFormGrid.append($shopFormGridItem);
$shopFormField.append($shopFormGrid);
$shopForm.append($shopFormField);

let $shopFormModalHead = document.createElement('div');
$shopFormModalHead.setAttribute('class', 'header');
$shopFormModalHead.innerHTML = 'VPS橱窗商家信息管理';
$shopFormModal.append($shopFormModalHead);
let $shopFormModalContent = document.createElement('div');
$shopFormModalContent.setAttribute('class', 'content');
$shopFormModalContent.append($shopForm);
$shopFormModal.append($shopFormModalContent);
let $shopFormModalActions = document.createElement('div');
$shopFormModalActions.setAttribute('class', 'actions');
let $shopFormModalActionsCancelBtn = document.createElement('div');
$shopFormModalActionsCancelBtn.setAttribute('class', 'ui negative button');
$shopFormModalActionsCancelBtn.innerHTML = '取消';
$shopFormModalActions.append($shopFormModalActionsCancelBtn);
let $shopFormModalActionsConfirmBtn = document.createElement('div');
$shopFormModalActionsConfirmBtn.setAttribute('class', 'ui positive nezha-primary-btn right labeled icon button vps-window-btn');
$shopFormModalActionsConfirmBtn.innerHTML = '确认<i class="checkmark icon"></i>';
$shopFormModalActions.append($shopFormModalActionsConfirmBtn);
$shopFormModal.append($shopFormModalActions);
isFirst && document.body.append($shopFormModal);
}

function showOnSubmitFormModal(modelSelector, formID, URL, getData) {
$(modelSelector)
.modal({
closable: true,
onApprove: function () {
let success = false;
const btn = $(modelSelector + " .vps-window-btn.button");
const form = $(modelSelector + " form");
if (btn.hasClass("loading")) {
return success;
}
form.children(".message").remove();
btn.toggleClass("loading");
const data = getData
? getData()
: $(formID)
.serializeArray()
.reduce(function (obj, item) {
// ID 类的数据
if (item.name === "pid") {
obj[item.name] = parseInt(item.value);
} else {
obj[item.name] = item.value;
}
return obj;
}, {});
$.post(URL, data)
.done(function (resp) {
if (resp.code == 200) {
window.location.reload()
} else {
form.append(
`<div class="ui negative message"><div class="header">操作失败</div><p>` +
resp.message +
`</p></div>`
);
}
})
.fail(function (err) {
form.append(
`<div class="ui negative message"><div class="header">网络错误</div><p>` +
err.responseText +
`</p></div>`
);
})
.always(function () {
btn.toggleClass("loading");
});
return success;
},
})
.modal("show");
}
}
</script>

前台效果图

QQ20240820-084107.png

QQ20240820-084036.png

WX20240508-233030@2x.png

后台效果图

QQ20240830-144534.png

QQ20240830-143825.png

20240817180140.png