From 0a28d7bb2a4de7605e8807c813d32e1e085c53bf Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 14 Mar 2026 23:23:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E5=BC=8F=E5=9C=B0=E5=9B=BE=E7=BC=A9=E6=94=BE=E5=92=8C=E5=AE=B9?= =?UTF-8?q?=E5=99=A8=E5=A4=A7=E5=B0=8F=E8=B0=83=E6=95=B4=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E6=80=A7=E8=83=BD=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/components/world-stage-map.tsx | 104 ++++++++++++++---- .../components/performance-page.tsx | 9 +- 2 files changed, 90 insertions(+), 23 deletions(-) diff --git a/atsf_server/web/features/dashboard/components/world-stage-map.tsx b/atsf_server/web/features/dashboard/components/world-stage-map.tsx index 842fdc90..dda08a02 100644 --- a/atsf_server/web/features/dashboard/components/world-stage-map.tsx +++ b/atsf_server/web/features/dashboard/components/world-stage-map.tsx @@ -7,7 +7,7 @@ import { ScatterChart } from 'echarts/charts'; import { GeoComponent, TooltipComponent } from 'echarts/components'; import { CanvasRenderer } from 'echarts/renderers'; import { useRouter } from 'next/navigation'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { EmptyState } from '@/components/feedback/empty-state'; import worldGeoJson from '@/features/dashboard/data/world-geo.json'; @@ -91,6 +91,9 @@ type CountryRegionDatum = { let worldMapRegistrationAttempted = false; let worldMapRegistrationSucceeded = false; +const baseWorldMapLayoutSizePercent = 180; +const baseWorldMapZoom = 1; + function buildNodeDetailHref(id?: number | null) { if (!id) { return '/node'; @@ -248,8 +251,10 @@ export function WorldStageMap({ nodes: DashboardNodeHealth[]; }) { const router = useRouter(); + const chartContainerRef = useRef(null); const [mapReady, setMapReady] = useState(false); const [mapFailed, setMapFailed] = useState(false); + const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }); useEffect(() => { const ready = ensureWorldMapRegistered(); @@ -257,6 +262,41 @@ export function WorldStageMap({ setMapFailed(!ready); }, []); + useEffect(() => { + const container = chartContainerRef.current; + if (!container) { + return; + } + + const updateSize = () => { + const nextWidth = container.clientWidth; + const nextHeight = container.clientHeight; + setContainerSize((previous) => + previous.width === nextWidth && previous.height === nextHeight + ? previous + : { width: nextWidth, height: nextHeight }, + ); + }; + + updateSize(); + + if (typeof ResizeObserver === 'undefined') { + window.addEventListener('resize', updateSize); + return () => { + window.removeEventListener('resize', updateSize); + }; + } + + const observer = new ResizeObserver(() => { + updateSize(); + }); + observer.observe(container); + + return () => { + observer.disconnect(); + }; + }, []); + const mapPalette = useMemo( () => isDark @@ -389,6 +429,27 @@ export function WorldStageMap({ }); }, [mapPalette, nodes]); + const responsiveMapScale = useMemo(() => { + const { width, height } = containerSize; + if (width <= 0 || height <= 0) { + return 1; + } + + const widthScale = Math.min(Math.max(width / 960, 0.76), 1.18); + const heightScale = Math.min(Math.max(height / 520, 0.82), 1.12); + + return Number(Math.min(widthScale, heightScale).toFixed(3)); + }, [containerSize]); + + const computedLayoutSize = useMemo( + () => `${Math.round(baseWorldMapLayoutSizePercent * responsiveMapScale)}%`, + [responsiveMapScale], + ); + const computedZoom = useMemo( + () => Number((baseWorldMapZoom * responsiveMapScale).toFixed(3)), + [responsiveMapScale], + ); + const mapOption = useMemo( () => ({ animation: false, @@ -431,8 +492,8 @@ export function WorldStageMap({ roam: false, silent: true, layoutCenter: ['50%', '50%'], - layoutSize: '180%', - zoom: 1, + layoutSize: computedLayoutSize, + zoom: computedZoom, regions: countryRegions, itemStyle: { areaColor: mapPalette.areaColor, @@ -453,7 +514,8 @@ export function WorldStageMap({ largeThreshold: 24, symbolSize: (value: unknown) => { const size = Array.isArray(value) && typeof value[2] === 'number' ? value[2] : 1; - return Math.max(8, Math.min(18, 8 + Math.log10(size + 1) * 3.6)); + const responsiveBase = 8 + Math.log10(size + 1) * 3.6; + return Math.max(7, Math.min(18, responsiveBase * Math.max(responsiveMapScale, 0.88))); }, label: { show: false, @@ -481,7 +543,7 @@ export function WorldStageMap({ }, ], }), - [countryRegions, isDark, mapNodes, mapPalette], + [computedLayoutSize, computedZoom, countryRegions, isDark, mapNodes, mapPalette, responsiveMapScale], ); if (!mapReady) { @@ -500,20 +562,22 @@ export function WorldStageMap({ } return ( - { - if (params.data?.route) { - router.push(params.data.route); - } - }, - }} - style={{ height: '100%', width: '100%' }} - /> +
+ { + if (params.data?.route) { + router.push(params.data.route); + } + }, + }} + style={{ height: '100%', width: '100%' }} + /> +
); } diff --git a/atsf_server/web/features/performance/components/performance-page.tsx b/atsf_server/web/features/performance/components/performance-page.tsx index b06558de..de4f37d5 100644 --- a/atsf_server/web/features/performance/components/performance-page.tsx +++ b/atsf_server/web/features/performance/components/performance-page.tsx @@ -27,6 +27,8 @@ import { const settingsQueryKey = ['settings', 'options'] as const; const previewQueryKey = ['performance', 'preview'] as const; +const templatePanelClassName = + 'h-[560px] w-full box-border overflow-auto rounded-2xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-4 py-3 font-mono text-xs leading-6'; const defaultPerformanceFields = { OpenRestyWorkerProcesses: 'auto', @@ -1266,7 +1268,7 @@ export function PerformancePage() { } > -
+
setTemplateContent(event.target.value)} - className="min-h-[560px] font-mono text-xs leading-6" + spellCheck={false} + className={`${templatePanelClassName} resize-none`} placeholder="请输入受控 nginx.conf 模板" /> @@ -1297,7 +1300,7 @@ export function PerformancePage() { />
+ className={`${templatePanelClassName} whitespace-pre-wrap break-words`}> {preview.main_config}