fix(index): improve mobile dashboard layout
- Move System History action from the 3X-UI card into the System Load card's #extra slot so the chart opener sits next to live load values. - Fix card widths on mobile by switching :sm="24" to :xs="24"; the sm breakpoint only kicks in at >=576px, so phones in portrait had no span set and cards shrank to content width. - Restore vertical spacing between cards (vertical gutter was 0 on mobile) and reduce content padding on small screens, reserving 64px top so the sidebar drawer handle no longer overlaps the StatusCard. - Wrap the 3X-UI link tags in a flex container so version/Telegram/docs chips wrap with consistent spacing on narrow widths. - Make Sparkline's viewBox track its actual rendered pixel width via ResizeObserver so X-axis time labels stop being squashed horizontally by preserveAspectRatio="none" on narrow containers. - Make the SystemHistory modal width responsive (95vw on mobile, was a fixed 900px that overflowed phone viewports). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Array, required: true },
|
||||
@@ -36,8 +36,37 @@ const props = defineProps({
|
||||
|
||||
const hoverIdx = ref(-1);
|
||||
|
||||
const viewBoxAttr = computed(() => `0 0 ${props.vbWidth} ${props.height}`);
|
||||
const drawWidth = computed(() => Math.max(1, props.vbWidth - props.paddingLeft - props.paddingRight));
|
||||
// Measured CSS width of the SVG. Drives the viewBox so SVG units stay
|
||||
// 1:1 with rendered pixels — otherwise `preserveAspectRatio="none"`
|
||||
// stretches the X axis and squashes axis text horizontally on narrow
|
||||
// containers (mobile). Falls back to the prop until the first measure.
|
||||
const svgRef = ref(null);
|
||||
const measuredWidth = ref(0);
|
||||
const effectiveVbWidth = computed(() => measuredWidth.value > 0 ? measuredWidth.value : props.vbWidth);
|
||||
|
||||
let resizeObserver = null;
|
||||
function measure() {
|
||||
const el = svgRef.value;
|
||||
if (!el) return;
|
||||
const w = el.getBoundingClientRect?.().width || 0;
|
||||
if (w > 0) measuredWidth.value = Math.round(w);
|
||||
}
|
||||
onMounted(() => {
|
||||
measure();
|
||||
if (typeof ResizeObserver !== 'undefined' && svgRef.value) {
|
||||
resizeObserver = new ResizeObserver(measure);
|
||||
resizeObserver.observe(svgRef.value);
|
||||
} else {
|
||||
window.addEventListener('resize', measure);
|
||||
}
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
if (resizeObserver) resizeObserver.disconnect();
|
||||
else window.removeEventListener('resize', measure);
|
||||
});
|
||||
|
||||
const viewBoxAttr = computed(() => `0 0 ${effectiveVbWidth.value} ${props.height}`);
|
||||
const drawWidth = computed(() => Math.max(1, effectiveVbWidth.value - props.paddingLeft - props.paddingRight));
|
||||
const drawHeight = computed(() => Math.max(1, props.height - props.paddingTop - props.paddingBottom));
|
||||
const nPoints = computed(() => Math.min(props.data.length, props.maxPoints));
|
||||
|
||||
@@ -164,7 +193,7 @@ function onMouseMove(evt) {
|
||||
if (!props.showTooltip || pointsArr.value.length === 0) return;
|
||||
const rect = evt.currentTarget.getBoundingClientRect();
|
||||
const px = evt.clientX - rect.left;
|
||||
const x = (px / rect.width) * props.vbWidth;
|
||||
const x = (px / rect.width) * effectiveVbWidth.value;
|
||||
const n = nPoints.value;
|
||||
const dx = n > 1 ? drawWidth.value / (n - 1) : 0;
|
||||
const idx = Math.max(0, Math.min(n - 1, Math.round((x - props.paddingLeft) / (dx || 1))));
|
||||
@@ -192,6 +221,7 @@ const gradId = `spkGrad-${Math.random().toString(36).slice(2, 9)}`;
|
||||
|
||||
<template>
|
||||
<svg
|
||||
ref="svgRef"
|
||||
width="100%"
|
||||
:height="height"
|
||||
:viewBox="viewBoxAttr"
|
||||
|
||||
Reference in New Issue
Block a user