mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 12:48:14 -05:00
feat: status-server主题增加agent账单信息展示 (#424)
1. 首页通过在后台配置PublicNote字段,实现agent账单信息展示 2. 优化顶部导航,增加当前位置标识 3. 优化network页移动端样式 4. 优化后台server页展示,隐藏掉一些不重要的信息 5. 一些其他小优化 使用说明:https://github.com/naiba/nezha/pull/424#issuecomment-2386837658
This commit is contained in:
parent
7940e010e0
commit
60ada33ee9
15
resource/l10n/en-US.toml
vendored
15
resource/l10n/en-US.toml
vendored
@ -690,3 +690,18 @@ other = "Cancel"
|
||||
|
||||
[FMError]
|
||||
other = "Agent returned an error, please view the console for details. To open a new connection, reopen the FM again."
|
||||
|
||||
[Remaining]
|
||||
other = "Remaining"
|
||||
|
||||
[Lifetime]
|
||||
other = "Lifetime"
|
||||
|
||||
[Price]
|
||||
other = "Price"
|
||||
|
||||
[Expired]
|
||||
other = "Expired"
|
||||
|
||||
[Days]
|
||||
other = "d"
|
||||
|
15
resource/l10n/es-ES.toml
vendored
15
resource/l10n/es-ES.toml
vendored
@ -690,3 +690,18 @@ other = "Cancelar"
|
||||
|
||||
[FMError]
|
||||
other = "Agent devolvió un error, consulte la consola para obtener más detalles. Para abrir una nueva conexión, vuelva a abrir el FM."
|
||||
|
||||
[Remaining]
|
||||
other = "Remaining"
|
||||
|
||||
[Lifetime]
|
||||
other = "Lifetime"
|
||||
|
||||
[Price]
|
||||
other = "Price"
|
||||
|
||||
[Expired]
|
||||
other = "Expired"
|
||||
|
||||
[Days]
|
||||
other = "d"
|
||||
|
15
resource/l10n/zh-CN.toml
vendored
15
resource/l10n/zh-CN.toml
vendored
@ -690,3 +690,18 @@ other = "取消"
|
||||
|
||||
[FMError]
|
||||
other = "Agent 返回了错误,请查看控制台获取详细信息。要建立新连接,请重新打开 FM。"
|
||||
|
||||
[Remaining]
|
||||
other = "剩余"
|
||||
|
||||
[Lifetime]
|
||||
other = "永续"
|
||||
|
||||
[Price]
|
||||
other = "价格"
|
||||
|
||||
[Expired]
|
||||
other = "已到期"
|
||||
|
||||
[Days]
|
||||
other = "天"
|
||||
|
15
resource/l10n/zh-TW.toml
vendored
15
resource/l10n/zh-TW.toml
vendored
@ -690,3 +690,18 @@ other = "取消"
|
||||
|
||||
[FMError]
|
||||
other = "Agent 回傳了錯誤,請查看主控台獲取詳細資訊。要建立新連線,請重新開啟 FM。"
|
||||
|
||||
[Remaining]
|
||||
other = "剩餘"
|
||||
|
||||
[Lifetime]
|
||||
other = "永續"
|
||||
|
||||
[Price]
|
||||
other = "價格"
|
||||
|
||||
[Expired]
|
||||
other = "已到期"
|
||||
|
||||
[Days]
|
||||
other = "天"
|
||||
|
11
resource/static/theme-server-status/css/dark.css
vendored
11
resource/static/theme-server-status/css/dark.css
vendored
@ -8,6 +8,11 @@ body[theme="dark"] .navbar .navbar-brand {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
body[theme="dark"] .navbar .navbar-nav li.pc-active a,
|
||||
body[theme="dark"] .navbar .navbar-nav li.m-active a{
|
||||
color: rgba(73, 146, 255, 1);
|
||||
}
|
||||
|
||||
body[theme="dark"] .navbar .dropdown-menu {
|
||||
background-color: rgba(23, 26, 30, 1);
|
||||
border-color: rgba(49, 54, 59, 1);
|
||||
@ -88,8 +93,8 @@ body[theme="dark"] .table > tbody > tr.expandRow.even > td:before{
|
||||
/* expandRow展开部分样式结束 */
|
||||
|
||||
body[theme="dark"] .progress {
|
||||
background-image: linear-gradient(#2c2c2c 0,rgba(28, 29, 38, 1) 100%);
|
||||
background-color: rgba(28, 29, 38, 1);
|
||||
background-image: none;
|
||||
background-color: rgba(255, 255, 255, 0.075);
|
||||
}
|
||||
|
||||
body[theme="dark"] .progress-bar {
|
||||
@ -174,5 +179,5 @@ body[theme="dark"] .toolbox i{
|
||||
}
|
||||
|
||||
body[theme="dark"] .network-box .network-box-header{
|
||||
border-bottom: 1px solid rgba(110, 112, 121, 0.25);
|
||||
border-bottom-color: rgba(110, 112, 121, 0.25);
|
||||
}
|
@ -16,10 +16,15 @@ body[theme="light"] .navbar .navbar-brand {
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
body[theme="light"] .navbar .navbar-nav > li > a{
|
||||
body[theme="light"] .navbar .navbar-nav > li > a {
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
body[theme="light"] .navbar .navbar-nav li.pc-active a,
|
||||
body[theme="light"] .navbar .navbar-nav li.m-active a {
|
||||
color: rgba(73, 146, 255, 1);
|
||||
}
|
||||
|
||||
body[theme="light"] .navbar .navbar-nav > .open > a:focus,
|
||||
body[theme="light"] .navbar .navbar-nav > .open > a:hover,
|
||||
body[theme="light"] .navbar .navbar-nav > .active > a,
|
||||
@ -198,7 +203,7 @@ body[theme="light"] .modal-header i.xclose{
|
||||
}
|
||||
|
||||
body[theme="light"] .network-box .network-box-header{
|
||||
border-bottom: 1px solid rgba(224, 230, 241, 0.6);
|
||||
border-bottom-color: rgba(224, 230, 241, 0.6);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
|
29
resource/static/theme-server-status/css/main.css
vendored
29
resource/static/theme-server-status/css/main.css
vendored
@ -237,8 +237,8 @@ tr.accordion-toggle{
|
||||
}
|
||||
|
||||
.node-cell.network {
|
||||
min-width: 100px;
|
||||
max-width: 100px;
|
||||
min-width: 110px;
|
||||
max-width: 110px;
|
||||
}
|
||||
|
||||
.node-cell.traffic {
|
||||
@ -250,6 +250,24 @@ tr.accordion-toggle{
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.node-cell.remaining {
|
||||
min-width: 65px;
|
||||
max-width: 65px;
|
||||
}
|
||||
|
||||
.node-cell.remaining .additional {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.node-cell.remaining .additional small{
|
||||
white-space: nowrap;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.temp-detail {
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -331,6 +349,8 @@ td.ping-network-quality {
|
||||
padding: 5px 0px 15px 5px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.network-box .network-box-header .dropdown-menu {
|
||||
@ -529,6 +549,10 @@ footer p{
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
.node-cell.remaining {
|
||||
min-width: 75px;
|
||||
max-width: 75px;
|
||||
}
|
||||
.accordian-body{
|
||||
margin: 5px 0px 5px 10px;
|
||||
}
|
||||
@ -572,6 +596,7 @@ footer p{
|
||||
.network-box .network-box-header {
|
||||
margin: 8px 0px 0px 8px;
|
||||
font-size: 16px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.network-box .chartTitle {
|
||||
font-size: 16px;
|
||||
|
@ -155,6 +155,11 @@ const mixinsVue = {
|
||||
checkIsMobile() { // 检测设备类型,页面宽度小于768px认为是移动设备
|
||||
return window.innerWidth <= 768;
|
||||
},
|
||||
isMenuActive(page){
|
||||
if(page == this.$root.page) {
|
||||
return this.isMobile ? 'm-active' : 'pc-active';
|
||||
}
|
||||
},
|
||||
setBenchmarkHeight() {
|
||||
let vh = window.innerHeight * 0.01;
|
||||
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||
|
18
resource/template/dashboard-default/server.html
vendored
18
resource/template/dashboard-default/server.html
vendored
@ -30,9 +30,6 @@
|
||||
<th>{{tr "VersionNumber"}}</th>
|
||||
<th>{{tr "HideForGuest"}}</th>
|
||||
<th>{{tr "EnableDDNS"}}</th>
|
||||
<th>{{tr "EnableIPv4"}}</th>
|
||||
<th>{{tr "EnableIpv6"}}</th>
|
||||
<th>{{tr "DDNSDomain"}}</th>
|
||||
<th>{{tr "Secret"}}</th>
|
||||
<th>{{tr "OneKeyInstall"}}</th>
|
||||
<th>{{tr "Note"}}</th>
|
||||
@ -51,9 +48,6 @@
|
||||
<td>{{$server.Host.Version}}</td>
|
||||
<td>{{$server.HideForGuest}}</td>
|
||||
<td>{{$server.EnableDDNS}}</td>
|
||||
<td>{{$server.EnableIPv4}}</td>
|
||||
<td>{{$server.EnableIpv6}}</td>
|
||||
<td>{{$server.DDNSDomain}}</td>
|
||||
<td>
|
||||
<button class="ui icon green mini button" data-clipboard-text="{{$server.Secret}}" data-tooltip="{{tr "ClickToCopy"}}">
|
||||
<i class="copy icon"></i>
|
||||
@ -76,8 +70,16 @@
|
||||
<i class="apple icon"></i>
|
||||
</button>
|
||||
</td>
|
||||
<td style="word-break: break-word;white-space: pre-wrap;">{{$server.Note}}</td>
|
||||
<td style="word-break: break-word;white-space: pre-wrap;">{{$server.PublicNote}}</td>
|
||||
<td>
|
||||
{{if $server.Note}}
|
||||
<button class="ui icon green mini button" title="{{ $server.Note }}" onclick="addOrEditServer({{$server.MarshalForDashboard}})"><i class="sticky note icon"></i></button>
|
||||
{{end}}
|
||||
</td>
|
||||
<td>
|
||||
{{if $server.PublicNote}}
|
||||
<button class="ui icon green mini button" title="{{$server.PublicNote}}" onclick="addOrEditServer({{$server.MarshalForDashboard}})"><i class="sticky clipboard icon"></i></button>
|
||||
{{end}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="ui mini icon buttons">
|
||||
<button class="ui button" onclick="connectToServer({{$server.ID}})">
|
||||
|
@ -26,10 +26,10 @@
|
||||
<script src="https://unpkg.com/bootstrap@3.4.1/dist/js/bootstrap.min.js"></script>
|
||||
<script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
|
||||
<script src="https://unpkg.com/echarts@5.5.0/dist/echarts.min.js"></script>
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240909">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v202408011">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240811">
|
||||
<script src="/static/theme-server-status/js/mixin.js?v20240907"></script>
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/main.css?v20240929">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/dark.css?v202409029">
|
||||
<link rel="stylesheet" href="/static/theme-server-status/css/light.css?v20240916">
|
||||
<script src="/static/theme-server-status/js/mixin.js?v20240915"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
|
@ -6,6 +6,7 @@
|
||||
<th class="node-cell name center">{{tr "Name"}}</th>
|
||||
<th class="node-cell os center">{{tr "Platform"}}</th>
|
||||
<th class="node-cell location center">{{tr "Location"}}</th>
|
||||
<th v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center">{{tr "Price"}}</th>
|
||||
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
||||
<th class="node-cell load center">{{tr "Load"}}</th>
|
||||
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
||||
@ -13,6 +14,7 @@
|
||||
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
||||
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
||||
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
||||
<th v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining center">{{tr "Remaining"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="servers">
|
||||
@ -36,6 +38,12 @@
|
||||
<i :class="'fi fi-' + node.location"></i>
|
||||
<span class="node-cell-location-text text-uppercase">@#node.location#@</span>
|
||||
</td>
|
||||
<td v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center">
|
||||
<template v-if="additional[node.ID] && additional[node.ID].price">
|
||||
<span v-if="additional[node.ID].price.amount == 0" class="node-cell-price-text">Free</span>
|
||||
<span v-else class="node-cell-price-text">@#additional[node.ID].price.amount#@@#(additional[node.ID].price.cycle ? '/' + additional[node.ID].price.cycle : '')#@</span>
|
||||
</template>
|
||||
</td>
|
||||
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
|
||||
<td style="text-align: center;" class="node-cell load">@#node.load#@</td>
|
||||
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
||||
@ -59,6 +67,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining">
|
||||
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||
<template v-if="additional[node.ID] && additional[node.ID].remaining.format">
|
||||
<div :style="additional[node.ID].remaining.format.style" :class="additional[node.ID].remaining.days == 'lifetime' ? '' : additional[node.ID].remaining.format.class"></div>
|
||||
<div class="additional">
|
||||
<small v-if="additional[node.ID].remaining.days == 'lifetime'">{{tr "Lifetime"}}</small>
|
||||
<small v-else-if="additional[node.ID].remaining.days < 0">{{tr "Expired"}}</small>
|
||||
<small v-else>
|
||||
<span class="node-cell-remaining-days">@#additional[node.ID].remaining.days#@{{tr "Days"}}</span>
|
||||
<span class="node-cell-remaining-percent">@#additional[node.ID].remaining.percent#@%</span>
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
||||
<td colspan="16">
|
||||
|
@ -9,6 +9,7 @@
|
||||
<th class="node-cell name center">{{tr "Name"}}</th>
|
||||
<th class="node-cell os center">{{tr "Platform"}}</th>
|
||||
<th class="node-cell location center">{{tr "Location"}}</th>
|
||||
<th v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center">{{tr "Price"}}</th>
|
||||
<th class="node-cell uptime center">{{tr "Uptime"}}</th>
|
||||
<th class="node-cell load center">{{tr "Load"}}</th>
|
||||
<th class="node-cell network center">{{tr "NetSpeed"}}↓|↑</th>
|
||||
@ -16,6 +17,7 @@
|
||||
<th class="node-cell cpu center">{{tr "CpuUsed"}}</th>
|
||||
<th class="node-cell memory center">{{tr "MemUsed"}}</th>
|
||||
<th class="node-cell hdd center">{{tr "DiskUsed"}}</th>
|
||||
<th v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining center">{{tr "Remaining"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="servers">
|
||||
@ -39,6 +41,12 @@
|
||||
<i :class="'fi fi-' + node.location"></i>
|
||||
<span class="node-cell-location-text text-uppercase"> @#node.location#@</span>
|
||||
</td>
|
||||
<td v-if="additional && Object.values(additional).filter(item => item.price).length > 0" class="node-cell price center">
|
||||
<template v-if="additional[node.ID] && additional[node.ID].price">
|
||||
<span v-if="additional[node.ID].price.amount == 0" class="node-cell-price-text">Free</span>
|
||||
<span v-else class="node-cell-price-text">@#additional[node.ID].price.amount#@@#(additional[node.ID].price.cycle ? '/' + additional[node.ID].price.cycle : '')#@</span>
|
||||
</template>
|
||||
</td>
|
||||
<td style="text-align: center;" class="node-cell uptime">@#node.uptime#@</td>
|
||||
<td style="text-align: center;" class="node-cell load">@#node.load#@</td>
|
||||
<td style="text-align: center;" class="node-cell network">@#node.network#@</td>
|
||||
@ -62,6 +70,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="additional && Object.values(additional).filter(item => item.remaining).length > 0" class="node-cell remaining">
|
||||
<div :class="['progress', node.online ? 'progress-online' : 'progress-offline']">
|
||||
<template v-if="additional[node.ID] && additional[node.ID].remaining.format">
|
||||
<div :style="additional[node.ID].remaining.format.style" :class="additional[node.ID].remaining.days == 'lifetime' ? '' : additional[node.ID].remaining.format.class"></div>
|
||||
<div class="additional">
|
||||
<small v-if="additional[node.ID].remaining.days == 'lifetime'">{{tr "Lifetime"}}</small>
|
||||
<small v-else-if="additional[node.ID].remaining.days < 0">{{tr "Expired"}}</small>
|
||||
<small v-else>
|
||||
<span class="node-cell-remaining-days">@#additional[node.ID].remaining.days#@{{tr "Days"}}</span>
|
||||
<span class="node-cell-remaining-percent">@#additional[node.ID].remaining.percent#@%</span>
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="expandRow" :class="index % 2 === 0 ? 'odd': 'even'">
|
||||
<td colspan="16">
|
||||
|
348
resource/template/theme-server-status/home.html
vendored
348
resource/template/theme-server-status/home.html
vendored
@ -42,6 +42,7 @@
|
||||
servers: [],
|
||||
nodesTag: [],
|
||||
nodesNoTag: [],
|
||||
additional: {},
|
||||
chartDataList: [],
|
||||
ws: null,
|
||||
language: {{.Conf.Language}},
|
||||
@ -81,7 +82,8 @@
|
||||
this.countryNameMap = this.initCountryNameMap();
|
||||
this.countryServer = this.initCountryServer();
|
||||
this.countryMapChartData = this.initCountryMapChartData();
|
||||
})
|
||||
});
|
||||
this.additional = this.initAdditional(this.servers);
|
||||
},
|
||||
mounted() {
|
||||
// 初始化时建立WebSocket连接
|
||||
@ -98,6 +100,25 @@
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initAdditional(servers) {
|
||||
let nodes = {};
|
||||
servers?.forEach(server => {
|
||||
if(server.PublicNote) {
|
||||
nodes[server.ID] = {
|
||||
"remaining": {
|
||||
format: this.getRemainingFormat(server.live, server.PublicNote),
|
||||
days: this.getRemainingDays(this.getNoteElementValue(server.PublicNote, "billingDataMod", "endDate"),server.PublicNote),
|
||||
percent: this.toFixed2(100 - this.getRemainingPercent(this.getNoteElementValue(server.PublicNote, "billingDataMod", "startDate"), this.getNoteElementValue(server.PublicNote, "billingDataMod", "endDate"), server.PublicNote))
|
||||
},
|
||||
"price": {
|
||||
amount: this.getNoteElementValue(server.PublicNote, "billingDataMod", "amount"),
|
||||
cycle: this.getNoteElementValue(server.PublicNote, "billingDataMod", "cycle")
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
return nodes;
|
||||
},
|
||||
initCountryMap() {
|
||||
return fetch(this.staticUrl + '/maps/nezha.countrymap.json')
|
||||
.then(response => response.json())
|
||||
@ -722,6 +743,331 @@
|
||||
this.renderCharts(id,true);
|
||||
}
|
||||
});
|
||||
},
|
||||
getNoteElementValue(string, elementName, childElementName) {
|
||||
let obj;
|
||||
|
||||
try {
|
||||
obj = JSON.parse(string);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!elementName) return null;
|
||||
|
||||
if (childElementName) {
|
||||
return obj[elementName] && childElementName in obj[elementName] ? obj[elementName][childElementName] : null;
|
||||
} else {
|
||||
return elementName in obj ? obj[elementName] : null;
|
||||
}
|
||||
},
|
||||
getBillingCycle(billingCycle) {
|
||||
// 统一转换为小写进行比较
|
||||
const cycle = billingCycle.toLowerCase();
|
||||
|
||||
switch (cycle) {
|
||||
case '月':
|
||||
case 'month':
|
||||
case 'monthly':
|
||||
case 'm':
|
||||
return "M";
|
||||
case '季':
|
||||
case 'quarterly':
|
||||
case 'q':
|
||||
return "Q";
|
||||
case '半':
|
||||
case '半年':
|
||||
case 'half':
|
||||
case 'semi-annually':
|
||||
case 'h':
|
||||
return "H";
|
||||
case '年':
|
||||
case 'year':
|
||||
case 'annually':
|
||||
case 'y':
|
||||
return "Y";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getAdjustTimezone(reference, target) {
|
||||
// 获取时区
|
||||
const referenceTimezoneOffset = reference.getTimezoneOffset();
|
||||
const targetTimezoneOffset = target.getTimezoneOffset();
|
||||
|
||||
// 计算时区差异
|
||||
const timezoneDifference = (referenceTimezoneOffset - targetTimezoneOffset) * 60 * 1000;
|
||||
|
||||
// 将 target 日期调整到 reference 时区
|
||||
return new Date(target.getTime() + timezoneDifference);
|
||||
},
|
||||
getAutoRenewalEndDate(endDate, billingCycle) {
|
||||
const expiration = new Date(endDate);
|
||||
const current = this.getAdjustTimezone(new Date(endDate), new Date());
|
||||
|
||||
// 如果 expiration 无效,返回 null 并记录日志
|
||||
if (isNaN(expiration.getTime())) {
|
||||
console.error("getAutoRenewalEndDate: Invalid expiration format");
|
||||
}
|
||||
|
||||
const result = {
|
||||
flag: 1, // 1表示需要更新
|
||||
check: 0, // 判断逻辑标记
|
||||
count: 0 // 周期累计计数
|
||||
};
|
||||
|
||||
// 如果当前时间还没到到期时间,直接返回到期时间
|
||||
if (current < expiration) {
|
||||
result.flag = 0;
|
||||
result.date = expiration;
|
||||
return result;
|
||||
}
|
||||
|
||||
let nextExpiration = new Date(expiration); // 初始化为原到期时间
|
||||
let newExpirationMonth = expiration.getMonth(); // 获取expiration初始月份
|
||||
|
||||
switch (billingCycle.toUpperCase()) {
|
||||
case 'M': // 月度
|
||||
nextExpiration.setFullYear(current.getFullYear());
|
||||
|
||||
const monthCheck = current < new Date(current.getFullYear(), current.getMonth(), expiration.getDate());
|
||||
|
||||
// 检查当前月是否在有效期内
|
||||
if (monthCheck) {
|
||||
nextExpiration.setMonth(current.getMonth());
|
||||
result.check = 1;
|
||||
} else {
|
||||
nextExpiration.setMonth(current.getMonth() + 1);
|
||||
result.check = 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Q': // 季度
|
||||
nextExpiration.setFullYear(current.getFullYear());
|
||||
|
||||
// 每次增加 3 个月,直到新的月份大于当前月份
|
||||
while (newExpirationMonth < current.getMonth()) {
|
||||
newExpirationMonth += 3;
|
||||
result.count += 1;
|
||||
}
|
||||
|
||||
// nextExpiration设置获取到的新月份
|
||||
nextExpiration.setMonth(newExpirationMonth);
|
||||
|
||||
const quarterlyCheck = current < nextExpiration;
|
||||
|
||||
// 检查新月份是否在有效期内
|
||||
if (quarterlyCheck) {
|
||||
result.check = 3;
|
||||
} else {
|
||||
nextExpiration.setMonth(current.getMonth() + 3);
|
||||
result.check = 4;
|
||||
result.count += 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'H': // 半年
|
||||
nextExpiration.setFullYear(current.getFullYear());
|
||||
|
||||
// 每次增加 6 个月,直到新的月份大于当前月份
|
||||
while (newExpirationMonth < current.getMonth()) {
|
||||
newExpirationMonth += 6;
|
||||
result.count += 1;
|
||||
}
|
||||
|
||||
// nextExpiration设置获取到的新月份
|
||||
nextExpiration.setMonth(newExpirationMonth);
|
||||
|
||||
const halfCheck = current < nextExpiration;
|
||||
|
||||
// 检查新月份是否在有效期内
|
||||
if (halfCheck) {
|
||||
result.check = 5;
|
||||
} else {
|
||||
nextExpiration.setMonth(current.getMonth() + 6);
|
||||
result.check = 6;
|
||||
result.count += 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Y': // 年度
|
||||
const yearCheck = current < new Date(current.getFullYear(), expiration.getMonth(), expiration.getDate());
|
||||
|
||||
// 如果当前时间比这一年有效期早,则到期为本年
|
||||
if (yearCheck) {
|
||||
nextExpiration.setFullYear(current.getFullYear());
|
||||
result.check = 7;
|
||||
} else {
|
||||
// 否则推到下一年
|
||||
nextExpiration.setFullYear(current.getFullYear() + 1);
|
||||
result.check = 8;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error("Invalid billing cycle");
|
||||
}
|
||||
|
||||
// 保持原到期时间的时分秒
|
||||
nextExpiration.setHours(expiration.getHours());
|
||||
nextExpiration.setMinutes(expiration.getMinutes());
|
||||
nextExpiration.setSeconds(expiration.getSeconds());
|
||||
|
||||
result.date = nextExpiration;
|
||||
|
||||
return result;
|
||||
},
|
||||
getAutoRenewalStartDate(flag, startDate, check, count) {
|
||||
//1.判断什么时候改变 2.如何改变
|
||||
const start = new Date(startDate);
|
||||
const current = this.getAdjustTimezone(start, new Date());
|
||||
|
||||
// 检查 startDate 格式是否有效,若无效返回 null
|
||||
if (isNaN(start.getTime())) {
|
||||
console.error("getAutoRenewalStartDate: Invalid startDate format");
|
||||
}
|
||||
|
||||
// 如果 flag 为 0,直接返回开始日期
|
||||
if (flag === 0) {
|
||||
return start;
|
||||
}
|
||||
|
||||
// 初始化新的开始日期
|
||||
const newStart = new Date(start);
|
||||
|
||||
switch (check) {
|
||||
case 1: // 处理月份:设置为上个月
|
||||
newStart.setFullYear(current.getFullYear());
|
||||
newStart.setMonth(current.getMonth() - 1);
|
||||
break;
|
||||
|
||||
case 2: // 处理月份:设置为当前月
|
||||
newStart.setFullYear(current.getFullYear());
|
||||
newStart.setMonth(current.getMonth());
|
||||
break;
|
||||
|
||||
case 3:
|
||||
case 4: // 处理季度
|
||||
newStart.setFullYear(current.getFullYear());
|
||||
newStart.setMonth(start.getMonth() + 3 * count);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
case 6: // 处理半年
|
||||
newStart.setFullYear(current.getFullYear());
|
||||
newStart.setMonth(start.getMonth() + 6 * count);
|
||||
break;
|
||||
|
||||
case 7: // 处理年份:设置为上一年
|
||||
newStart.setFullYear(current.getFullYear() - 1);
|
||||
break;
|
||||
|
||||
case 8: // 处理年份:保持当前年份
|
||||
newStart.setFullYear(current.getFullYear());
|
||||
break;
|
||||
|
||||
default: // 默认处理:直接返回
|
||||
return;
|
||||
}
|
||||
|
||||
return newStart;
|
||||
},
|
||||
getRemainingFormat(online, note) {
|
||||
if (!note) return null;
|
||||
const startDate = this.getNoteElementValue(note, "billingDataMod", "startDate");
|
||||
const endDate = this.getNoteElementValue(note, "billingDataMod", "endDate");
|
||||
|
||||
// 检查 startDate 和 endDate 是否有效
|
||||
if (!startDate || !endDate || typeof startDate !== 'string' || typeof endDate !== 'string') {
|
||||
console.error("getRemainingFormat: Invalid startDate or endDate in note");
|
||||
return null; // 如果无效,返回 null 或其他错误处理逻辑
|
||||
}
|
||||
|
||||
//处理特殊时间格式
|
||||
if (startDate.includes('0000-00-00') || endDate.includes("0000-00-00")) {
|
||||
return this.formatPercents(online, this.toFixed2(100));
|
||||
} else {
|
||||
const percent = this.getRemainingPercent(startDate, endDate, note);
|
||||
return this.formatPercents(online, this.toFixed2(percent));
|
||||
}
|
||||
},
|
||||
getRemainingDays(endDate, note) {
|
||||
// 检查 endDate 是否有效
|
||||
if (!endDate || typeof endDate !== 'string') {
|
||||
console.error("getRemainingDays: Invalid endDate format");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 处理特殊时间格式
|
||||
if (endDate.includes("0000-00-00")) return "lifetime";
|
||||
|
||||
// 获取当前时间,并调整时区
|
||||
const currentTime = this.getAdjustTimezone(new Date(endDate), new Date());
|
||||
|
||||
// 获取计费周期和自动续订日期
|
||||
const billingCycle = this.getNoteElementValue(note, "billingDataMod", "cycle") || "月";
|
||||
const autoEndDate = this.getAutoRenewalEndDate(
|
||||
this.getNoteElementValue(note, "billingDataMod", "endDate"),
|
||||
this.getBillingCycle(billingCycle)
|
||||
);
|
||||
|
||||
// 检查 autoRenewal 状态
|
||||
const autoRenewal = this.getNoteElementValue(note, "billingDataMod", "autoRenewal") == 1;
|
||||
|
||||
// 确定到期时间
|
||||
const end = autoRenewal ? autoEndDate.date : new Date(endDate);
|
||||
|
||||
// 计算剩余天数
|
||||
const timeDiff = end - currentTime;
|
||||
const daysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
|
||||
|
||||
return daysDiff;
|
||||
},
|
||||
getRemainingPercent(startDate, endDate, note) {
|
||||
// 检查 startDate 和 endDate 是否为有效字符串并处理特殊格式
|
||||
if (typeof startDate !== 'string' || typeof endDate !== 'string') {
|
||||
console.error("getRemainingPercent: Invalid startDate or endDate format");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (startDate.includes("0000-00-00") || endDate.includes("0000-00-00")) return 100;
|
||||
|
||||
// 获取当前时间并调整时区
|
||||
const now = this.getAdjustTimezone(new Date(endDate), new Date());
|
||||
|
||||
// 获取计费周期
|
||||
const billingCycle = this.getNoteElementValue(note, "billingDataMod", "cycle") || "月";
|
||||
|
||||
// 自动获取结束日期
|
||||
const autoEndDate = this.getAutoRenewalEndDate(
|
||||
this.getNoteElementValue(note, "billingDataMod", "endDate"),
|
||||
this.getBillingCycle(billingCycle)
|
||||
);
|
||||
|
||||
// 自动获取开始日期
|
||||
const autoStartDate = autoEndDate.flag == 1
|
||||
? this.getAutoRenewalStartDate(autoEndDate.flag, this.getNoteElementValue(note, "billingDataMod", "startDate"), autoEndDate.check, autoEndDate.count)
|
||||
: new Date(startDate);
|
||||
|
||||
// 计算开始和结束时间
|
||||
const autoRenewal = this.getNoteElementValue(note, "billingDataMod", "autoRenewal") == 1;
|
||||
const start = autoRenewal ? autoStartDate : new Date(startDate);
|
||||
const end = autoRenewal ? autoEndDate.date : new Date(endDate);
|
||||
|
||||
// 计算剩余百分比
|
||||
if (now < start) {
|
||||
return 0;
|
||||
}
|
||||
if (now >= end) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
const totalDuration = end - start;
|
||||
const elapsedDuration = now - start;
|
||||
const percent = (elapsedDuration / totalDuration) * 100;
|
||||
|
||||
// 确保百分比在 0-100 之间
|
||||
return Math.min(Math.max(percent, 0), 100);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
17
resource/template/theme-server-status/menu.html
vendored
17
resource/template/theme-server-status/menu.html
vendored
@ -15,20 +15,9 @@
|
||||
</div>
|
||||
<nav id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/"><i class="home icon"></i>{{tr "Home" }}</a></li>
|
||||
<template v-if="isMobile">
|
||||
<li class="dropdown">
|
||||
<a data-toggle="dropdown"><i class="bi bi-gear-wide-connected" style="position:relative;top:1px;margin-right:3px;font-size:1.1rem;"></i>{{tr "Feature" }}<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu" style="min-width:100px;">
|
||||
<li><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
|
||||
<li><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
<template v-else>
|
||||
<li><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
|
||||
<li><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
|
||||
</template>
|
||||
<li :class="isMenuActive('index')"><a href="/"><i class="home icon"></i>{{tr "Home" }}</a></li>
|
||||
<li :class="isMenuActive('service')"><a href="/service"><i class="rss icon"></i>{{tr "Services" }}</a></li>
|
||||
<li :class="isMenuActive('network')"><a href="/network"><i class="bi bi-hdd-network icon"></i>{{tr "NetworkSpiter"}}</a></li>
|
||||
{{ if not .Conf.DisableSwitchTemplateInFrontend }}
|
||||
<li class="dropdown">
|
||||
<a data-toggle="dropdown"><i class="bi bi-incognito" style="position:relative;top:1px;margin-right:3px;font-size:1.2rem;vertical-align:top;"></i>{{tr "Template" }}<b class="caret"></b></a>
|
||||
|
Loading…
Reference in New Issue
Block a user