ServerStatus风格的表格模式

This commit is contained in:
hi2hi 2025-12-08 23:57:28 +08:00
parent 33f1625ab1
commit 69ab11babc
15 changed files with 1275 additions and 6 deletions

View File

@ -11,7 +11,8 @@ window.$$nazhuaConfig = {
// showLantern: true, // 是否显示灯笼 // showLantern: true, // 是否显示灯笼
enableInnerSearch: true, // 启用内部搜索 enableInnerSearch: true, // 启用内部搜索
// listServerItemTypeToggle: true, // 服务器列表项类型切换 // listServerItemTypeToggle: true, // 服务器列表项类型切换
// listServerItemType: 'row', // 服务器列表项类型 card/row row列表模式移动端自动切换至card listServerItemType: 'row', // 服务器列表项类型 card/row/status row列表模式移动端自动切换至card
// serverStatusColumnsTpl: null, // 服务器状态列配置模板
// listServerStatusType: 'progress', // 服务器状态类型--列表 // listServerStatusType: 'progress', // 服务器状态类型--列表
// listServerRealTimeShowLoad: true, // 列表显示服务器实时负载 // listServerRealTimeShowLoad: true, // 列表显示服务器实时负载
// detailServerStatusType: 'progress', // 服务器状态类型--详情页 // detailServerStatusType: 'progress', // 服务器状态类型--详情页

View File

@ -0,0 +1,122 @@
<template>
<dot-dot-box
v-if="tableData"
class="server-status"
>
<table class="server-status-table">
<thead class="server-status-table-header">
<tr class="server-status-table-header-row">
<template
v-for="column in tableData.columnProps"
:key="`th_${column.prop}`"
>
<template v-if="['billing', 'remainingTime'].includes(column.prop)">
<server-status-th
v-if="tableData.showBilling && column.prop === 'billing'"
:column="column"
/>
<server-status-th
v-if="tableData.showRemainingTime && column.prop === 'remainingTime'"
:column="column"
/>
</template>
<template v-else>
<server-status-th
:column="column"
/>
</template>
</template>
</tr>
</thead>
<tbody class="server-status-table-body">
<tr
v-for="itemData in tableData.list"
:key="itemData.info.ID"
class="server-status-table-body-row"
:class="{
'server-status-table-body-row--offline': itemData.info?.Online === -1,
'server-status-table-body-row--online': itemData.info?.Online === 1,
[`server-item--${itemData.info?.ID}`]: true,
}"
>
<template
v-for="column in itemData.columnData"
:key="`td_${itemData.info?.ID}_${column.prop}`"
>
<template v-if="['billing', 'remainingTime'].includes(column.prop)">
<server-status-td
v-if="tableData.showBilling && column.prop === 'billing'"
:column="column"
/>
<server-status-td
v-if="tableData.showRemainingTime && column.prop === 'remainingTime'"
:column="column"
/>
</template>
<template v-else>
<server-status-td
:column="column"
/>
</template>
</template>
</tr>
</tbody>
</table>
</dot-dot-box>
</template>
<script setup>
/**
* ServerStatus风格的列表
*/
import {
computed,
} from 'vue';
import config from '@/config';
import {
handleServerListColumn,
} from './server-status';
import ServerStatusTh from './table/th.vue';
import ServerStatusTd from './table/td.vue';
const props = defineProps({
serverList: {
type: Array,
default: () => [],
},
});
const tableData = computed(() => {
const result = handleServerListColumn(props.serverList, config.nazhua.serverStatusColumnsTpl);
return result;
});
</script>
<style lang="scss" scoped>
.server-status {
--server-status-cell-padding: 0 5px;
--server-status-td-height: 32px;
--progress-bar-height: 18px;
@media screen and (max-width: 350px) {
--progress-bar-height: 16px;
padding: 0 15px;
}
}
.server-status-table {
width: 100%;
border-collapse: collapse;
.server-status-table-body-row {
&--offline {
filter: grayscale(1);
}
}
}
</style>

View File

@ -0,0 +1,68 @@
<template>
<div class="conn-group">
<div class="conn--tcp">
{{ tcpConnCount }}
</div>
<div class="split-line">
|
</div>
<div class="conn--udp">
{{ udpConnCount }}
</div>
</div>
</template>
<script setup>
/**
* 连接信息
*/
import {
computed,
} from 'vue';
const props = defineProps({
realTimeData: {
type: Object,
default: () => ({}),
},
});
const tcpConnCount = computed(() => {
const { item } = props.realTimeData?.conns || {};
const { value } = item?.data?.tcp || {};
return value || '-';
});
const udpConnCount = computed(() => {
const { item } = props.realTimeData?.conns || {};
const { value } = item?.data?.udp || {};
return value || '-';
});
</script>
<style lang="scss" scoped>
.conn-group {
display: flex;
align-items: center;
justify-content: space-between;
gap: 5px;
width: 100%;
.conn--tcp {
flex: 1;
text-align: right;
color: var(--conn-tcp-color);
}
.conn--udp {
flex: 1;
text-align: left;
color: var(--conn-udp-color);
}
.split-line {
width: 4px;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,35 @@
<template>
<div class="country-content">
<server-flag :info="info" />
<span class="country-label">
{{ countryLabel }}
</span>
</div>
</template>
<script setup>
/**
* 地区信息
*/
import {
computed,
} from 'vue';
const props = defineProps({
info: {
type: Object,
default: () => ({}),
},
});
const countryLabel = computed(() => props.info?.Host?.CountryCode?.toUpperCase() || 'UN');
</script>
<style lang="scss" scoped>
.country-content {
display: flex;
align-items: center;
gap: 5px;
}
</style>

View File

@ -0,0 +1,74 @@
<template>
<div class="net-speed-group">
<div class="net-speed--in">
{{ inSpeed }}
</div>
<div class="split-line">
|
</div>
<div class="net-speed--out">
{{ outSpeed }}
</div>
</div>
</template>
<script setup>
/**
* 网速信息
*/
import {
computed,
} from 'vue';
const props = defineProps({
realTimeData: {
type: Object,
default: () => ({}),
},
});
const inSpeed = computed(() => {
const { item } = props.realTimeData?.speeds || {};
if (item?.data?.in) {
const { value, unit } = item.data.in;
return `${value}${unit}`;
}
return '-';
});
const outSpeed = computed(() => {
const { item } = props.realTimeData?.speeds || {};
if (item?.data?.out) {
const { value, unit } = item.data.out;
return `${value}${unit}`;
}
return '-';
});
</script>
<style lang="scss" scoped>
.net-speed-group {
display: flex;
align-items: center;
justify-content: space-between;
gap: 5px;
width: 100%;
.net-speed--in {
flex: 1;
text-align: right;
color: var(--net-speed-in-color);
}
.net-speed--out {
flex: 1;
text-align: left;
color: var(--net-speed-out-color);
}
.split-line {
width: 4px;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,47 @@
<template>
<div class="status-icon-box">
<div
class="status-icon"
:class="{
online: info.online === 1,
offline: info.online === -1,
}"
/>
</div>
</template>
<script setup>
/**
* 状态图标
*/
defineProps({
info: {
type: Object,
default: () => ({}),
},
});
</script>
<style lang="scss" scoped>
.status-icon-box {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.status-icon {
width: 12px;
height: 12px;
border-radius: 50%;
}
.status-icon.online {
background-image: linear-gradient(rgba(77, 133, 58, 1) 0, rgba(54, 126, 54, 1) 100%);
}
.status-icon.offline {
background-image: linear-gradient(rgba(155, 37, 34, 1) 0, rgba(161, 38, 35, 1) 100%);
}
</style>

View File

@ -0,0 +1,39 @@
<template>
<div class="system-os-content">
<span class="system-icon">
<span :class="platformLogoIconClassName" />
</span>
<span class="system-label">
{{ systemOSLabel }}
</span>
</div>
</template>
<script setup>
/**
* 系统信息
*/
import {
computed,
} from 'vue';
import * as hostUtils from '@/utils/host';
const props = defineProps({
info: {
type: Object,
default: () => ({}),
},
});
const platformLogoIconClassName = computed(() => hostUtils.getPlatformLogoIconClassName(props.info?.Host?.Platform));
const systemOSLabel = computed(() => hostUtils.getSystemOSLabel(props.info?.Host?.Platform, true));
</script>
<style lang="scss" scoped>
.system-os-content {
display: flex;
align-items: center;
gap: 5px;
}
</style>

View File

@ -0,0 +1,74 @@
<template>
<div class="transfer-group">
<div class="transfer--in">
{{ transferIn }}
</div>
<div class="split-line">
|
</div>
<div class="transfer--out">
{{ transferOut }}
</div>
</div>
</template>
<script setup>
/**
* 流量信息
*/
import {
computed,
} from 'vue';
const props = defineProps({
realTimeData: {
type: Object,
default: () => ({}),
},
});
const transferIn = computed(() => {
const { item } = props.realTimeData?.transfer || {};
if (item?.data?.in) {
const { value, unit } = item.data.in;
return `${value}${unit}`;
}
return '-';
});
const transferOut = computed(() => {
const { item } = props.realTimeData?.transfer || {};
if (item?.data?.out) {
const { value, unit } = item.data.out;
return `${value}${unit}`;
}
return '-';
});
</script>
<style lang="scss" scoped>
.transfer-group {
display: flex;
align-items: center;
justify-content: space-between;
gap: 5px;
width: 100%;
.transfer--in {
flex: 1;
text-align: right;
color: var(--transfer-in-color);
}
.transfer--out {
flex: 1;
text-align: left;
color: var(--transfer-out-color);
}
.split-line {
width: 4px;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,438 @@
/**
* ServerStatus风格的列表列配置
*/
import {
h,
} from 'vue';
// import * as hostUtils from '@/utils/host';
import handleServerStatus from '@/views/composable/server-status';
import handleServerInfo from '@/views/composable/server-info';
import handleServerRealTime from '@/views/composable/server-real-time';
import handleServerBillAndPlan from '@/views/composable/server-bill-and-plan';
import ServerStatusProgress from '@/views/components/server/server-status-progress.vue';
import StatusIcon from '@/views/components/server-list-by-server-status/server-info/status-icon.vue';
import SystemOS from '@/views/components/server-list-by-server-status/server-info/system-os.vue';
import Country from '@/views/components/server-list-by-server-status/server-info/country.vue';
import NetSpeed from '@/views/components/server-list-by-server-status/server-info/net-speed.vue';
import Transfer from '@/views/components/server-list-by-server-status/server-info/transfer.vue';
import Conns from '@/views/components/server-list-by-server-status/server-info/conns.vue';
const COLUMN_MAP = Object.freeze({
status: {
label: '状态',
width: 40,
},
name: {
label: '名称',
minWidth: 100,
align: 'left',
},
config: {
label: '规格',
width: 80,
align: 'left',
},
system: {
label: '系统',
width: 90,
align: 'left',
},
country: {
label: '地区',
width: 60,
align: 'left',
},
duration: {
label: '在线',
width: 60,
align: 'left',
},
load: {
label: '负载',
width: 45,
align: 'center',
},
speeds: {
label: '网速',
width: 112,
align: 'center',
},
inSpeed: {
label: '入网',
width: 60,
align: 'left',
},
outSpeed: {
label: '出网',
width: 60,
align: 'left',
},
transfer: {
label: '流量',
width: 112,
align: 'center',
},
inTransfer: {
label: '入网流量',
width: 60,
align: 'left',
},
outTransfer: {
label: '出网流量',
width: 60,
align: 'left',
},
conns: {
label: '连接',
width: 72,
align: 'center',
},
tcp: {
label: 'TCP',
width: 40,
align: 'left',
},
udp: {
label: 'UDP',
width: 40,
align: 'left',
},
cpu: {
label: 'CPU',
width: 80,
align: 'center',
},
cpuText: {
valProp: 'cpu',
label: 'CPU',
width: 40,
align: 'center',
},
mem: {
label: '内存',
width: 80,
align: 'center',
},
memText: {
valProp: 'mem',
label: '内存',
width: 40,
align: 'center',
},
swap: {
label: '交换',
width: 80,
align: 'center',
},
swapText: {
valProp: 'swap',
label: '交换',
width: 40,
align: 'center',
},
disk: {
label: '硬盘',
width: 80,
align: 'center',
},
diskText: {
valProp: 'disk',
label: '硬盘',
width: 40,
align: 'center',
},
billing: {
label: '价格',
width: 110,
align: 'right',
},
remainingTime: {
label: '剩余',
width: 70,
align: 'right',
},
});
/**
* 默认列配置
*/
// eslint-disable-next-line max-len, vue/max-len
const DEFAULT_COLUMNS = 'status,name,country,system,config,duration,speeds,transfer,conns,load,cpuText,memText,diskText,billing,remainingTime';
/**
* 需要实时更新的数据
*/
const RELD_TIME_DATA = [
'speeds', 'inSpeed', 'outSpeed',
'transfer', 'inTransfer', 'outTransfer',
'conns', 'tcp', 'udp',
'duration', 'load',
];
/**
* 获取列配置
* @param {string} columnsTpls 列配置模板
* @returns {Object} 列配置
* @property {Array} columns 列配置
*/
export const getColumnPropsConfig = (tpls = DEFAULT_COLUMNS) => {
const tplList = tpls.split(',');
const columnList = [];
tplList.forEach((tpl) => {
if (COLUMN_MAP[tpl]) {
columnList.push({
prop: tpl,
...COLUMN_MAP[tpl],
});
}
});
return columnList;
};
/**
* 将服务器数据转换为表格数据
* @param {Object} server 服务器数据
* @returns {Object} 表格数据
*/
export const handleServerItemData = (params) => {
const {
column,
server,
realTimeData,
progressData,
billAndPlan,
} = params || {};
switch (column.prop) {
case 'status':
return {
type: 'component',
component: h(StatusIcon, { info: server }),
originalData: params,
};
case 'name':
return {
type: 'text',
value: server.Name,
originalData: params,
};
case 'config':
{
const { cpuAndMemAndDisk } = handleServerInfo({
props: {
info: server,
},
originalData: params,
});
return {
type: 'text',
value: cpuAndMemAndDisk,
originalData: params,
};
}
case 'system':
return {
type: 'component',
component: h(SystemOS, { info: server }),
originalData: params,
};
case 'country':
return {
type: 'component',
component: h(Country, { info: server }),
originalData: params,
};
case 'speeds':
return {
type: 'component',
component: h(NetSpeed, { realTimeData }),
originalData: params,
};
case 'transfer':
return {
type: 'component',
component: h(Transfer, { realTimeData }),
originalData: params,
};
case 'conns':
return {
type: 'component',
component: h(Conns, { realTimeData }),
originalData: params,
};
case 'cpu':
case 'mem':
case 'disk':
case 'swap':
{
const progressItem = progressData[column.prop];
return {
type: 'component',
component: h(ServerStatusProgress, {
type: column.prop,
used: progressItem?.used || 0,
colors: progressItem?.colors || {},
valText: progressItem?.valPercent || '',
}),
originalData: params,
};
}
case 'cpuText':
case 'memText':
case 'diskText':
case 'swapText':
{
const progressItem = progressData[column.valProp];
return {
prop: column.prop,
type: 'text',
value: parseFloat(progressItem?.used || 0).toFixed(1),
unit: '%',
text: progressItem?.valPercent || '',
originalData: params,
};
}
case 'billing':
case 'remainingTime':
{
const item = billAndPlan?.value?.[column.prop];
const texts = [];
if (item?.value) {
texts.push(item.value || '-');
}
if (item?.cycleLabel) {
texts.push(item.cycleLabel);
}
return {
prop: column.prop,
type: 'text',
text: texts.join('/'),
originalData: params,
};
}
default: {
if (RELD_TIME_DATA.includes(column.prop) && realTimeData[column.prop]) {
const item = realTimeData[column.prop];
return {
prop: column.prop,
type: 'text',
text: item?.text,
value: item?.value,
unit: item?.unit,
originalData: params,
};
}
return {
prop: column.prop,
type: 'text',
value: '-',
originalData: params,
};
}
}
};
/**
* 将服务器数据转换为表格数据
* @param {Object} server 服务器数据
* @param {Array} columns 列配置
* @returns {Array} 表格数据
*/
export const handleServerListColumn = (serverList, columnTpls = DEFAULT_COLUMNS) => {
const columnProps = getColumnPropsConfig(columnTpls);
const tpls = columnProps.map((column) => column.valProp || column.prop).join(',');
const hasBilling = columnTpls.includes('billing');
const hasRemainingTime = columnTpls.includes('remainingTime');
let showBilling = false;
let showRemainingTime = false;
const list = serverList.map((server) => {
// 负载\网速\流量\在线等
const realTimeResult = handleServerRealTime({
props: {
info: server,
},
serverRealTimeListTpls: tpls,
});
const realTimeData = {};
realTimeResult?.serverRealTimeList?.value?.forEach?.((item) => {
if (item.show) {
const text = [item.value];
if (item.unit) {
text.push(item.unit);
}
realTimeData[item.key] = {
value: item.value,
unit: item.unit,
text: text.join(''),
item,
};
} else {
realTimeData[item.key] = {
text: '-',
item,
};
}
});
// CPU\内存\硬盘\交换 进度条
const {
serverStatusList,
} = handleServerStatus({
props: {
info: server,
},
statusListTpl: tpls,
statusListItemContent: false,
});
const progressData = {};
serverStatusList.value?.forEach?.((item) => {
progressData[item.type] = item;
});
let billAndPlan = null;
if (hasBilling || hasRemainingTime) {
const result = handleServerBillAndPlan({
props: {
info: server,
},
});
billAndPlan = result.billAndPlan;
if (billAndPlan?.value?.billing) {
showBilling = true;
}
if (billAndPlan?.value?.remainingTime) {
console.log('remainingTime', billAndPlan.value.remainingTime);
showRemainingTime = true;
}
}
const columnData = [];
columnProps.forEach((columnItem) => {
columnData.push({
...columnItem,
data: handleServerItemData({
column: columnItem,
server,
realTimeData,
progressData,
billAndPlan,
}),
});
});
return {
info: server,
columnData,
computedData: {
realTimeData,
progressData,
billAndPlan,
},
};
});
return {
list,
columnProps,
showBilling,
showRemainingTime,
};
};

View File

@ -0,0 +1,186 @@
<template>
<td
class="server-status-td server-status-body-td"
:class="columnClass"
:style="columnStyle"
>
<div
class="server-status-td-content"
:class="'server-status-td-content--' + tdContent.prop"
>
<template
v-if="tdContent.type === 'text'"
>
<span
v-if="isSet(tdContent.value)"
class="text--value"
>
{{ tdContent.value }}
</span>
<span
v-if="isSet(tdContent.unit)"
class="text--unit"
>
{{ tdContent.unit }}
</span>
<span
v-if="!isSet(tdContent.value) && isSet(tdContent.text)"
class="text"
>
{{ tdContent.text }}
</span>
</template>
<template
v-if="tdContent.type === 'component'"
>
<component :is="tdContent.component" />
</template>
</div>
</td>
</template>
<script setup>
/**
* 自定义TD组件
*/
import {
computed,
} from 'vue';
const props = defineProps({
column: {
type: Object,
default: () => ({}),
},
});
// css
const getCssLengthUnit = (value) => {
if (typeof value === 'number') {
return `${value}px`;
}
return value;
};
const columnClass = computed(() => {
const className = {};
if (props.column.align) {
className[`server-status-td--align-${props.column.align}`] = true;
}
return className;
});
const columnStyle = computed(() => {
const style = {};
if (props.column.width) {
style.width = getCssLengthUnit(props.column.width);
}
if (props.column.minWidth) {
style.minWidth = getCssLengthUnit(props.column.minWidth);
}
return style;
});
const tdContent = computed(() => {
if (['text', 'component'].includes(props.column.data.type)) {
return props.column.data;
}
return '';
});
function isSet(value) {
return value !== undefined && value !== null && value !== '';
}
</script>
<style lang="scss" scoped>
.server-status-td {
height: var(--server-status-td-height);
padding: var(--server-status-cell-padding);
--td-content-justify-content: center;
&--align-center {
--td-content-justify-content: center;
}
&--align-right {
--td-content-justify-content: flex-end;
}
&--align-left {
--td-content-justify-content: flex-start;
}
.server-status-td-content {
display: flex;
align-items: center;
justify-content: var(--td-content-justify-content);
width: 100%;
&--transfer {
.text--value {
color: var(--transfer-color);
}
}
&--inTransfer {
.text--value {
color: var(--transfer-in-color);
}
}
&--outTransfer {
.text--value {
color: var(--transfer-out-color);
}
}
&--inSpeed {
.text--value {
color: var(--net-speed-in-color);
}
}
&--outSpeed {
.text--value {
color: var(--net-speed-out-color);
}
}
&--load {
.text--value {
color: var(--load-color);
}
}
&--duration {
.text--value {
color: var(--duration-color);
}
}
&--cpu {
.text--value {
color: var(--cpu-text-color);
}
}
&--mem {
.text--value {
color: var(--mem-text-color);
}
}
&--swap {
.text--value {
color: var(--swap-text-color);
}
}
&--disk {
.text--value {
color: var(--disk-text-color);
}
}
}
}
</style>

View File

@ -0,0 +1,72 @@
<template>
<th
class="server-status-th"
:class="columnClass"
:style="columnStyle"
>
{{ column.label }}
</th>
</template>
<script setup>
/**
* 自定义TH组件
*/
import {
computed,
} from 'vue';
const props = defineProps({
column: {
type: Object,
default: () => ({}),
},
});
// css
const getCssLengthUnit = (value) => {
if (typeof value === 'number') {
return `${value}px`;
}
return value;
};
const columnClass = computed(() => {
const className = {};
if (props.column.align) {
className[`server-status-th--align-${props.column.align}`] = true;
}
return className;
});
const columnStyle = computed(() => {
const style = {};
if (props.column.width) {
style.width = getCssLengthUnit(props.column.width);
}
if (props.column.minWidth) {
style.minWidth = getCssLengthUnit(props.column.minWidth);
}
return style;
});
</script>
<style lang="scss" scoped>
.server-status-th {
padding: var(--server-status-cell-padding);
text-align: center;
&--align-center {
text-align: center;
}
&--align-right {
text-align: right;
}
&--align-left {
text-align: left;
}
}
</style>

View File

@ -7,6 +7,7 @@
:class="{ :class="{
'server-list--row': showListRow, 'server-list--row': showListRow,
'server-list--card': showListCard, 'server-list--card': showListCard,
'server-list--status': showListByServerStatus,
}" }"
> >
<slot /> <slot />
@ -17,6 +18,7 @@
:class="{ :class="{
'server-list--row': showListRow, 'server-list--row': showListRow,
'server-list--card': showListCard, 'server-list--card': showListCard,
'server-list--status': showListByServerStatus,
}" }"
> >
<slot /> <slot />
@ -41,6 +43,10 @@ defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
showListByServerStatus: {
type: Boolean,
default: false,
},
}); });
</script> </script>
@ -94,6 +100,18 @@ defineProps({
margin: auto; margin: auto;
} }
.server-list-container.server-list--status {
--list-padding: 20px;
--list-gap-size: 12px;
position: relative;
display: flex;
flex-direction: column;
gap: var(--list-gap-size);
width: var(--list-container-width);
padding: 0 var(--list-padding);
margin: auto;
}
.list-move, .list-move,
.list-enter-active, .list-enter-active,
.list-leave-active { .list-leave-active {

View File

@ -12,7 +12,10 @@
class="progress-bar-label" class="progress-bar-label"
:title="label + '使用' + used + '%'" :title="label + '使用' + used + '%'"
> >
<span class="server-status-label"> <span
v-if="label"
class="server-status-label"
>
{{ label }}: {{ label }}:
</span> </span>
<span class="server-status-val-text"> <span class="server-status-val-text">

View File

@ -221,6 +221,18 @@ export default (params) => {
value: transfer.value?.value, value: transfer.value?.value,
unit: transfer.value?.unit, unit: transfer.value?.unit,
show: validate.isSet(transfer.value?.value), show: validate.isSet(transfer.value?.value),
data: {
in: {
value: inTransfer.value?.value,
unit: inTransfer.value?.unit,
show: validate.isSet(inTransfer.value?.value),
},
out: {
value: outTransfer.value?.value,
unit: outTransfer.value?.unit,
show: validate.isSet(outTransfer.value?.value),
},
},
}; };
case 'inTransfer': case 'inTransfer':
return { return {
@ -263,6 +275,18 @@ export default (params) => {
`${netOutSpeed.value?.value}${netOutSpeed.value?.unit}`, `${netOutSpeed.value?.value}${netOutSpeed.value?.unit}`,
].join('|'), ].join('|'),
show: validate.isSet(netInSpeed.value?.value) && validate.isSet(netOutSpeed.value?.value), show: validate.isSet(netInSpeed.value?.value) && validate.isSet(netOutSpeed.value?.value),
data: {
in: {
value: netInSpeed.value?.value,
unit: netInSpeed.value?.unit,
show: validate.isSet(netInSpeed.value?.value),
},
out: {
value: netOutSpeed.value?.value,
unit: netOutSpeed.value?.unit,
show: validate.isSet(netOutSpeed.value?.value),
},
},
}; };
case 'load': case 'load':
return { return {
@ -271,12 +295,49 @@ export default (params) => {
value: (props.info.State?.Load1 || 0).toFixed(2), value: (props.info.State?.Load1 || 0).toFixed(2),
show: validate.isSet(props.info.State?.Load1), show: validate.isSet(props.info.State?.Load1),
}; };
case 'loads':
{
const loads = [];
loads.push((props.info.State?.Load1 || 0).toFixed(2));
loads.push((props.info.State?.Load5 || 0).toFixed(2));
loads.push((props.info.State?.Load15 || 0).toFixed(2));
return {
key,
label: '负载',
value: loads.join(','),
show: loads.some((load) => validate.isSet(load)),
data: {
load1: {
value: (props.info.State?.Load1 || 0).toFixed(2),
show: validate.isSet(props.info.State?.Load1),
},
load5: {
value: (props.info.State?.Load5 || 0).toFixed(2),
show: validate.isSet(props.info.State?.Load5),
},
load15: {
value: (props.info.State?.Load15 || 0).toFixed(2),
show: validate.isSet(props.info.State?.Load15),
},
},
};
}
case 'conns': case 'conns':
return { return {
key, key,
label: '连接', label: '连接',
value: `${props.info.State?.TcpConnCount || 0}|${props.info.State?.UdpConnCount || 0}`, value: `${props.info.State?.TcpConnCount || 0}|${props.info.State?.UdpConnCount || 0}`,
show: true, show: true,
data: {
tcp: {
value: props.info.State?.TcpConnCount || 0,
show: validate.isSet(props.info.State?.TcpConnCount),
},
udp: {
value: props.info.State?.UdpConnCount || 0,
show: validate.isSet(props.info.State?.UdpConnCount),
},
},
}; };
case 'tcp': case 'tcp':
return { return {
@ -292,6 +353,7 @@ export default (params) => {
value: props.info.State?.UdpConnCount || 0, value: props.info.State?.UdpConnCount || 0,
show: validate.isSet(props.info.State?.UdpConnCount), show: validate.isSet(props.info.State?.UdpConnCount),
}; };
// 入网和出网
case 'I-A-O': case 'I-A-O':
return { return {
key, key,
@ -314,6 +376,7 @@ export default (params) => {
], ],
show: validate.isSet(netInSpeed.value?.value) && validate.isSet(netOutSpeed.value?.value), show: validate.isSet(netInSpeed.value?.value) && validate.isSet(netOutSpeed.value?.value),
}; };
// 负载和进程
case 'L-A-P': case 'L-A-P':
return { return {
key, key,
@ -334,6 +397,7 @@ export default (params) => {
], ],
show: validate.isSet(props.info.State?.Load1) || validate.isSet(props.info.State?.ProcessCount), show: validate.isSet(props.info.State?.Load1) || validate.isSet(props.info.State?.ProcessCount),
}; };
// 连接 TCP和UDP
case 'T-A-U': case 'T-A-U':
return { return {
key, key,
@ -354,6 +418,7 @@ export default (params) => {
], ],
show: validate.isSet(props.info.State?.TcpConnCount) || validate.isSet(props.info.State?.UdpConnCount), show: validate.isSet(props.info.State?.TcpConnCount) || validate.isSet(props.info.State?.UdpConnCount),
}; };
// 在线和流量
case 'D-A-T': case 'D-A-T':
return { return {
key, key,

View File

@ -40,6 +40,7 @@
/> />
</div> </div>
</div> </div>
<!-- 列表模式 -->
<server-list-warp <server-list-warp
v-if="showListRow" v-if="showListRow"
:show-transition="showTransition" :show-transition="showTransition"
@ -51,6 +52,17 @@
:info="item" :info="item"
/> />
</server-list-warp> </server-list-warp>
<!-- ServerStatus模式 -->
<server-list-warp
v-if="showListByServerStatus"
:show-transition="showTransition"
:show-list-by-server-status="showListByServerStatus"
>
<server-list-by-server-status
:server-list="filterServerList.list"
/>
</server-list-warp>
<!-- 卡片模式 -->
<server-list-warp <server-list-warp
v-if="showListCard" v-if="showListCard"
:show-transition="showTransition" :show-transition="showTransition"
@ -109,6 +121,7 @@ import ServerOptionBox from './components/server-list/server-option-box.vue';
import ServerListWarp from './components/server-list/server-list-warp.vue'; import ServerListWarp from './components/server-list/server-list-warp.vue';
import ServerCardItem from './components/server-list/card/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'; import ServerRowItem from './components/server-list/row/server-list-item.vue';
import ServerListByServerStatus from './components/server-list-by-server-status/main.vue';
const store = useStore(); const store = useStore();
const worldMapWidth = ref(); const worldMapWidth = ref();
@ -140,12 +153,21 @@ const showListRow = computed(() => {
const showListCard = computed(() => { const showListCard = computed(() => {
if (windowWidth.value > 1024) { if (windowWidth.value > 1024) {
if (config.nazhua.listServerItemTypeToggle) { if (config.nazhua.listServerItemTypeToggle) {
return listType.value !== 'row'; return listType.value === 'card';
} }
return config.nazhua.listServerItemType !== 'row'; return config.nazhua.listServerItemType === 'card';
} }
return true; return true;
}); });
const showListByServerStatus = computed(() => {
if (windowWidth.value > 1024) {
if (config.nazhua.listServerItemTypeToggle) {
return listType.value === 'status';
}
return config.nazhua.listServerItemType === 'status';
}
return false;
});
const showFilter = computed(() => config.nazhua.hideFilter !== true); const showFilter = computed(() => config.nazhua.hideFilter !== true);
const filterFormData = ref({ const filterFormData = ref({
@ -217,14 +239,19 @@ watch(() => serverCount.value, () => {
const listTypeOptions = computed(() => [{ const listTypeOptions = computed(() => [{
key: 'card', key: 'card',
label: '卡片', label: '卡片模式',
value: 'card', value: 'card',
icon: 'ri-gallery-view-2', icon: 'ri-gallery-view-2',
}, { }, {
key: 'row', key: 'row',
label: '列表', label: '列表模式',
value: 'row', value: 'row',
icon: 'ri-list-view', icon: 'ri-list-view',
}, {
key: 'status',
label: 'ServerStatus模式',
value: 'status',
icon: 'ri-server-line',
}]); }]);
const filterServerList = computed(() => { const filterServerList = computed(() => {