From e60d8c06582b4cebaa4e26161273571a9be02b56 Mon Sep 17 00:00:00 2001 From: qixinbo Date: Sun, 15 Mar 2026 18:04:23 +0800 Subject: [PATCH] fix: dashboard polish --- frontend/src/components/VegaChart.tsx | 35 +++++++++++++++++++++------ frontend/src/pages/Dashboard.tsx | 16 ++++++++---- frontend/src/store/dashboardStore.ts | 6 ++--- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/VegaChart.tsx b/frontend/src/components/VegaChart.tsx index 4826e1b..73196fd 100644 --- a/frontend/src/components/VegaChart.tsx +++ b/frontend/src/components/VegaChart.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { VegaEmbed } from 'react-vega'; import type { ChartSpec } from '@/store/visualizationStore'; @@ -8,17 +8,38 @@ interface VegaChartProps { } export const VegaChart: React.FC = ({ data, spec }) => { - const vegaSpec: any = { + const containerRef = useRef(null); + const [size, setSize] = useState({ width: 0, height: 0 }); + + useEffect(() => { + const node = containerRef.current; + if (!node) return; + const observer = new ResizeObserver((entries) => { + const entry = entries[0]; + if (!entry) return; + const nextWidth = Math.max(0, Math.floor(entry.contentRect.width)); + const nextHeight = Math.max(0, Math.floor(entry.contentRect.height)); + setSize((prev) => ( + prev.width === nextWidth && prev.height === nextHeight + ? prev + : { width: nextWidth, height: nextHeight } + )); + }); + observer.observe(node); + return () => observer.disconnect(); + }, []); + + const vegaSpec: any = useMemo(() => ({ $schema: typeof spec.$schema === 'string' ? spec.$schema : 'https://vega.github.io/schema/vega-lite/v5.json', ...spec, - width: "container", - height: "container", + width: size.width > 0 ? size.width : "container", + height: size.height > 0 ? size.height : "container", data: { values: data }, - autosize: { type: "fit", contains: "padding" }, - }; + autosize: { type: "fit", contains: "padding", resize: true }, + }), [data, size.height, size.width, spec]); return ( -
+
[]) { } export function Dashboard() { - const { charts, removeChart } = useDashboardStore(); + const { charts, removeChart, updateLayout } = useDashboardStore(); const ResponsiveGridLayout = useMemo( () => WidthProvider(Responsive as any) as any, [] @@ -49,10 +49,16 @@ export function Dashboard() { lg: charts.map((c) => c.layout) }), [charts]); - const onLayoutChange = (_currentLayout: any, _allLayouts: any) => { - // updateLayout(currentLayout); // This might cause infinite loops if not handled carefully - // For simplicity, we just log it or update it if needed. - // In a real app, we would debounce this and save to backend. + const onLayoutChange = (currentLayout: any[]) => { + updateLayout( + currentLayout.map((item) => ({ + i: item.i, + x: item.x, + y: item.y, + w: item.w, + h: item.h, + })) + ); }; if (charts.length === 0) { diff --git a/frontend/src/store/dashboardStore.ts b/frontend/src/store/dashboardStore.ts index 67cacf4..06901fe 100644 --- a/frontend/src/store/dashboardStore.ts +++ b/frontend/src/store/dashboardStore.ts @@ -26,10 +26,10 @@ export const useDashboardStore = create((set) => ({ addChart: (chart) => set((state) => { const newLayout: GridLayout = { i: chart.id, - x: (state.charts.length * 6) % 12, + x: (state.charts.length * 4) % 12, y: Infinity, - w: 6, - h: 8, + w: 4, + h: 4, }; return { charts: [...state.charts, { ...chart, layout: newLayout }] }; }),