| | |
| | | loading.value = false; |
| | | await nextTick(); |
| | | if (!disposed && sequence === requestSequence) { |
| | | layoutFill(); |
| | | resizeAllCharts(); |
| | | if (!background) { |
| | | const intervals = charts.value.map((chart) => chart.refreshInterval).filter((interval) => interval > 0); |
| | |
| | | const dashboardRef = ref<HTMLElement>(); |
| | | let resizeObserver: ResizeObserver | null = null; |
| | | |
| | | /** |
| | | * 精确计算图表区可用高度,写入 CSS 变量 --fill-h |
| | | * |
| | | * 仅靠 flex + 1fr 在部分场景不生效,这里直接量测头部、KPI 行、 |
| | | * 表格卡片占用高度,把剩余高度算出来给图表区。 |
| | | */ |
| | | function layoutFill() { |
| | | const el = dashboardRef.value; |
| | | if (!el) return; |
| | | if (!isFullscreen.value) { |
| | | el.style.removeProperty('--fill-h'); |
| | | return; |
| | | } |
| | | const header = el.querySelector('.dash-header') as HTMLElement | null; |
| | | const kpi = el.querySelector('.kpi-row') as HTMLElement | null; |
| | | const tables = el.querySelectorAll<HTMLElement>('.table-card'); |
| | | const cs = getComputedStyle(el); |
| | | let used = (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0); |
| | | if (header) used += header.offsetHeight + 24; |
| | | if (kpi) used += kpi.offsetHeight + 16; |
| | | tables.forEach((t) => { used += t.offsetHeight + 16; }); |
| | | const avail = Math.max(240, window.innerHeight - used); |
| | | el.style.setProperty('--fill-h', `${avail}px`); |
| | | } |
| | | |
| | | function resizeAllCharts() { |
| | | setTimeout(() => window.dispatchEvent(new Event('resize')), 100); |
| | | } |
| | |
| | | await document.exitFullscreen(); |
| | | isFullscreen.value = false; |
| | | } |
| | | setTimeout(resizeAllCharts, 400); |
| | | setTimeout(() => { layoutFill(); resizeAllCharts(); }, 400); |
| | | } |
| | | function onFullscreenChange() { |
| | | isFullscreen.value = !!document.fullscreenElement; |
| | | setTimeout(resizeAllCharts, 300); |
| | | setTimeout(() => { layoutFill(); resizeAllCharts(); }, 300); |
| | | } |
| | | |
| | | // ======== KPI 数字动画 ======== |
| | |
| | | onMounted(() => { |
| | | document.addEventListener('fullscreenchange', onFullscreenChange); |
| | | setupResizeObserver(); |
| | | window.addEventListener('resize', layoutFill); |
| | | loadData(); |
| | | }); |
| | | onBeforeUnmount(() => { |
| | |
| | | requestSequence++; |
| | | if (dataRefreshTimer) clearInterval(dataRefreshTimer); |
| | | document.removeEventListener('fullscreenchange', onFullscreenChange); |
| | | window.removeEventListener('resize', layoutFill); |
| | | resizeObserver?.disconnect(); |
| | | }); |
| | | </script> |
| | |
| | | |
| | | /* 图表区吃掉剩余高度,行高均分,避免底部大片留白 */ |
| | | .is-fullscreen > .chart-row { |
| | | flex: 1 0 auto; |
| | | grid-auto-rows: minmax(240px, 1fr); |
| | | flex: 0 0 auto; |
| | | /* 高度由 layoutFill() 量测后写入,保证正好填满视口 */ |
| | | height: var(--fill-h, auto); |
| | | grid-auto-rows: minmax(200px, 1fr); |
| | | align-content: stretch; |
| | | } |
| | | |