diff --git a/frontend/src/components/AuthModal.tsx b/frontend/src/components/AuthModal.tsx index c72649a..584c203 100644 --- a/frontend/src/components/AuthModal.tsx +++ b/frontend/src/components/AuthModal.tsx @@ -124,7 +124,7 @@ export function AuthModal({ isOpen, onClose, onLoginSuccess }: AuthModalProps) { value={formData.username} onChange={handleChange} placeholder="Username" - className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-4 py-2.5 text-white focus:outline-none focus:border-blue-500 transition-colors" + className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-4 py-2.5 text-white focus:outline-none focus:border-[#238636] transition-colors" /> @@ -141,7 +141,7 @@ export function AuthModal({ isOpen, onClose, onLoginSuccess }: AuthModalProps) { value={formData.email} onChange={handleChange} placeholder="Email" - className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-4 py-2.5 text-white focus:outline-none focus:border-blue-500 transition-colors" + className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-4 py-2.5 text-white focus:outline-none focus:border-[#238636] transition-colors" /> @@ -155,7 +155,7 @@ export function AuthModal({ isOpen, onClose, onLoginSuccess }: AuthModalProps) { value={formData.fullName} onChange={handleChange} placeholder="Full Name" - className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-4 py-2.5 text-white focus:outline-none focus:border-blue-500 transition-colors" + className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-4 py-2.5 text-white focus:outline-none focus:border-[#238636] transition-colors" /> @@ -173,7 +173,7 @@ export function AuthModal({ isOpen, onClose, onLoginSuccess }: AuthModalProps) { value={formData.password} onChange={handleChange} placeholder="Password" - className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-10 py-2.5 text-white focus:outline-none focus:border-blue-500 transition-colors" + className="w-full bg-black/50 border border-gray-700 rounded-lg pl-10 pr-10 py-2.5 text-white focus:outline-none focus:border-[#238636] transition-colors" /> @@ -200,7 +200,7 @@ export function AuthModal({ isOpen, onClose, onLoginSuccess }: AuthModalProps) { {isLogin ? '还没有账号?' : '已有账号?'} diff --git a/frontend/src/components/CelestialBody.tsx b/frontend/src/components/CelestialBody.tsx index 3f20052..58a196a 100644 --- a/frontend/src/components/CelestialBody.tsx +++ b/frontend/src/components/CelestialBody.tsx @@ -112,7 +112,9 @@ function Planet({ body, size, emissive, emissiveIntensity, allBodies, isSelected } }) .catch((err) => { - console.error(`Failed to load texture for ${body.name}:`, err); + if (import.meta.env.DEV) { + console.error(`Failed to load texture for ${body.name}:`, err); + } setTexturePath(null); }); }, [body.id, body.name]); diff --git a/frontend/src/components/ControlPanel.tsx b/frontend/src/components/ControlPanel.tsx index cde8ce1..8995f51 100644 --- a/frontend/src/components/ControlPanel.tsx +++ b/frontend/src/components/ControlPanel.tsx @@ -34,8 +34,8 @@ export function ControlPanel({ }: ControlPanelProps) { const buttonClass = (isActive: boolean) => ` p-2 rounded-lg transition-all duration-200 relative group - ${isActive - ? 'bg-blue-600 text-white shadow-lg shadow-blue-500/50' + ${isActive + ? 'bg-[#238636] text-white shadow-lg shadow-[#238636]/50' : 'bg-white/10 text-gray-300 hover:bg-white/20 border border-white/5' } `; diff --git a/frontend/src/components/FocusInfo.tsx b/frontend/src/components/FocusInfo.tsx index 74e5634..a97f593 100644 --- a/frontend/src/components/FocusInfo.tsx +++ b/frontend/src/components/FocusInfo.tsx @@ -77,9 +77,9 @@ export function FocusInfo({ body, onClose, toast }: FocusInfoProps) { {body.name_zh || body.name} {isProbe ? '探测器' : '天体'} @@ -94,7 +94,7 @@ export function FocusInfo({ body, onClose, toast }: FocusInfoProps) {
{/* Column 1: Heliocentric Distance Card */}
-
+
diff --git a/frontend/src/components/Loading.tsx b/frontend/src/components/Loading.tsx index 8c10524..3f42207 100644 --- a/frontend/src/components/Loading.tsx +++ b/frontend/src/components/Loading.tsx @@ -1,15 +1,111 @@ /** - * Loading component + * Loading component with green theme */ -export function Loading() { +import { useEffect, useState } from 'react'; + +interface LoadingProps { + message?: string; + showProgress?: boolean; +} + +export function Loading({ message = "正在加载深空探索器...", showProgress = true }: LoadingProps) { + const [dots, setDots] = useState(''); + const [progress, setProgress] = useState(0); + + // Animate dots + useEffect(() => { + const interval = setInterval(() => { + setDots(prev => prev.length >= 3 ? '' : prev + '.'); + }, 500); + return () => clearInterval(interval); + }, []); + + // Simulate progress + useEffect(() => { + if (!showProgress) return; + + const interval = setInterval(() => { + setProgress(prev => { + if (prev >= 90) return prev; // Stop at 90% until real data loads + return prev + Math.random() * 10; + }); + }, 300); + + return () => clearInterval(interval); + }, [showProgress]); + return ( -
-
-
-
-
-

Loading celestial data from NASA JPL Horizons...

+
+ {/* Starfield background effect */} +
+ {[...Array(50)].map((_, i) => ( +
+ ))}
+ + {/* Loading content */} +
+ {/* Spinning loader with green theme */} +
+
+ {/* Outer ring */} +
+ {/* Spinning ring */} +
+ {/* Inner glow */} +
+ + {/* Center icon */} +
+ + + +
+
+
+ + {/* Loading text with green theme */} +
+ {message}{dots} +
+ + {/* Subtitle */} +
+ 加载天体模型与轨道数据 +
+ + {/* Progress bar */} + {showProgress && ( +
+
+
+
+
+ {Math.round(progress)}% +
+
+ )} +
+ + {/* Add twinkle animation */} +
); } diff --git a/frontend/src/components/Probe.tsx b/frontend/src/components/Probe.tsx index b241d38..562d514 100644 --- a/frontend/src/components/Probe.tsx +++ b/frontend/src/components/Probe.tsx @@ -227,12 +227,10 @@ export function Probe({ body, allBodies, isSelected = false }: ProbeProps) { // Fetch model from backend API useEffect(() => { - console.log(`[Probe ${body.name}] Fetching resources...`); setLoadError(false); // Reset error state fetchBodyResources(body.id, 'model') .then((response) => { - console.log(`[Probe ${body.name}] Resources response:`, response); if (response.resources.length > 0) { // Get the first model resource const modelResource = response.resources[0]; @@ -240,47 +238,40 @@ export function Probe({ body, allBodies, isSelected = false }: ProbeProps) { // Get scale from extra_data const scale = modelResource.extra_data?.scale || 1.0; setResourceScale(scale); - console.log(`[Probe ${body.name}] Scale from resource:`, scale); // Construct path for Nginx proxy // Database stores relative path like "texture/2k_mercury.jpg" or "model/webb.glb" // We need to add /upload/ prefix for Nginx to proxy to backend const fullPath = `/upload/${modelResource.file_path}`; - console.log(`[Probe ${body.name}] Model path:`, fullPath); // Preload the model before setting the path useGLTF.preload(fullPath); - console.log(`[Probe ${body.name}] Model preloaded`); setModelPath(fullPath); } else { - console.log(`[Probe ${body.name}] No resources found, using fallback`); setModelPath(null); } }) .catch((err) => { - console.error(`[Probe ${body.name}] Failed to load model:`, err); + if (import.meta.env.DEV) { + console.error(`[Probe ${body.name}] Failed to load model:`, err); + } setLoadError(true); setModelPath(null); }); }, [body.id, body.name]); if (!position) { - console.log(`[Probe ${body.name}] No position data`); return null; } if (modelPath === undefined) { - console.log(`[Probe ${body.name}] Waiting for model path...`); return null; // Wait for model to load } - console.log(`[Probe ${body.name}] Rendering with modelPath:`, modelPath, 'loadError:', loadError); - // Use model if available and no load error, otherwise use fallback if (modelPath && !loadError) { return { - console.error(`[Probe ${body.name}] ProbeModel rendering failed, switching to fallback`); setLoadError(true); }} />; } diff --git a/frontend/src/components/ProbeList.tsx b/frontend/src/components/ProbeList.tsx index 0001b5f..3ae76cd 100644 --- a/frontend/src/components/ProbeList.tsx +++ b/frontend/src/components/ProbeList.tsx @@ -85,7 +85,7 @@ export function ProbeList({ probes, planets, onBodySelect, selectedBody, onReset placeholder="搜索天体..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} - className="w-full bg-white/5 border border-white/10 rounded-lg pl-9 pr-3 py-2 text-xs text-white placeholder-gray-500 focus:outline-none focus:border-blue-500/50 transition-colors" + className="w-full bg-white/5 border border-white/10 rounded-lg pl-9 pr-3 py-2 text-xs text-white placeholder-gray-500 focus:outline-none focus:border-[#238636]/50 transition-colors" />
@@ -178,7 +178,7 @@ export function ProbeList({ probes, planets, onBodySelect, selectedBody, onReset absolute top-0 ${isCollapsed ? 'left-0' : '-right-4'} w-8 h-8 flex items-center justify-center bg-black/80 backdrop-blur-md border border-white/10 rounded-full - text-white hover:bg-blue-600 transition-all shadow-lg z-50 + text-white hover:bg-[#238636] transition-all shadow-lg z-50 ${!isCollapsed && 'translate-x-1/2'} `} style={{ top: '20px' }} @@ -256,16 +256,16 @@ function BodyItem({ body, distance, isSelected, onClick }: { disabled={isInactive} className={` w-full flex items-center justify-between p-2 rounded-lg text-left transition-all duration-200 group - ${isSelected - ? 'bg-blue-600/20 border border-blue-500/50 shadow-[0_0_15px_rgba(37,99,235,0.2)]' - : isInactive - ? 'opacity-40 cursor-not-allowed' + ${isSelected + ? 'bg-[#238636]/20 border border-[#238636]/50 shadow-[0_0_15px_rgba(35,134,54,0.2)]' + : isInactive + ? 'opacity-40 cursor-not-allowed' : 'hover:bg-white/10 border border-transparent' } `} >
-
{/* text-sm -> text-xs */} +
{/* text-sm -> text-xs */} {body.name_zh || body.name}
{/* text-[10px] -> text-[9px] */} @@ -274,7 +274,7 @@ function BodyItem({ body, distance, isSelected, onClick }: {
{isSelected && ( -
+
)} ); diff --git a/frontend/src/components/TimelineController.tsx b/frontend/src/components/TimelineController.tsx index f6363bf..df30cde 100644 --- a/frontend/src/components/TimelineController.tsx +++ b/frontend/src/components/TimelineController.tsx @@ -183,9 +183,9 @@ export function TimelineController({ onTimeChange, minDate, maxDate }: TimelineC onClick={handlePlayPause} className={` p-3 rounded-full text-white shadow-lg transition-all transform hover:scale-105 active:scale-95 - ${isPlaying - ? 'bg-amber-500 hover:bg-amber-600 shadow-amber-500/30' - : 'bg-blue-600 hover:bg-blue-700 shadow-blue-600/30' + ${isPlaying + ? 'bg-amber-500 hover:bg-amber-600 shadow-amber-500/30' + : 'bg-[#238636] hover:bg-[#2ea043] shadow-[#238636]/30' } `} title={isPlaying ? "暂停" : "播放"} diff --git a/frontend/src/utils/api.ts b/frontend/src/utils/api.ts index 0112a20..a3b8368 100644 --- a/frontend/src/utils/api.ts +++ b/frontend/src/utils/api.ts @@ -8,61 +8,56 @@ import { auth } from './auth'; // Dynamically determine the API base URL const getApiBaseUrl = () => { if (import.meta.env.VITE_API_BASE_URL) { - console.log('[API] Using VITE_API_BASE_URL:', import.meta.env.VITE_API_BASE_URL); return import.meta.env.VITE_API_BASE_URL; } // In production, use relative path /api (proxied by Nginx) // This works for both internal IP and external domain access if (import.meta.env.PROD) { - console.log('[API] Using production relative path: /api'); return '/api'; } // In development, proxy is configured in vite.config.ts - console.log('[API] Using development relative path: /api'); return '/api'; }; const API_BASE_URL = getApiBaseUrl(); -console.log('[API] Final API_BASE_URL:', API_BASE_URL); export const api = axios.create({ baseURL: API_BASE_URL, timeout: 120000, // Increase timeout to 120 seconds for historical data queries }); -// Add request interceptor for debugging and auth +// Add request interceptor for auth api.interceptors.request.use( (config) => { - console.log('[API Request]', config.method?.toUpperCase(), config.url, config.params); - // Add token if available const token = auth.getToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } - + return config; }, (error) => { - console.error('[API Request Error]', error); return Promise.reject(error); } ); -// Add response interceptor for debugging +// Add response interceptor for error handling api.interceptors.response.use( (response) => { - console.log('[API Response]', response.config.url, response.status, 'Data:', response.data); return response; }, (error) => { - console.error('[API Error]', error.config?.url, error.message); - if (error.response) { - console.error('[API Error Response]', error.response.status, error.response.data); - } else if (error.request) { - console.error('[API Error Request]', error.request); + // Only log errors in development + if (import.meta.env.DEV) { + console.error('[API Error]', error.config?.url, error.message); + if (error.response) { + console.error('[API Error Response]', error.response.status, error.response.data); + } else if (error.request) { + console.error('[API Error Request]', error.request); + } } return Promise.reject(error); }