1.0.4
parent
72d1fd5ef2
commit
246ae12287
|
|
@ -23,6 +23,7 @@ class CelestialBodyCreate(BaseModel):
|
|||
id: str
|
||||
name: str
|
||||
name_zh: Optional[str] = None
|
||||
short_name: Optional[str] = None
|
||||
type: str
|
||||
system_id: Optional[int] = None
|
||||
description: Optional[str] = None
|
||||
|
|
@ -34,6 +35,7 @@ class CelestialBodyCreate(BaseModel):
|
|||
class CelestialBodyUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
name_zh: Optional[str] = None
|
||||
short_name: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
system_id: Optional[int] = None
|
||||
description: Optional[str] = None
|
||||
|
|
@ -224,6 +226,7 @@ async def list_bodies(
|
|||
"id": body.id,
|
||||
"name": body.name,
|
||||
"name_zh": body.name_zh,
|
||||
"short_name": body.short_name,
|
||||
"type": body.type,
|
||||
"system_id": body.system_id, # Add system_id field
|
||||
"description": body.description,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,43 @@ async def get_orbits(
|
|||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/orbits/{body_id}")
|
||||
async def get_orbit_by_id(
|
||||
body_id: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Get orbit data for a specific celestial body
|
||||
|
||||
Path parameters:
|
||||
- body_id: Celestial body ID
|
||||
|
||||
Returns:
|
||||
- Orbit data with points, num_points, period_days, and color
|
||||
"""
|
||||
logger.info(f"Fetching orbit for body {body_id}")
|
||||
|
||||
try:
|
||||
orbit = await orbit_service.get_orbit(body_id, db)
|
||||
|
||||
if not orbit:
|
||||
raise HTTPException(status_code=404, detail=f"Orbit not found for body {body_id}")
|
||||
|
||||
return {
|
||||
"body_id": orbit.body_id,
|
||||
"num_points": orbit.num_points,
|
||||
"period_days": orbit.period_days,
|
||||
"color": orbit.color,
|
||||
"updated_at": orbit.updated_at.isoformat() if orbit.updated_at else None
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch orbit for {body_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/admin/orbits/generate")
|
||||
async def generate_orbits(
|
||||
background_tasks: BackgroundTasks,
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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 响应
|
||||
|
||||
## 当前最快优化方案
|
||||
|
||||
删除冗余日志 + 延迟加载非关键组件:
|
||||
|
|
@ -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<string, number>)
|
||||
});
|
||||
}, [bodies, isTimelineMode]);
|
||||
|
||||
const [selectedBody, setSelectedBody] = useState<CelestialBody | null>(null);
|
||||
const { trajectoryPositions } = useTrajectory(selectedBody);
|
||||
|
||||
|
|
@ -226,17 +214,19 @@ function App() {
|
|||
/>
|
||||
|
||||
{/* Auth Modal */}
|
||||
<AuthModal
|
||||
isOpen={showAuthModal}
|
||||
onClose={() => setShowAuthModal(false)}
|
||||
onLoginSuccess={handleLoginSuccess}
|
||||
<AuthModal
|
||||
isOpen={showAuthModal}
|
||||
onClose={() => setShowAuthModal(false)}
|
||||
onLoginSuccess={handleLoginSuccess}
|
||||
/>
|
||||
|
||||
{/* Message Board */}
|
||||
<MessageBoard
|
||||
open={showMessageBoard}
|
||||
onClose={() => setShowMessageBoard(false)}
|
||||
/>
|
||||
{/* Message Board - Lazy Loaded */}
|
||||
<Suspense fallback={null}>
|
||||
<MessageBoard
|
||||
open={showMessageBoard}
|
||||
onClose={() => setShowMessageBoard(false)}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* View Mode Rendering */}
|
||||
{viewMode === 'solar' ? (
|
||||
|
|
@ -290,14 +280,18 @@ function App() {
|
|||
<GalaxyScene />
|
||||
)}
|
||||
|
||||
{/* Interstellar Ticker Sound (Controlled) */}
|
||||
<InterstellarTicker isPlaying={isSoundOn} />
|
||||
{/* Interstellar Ticker Sound (Controlled) - Lazy Loaded */}
|
||||
<Suspense fallback={null}>
|
||||
<InterstellarTicker isPlaying={isSoundOn} />
|
||||
</Suspense>
|
||||
|
||||
{/* Body Detail Overlay */}
|
||||
<BodyDetailOverlay
|
||||
bodyId={showDetailOverlayId}
|
||||
onClose={() => setShowDetailOverlayId(null)}
|
||||
/>
|
||||
{/* Body Detail Overlay - Lazy Loaded */}
|
||||
<Suspense fallback={null}>
|
||||
<BodyDetailOverlay
|
||||
bodyId={showDetailOverlayId}
|
||||
onClose={() => setShowDetailOverlayId(null)}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<string | null>('planet'); // Default expand planets
|
||||
const [expandedGroup, setExpandedGroup] = useState<string | null>(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 && (
|
||||
<BodyGroup
|
||||
title="探测器"
|
||||
icon={<Rocket size={14} />}
|
||||
count={groups.probe.length}
|
||||
bodies={groups.probe}
|
||||
isExpanded={expandedGroup === 'probe'}
|
||||
onToggle={() => toggleGroup('probe')}
|
||||
selectedBody={selectedBody}
|
||||
onBodySelect={onBodySelect}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Comets Group */}
|
||||
{groups.comet.length > 0 && (
|
||||
<BodyGroup
|
||||
|
|
@ -230,6 +220,20 @@ export function ProbeList({ probes, planets, onBodySelect, selectedBody, onReset
|
|||
/>
|
||||
)}
|
||||
|
||||
{/* Probes Group */}
|
||||
{groups.probe.length > 0 && (
|
||||
<BodyGroup
|
||||
title="探测器"
|
||||
icon={<Rocket size={14} />}
|
||||
count={groups.probe.length}
|
||||
bodies={groups.probe}
|
||||
isExpanded={expandedGroup === 'probe'}
|
||||
onToggle={() => toggleGroup('probe')}
|
||||
selectedBody={selectedBody}
|
||||
onBodySelect={onBodySelect}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* No results message */}
|
||||
{allBodies.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-500 text-xs">
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="short_name"
|
||||
label="简称"
|
||||
tooltip="NASA SBDB API使用的短名称(如Jupiter的简称是Juptr)"
|
||||
>
|
||||
<Input placeholder="例如:Juptr" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item
|
||||
name="description"
|
||||
label="描述"
|
||||
|
|
@ -603,46 +637,79 @@ export function CelestialBodies() {
|
|||
<Input.TextArea rows={2} />
|
||||
</Form.Item>
|
||||
|
||||
{/* Orbit parameters for planets and dwarf planets */}
|
||||
<Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.type !== currentValues.type}>
|
||||
{/* Orbit parameters and info for planets and dwarf planets */}
|
||||
<Form.Item noStyle shouldUpdate={(prevValues, currentValues) =>
|
||||
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 (
|
||||
<Alert
|
||||
message="轨道参数"
|
||||
message="轨道参数与信息"
|
||||
description={
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name={['extra_data', 'orbit_period_days']}
|
||||
label="轨道周期(天)"
|
||||
tooltip="完整公转一周所需的天数"
|
||||
>
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
min={0}
|
||||
step={1}
|
||||
placeholder="例如:365.25(地球)"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name={['extra_data', 'orbit_color']}
|
||||
label="轨道颜色"
|
||||
tooltip="轨道线的显示颜色(HEX格式)"
|
||||
>
|
||||
<Input
|
||||
type="color"
|
||||
placeholder="#4A90E2"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<div>
|
||||
{/* Editable orbit parameters */}
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name={['extra_data', 'orbit_period_days']}
|
||||
label="轨道周期(天)"
|
||||
tooltip="完整公转一周所需的天数"
|
||||
>
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
min={0}
|
||||
step={1}
|
||||
placeholder="例如:365.25(地球)"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name={['extra_data', 'orbit_color']}
|
||||
label="轨道颜色"
|
||||
tooltip="轨道线的显示颜色(HEX格式)"
|
||||
>
|
||||
<Input
|
||||
type="color"
|
||||
placeholder="#4A90E2"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* Display orbit info if exists (read-only) */}
|
||||
{editingRecord && orbitInfo && orbitInfo.num_points && (
|
||||
<div style={{
|
||||
marginTop: 16,
|
||||
paddingTop: 16,
|
||||
borderTop: '1px solid #d9d9d9',
|
||||
background: '#f0f9ff',
|
||||
padding: '12px',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
<div style={{ fontWeight: 600, marginBottom: 8, color: '#1890ff' }}>
|
||||
已生成的轨道数据:
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '24px' }}>
|
||||
<div>
|
||||
<strong>轨道点数:</strong> {orbitInfo.num_points?.toLocaleString()} 个点
|
||||
</div>
|
||||
{orbitInfo.period_days && (
|
||||
<div>
|
||||
<strong>实际周期:</strong> {orbitInfo.period_days.toFixed(2)} 天
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
type="info"
|
||||
style={{ marginBottom: 16 }}
|
||||
|
|
@ -769,6 +836,18 @@ export function CelestialBodies() {
|
|||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="short_name"
|
||||
label="简称"
|
||||
tooltip="NASA SBDB API使用的短名称(如Jupiter的简称是Juptr)"
|
||||
>
|
||||
<Input placeholder="例如:Juptr" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item
|
||||
name="description"
|
||||
label="描述"
|
||||
|
|
|
|||
Loading…
Reference in New Issue