feat: 优化状态监视器,重构为单例模式

This commit is contained in:
Baobhan Sith
2025-05-12 21:33:23 +08:00
parent 3104367a31
commit 2e69a00049
5 changed files with 377 additions and 308 deletions
@@ -6,14 +6,20 @@
{{ t('statusMonitor.title') }}
</h4>
<!-- 无活动会话状态 -->
<div v-if="!activeSessionId" class="no-session-status flex flex-col items-center justify-center text-center text-text-secondary mt-4 h-full">
<i class="fas fa-plug text-4xl mb-3 text-text-secondary"></i>
<span class="text-lg font-medium mb-2">{{ t('layout.noActiveSession.title') }}</span>
</div>
<!-- 错误状态 -->
<div v-if="statusError" class="status-error flex flex-col items-center justify-center text-center text-red-500 mt-4 h-full">
<div v-else-if="currentStatusError" class="status-error flex flex-col items-center justify-center text-center text-red-500 mt-4 h-full">
<i class="fas fa-exclamation-triangle text-2xl mb-2"></i>
<span>{{ t('statusMonitor.errorPrefix') }} {{ statusError }}</span>
<span>{{ t('statusMonitor.errorPrefix') }} {{ currentStatusError }}</span>
</div>
<!-- 加载状态 -->
<div v-else-if="!serverStatus" class="loading-status flex flex-col items-center justify-center text-center text-text-secondary mt-4 h-full">
<div v-else-if="!currentServerStatus" class="loading-status flex flex-col items-center justify-center text-center text-text-secondary mt-4 h-full">
<i class="fas fa-spinner fa-spin text-2xl mb-2"></i>
<span>{{ t('statusMonitor.loading') }}</span>
</div>
@@ -40,10 +46,10 @@
<label class="font-semibold text-text-secondary text-left whitespace-nowrap">{{ t('statusMonitor.cpuLabel') }}</label>
<div class="value-wrapper flex items-center gap-2">
<div class="progress-bar-container bg-header rounded h-3 overflow-hidden flex-grow"> <!-- 减小高度 -->
<div class="progress-bar bg-blue-500 h-full transition-width duration-300 ease-in-out" :style="{ width: `${serverStatus.cpuPercent ?? 0}%` }"></div>
<div class="progress-bar bg-blue-500 h-full" :class="{ 'transition-width duration-300 ease-in-out': !isSwitchingSession }" :style="{ width: `${displayCpuPercent}%` }"></div>
</div>
<!-- 移除 w-12 text-right 以实现左对齐 -->
<span class="font-mono text-left text-xs">{{ serverStatus.cpuPercent?.toFixed(1) ?? t('statusMonitor.notAvailable') }}%</span>
<span class="font-mono text-left text-xs">{{ currentServerStatus.cpuPercent?.toFixed(1) ?? t('statusMonitor.notAvailable') }}%</span>
</div>
</div>
@@ -53,7 +59,7 @@
<label class="font-semibold text-text-secondary text-left whitespace-nowrap">{{ t('statusMonitor.memoryLabel') }}</label>
<div class="value-wrapper flex items-center gap-2">
<div class="progress-bar-container bg-header rounded h-3 overflow-hidden flex-grow">
<div class="progress-bar bg-green-500 h-full transition-width duration-300 ease-in-out" :style="{ width: `${serverStatus.memPercent ?? 0}%` }"></div>
<div class="progress-bar bg-green-500 h-full" :class="{ 'transition-width duration-300 ease-in-out': !isSwitchingSession }" :style="{ width: `${displayMemPercent}%` }"></div>
</div>
<span class="mem-disk-details font-mono text-xs whitespace-nowrap text-left">{{ memDisplay }}</span>
</div>
@@ -66,9 +72,9 @@
<div class="value-wrapper flex items-center gap-2">
<div class="progress-bar-container bg-header rounded h-3 overflow-hidden flex-grow">
<!-- swap颜色 -->
<div class="progress-bar h-full transition-width duration-300 ease-in-out"
:class="serverStatus?.swapPercent && serverStatus.swapPercent > 0 ? 'bg-yellow-500' : 'bg-gray-500'"
:style="{ width: `${serverStatus?.swapPercent ?? 0}%` }"></div>
<div class="progress-bar h-full"
:class="[{ 'transition-width duration-300 ease-in-out': !isSwitchingSession }, (currentServerStatus?.swapPercent ?? 0) > 0 ? 'bg-yellow-500' : 'bg-gray-500']"
:style="{ width: `${displaySwapPercent}%` }"></div>
</div>
<span class="mem-disk-details font-mono text-xs whitespace-nowrap text-left">{{ swapDisplay }}</span>
</div>
@@ -80,7 +86,7 @@
<label class="font-semibold text-text-secondary text-left whitespace-nowrap">{{ t('statusMonitor.diskLabel') }}</label>
<div class="value-wrapper flex items-center gap-2">
<div class="progress-bar-container bg-header rounded h-3 overflow-hidden flex-grow">
<div class="progress-bar bg-purple-500 h-full transition-width duration-300 ease-in-out" :style="{ width: `${serverStatus.diskPercent ?? 0}%` }"></div>
<div class="progress-bar bg-purple-500 h-full" :class="{ 'transition-width duration-300 ease-in-out': !isSwitchingSession }" :style="{ width: `${displayDiskPercent}%` }"></div>
</div>
<span class="mem-disk-details font-mono text-xs whitespace-nowrap text-left">{{ diskDisplay }}</span>
</div>
@@ -91,32 +97,36 @@
<!-- 网络速率 -->
<div class="status-item grid grid-cols-[auto_1fr] items-center gap-3 mt-2">
<label class="font-semibold text-text-secondary text-left whitespace-nowrap">{{ t('statusMonitor.networkLabel') }} ({{ serverStatus?.netInterface || '...' }}):</label>
<label class="font-semibold text-text-secondary text-left whitespace-nowrap">{{ t('statusMonitor.networkLabel') }} ({{ currentServerStatus?.netInterface || '...' }}):</label>
<div class="network-values flex items-center justify-start gap-4"> <!-- 减小间距 -->
<span class="rate down inline-flex items-center gap-1 text-green-500 text-xs whitespace-nowrap">
<i class="fas fa-arrow-down w-3 text-center"></i> <!-- Font Awesome 图标 -->
<span class="font-mono">{{ formatBytesPerSecond(serverStatus?.netRxRate) }}</span>
<span class="font-mono">{{ formatBytesPerSecond(currentServerStatus?.netRxRate) }}</span>
</span>
<span class="rate up inline-flex items-center gap-1 text-orange-500 text-xs whitespace-nowrap">
<i class="fas fa-arrow-up w-3 text-center"></i> <!-- Font Awesome 图标 -->
<span class="font-mono">{{ formatBytesPerSecond(serverStatus?.netTxRate) }}</span>
<span class="font-mono">{{ formatBytesPerSecond(currentServerStatus?.netTxRate) }}</span>
</span>
</div>
</div>
<!-- 图表组件 -->
<StatusCharts :server-status="serverStatus" />
<!-- 仅当有活动会话且有数据时渲染图表 -->
<StatusCharts v-if="activeSessionId && currentServerStatus" :server-status="currentServerStatus" :active-session-id="activeSessionId" />
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { ref, computed, watch, type PropType, nextTick } from 'vue'; // 添加 PropType 和 nextTick
import { useI18n } from 'vue-i18n';
import StatusCharts from './StatusCharts.vue';
import { useSessionStore } from '../stores/session.store'; // 注入 sessionStore
import { storeToRefs } from 'pinia'; // 导入 storeToRefs
const { t } = useI18n();
const sessionStore = useSessionStore();
const { sessions } = storeToRefs(sessionStore); // 获取响应式的 sessions
const isSwitchingSession = ref(false);
interface ServerStatus {
cpuPercent?: number;
@@ -136,17 +146,53 @@ interface ServerStatus {
osName?: string;
}
const props = defineProps<{
sessionId: string;
serverStatus: ServerStatus | null;
statusError?: string | null;
}>();
// --- Props ---
const props = defineProps({
activeSessionId: {
type: String as PropType<string | null>,
required: false, // 允许为 null
default: null,
},
});
// --- Computed properties to get current session data ---
const currentSessionState = computed(() => {
return props.activeSessionId ? sessions.value.get(props.activeSessionId) : null;
});
const currentServerStatus = computed<ServerStatus | null>(() => {
return currentSessionState.value?.statusMonitorManager?.serverStatus?.value ?? null;
});
// --- 计算属性,用于绑定到进度条宽度 ---
// 始终返回当前状态的百分比。动画由 CSS 类控制。
const displayCpuPercent = computed(() => {
return currentServerStatus.value?.cpuPercent ?? 0;
});
const displayMemPercent = computed(() => {
return currentServerStatus.value?.memPercent ?? 0;
});
const displaySwapPercent = computed(() => {
return currentServerStatus.value?.swapPercent ?? 0;
});
const displayDiskPercent = computed(() => {
return currentServerStatus.value?.diskPercent ?? 0;
});
const currentStatusError = computed<string | null>(() => {
return currentSessionState.value?.statusMonitorManager?.statusError?.value ?? null;
});
// --- 缓存逻辑保持不变 ---
const cachedCpuModel = ref<string | null>(null);
const cachedOsName = ref<string | null>(null);
watch(() => props.serverStatus, (newData) => {
// --- Watcher for caching CPU Model and OS Name ---
// 现在监听 currentServerStatus
watch(currentServerStatus, (newData) => {
if (newData) {
if (newData.cpuModel !== undefined && newData.cpuModel !== null && newData.cpuModel !== '') {
cachedCpuModel.value = newData.cpuModel;
@@ -157,13 +203,24 @@ watch(() => props.serverStatus, (newData) => {
}
}, { immediate: true });
// --- 计算属性保持不变 ---
// --- 监听 activeSessionId 变化以处理会话切换状态 ---
watch(() => props.activeSessionId, async (newId, oldId) => {
if (newId !== oldId) {
isSwitchingSession.value = true;
await nextTick(); // 等待DOM更新(currentServerStatus已改变,displayPercent们会返回0
isSwitchingSession.value = false;
}
});
// --- Computed properties for display ---
const displayCpuModel = computed(() => {
return (props.serverStatus?.cpuModel ?? cachedCpuModel.value) || t('statusMonitor.notAvailable');
// 使用 currentServerStatus
return (currentServerStatus.value?.cpuModel ?? cachedCpuModel.value) || t('statusMonitor.notAvailable');
});
const displayOsName = computed(() => {
return (props.serverStatus?.osName ?? cachedOsName.value) || t('statusMonitor.notAvailable');
// 使用 currentServerStatus
return (currentServerStatus.value?.osName ?? cachedOsName.value) || t('statusMonitor.notAvailable');
});
const formatBytesPerSecond = (bytes?: number): string => {
@@ -194,21 +251,21 @@ const formatMemorySize = (mb?: number): string => {
};
const memDisplay = computed(() => {
const data = props.serverStatus;
const data = currentServerStatus.value; // 使用 currentServerStatus
if (!data || data.memUsed === undefined || data.memTotal === undefined) return t('statusMonitor.notAvailable');
const percent = data.memPercent !== undefined ? `(${(data.memPercent).toFixed(1)}%)` : ''; // 百分比保留 1 位小数
return `${formatMemorySize(data.memUsed)} / ${formatMemorySize(data.memTotal)} ${percent}`;
});
const diskDisplay = computed(() => {
const data = props.serverStatus;
const data = currentServerStatus.value; // 使用 currentServerStatus
if (!data || data.diskUsed === undefined || data.diskTotal === undefined) return t('statusMonitor.notAvailable');
const percent = data.diskPercent !== undefined ? `(${(data.diskPercent).toFixed(1)}%)` : ''; // 百分比保留 1 位小数
return `${formatKbToGb(data.diskUsed)} / ${formatKbToGb(data.diskTotal)} ${percent}`;
});
const swapDisplay = computed(() => {
const data = props.serverStatus;
const data = currentServerStatus.value; // 使用 currentServerStatus
const used = data?.swapUsed ?? 0;
const total = data?.swapTotal ?? 0;
const percentVal = data?.swapPercent ?? 0;
@@ -223,4 +280,3 @@ const swapDisplay = computed(() => {
});
</script>