fix(frontend): redraw status charts on container resize in narrow sidebar

observe the chart host width and force cpu/network chart remount when the
sidebar width changes, preventing stale canvas dimensions after layout
compression.

add min-width/width constraints on chart containers and canvas to keep chart
rendering bounded to the current panel width, improving responsiveness in
tight layouts.

archive the completed layout-parity proposal/tasks and sync changelog/module
records to reflect the delivered frontend updates.
This commit is contained in:
yinjianm
2026-04-16 00:47:25 +08:00
parent a28822ec79
commit 34d6c3f499
8 changed files with 260 additions and 172 deletions
@@ -1,5 +1,5 @@
<template>
<div class="status-charts">
<div ref="chartHostRef" class="status-charts">
<section class="chart-panel">
<div class="chart-panel__header">
<div>
@@ -31,7 +31,7 @@
</template>
<script setup lang="ts">
import { ref, watch, computed, type PropType } from 'vue';
import { ref, watch, computed, onMounted, onBeforeUnmount, nextTick, type PropType } from 'vue';
import { useI18n } from 'vue-i18n';
import { Line } from 'vue-chartjs';
import { useSessionStore } from '../stores/session.store';
@@ -96,6 +96,9 @@ const networkChartKey = ref(0);
const networkRateUnitIsMB = ref(false);
const memoryUnitIsGB = ref(false);
const chartHostRef = ref<HTMLElement | null>(null);
let chartResizeObserver: ResizeObserver | null = null;
let lastChartHostWidth = 0;
// const networkChartTitle = ref('网络速度 (KB/s)'); // Will be replaced by i18n
// const memoryChartTitle = ref('内存使用情况 (MB)'); // Will be replaced by i18n
@@ -433,12 +436,49 @@ const updateAxisAndUnits = () => {
}
};
const rerenderVisibleCharts = () => {
cpuChartKey.value++;
networkChartKey.value++;
};
// --- 监听 props.serverStatus 的变化,仅用于更新 Y 轴范围和单位 ---
// 数据本身由 computed 属性从 store 获取
watch(() => props.serverStatus, () => {
updateAxisAndUnits();
}, { deep: true, immediate: true }); // immediate: true 确保初始加载时设置好轴
onMounted(() => {
const host = chartHostRef.value;
if (!host || typeof ResizeObserver === 'undefined') {
return;
}
lastChartHostWidth = Math.round(host.getBoundingClientRect().width);
chartResizeObserver = new ResizeObserver(entries => {
const entry = entries[0];
if (!entry) {
return;
}
const nextWidth = Math.round(entry.contentRect.width);
if (!nextWidth || Math.abs(nextWidth - lastChartHostWidth) < 2) {
return;
}
lastChartHostWidth = nextWidth;
nextTick(() => {
rerenderVisibleCharts();
});
});
chartResizeObserver.observe(host);
});
onBeforeUnmount(() => {
chartResizeObserver?.disconnect();
chartResizeObserver = null;
});
</script>
<style scoped>
@@ -446,10 +486,12 @@ watch(() => props.serverStatus, () => {
display: grid;
gap: 12px;
margin-top: 2px;
min-width: 0;
container-type: inline-size;
}
.chart-panel {
min-width: 0;
border-radius: 18px;
border: 1px solid rgba(148, 163, 184, 0.12);
background:
@@ -508,7 +550,15 @@ watch(() => props.serverStatus, () => {
}
.chart-wrapper {
min-width: 0;
width: 100%;
height: 164px;
overflow: hidden;
}
.chart-wrapper :deep(canvas) {
width: 100% !important;
max-width: 100% !important;
}
@container (min-width: 860px) {