mirror of
https://github.com/hi2shark/nazhua.git
synced 2026-01-15 00:30:43 +08:00
✨ 新增"行"模式的服务器列表
This commit is contained in:
parent
b235be5fa1
commit
dbecdcf379
@ -3,6 +3,7 @@ window.$$nazhuaConfig = {
|
||||
// freeAmount: '白嫖', // 免费服务的费用名称
|
||||
// infinityCycle: '长期有效', // 无限周期名称
|
||||
// buyBtnText: '购买', // 购买按钮文案
|
||||
// listServerItemType: 'row', // 服务器列表项类型 card/row
|
||||
// listServerStatusType: 'progress', // 服务器状态类型--列表
|
||||
// listServerRealTimeShowLoad: false, // 列表显示服务器实时负载
|
||||
// detailServerStatusType: 'progress', // 服务器状态类型--详情页
|
||||
|
||||
140
src/views/components/server-list/row/server-list-item-bill.vue
Normal file
140
src/views/components/server-list/row/server-list-item-bill.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="extraFields?.remainingTime"
|
||||
class="list-column-item list-column-item--remaining-time"
|
||||
>
|
||||
<div class="list-column-item-content">
|
||||
<span class="item-label">剩余</span>
|
||||
<div class="item-content">
|
||||
<span class="text-item value-text">{{ billAndPlan?.remainingTime?.value || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="extraFields?.billing"
|
||||
class="list-column-item list-column-item--billing"
|
||||
>
|
||||
<div class="list-column-item-content">
|
||||
<span
|
||||
v-if="!billAndPlan?.billing?.isFree && billAndPlan?.billing?.label"
|
||||
class="item-label"
|
||||
>
|
||||
{{ billAndPlan.billing.label }}
|
||||
</span>
|
||||
<div class="item-content">
|
||||
<span class="text-item value-text">{{ billAndPlan?.billing?.value || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="extraFields?.orderLink"
|
||||
class="list-column-item list-column-item--order-link"
|
||||
>
|
||||
<div class="list-column-item-content">
|
||||
<span class="item-label">链接</span>
|
||||
<div class="item-content">
|
||||
<span
|
||||
v-if="showBuyBtn"
|
||||
class="text-item value-text"
|
||||
@click="toBuy"
|
||||
>
|
||||
{{ buyBtnText }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 套餐信息
|
||||
*/
|
||||
import {
|
||||
inject,
|
||||
computed,
|
||||
} from 'vue';
|
||||
|
||||
import config from '@/config';
|
||||
|
||||
import handleServerBillAndPlan from '@/views/composable/server-bill-and-plan';
|
||||
|
||||
const props = defineProps({
|
||||
info: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const filterServerList = inject('filterServerList', {
|
||||
value: null,
|
||||
});
|
||||
|
||||
const extraFields = computed(() => filterServerList.value?.fields || {});
|
||||
|
||||
const {
|
||||
billAndPlan,
|
||||
} = handleServerBillAndPlan({
|
||||
props,
|
||||
});
|
||||
|
||||
const buyBtnText = computed(() => config.nazhua.buyBtnText || '购买');
|
||||
const showBuyBtn = computed(() => !!props.info?.PublicNote?.customData?.orderLink);
|
||||
|
||||
// function toBuy() {
|
||||
// const decodeUrl = decodeURIComponent(props.info?.PublicNote?.customData?.orderLink);
|
||||
// window.open(decodeUrl, '_blank');
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-column-item {
|
||||
|
||||
.list-column-item-content {
|
||||
--item-content-label-height: 16px;
|
||||
--item-content-value-height: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: var(--list-item-height);
|
||||
|
||||
.item-label {
|
||||
padding-top: 6px; // 视觉修正
|
||||
line-height: var(--item-content-label-height);
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
line-height: var(--item-content-value-height);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&--remaining-time {
|
||||
width: 60px;
|
||||
|
||||
.value-text {
|
||||
color: #74dbef;
|
||||
}
|
||||
}
|
||||
|
||||
&--billing {
|
||||
width: 60px;
|
||||
|
||||
.value-text {
|
||||
color: var(--list-item-price-color);
|
||||
}
|
||||
}
|
||||
|
||||
&--order-link {
|
||||
width: 60px;
|
||||
|
||||
.value-text {
|
||||
color: var(--list-item-buy-link-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div
|
||||
v-for="item in serverRealTimeList"
|
||||
:key="item.key"
|
||||
class="list-column-item list-column-item--real-time"
|
||||
:class="`list-column-item--real-time-${item.key}`"
|
||||
>
|
||||
<div class="real-time-content">
|
||||
<span class="item-label">{{ item.label }}</span>
|
||||
<div class="item-content">
|
||||
<span class="item-value">{{ item.show ? item?.value : '-' }}</span>
|
||||
<span class="item-unit item-text">{{ item.show ? item?.unit : '' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 服务器数据统计
|
||||
*/
|
||||
import {
|
||||
inject,
|
||||
} from 'vue';
|
||||
import handleServerRealTime from '@/views/composable/server-real-time';
|
||||
|
||||
const props = defineProps({
|
||||
info: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
serverRealTimeListTpls: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const currentTime = inject('currentTime', {
|
||||
value: Date.now(),
|
||||
});
|
||||
|
||||
const {
|
||||
serverRealTimeList,
|
||||
} = handleServerRealTime({
|
||||
props,
|
||||
currentTime,
|
||||
serverRealTimeListTpls: props.serverRealTimeListTpls,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-column-item {
|
||||
.real-time-content {
|
||||
--real-time-label-height: 16px;
|
||||
--real-time-value-height: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: var(--list-item-height);
|
||||
|
||||
.item-label {
|
||||
padding-top: 6px; // 视觉修正
|
||||
line-height: var(--real-time-label-height);
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
}
|
||||
.item-content {
|
||||
line-height: var(--real-time-value-height);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&--real-time-duration {
|
||||
width: 50px;
|
||||
.item-value {
|
||||
color: var(--duration-color);
|
||||
}
|
||||
}
|
||||
&--real-time-load {
|
||||
width: 50px;
|
||||
.item-value {
|
||||
color: var(--load-color);
|
||||
}
|
||||
}
|
||||
&--real-time-transfer {
|
||||
width: 80px;
|
||||
.item-value {
|
||||
color: var(--transfer-color);
|
||||
}
|
||||
}
|
||||
&--real-time-inSpeed {
|
||||
width: 50px;
|
||||
.item-value {
|
||||
color: var(--net-speed-in-color);
|
||||
}
|
||||
}
|
||||
&--real-time-outSpeed {
|
||||
width: 50px;
|
||||
.item-value {
|
||||
color: var(--net-speed-out-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div
|
||||
class="server-list-item-status-progress"
|
||||
:class="'server-status--' + type"
|
||||
:title="valPercent"
|
||||
>
|
||||
<span class="progress-label">
|
||||
{{ label }}
|
||||
</span>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar-box">
|
||||
<div
|
||||
class="progress-bar-inner"
|
||||
:style="progressStyle"
|
||||
/>
|
||||
<span
|
||||
class="progress-bar-used"
|
||||
>
|
||||
{{ valText }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 服务器状态进度调单项
|
||||
*/
|
||||
|
||||
import {
|
||||
computed,
|
||||
} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
used: {
|
||||
type: [Number, String],
|
||||
default: 1,
|
||||
},
|
||||
colors: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
valText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
valPercent: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
content: {
|
||||
type: [String, Object],
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const progressStyle = computed(() => {
|
||||
const style = {};
|
||||
style.width = `${Math.min(props.used, 100)}%`;
|
||||
const color = typeof props.colors === 'string' ? props.colors : props.colors?.used;
|
||||
if (color) {
|
||||
if (Array.isArray(color)) {
|
||||
style.background = `linear-gradient(-35deg, ${color.join(',')})`;
|
||||
} else {
|
||||
style.backgroundColor = color;
|
||||
}
|
||||
}
|
||||
return style;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.server-list-item-status-progress {
|
||||
--progress-label-height: 16px;
|
||||
--progress-bar-height: 24px;
|
||||
--progress-bar-box-height: 14px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: var(--list-item-height);
|
||||
|
||||
.progress-label {
|
||||
padding-top: 6px; // 视觉修正
|
||||
line-height: var(--progress-label-height);
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: var(--progress-bar-height);
|
||||
}
|
||||
|
||||
.progress-bar-box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: var(--progress-bar-box-height);
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: calc(var(--progress-bar-box-height) / 2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar-inner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-color: #08f;
|
||||
border-radius: calc(var(--progress-bar-box-height) / 2);
|
||||
box-shadow: 2px 0 2px rgba(#000, 0.2);
|
||||
}
|
||||
|
||||
.progress-bar-used {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
line-height: var(--progress-bar-box-height);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
text-shadow: 1px 1px 2px rgba(#000, 0.8), 0 0 1px rgba(#fff, 0.5);
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div
|
||||
v-for="item in serverStatusList"
|
||||
:key="item.type"
|
||||
class="list-column-item list-column-item--status"
|
||||
:class="`list-column-item--status-${componentName} list-column-item--status-type-${item.type}`"
|
||||
>
|
||||
<component
|
||||
:is="componentMaps[componentName]"
|
||||
:type="item.type"
|
||||
:used="item.used"
|
||||
:colors="item.colors"
|
||||
:val-text="item.valPercent"
|
||||
:val-percent="`${item.label}使用${item.valText}`"
|
||||
:label="item.label"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 服务器状态盒子
|
||||
*/
|
||||
|
||||
import config from '@/config';
|
||||
|
||||
import handleServerStatus from '@/views/composable/server-status';
|
||||
import ServerStatusDonut from '@/views/components/server/server-status-donut.vue';
|
||||
import ServerStatusProgress from './server-list-item-status-progress.vue';
|
||||
|
||||
const props = defineProps({
|
||||
info: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const componentMaps = {
|
||||
donut: ServerStatusDonut,
|
||||
progress: ServerStatusProgress,
|
||||
};
|
||||
|
||||
const componentName = [
|
||||
'donut',
|
||||
'progress',
|
||||
].includes(config.nazhua.listServerStatusType) ? config.nazhua.listServerStatusType : 'donut';
|
||||
|
||||
const {
|
||||
serverStatusList,
|
||||
} = handleServerStatus({
|
||||
props,
|
||||
statusListTpl: 'cpu,mem,disk',
|
||||
statusListItemContent: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-column-item {
|
||||
&--status-progress {
|
||||
width: 72px;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
&--status-donut {
|
||||
--server-status-size: 66px;
|
||||
--server-status-label-scale: 0.8;
|
||||
--server-status-val-text-font-size: 16px;
|
||||
--server-status-label-font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
166
src/views/components/server-list/row/server-list-item.vue
Normal file
166
src/views/components/server-list/row/server-list-item.vue
Normal file
@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<dot-dot-box
|
||||
border-radius="var(--list-item-border-radius)"
|
||||
padding="var(--list-item-padding)"
|
||||
class="server-list-row-item"
|
||||
:class="{
|
||||
'server-list-row-item--offline': info.online === -1,
|
||||
}"
|
||||
@click="openDetail"
|
||||
>
|
||||
<div class="list-column-item list-column-item--server-flag">
|
||||
<span
|
||||
class="server-flag"
|
||||
>
|
||||
<span
|
||||
class="fi"
|
||||
:class="'fi-' + (info?.Host?.CountryCode || 'un')"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="list-column-item list-column-item--server-name">
|
||||
<span
|
||||
class="server-name"
|
||||
:title="info.Name"
|
||||
>
|
||||
{{ info.Name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="list-column-item list-column-item--server-system">
|
||||
<span :class="platformLogoIconClassName" />
|
||||
</div>
|
||||
<div class="list-column-item list-column-item--cpu-mem">
|
||||
<span class="core-mem">{{ cpuAndMemAndDisk || '-' }}</span>
|
||||
</div>
|
||||
<server-list-item-status
|
||||
v-if="$config.nazhua.hideListItemStatusDonut !== true"
|
||||
:info="info"
|
||||
/>
|
||||
<server-list-item-real-time
|
||||
v-if="$config.nazhua.hideListItemStat !== true"
|
||||
:info="info"
|
||||
server-real-time-list-tpls="load,inSpeed,outSpeed,transfer,duration"
|
||||
/>
|
||||
<server-list-item-bill
|
||||
v-if="$config.nazhua.hideListItemBill !== true"
|
||||
:info="info"
|
||||
/>
|
||||
</dot-dot-box>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 单节点
|
||||
*/
|
||||
|
||||
import {
|
||||
computed,
|
||||
} from 'vue';
|
||||
import {
|
||||
useRouter,
|
||||
} from 'vue-router';
|
||||
import * as hostUtils from '@/utils/host';
|
||||
|
||||
import handleServerInfo from '@/views/composable/server-info';
|
||||
import ServerListItemStatus from './server-list-item-status.vue';
|
||||
import ServerListItemRealTime from './server-list-item-real-time.vue';
|
||||
import ServerListItemBill from './server-list-item-bill.vue';
|
||||
|
||||
const props = defineProps({
|
||||
info: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* XCore XGB
|
||||
*/
|
||||
const { cpuAndMemAndDisk } = handleServerInfo({
|
||||
props,
|
||||
});
|
||||
|
||||
const platformLogoIconClassName = computed(() => hostUtils.getPlatformLogoIconClassName(props.info?.Host?.Platform));
|
||||
|
||||
function openDetail() {
|
||||
router.push({
|
||||
name: 'ServerDetail',
|
||||
params: {
|
||||
serverId: props.info.ID,
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.server-list-row-item {
|
||||
--list-item-height: 64px;
|
||||
--list-item-border-radius: 8px;
|
||||
--list-item-gap: 10px;
|
||||
--list-item-padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: var(--list-item-height);
|
||||
gap: var(--list-item-gap);
|
||||
transition: 0.3s;
|
||||
|
||||
&--offline {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.list-column-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
&--server-flag {
|
||||
--server-flag-size: 24px;
|
||||
width: calc(var(--server-flag-size) * 1.5);
|
||||
.server-flag {
|
||||
width: calc(var(--server-flag-size) * 1.5);
|
||||
height: var(--server-flag-size);
|
||||
line-height: var(--server-flag-size);
|
||||
font-size: var(--server-flag-size);
|
||||
}
|
||||
}
|
||||
&--server-name {
|
||||
flex: 1;
|
||||
|
||||
.server-name {
|
||||
height: 32px;
|
||||
line-height: 34px;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
&--server-system {
|
||||
width: 24px;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
&--cpu-mem {
|
||||
width: 100px;
|
||||
.core-mem {
|
||||
height: 30px;
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 针对1440px以下的屏幕
|
||||
@media screen and (max-width: 1440px) {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -12,7 +12,7 @@
|
||||
<template #default>
|
||||
<div
|
||||
class="chart-donut-label"
|
||||
:title="`${(used).toFixed(1) * 1}%`"
|
||||
:title="valPercent ? valPercent : `${(used).toFixed(1) * 1}%`"
|
||||
>
|
||||
<div class="server-status-val-text">
|
||||
<span>{{ valText }}</span>
|
||||
@ -73,6 +73,10 @@ defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
valPercent: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
@ -103,6 +107,7 @@ defineProps({
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: scale(var(--server-status-label-scale, 1));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
@ -65,6 +65,8 @@ export default (params) => {
|
||||
{
|
||||
const CoresVal = cpuInfo.value?.cores ? `${cpuInfo.value?.cores}C` : '-';
|
||||
const usedColor = config.nazhua.serverStatusLinear ? ['#0088FF', '#72B7FF'] : '#0088FF';
|
||||
const valPercent = `${(props.info.State?.CPU || 0).toFixed(1) * 1}%`;
|
||||
const valText = valPercent;
|
||||
return {
|
||||
type: 'cpu',
|
||||
used: (props.info.State?.CPU || 0).toFixed(1) * 1,
|
||||
@ -72,7 +74,8 @@ export default (params) => {
|
||||
used: usedColor,
|
||||
total: 'rgba(255, 255, 255, 0.25)',
|
||||
},
|
||||
valText: `${(props.info.State?.CPU || 0).toFixed(1) * 1}%`,
|
||||
valText,
|
||||
valPercent,
|
||||
label: 'CPU',
|
||||
content: {
|
||||
default: cpuInfo.value?.core || CoresVal,
|
||||
@ -82,11 +85,11 @@ export default (params) => {
|
||||
}
|
||||
case 'mem':
|
||||
{
|
||||
let usedVal;
|
||||
let valText;
|
||||
if (useMemAndTotalMem.value.used.g >= 10 && useMemAndTotalMem.value.total.g >= 10) {
|
||||
usedVal = `${(useMemAndTotalMem.value.used.g).toFixed(1) * 1}G`;
|
||||
valText = `${(useMemAndTotalMem.value.used.g).toFixed(1) * 1}G`;
|
||||
} else {
|
||||
usedVal = `${Math.ceil(useMemAndTotalMem.value.used.m)}M`;
|
||||
valText = `${Math.ceil(useMemAndTotalMem.value.used.m)}M`;
|
||||
}
|
||||
let contentVal;
|
||||
if (useMemAndTotalMem.value.total.g > 4) {
|
||||
@ -102,7 +105,8 @@ export default (params) => {
|
||||
used: usedColor,
|
||||
total: 'rgba(255, 255, 255, 0.25)',
|
||||
},
|
||||
valText: usedVal,
|
||||
valText,
|
||||
valPercent: `${useMemAndTotalMem.value.usePercent.toFixed(1) * 1}%`,
|
||||
label: '内存',
|
||||
content: {
|
||||
default: `运行内存${contentVal}`,
|
||||
@ -115,11 +119,11 @@ export default (params) => {
|
||||
if (!useSwapAndTotalSwap.value) {
|
||||
return null;
|
||||
}
|
||||
let usedVal;
|
||||
let valText;
|
||||
if (useSwapAndTotalSwap.value.used.g >= 10 && useSwapAndTotalSwap.value.total.g >= 10) {
|
||||
usedVal = `${(useSwapAndTotalSwap.value.used.g).toFixed(1) * 1}G`;
|
||||
valText = `${(useSwapAndTotalSwap.value.used.g).toFixed(1) * 1}G`;
|
||||
} else {
|
||||
usedVal = `${Math.ceil(useSwapAndTotalSwap.value.used.m)}M`;
|
||||
valText = `${Math.ceil(useSwapAndTotalSwap.value.used.m)}M`;
|
||||
}
|
||||
let contentVal;
|
||||
if (useSwapAndTotalSwap.value.total.g > 4) {
|
||||
@ -135,7 +139,8 @@ export default (params) => {
|
||||
used: usedColor,
|
||||
total: 'rgba(255, 255, 255, 0.25)',
|
||||
},
|
||||
valText: usedVal,
|
||||
valText,
|
||||
valPercent: `${useSwapAndTotalSwap.value.usePercent.toFixed(1) * 1}%`,
|
||||
label: '交换',
|
||||
content: {
|
||||
default: `交换内存${contentVal}`,
|
||||
@ -160,6 +165,7 @@ export default (params) => {
|
||||
total: 'rgba(255, 255, 255, 0.25)',
|
||||
},
|
||||
valText: `${(useDiskAndTotalDisk.value.used.g).toFixed(1) * 1}G`,
|
||||
valPercent: `${useDiskAndTotalDisk.value.usePercent.toFixed(1) * 1}%`,
|
||||
label: '磁盘',
|
||||
content: {
|
||||
default: `磁盘容量${contentValue}`,
|
||||
|
||||
@ -33,9 +33,11 @@
|
||||
name="list"
|
||||
tag="div"
|
||||
class="server-list-container"
|
||||
:class="`server-list--${serverItemComponentName}`"
|
||||
>
|
||||
<server-item
|
||||
v-for="item in filterServerList"
|
||||
<component
|
||||
:is="serverItemComponentMaps[serverItemComponentName]"
|
||||
v-for="item in filterServerList.list"
|
||||
:key="item.ID"
|
||||
:info="item"
|
||||
/>
|
||||
@ -51,6 +53,7 @@
|
||||
|
||||
import {
|
||||
ref,
|
||||
provide,
|
||||
computed,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
@ -66,10 +69,21 @@ import {
|
||||
count2size,
|
||||
} from '@/utils/world-map';
|
||||
import uuid from '@/utils/uuid';
|
||||
import validate from '@/utils/validate';
|
||||
|
||||
import WorldMap from '@/components/world-map/world-map.vue';
|
||||
import ServerOptionBox from './components/server-list/server-option-box.vue';
|
||||
import ServerItem from './components/server-list/server-list-item.vue';
|
||||
import ServerCardItem from './components/server-list/card/server-list-item.vue';
|
||||
import ServerRowItem from './components/server-list/row/server-list-item.vue';
|
||||
|
||||
const serverItemComponentMaps = {
|
||||
card: ServerCardItem,
|
||||
row: ServerRowItem,
|
||||
};
|
||||
const serverItemComponentName = [
|
||||
'card',
|
||||
'row',
|
||||
].includes(config.nazhua.listServerItemType) ? config.nazhua.listServerItemType : 'card';
|
||||
|
||||
const store = useStore();
|
||||
const worldMapWidth = ref();
|
||||
@ -120,45 +134,80 @@ const onlineOptions = computed(() => {
|
||||
return [];
|
||||
});
|
||||
|
||||
const filterServerList = computed(() => serverList.value.filter((i) => {
|
||||
const isFilterArr = [];
|
||||
if (filterFormData.value.tag) {
|
||||
const group = store.state.serverGroup.find((o) => o.name === filterFormData.value.tag);
|
||||
isFilterArr.push((group?.servers || []).includes(i.ID));
|
||||
}
|
||||
if (filterFormData.value.online) {
|
||||
isFilterArr.push(i.online === (filterFormData.value.online * 1));
|
||||
}
|
||||
return isFilterArr.length ? isFilterArr.every((o) => o) : true;
|
||||
}));
|
||||
const filterServerList = computed(() => {
|
||||
const fields = {};
|
||||
const locationMap = {};
|
||||
|
||||
const list = serverList.value.filter((i) => {
|
||||
const isFilterArr = [];
|
||||
if (filterFormData.value.tag) {
|
||||
const group = store.state.serverGroup.find((o) => o.name === filterFormData.value.tag);
|
||||
isFilterArr.push((group?.servers || []).includes(i.ID));
|
||||
}
|
||||
if (filterFormData.value.online) {
|
||||
isFilterArr.push(i.online === (filterFormData.value.online * 1));
|
||||
}
|
||||
const status = isFilterArr.length ? isFilterArr.every((o) => o) : true;
|
||||
if (!status) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 判断是否有字段
|
||||
if (i.PublicNote) {
|
||||
const {
|
||||
billingDataMod,
|
||||
planDataMod,
|
||||
customData,
|
||||
} = i.PublicNote;
|
||||
if (validate.isSet(billingDataMod?.amount)) {
|
||||
fields.billing = true;
|
||||
}
|
||||
if (validate.isSet(billingDataMod?.endDate)) {
|
||||
fields.remainingTime = true;
|
||||
}
|
||||
if (validate.isSet(planDataMod?.bandwidth)) {
|
||||
fields.bandwidth = true;
|
||||
}
|
||||
if (validate.isSet(customData?.orderLink)) {
|
||||
fields.orderLink = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 位置
|
||||
if (i.online === 1) {
|
||||
let aliasCode;
|
||||
let locationCode;
|
||||
if (i?.PublicNote?.customData?.location) {
|
||||
aliasCode = i.PublicNote.customData.location;
|
||||
locationCode = i.PublicNote.customData.location;
|
||||
} else if (i?.Host?.CountryCode) {
|
||||
aliasCode = i.Host.CountryCode.toUpperCase();
|
||||
}
|
||||
const code = alias2code(aliasCode) || locationCode;
|
||||
if (code) {
|
||||
if (!locationMap[code]) {
|
||||
locationMap[code] = [];
|
||||
}
|
||||
locationMap[code].push(i);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return {
|
||||
fields,
|
||||
list,
|
||||
locationMap,
|
||||
};
|
||||
});
|
||||
provide('filterServerList', filterServerList);
|
||||
|
||||
/**
|
||||
* 解构服务器列表的位置数据
|
||||
*/
|
||||
const serverLocations = computed(() => {
|
||||
const locationMap = {};
|
||||
filterServerList.value.forEach((i) => {
|
||||
if (i.online === -1) {
|
||||
return;
|
||||
}
|
||||
let aliasCode;
|
||||
let locationCode;
|
||||
if (i?.PublicNote?.customData?.location) {
|
||||
aliasCode = i.PublicNote.customData.location;
|
||||
locationCode = i.PublicNote.customData.location;
|
||||
} else if (i?.Host?.CountryCode) {
|
||||
aliasCode = i.Host.CountryCode.toUpperCase();
|
||||
}
|
||||
const code = alias2code(aliasCode) || locationCode;
|
||||
if (code) {
|
||||
if (!locationMap[code]) {
|
||||
locationMap[code] = [];
|
||||
}
|
||||
locationMap[code].push(i);
|
||||
}
|
||||
});
|
||||
const locations = [];
|
||||
Object.entries(locationMap).forEach(([code, servers]) => {
|
||||
Object.entries(filterServerList.value.locationMap).forEach(([code, servers]) => {
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
@ -214,6 +263,31 @@ onUnmounted(() => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.scroll-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.world-map-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.fitler-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 10px 20px;
|
||||
width: var(--list-container-width);
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.server-list-container.server-list--card {
|
||||
--list-padding: 20px;
|
||||
--list-gap-size: 20px;
|
||||
--list-item-num: 3;
|
||||
@ -228,29 +302,13 @@ onUnmounted(() => {
|
||||
)
|
||||
/ var(--list-item-num)
|
||||
);
|
||||
|
||||
.scroll-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.world-map-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.server-list-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--list-gap-size);
|
||||
padding: 0 var(--list-padding);
|
||||
width: var(--list-container-width);
|
||||
margin: auto;
|
||||
}
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--list-gap-size);
|
||||
padding: 0 var(--list-padding);
|
||||
width: var(--list-container-width);
|
||||
margin: auto;
|
||||
|
||||
// 针对1440px以下的屏幕
|
||||
@media screen and (max-width: 1440px) {
|
||||
@ -266,11 +324,14 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.fitler-group {
|
||||
.server-list-container.server-list--row {
|
||||
--list-padding: 20px;
|
||||
--list-gap-size: 12px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 10px 20px;
|
||||
flex-direction: column;
|
||||
gap: var(--list-gap-size);
|
||||
// padding: 0 var(--list-padding);
|
||||
width: var(--list-container-width);
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user