From 246ae122874d09335858b245097fed2f6bbaeeef Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Sun, 28 Dec 2025 10:35:34 +0800 Subject: [PATCH] 1.0.4 --- .DS_Store | Bin 14340 -> 14340 bytes backend/app/api/celestial_body.py | 3 + backend/app/api/celestial_orbit.py | 37 +++++ data/.DS_Store | Bin 6148 -> 6148 bytes frontend/PERFORMANCE_OPTIMIZATION.md | 48 ++++++ frontend/src/App.tsx | 62 ++++---- frontend/src/components/OrbitRenderer.tsx | 9 -- frontend/src/components/ProbeList.tsx | 38 ++--- frontend/src/hooks/useHistoricalData.ts | 5 - frontend/src/hooks/useSpaceData.ts | 28 ---- frontend/src/pages/admin/CelestialBodies.tsx | 145 ++++++++++++++----- 11 files changed, 249 insertions(+), 126 deletions(-) create mode 100644 frontend/PERFORMANCE_OPTIMIZATION.md diff --git a/.DS_Store b/.DS_Store index d378f882249fc17d006d8416ab82f604c7b01d36..c04387d3ccfeb3c98ceaea5490fd2aa880f9718d 100644 GIT binary patch delta 152 zcmV;J0B8S%aD;G>8v%!r95WR)00003000mG06_p#07U>a0AB!5k03Zyp(eDG34>T<}EYH6_ym Gk&qYVw zoSc-OpToFWN3oNck#X}j6?5jzhs6G~Pi8j|VQphzP~h1t$oQUlGQWu~*qQ^AZB6!U Jex$ delta 32 ocmZoMXfc@J&&a+pU^gQp`(_>{amLMd%qLkUHb`$~=lIJH0G=xesQ>@~ diff --git a/frontend/PERFORMANCE_OPTIMIZATION.md b/frontend/PERFORMANCE_OPTIMIZATION.md new file mode 100644 index 0000000..b6ccaae --- /dev/null +++ b/frontend/PERFORMANCE_OPTIMIZATION.md @@ -0,0 +1,48 @@ +# 页面加载速度优化方案 + +## 当前加载流程分析 + +1. **数据获取顺序**: + - useDataCutoffDate hook 获取截止日期 + - 等待截止日期返回 + - useSpaceData hook 获取天体位置数据 + - 渲染 3D 场景 + +2. **潜在优化点**: + +### 优化1:并行加载数据(推荐) +当前是串行加载(先获取日期,再获取位置),可以改为并行或使用默认日期。 + +### 优化2:减少不必要的日志输出 +大量 console.log 会影响性能。 + +### 优化3:延迟加载非关键组件 +- InterstellarTicker(音效) +- MessageBoard(留言板) +- BodyDetailOverlay(详情覆盖层) + +### 优化4:优化 API 响应 +后端可以: +- 减少返回字段 +- 只返回活跃天体 +- 启用 gzip 压缩 + +### 优化5:前端缓存 +- 使用 React Query 或 SWR 缓存 API 响应 +- 使用 localStorage 缓存最近的数据 + +## 实施建议 + +### 立即可做(不需要大改): +1. ✅ 删除大量 console.log +2. ✅ 使用 lazy loading 加载非关键组件 +3. ✅ 添加骨架屏提升体验 + +### 需要架构调整: +1. 重构数据获取流程(使用 React Query) +2. 实现前端缓存策略 +3. 优化后端 API 响应 + +## 当前最快优化方案 + +删除冗余日志 + 延迟加载非关键组件: diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 35dae5a..5e04649 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect } from 'react'; +import { useState, useCallback, useEffect, lazy, Suspense } from 'react'; import { useNavigate } from 'react-router-dom'; import { useSpaceData } from './hooks/useSpaceData'; import { useHistoricalData } from './hooks/useHistoricalData'; @@ -12,15 +12,17 @@ import { GalaxyScene } from './components/GalaxyScene'; import { ProbeList } from './components/ProbeList'; import { TimelineController } from './components/TimelineController'; import { Loading } from './components/Loading'; -import { InterstellarTicker } from './components/InterstellarTicker'; import { ControlPanel } from './components/ControlPanel'; import { AuthModal } from './components/AuthModal'; -import { MessageBoard } from './components/MessageBoard'; -import { BodyDetailOverlay } from './components/BodyDetailOverlay'; import { auth } from './utils/auth'; import type { CelestialBody } from './types'; import { useToast } from './contexts/ToastContext'; +// Lazy load non-critical components for better performance +const InterstellarTicker = lazy(() => import('./components/InterstellarTicker').then(m => ({ default: m.InterstellarTicker }))); +const MessageBoard = lazy(() => import('./components/MessageBoard').then(m => ({ default: m.MessageBoard }))); +const BodyDetailOverlay = lazy(() => import('./components/BodyDetailOverlay').then(m => ({ default: m.BodyDetailOverlay }))); + // Timeline configuration - will be fetched from backend later const TIMELINE_DAYS = 30; // Total days in timeline range @@ -62,7 +64,6 @@ function App() { // Use system setting if available, otherwise use localStorage, finally fallback to 'solar' const initialViewMode = systemViewMode || localViewMode || 'solar'; setViewMode(initialViewMode); - console.log('[App] Initial viewMode:', initialViewMode, '(system:', systemViewMode, ', local:', localViewMode, ')'); } }, [systemViewMode, systemViewModeLoading]); @@ -95,19 +96,6 @@ function App() { const loading = isTimelineMode ? historicalLoading : realTimeLoading; const error = isTimelineMode ? historicalError : realTimeError; - // Debug: log bodies when they change - useEffect(() => { - console.log('[App] Bodies updated:', { - isTimelineMode, - totalBodies: bodies.length, - bodiesWithPositions: bodies.filter(b => b.positions && b.positions.length > 0).length, - bodyTypes: bodies.reduce((acc, b) => { - acc[b.type] = (acc[b.type] || 0) + 1; - return acc; - }, {} as Record) - }); - }, [bodies, isTimelineMode]); - const [selectedBody, setSelectedBody] = useState(null); const { trajectoryPositions } = useTrajectory(selectedBody); @@ -226,17 +214,19 @@ function App() { /> {/* Auth Modal */} - setShowAuthModal(false)} - onLoginSuccess={handleLoginSuccess} + setShowAuthModal(false)} + onLoginSuccess={handleLoginSuccess} /> - {/* Message Board */} - setShowMessageBoard(false)} - /> + {/* Message Board - Lazy Loaded */} + + setShowMessageBoard(false)} + /> + {/* View Mode Rendering */} {viewMode === 'solar' ? ( @@ -290,14 +280,18 @@ function App() { )} - {/* Interstellar Ticker Sound (Controlled) */} - + {/* Interstellar Ticker Sound (Controlled) - Lazy Loaded */} + + + - {/* Body Detail Overlay */} - setShowDetailOverlayId(null)} - /> + {/* Body Detail Overlay - Lazy Loaded */} + + setShowDetailOverlayId(null)} + /> + ); } diff --git a/frontend/src/components/OrbitRenderer.tsx b/frontend/src/components/OrbitRenderer.tsx index 4becc26..e11228f 100644 --- a/frontend/src/components/OrbitRenderer.tsx +++ b/frontend/src/components/OrbitRenderer.tsx @@ -29,8 +29,6 @@ export function OrbitRenderer({ visible = true }: OrbitRendererProps) { useEffect(() => { const fetchOrbits = async () => { - console.log('🌌 Fetching orbital data from backend...'); - try { // Fetch precomputed orbits from backend const response = await request.get('/celestial/orbits'); @@ -43,8 +41,6 @@ export function OrbitRenderer({ visible = true }: OrbitRendererProps) { return; } - console.log(`📊 Processing ${data.orbits.length} orbits...`); - // Convert to Three.js format const orbitData: OrbitData[] = data.orbits.map((orbit: any) => { // Convert position points to Vector3 with scaling @@ -66,8 +62,6 @@ export function OrbitRenderer({ visible = true }: OrbitRendererProps) { } } - console.log(` ✅ ${orbit.body_name_zh || orbit.body_name}: ${points.length} points`); - return { bodyId: orbit.body_id, bodyName: orbit.body_name, @@ -81,7 +75,6 @@ export function OrbitRenderer({ visible = true }: OrbitRendererProps) { setOrbits(orbitData); setLoading(false); - console.log(`🎉 Loaded ${orbitData.length} orbits successfully`); } catch (err) { console.error('❌ Failed to load orbits:', err); @@ -94,7 +87,6 @@ export function OrbitRenderer({ visible = true }: OrbitRendererProps) { }, []); if (loading) { - console.log('⏳ Loading orbits...'); return null; } @@ -104,7 +96,6 @@ export function OrbitRenderer({ visible = true }: OrbitRendererProps) { } if (orbits.length === 0) { - console.warn('⚠️ No orbits to render'); return null; } diff --git a/frontend/src/components/ProbeList.tsx b/frontend/src/components/ProbeList.tsx index be09842..564373c 100644 --- a/frontend/src/components/ProbeList.tsx +++ b/frontend/src/components/ProbeList.tsx @@ -11,14 +11,18 @@ interface ProbeListProps { } export function ProbeList({ probes, planets, onBodySelect, selectedBody, onResetCamera }: ProbeListProps) { - const [isCollapsed, setIsCollapsed] = useState(false); + const [isCollapsed, setIsCollapsed] = useState(true); // 默认关闭左侧面板 const [searchTerm, setSearchTerm] = useState(''); - const [expandedGroup, setExpandedGroup] = useState('planet'); // Default expand planets + const [expandedGroup, setExpandedGroup] = useState(null); // 默认不展开任何分类 - // Auto-collapse when a body is selected (focus mode) + // Auto-expand the group when a body is selected from the 3D scene useEffect(() => { if (selectedBody) { + // Auto-collapse panel for focus mode setIsCollapsed(true); + + // Auto-expand the group that contains the selected body + setExpandedGroup(selectedBody.type); } }, [selectedBody]); @@ -202,20 +206,6 @@ export function ProbeList({ probes, planets, onBodySelect, selectedBody, onReset /> )} - {/* Probes Group */} - {groups.probe.length > 0 && ( - } - count={groups.probe.length} - bodies={groups.probe} - isExpanded={expandedGroup === 'probe'} - onToggle={() => toggleGroup('probe')} - selectedBody={selectedBody} - onBodySelect={onBodySelect} - /> - )} - {/* Comets Group */} {groups.comet.length > 0 && ( )} + {/* Probes Group */} + {groups.probe.length > 0 && ( + } + count={groups.probe.length} + bodies={groups.probe} + isExpanded={expandedGroup === 'probe'} + onToggle={() => toggleGroup('probe')} + selectedBody={selectedBody} + onBodySelect={onBodySelect} + /> + )} + {/* No results message */} {allBodies.length === 0 && (
diff --git a/frontend/src/hooks/useHistoricalData.ts b/frontend/src/hooks/useHistoricalData.ts index 5cbda2a..a254571 100644 --- a/frontend/src/hooks/useHistoricalData.ts +++ b/frontend/src/hooks/useHistoricalData.ts @@ -36,8 +36,6 @@ export function useHistoricalData(selectedDate: Date | null) { setLoading(true); setError(null); - console.log(`[useHistoricalData] Fetching data for ${dateKey}`); - // Set start and end to the same time to get a single snapshot const data = await fetchCelestialPositions( targetDate.toISOString(), @@ -49,10 +47,7 @@ export function useHistoricalData(selectedDate: Date | null) { if (isActive) { setBodies(data.bodies); lastFetchedDateRef.current = dateKey; // 记录已请求的时间 - console.log(`[useHistoricalData] Loaded ${data.bodies.length} bodies`); setLoading(false); - } else { - console.log(`[useHistoricalData] Ignored stale data for ${dateKey}`); } } catch (err) { if (isActive) { diff --git a/frontend/src/hooks/useSpaceData.ts b/frontend/src/hooks/useSpaceData.ts index ba06a9d..f186b78 100644 --- a/frontend/src/hooks/useSpaceData.ts +++ b/frontend/src/hooks/useSpaceData.ts @@ -30,41 +30,13 @@ export function useSpaceData() { const targetDate = new Date(cutoffDate!); targetDate.setUTCHours(0, 0, 0, 0); - console.log('[useSpaceData] Loading data for date:', targetDate.toISOString()); - const data = await fetchCelestialPositions( targetDate.toISOString(), targetDate.toISOString(), // Same as start - single point in time '1d' // Use 1d step for consistency ); - console.log('[useSpaceData] API response:', { - totalBodies: data.bodies.length, - bodiesWithPositions: data.bodies.filter(b => b.positions && b.positions.length > 0).length, - sample: data.bodies.slice(0, 2).map(b => ({ - id: b.id, - name: b.name, - type: b.type, - hasPositions: b.positions && b.positions.length > 0, - positionCount: b.positions?.length || 0, - firstPosition: b.positions?.[0] - })) - }); - - // Check if positions have the expected structure - const firstBody = data.bodies[0]; - if (firstBody && firstBody.positions && firstBody.positions.length > 0) { - console.log('[useSpaceData] First body position structure:', { - body: firstBody.name, - position: firstBody.positions[0], - hasX: 'x' in firstBody.positions[0], - hasY: 'y' in firstBody.positions[0], - hasZ: 'z' in firstBody.positions[0] - }); - } - setBodies(data.bodies); - console.log('[useSpaceData] State updated with', data.bodies.length, 'bodies'); } catch (err) { console.error('Failed to fetch celestial data:', err); setError(err instanceof Error ? err.message : 'Unknown error'); diff --git a/frontend/src/pages/admin/CelestialBodies.tsx b/frontend/src/pages/admin/CelestialBodies.tsx index 6a00ee7..d7e141a 100644 --- a/frontend/src/pages/admin/CelestialBodies.tsx +++ b/frontend/src/pages/admin/CelestialBodies.tsx @@ -17,6 +17,7 @@ interface CelestialBody { id: string; name: string; name_zh: string; + short_name?: string; // NASA SBDB API short name type: string; system_id?: number; description: string; @@ -36,6 +37,10 @@ interface CelestialBody { }>; }; has_resources?: boolean; + orbit_info?: { + num_points: number; + period_days?: number; + }; } interface StarSystem { @@ -208,7 +213,7 @@ export function CelestialBodies() { }; // Edit handler - const handleEdit = (record: CelestialBody) => { + const handleEdit = async (record: CelestialBody) => { setEditingRecord(record); // Parse extra_data if it's a string (from backend JSON field) @@ -222,10 +227,27 @@ export function CelestialBodies() { } } - // Properly set form values including nested extra_data + // Fetch orbit information if exists + let orbitInfo = null; + try { + const { data: orbitData } = await request.get(`/celestial/orbits/${record.id}`); + if (orbitData) { + orbitInfo = { + num_points: orbitData.num_points, + period_days: orbitData.period_days + }; + console.log('Loaded orbit info for', record.id, orbitInfo); + } + } catch (e) { + // Orbit not found or error - this is fine, not all bodies have orbits + console.log('No orbit data for', record.id); + } + + // Properly set form values including nested extra_data and orbit info const formValues = { ...record, extra_data: extraData || {}, // Ensure extra_data is an object + orbit_info: orbitInfo }; form.setFieldsValue(formValues); @@ -596,6 +618,18 @@ export function CelestialBodies() { + + + + + + + + - {/* Orbit parameters for planets and dwarf planets */} - prevValues.type !== currentValues.type}> + {/* Orbit parameters and info for planets and dwarf planets */} + + prevValues.type !== currentValues.type || prevValues.orbit_info !== currentValues.orbit_info + }> {({ getFieldValue }) => { const bodyType = getFieldValue('type'); + const orbitInfo = getFieldValue('orbit_info'); + if (!['planet', 'dwarf_planet'].includes(bodyType)) { return null; } return ( - - - - - - - - - - - +
+ {/* Editable orbit parameters */} + + + + + + + + + + + + + + {/* Display orbit info if exists (read-only) */} + {editingRecord && orbitInfo && orbitInfo.num_points && ( +
+
+ 已生成的轨道数据: +
+
+
+ 轨道点数: {orbitInfo.num_points?.toLocaleString()} 个点 +
+ {orbitInfo.period_days && ( +
+ 实际周期: {orbitInfo.period_days.toFixed(2)} 天 +
+ )} +
+
+ )} +
} type="info" style={{ marginBottom: 16 }} @@ -769,6 +836,18 @@ export function CelestialBodies() { + + + + + + + +