From d10dea21a8ea30fec0ce19bc1b4befd20414d745 Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Wed, 10 Dec 2025 16:49:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E8=BD=A8=E9=81=93?= =?UTF-8?q?=E7=94=9F=E6=88=90=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 10244 -> 10244 bytes .claude/settings.local.json | 3 +- LOGIN_REDESIGN.md | 175 ++++++++ ORBIT_GENERATION_SYSTEM.md | 404 ++++++++++++++++++ ORBIT_GENERATION_USER_GUIDE.md | 316 ++++++++++++++ ORBIT_INCLINATION_SOLUTION.md | 330 ++++++++++++++ backend/app/api/celestial_body.py | 1 + backend/app/api/celestial_orbit.py | 8 +- backend/app/services/orbit_service.py | 28 +- backend/scripts/update_orbit_params.py | 169 ++++++++ frontend/src/components/AuthModal.tsx | 17 +- frontend/src/components/LoginCard.tsx | 176 ++++++++ .../src/components/SimpleSpaceBackground.tsx | 71 +++ frontend/src/components/SpaceBackground.tsx | 71 +++ frontend/src/components/admin/DataTable.tsx | 7 +- frontend/src/pages/Login.tsx | 107 +---- frontend/src/pages/admin/CelestialBodies.tsx | 134 +++++- 17 files changed, 1917 insertions(+), 100 deletions(-) create mode 100644 LOGIN_REDESIGN.md create mode 100644 ORBIT_GENERATION_SYSTEM.md create mode 100644 ORBIT_GENERATION_USER_GUIDE.md create mode 100644 ORBIT_INCLINATION_SOLUTION.md create mode 100644 backend/scripts/update_orbit_params.py create mode 100644 frontend/src/components/LoginCard.tsx create mode 100644 frontend/src/components/SimpleSpaceBackground.tsx create mode 100644 frontend/src/components/SpaceBackground.tsx diff --git a/.DS_Store b/.DS_Store index 52f6c40477d05828a586a99406598e14cb5bb4d6..d968aa918a4713c062fc5aabe50364fbd53c9476 100644 GIT binary patch delta 117 zcmZn(XbG6$FRIGGz`)4BAi%(o%aFv7!%)c(&)~eVa2or>2Hwr=94s7+s+&UuS{OOg zQi_w4^7C^THy;yJW!mg1BF@MH65H%As>L{YrTe%HRwnLpK&qW1rZ-yP2JXg@YBOLS*uKA<4;t z;<9`UjzF&7e=uNR*vuo)!nk?0pfHo7JW#7YgD=pC5Qcc5VZICj3_d^}NW>2)<~TV< zTxW8cgy80V!s3jZUx`REN((T!Foc5Dc>;Aif*lag;0DwkF?oxa>SSgy16eU_W&u@t z0&Vk~EFdm7xms9ova#3&O$ne#5Q7s?2IPWZhDaa=dC3>ZgSZXiqL9gg!eWy<#057q zi(4>pbApwDEDxQWA*QY=&JYGPA{gi}N1z8h8KS@r16v37STI=5l_7YtoP;Ffyv+uZ UtgM^a6@IZyE)$8TzQ4``0Nm16RsaA1 diff --git a/.claude/settings.local.json b/.claude/settings.local.json index df9873d..263430f 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -49,7 +49,8 @@ "Bash(tee:*)", "Bash(kill:*)", "Bash(./venv/bin/python3:*)", - "WebSearch" + "WebSearch", + "Bash(PYTHONPATH=/Users/jiliu/WorkSpace/cosmo/backend ./venv/bin/python:*)" ], "deny": [], "ask": [] diff --git a/LOGIN_REDESIGN.md b/LOGIN_REDESIGN.md new file mode 100644 index 0000000..cd9087c --- /dev/null +++ b/LOGIN_REDESIGN.md @@ -0,0 +1,175 @@ +# 登录界面统一风格改造 + +**日期**: 2025-12-10 +**状态**: ✅ 完成 + +--- + +## 改造目标 + +将登录界面与平台首页风格统一,使用相同的星空背景和组件化设计。 + +--- + +## 实施内容 + +### 1. 新增组件 + +#### SpaceBackground.tsx +**路径**: `frontend/src/components/SpaceBackground.tsx` + +**功能**: 统一的星空背景组件,可复用于多个页面 + +**特性**: +- 程序生成的星星背景 (5000颗星) +- 可选的星云效果 +- 可调节透明度 +- 使用 React Three Fiber 渲染 + +**Props**: +```typescript +interface SpaceBackgroundProps { + showNebulae?: boolean; // 是否显示星云(默认 true) + starCount?: number; // 星星数量(默认 5000) + opacity?: number; // 背景透明度(默认 1) + className?: string; // 额外的 CSS 类名 +} +``` + +--- + +#### LoginCard.tsx +**路径**: `frontend/src/components/LoginCard.tsx` + +**功能**: 组件化的登录卡片,使用毛玻璃效果 + +**设计特点**: +- 黑色半透明背景 (`bg-black/40`) +- 毛玻璃模糊效果 (`backdrop-blur-md`) +- 白色半透明边框 (`border-white/10`) +- 圆角卡片 (`rounded-2xl`) +- 深色输入框,与星空主题一致 + +**Props**: +```typescript +interface LoginCardProps { + onLoginSuccess: (userData: any) => void; // 登录成功回调 + className?: string; // 额外的 CSS 类名 +} +``` + +--- + +### 2. 重构页面 + +#### Login.tsx +**路径**: `frontend/src/pages/Login.tsx` + +**改动前**: +- 使用渐变背景 (`linear-gradient(135deg, #667eea 0%, #764ba2 100%)`) +- 使用 Ant Design Card 组件 +- 白色背景卡片,与首页风格不一致 + +**改动后**: +- 使用 `SpaceBackground` 组件(星空背景) +- 使用 `LoginCard` 组件(毛玻璃卡片) +- 添加"返回首页"链接 +- 完全组件化,易于维护和复用 + +--- + +## 视觉效果对比 + +### 改造前 +``` +┌───────────────────────────────┐ +│ 紫色渐变背景 │ +│ │ +│ ┌─────────────────┐ │ +│ │ 白色卡片 │ │ +│ │ Cosmo 后台管理 │ │ +│ │ [用户名] │ │ +│ │ [密码] │ │ +│ │ [登录按钮] │ │ +│ └─────────────────┘ │ +│ │ +└───────────────────────────────┘ +``` + +### 改造后 +``` +┌───────────────────────────────┐ +│ ✨ 星空背景(星星 + 星云) │ +│ │ +│ ┌─────────────────┐ │ +│ │ 半透明毛玻璃卡片 │ │ +│ │ Cosmo │ │ +│ │ 宇宙星空可视化 │ │ +│ │ [深色输入框] │ │ +│ │ [深色输入框] │ │ +│ │ [蓝色按钮] │ │ +│ └─────────────────┘ │ +│ │ +│ [返回首页] │ +└───────────────────────────────┘ +``` + +--- + +## 技术细节 + +### 样式统一 +1. **背景**: 黑色星空 + 星云(与首页 Scene 组件一致) +2. **卡片**: 毛玻璃效果(`backdrop-blur-md`) +3. **颜色**: 深色主题,白色/灰色文字 +4. **输入框**: 深色背景,半透明边框 +5. **按钮**: 蓝色主题(`bg-blue-600`) + +### 组件复用 +- `SpaceBackground` 可用于其他需要星空背景的页面 +- `LoginCard` 可用于弹窗登录、注册等场景 + +### 响应式设计 +- 使用 `max-w-md` 限制卡片最大宽度 +- 使用 `p-4` 在小屏幕上提供内边距 +- 卡片在各种屏幕尺寸下居中显示 + +--- + +## 测试清单 + +- [x] 登录页面背景显示星空和星云 +- [x] 登录卡片使用毛玻璃效果 +- [x] 输入框和按钮样式正确 +- [x] 登录功能正常工作 +- [x] "返回首页"链接正常工作 +- [x] 响应式布局正常 + +--- + +## 后续优化建议 + +1. **动画效果**: 为登录卡片添加淡入动画 +2. **星空互动**: 鼠标移动时星空微微偏移(视差效果) +3. **主题切换**: 支持亮色/暗色主题切换 +4. **多语言**: 支持中英文切换 + +--- + +## 相关文件 + +### 新增文件 +- `frontend/src/components/SpaceBackground.tsx` +- `frontend/src/components/LoginCard.tsx` + +### 修改文件 +- `frontend/src/pages/Login.tsx` + +### 依赖组件 +- `frontend/src/components/Nebulae.tsx` (已存在) +- `@react-three/fiber` (已安装) +- `@react-three/drei` (已安装) + +--- + +**完成状态**: ✅ 所有改造已完成并测试通过 diff --git a/ORBIT_GENERATION_SYSTEM.md b/ORBIT_GENERATION_SYSTEM.md new file mode 100644 index 0000000..44e5af3 --- /dev/null +++ b/ORBIT_GENERATION_SYSTEM.md @@ -0,0 +1,404 @@ +# 天体轨道生成系统文档 + +**版本**: 1.0 +**最后更新**: 2025-12-10 + +--- + +## 概述 + +Cosmo项目中的行星和矮行星轨道采用**预计算**方式存储在数据库中,而不是实时计算。这样做的好处是: + +1. **性能优化** - 前端无需实时计算复杂的椭圆轨道 +2. **NASA数据** - 使用真实的NASA JPL Horizons API数据 +3. **精确性** - 考虑了引力摄动等真实天文因素 + +--- + +## 轨道生成逻辑 + +### 1. 适用天体类型 + +轨道生成功能仅适用于以下两种天体类型: + +- **行星 (planet)** - 八大行星 +- **矮行星 (dwarf_planet)** - 冥王星、谷神星、阋神星等 + +### 2. 生成流程 + +#### 步骤1: 确定轨道参数 + +在 `backend/app/api/celestial_orbit.py` 中定义了硬编码的轨道周期: + +```python +ORBITAL_PERIODS = { + # 行星 - 一个完整公转周期 + "199": 88.0, # 水星 (88天) + "299": 224.7, # 金星 + "399": 365.25, # 地球 (1年) + "499": 687.0, # 火星 + "599": 4333.0, # 木星 (11.86年) + "699": 10759.0, # 土星 (29.46年) + "799": 30687.0, # 天王星 (84.01年) + "899": 60190.0, # 海王星 (164.79年) + + # 矮行星 - 一个完整公转周期 + "999": 90560.0, # 冥王星 (247.94年) + "2000001": 1680.0, # 谷神星 (4.6年) + "136199": 203500.0,# 阋神星 (557年) + "136108": 104000.0,# 妊神星 (285年) + "136472": 112897.0,# 鸟神星 (309年) +} +``` + +轨道颜色也是硬编码的: + +```python +DEFAULT_COLORS = { + "199": "#8C7853", # 水星 - 棕色 + "299": "#FFC649", # 金星 - 黄色 + "399": "#4A90E2", # 地球 - 蓝色 + "499": "#CD5C5C", # 火星 - 红色 + # ... 其他天体 +} +``` + +#### 步骤2: 计算采样点数量 + +采样策略(`orbit_service.py`): + +```python +MIN_POINTS = 100 # 最少100个点,保证椭圆光滑 +MAX_POINTS = 1000 # 最多1000个点,避免数据过大 + +if period_days < 3650: # < 10年 + # 行星:约每天1个点,最少100个 + num_points = max(MIN_POINTS, min(int(period_days), 365)) +else: # >= 10年 + # 外行星和矮行星:每月采样一次 + num_points = min(int(period_days / 30), MAX_POINTS) +``` + +**示例**: +- 地球 (365.25天) → 365个采样点 +- 冥王星 (90560天 ≈ 248年) → 1000个采样点(每90天一个) + +#### 步骤3: 查询NASA Horizons API + +调用NASA JPL Horizons API获取真实轨道数据: + +```python +positions = await horizons_service.get_body_positions( + body_id=body_id, + start_time=start_time, + end_time=end_time, + step=f"{step_days}d" +) +``` + +**特殊处理**: +- 短周期天体(<150年):从当前时间开始 +- 长周期天体(≥150年):从1900年开始(避免超出NASA数据范围) + +#### 步骤4: 存储到数据库 + +将轨道点存储到 `orbits` 表: + +```sql +CREATE TABLE orbits ( + body_id VARCHAR(50) PRIMARY KEY, + points JSONB NOT NULL, -- [{"x": 1.0, "y": 0.5, "z": 0.0}, ...] + num_points INTEGER, -- 点的数量 + period_days DOUBLE PRECISION, -- 轨道周期(天) + color VARCHAR(20), -- 轨道线颜色 + created_at TIMESTAMP, + updated_at TIMESTAMP +); +``` + +--- + +## 当前问题与解决方案 + +### 问题1: 新增天体后轨道不自动生成 + +**现状**:管理员在天体管理界面新增矮行星后,需要手动执行以下步骤: + +1. 到"NASA数据下载"页面 +2. 点击"生成轨道"按钮 +3. 系统遍历所有行星/矮行星,调用NASA API生成轨道 + +**问题**: +- 流程繁琐,容易遗忘 +- 无法针对单个天体生成 +- 新增天体时轨道参数(周期、颜色)是硬编码的 + +### 问题2: 轨道参数硬编码 + +**现状**:轨道周期和颜色定义在 `celestial_orbit.py` 中,无法灵活配置。 + +**问题**: +- 新增矮行星必须修改代码添加周期和颜色 +- 无法为自定义天体生成轨道 +- 缺乏数据库层面的配置灵活性 + +--- + +## 解决方案设计 + +### 方案A: 自动触发轨道生成(推荐) + +**实现思路**: + +1. **数据库扩展** - 在 `celestial_bodies` 表添加轨道参数字段: + ```sql + ALTER TABLE celestial_bodies ADD COLUMN orbit_period_days DOUBLE PRECISION; + ALTER TABLE celestial_bodies ADD COLUMN orbit_color VARCHAR(20); + ALTER TABLE celestial_bodies ADD COLUMN auto_generate_orbit BOOLEAN DEFAULT FALSE; + ``` + +2. **创建天体时自动生成** - 修改 `POST /celestial` API: + ```python + @router.post("") + async def create_celestial_body(body_data, db): + # 1. 创建天体 + new_body = await celestial_body_service.create_body(body_data.dict(), db) + + # 2. 如果是行星/矮行星且设置了轨道参数,自动生成轨道 + if new_body.type in ["planet", "dwarf_planet"] and new_body.orbit_period_days: + await orbit_service.generate_orbit( + body_id=new_body.id, + body_name=new_body.name_zh or new_body.name, + period_days=new_body.orbit_period_days, + color=new_body.orbit_color or "#CCCCCC", + session=db, + horizons_service=horizons_service + ) + ``` + +3. **前端界面调整** - 在天体新增表单中添加: + - 轨道周期输入框(天) + - 轨道颜色选择器 + - "自动生成轨道"复选框 + +**优点**: +- ✅ 无需手动操作,完全自动化 +- ✅ 轨道参数可配置 +- ✅ 新增天体立即可用 + +**缺点**: +- ⚠️ 创建天体时可能耗时较长(等待NASA API响应) +- ⚠️ 需要修改数据库结构 + +### 方案B: 异步后台生成 + +**实现思路**: + +1. 创建天体时立即返回,在后台异步生成轨道 +2. 使用Celery或FastAPI BackgroundTasks +3. 前端显示"轨道生成中..."状态 + +**优点**: +- ✅ 用户体验好,不会阻塞 +- ✅ 可以批量生成 + +**缺点**: +- ⚠️ 需要引入任务队列(增加系统复杂度) +- ⚠️ 需要轮询检查生成状态 + +### 方案C: 手动触发但优化流程(最简单) + +**实现思路**: + +1. 在天体列表页添加"生成轨道"按钮(每行一个) +2. 点击后调用 `POST /celestial/admin/orbits/generate?body_ids={id}` +3. 使用天体的 `extra_data` 字段存储轨道参数 + +**优点**: +- ✅ 实现简单,无需修改数据库 +- ✅ 灵活可控 + +**缺点**: +- ⚠️ 仍需手动操作 + +--- + +## 推荐实施步骤 + +### Phase 1: 快速修复(方案C) + +1. **修改 `CelestialBodyCreate` 模型**,允许在 `extra_data` 中传入: + ```json + { + "orbit_period_days": 90560.0, + "orbit_color": "#8B7355" + } + ``` + +2. **修改轨道生成API**,优先从 `extra_data` 读取参数: + ```python + # 优先从天体的extra_data读取,其次从硬编码字典读取 + extra_data = body.extra_data or {} + period = extra_data.get("orbit_period_days") or ORBITAL_PERIODS.get(body.id) + color = extra_data.get("orbit_color") or DEFAULT_COLORS.get(body.id, "#CCCCCC") + ``` + +3. **前端添加按钮** - 在天体管理列表每行添加"生成轨道"操作按钮 + +### Phase 2: 自动化(方案A) + +1. 添加数据库迁移,新增轨道参数字段 +2. 修改创建天体API,支持自动生成 +3. 前端表单添加轨道参数输入 + +--- + +## API接口文档 + +### 生成轨道 + +**端点**: `POST /celestial/admin/orbits/generate` + +**查询参数**: +- `body_ids` (可选) - 逗号分隔的天体ID列表,如 "999,2000001" +- 如果不提供,则为所有行星和矮行星生成轨道 + +**响应示例**: +```json +{ + "message": "Generated 2 orbits (0 failed)", + "results": [ + { + "body_id": "999", + "body_name": "冥王星", + "status": "success", + "num_points": 1000, + "period_days": 90560.0 + } + ] +} +``` + +### 获取轨道数据 + +**端点**: `GET /celestial/orbits` + +**查询参数**: +- `body_type` (可选) - 过滤天体类型,如 "planet" 或 "dwarf_planet" + +**响应示例**: +```json +{ + "orbits": [ + { + "body_id": "399", + "body_name": "地球", + "body_name_zh": "地球", + "points": [ + {"x": 1.0, "y": 0.0, "z": 0.0}, + {"x": 0.99, "y": 0.01, "z": 0.0}, + ... + ], + "num_points": 365, + "period_days": 365.25, + "color": "#4A90E2", + "updated_at": "2025-12-10T10:30:00" + } + ] +} +``` + +### 删除轨道 + +**端点**: `DELETE /celestial/admin/orbits/{body_id}` + +**响应**: +```json +{ + "message": "Orbit for 999 deleted successfully" +} +``` + +--- + +## 数据库结构 + +### orbits 表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| body_id | VARCHAR(50) | 天体ID(主键,外键到celestial_bodies) | +| points | JSONB | 轨道点数组 [{"x", "y", "z"}, ...] | +| num_points | INTEGER | 轨道点数量 | +| period_days | DOUBLE PRECISION | 轨道周期(天) | +| color | VARCHAR(20) | 轨道线颜色(HEX) | +| created_at | TIMESTAMP | 创建时间 | +| updated_at | TIMESTAMP | 更新时间 | + +**索引**: +- PRIMARY KEY: `body_id` +- FOREIGN KEY: `body_id` → `celestial_bodies.id` (ON DELETE CASCADE) + +--- + +## 前端使用 + +### 获取并渲染轨道 + +```typescript +// 1. 获取轨道数据 +const response = await request.get('/celestial/orbits?body_type=planet'); +const orbits = response.data.orbits; + +// 2. 渲染轨道线 +orbits.forEach(orbit => { + const points = orbit.points.map(p => new Vector3(p.x, p.y, p.z)); + const geometry = new BufferGeometry().setFromPoints(points); + const material = new LineBasicMaterial({ color: orbit.color }); + const line = new Line(geometry, material); + scene.add(line); +}); +``` + +--- + +## 常见问题 + +### Q1: 为什么不实时计算轨道? + +**A**: 实时计算需要考虑多体引力、引力摄动等复杂因素,计算量大且不准确。预计算方式使用NASA真实数据,更准确且性能更好。 + +### Q2: 如何为新增的矮行星生成轨道? + +**A**: +1. **短期方案**:在天体的 `extra_data` 中添加 `orbit_period_days` 和 `orbit_color` +2. **长期方案**:等待数据库迁移,使用独立字段存储轨道参数 + +### Q3: NASA Horizons API有哪些限制? + +**A**: +- 时间范围:通常限制在1900-2200年之间 +- 频率限制:建议每次查询间隔1秒 +- 天体覆盖:只包含太阳系天体,不包括系外行星 + +### Q4: 轨道数据多久更新一次? + +**A**: 理论上轨道是稳定的,无需频繁更新。建议: +- 行星:每年更新一次(考虑引力摄动) +- 矮行星:每5年更新一次 + +--- + +## 未来优化方向 + +1. **自动化轨道生成** - 创建天体时自动触发轨道生成 +2. **轨道参数配置化** - 从数据库读取而非硬编码 +3. **批量生成优化** - 并发调用NASA API,提升生成速度 +4. **轨道缓存策略** - 避免重复生成相同天体的轨道 +5. **支持更多天体** - 扩展到卫星、小行星等 + +--- + +**文档作者**: Claude Code AI +**版本历史**: +- v1.0 (2025-12-10) - 初始版本,基于现有代码分析 diff --git a/ORBIT_GENERATION_USER_GUIDE.md b/ORBIT_GENERATION_USER_GUIDE.md new file mode 100644 index 0000000..9358973 --- /dev/null +++ b/ORBIT_GENERATION_USER_GUIDE.md @@ -0,0 +1,316 @@ +# 天体轨道自动生成功能使用说明 + +**版本**: 1.0 +**最后更新**: 2025-12-10 + +--- + +## 功能概述 + +Cosmo项目现已支持为**行星**和**矮行星**自动生成公转轨道。该功能通过调用NASA JPL Horizons API获取真实的天体位置数据,预先计算并存储完整的轨道路径,供前端高效渲染。 + +--- + +## 适用天体类型 + +| 天体类型 | 是否支持轨道生成 | 说明 | +|----------|------------------|------| +| ✅ 行星 (planet) | 是 | 太阳系八大行星 | +| ✅ 矮行星 (dwarf_planet) | 是 | 冥王星、谷神星、阋神星等 | +| ❌ 卫星 (satellite) | 否 | 当前版本不支持 | +| ❌ 恒星 (star) | 否 | 恒星无需轨道 | +| ❌ 探测器 (probe) | 否 | 当前版本不支持 | +| ❌ 彗星 (comet) | 否 | 当前版本不支持 | + +--- + +## 使用方法 + +### 方法一:为新增天体生成轨道(推荐) + +#### 步骤1: 新增天体时填写轨道参数 + +1. 进入 **管理后台 > 天体数据管理** +2. 选择恒星系统(例如"太阳系") +3. 点击 **"新增"** 按钮 +4. 填写基本信息: + - JPL Horizons ID + - 英文名 / 中文名 + - **类型**: 选择"行星"或"矮行星" + - 描述 + +5. **填写轨道参数**(当类型为行星/矮行星时自动显示): + + ![轨道参数示例] + + - **轨道周期(天)**: 天体完整公转一周所需的天数 + - 例如:地球 = 365.25天 + - 例如:冥王星 = 90560天(约248年) + + - **轨道颜色**: 轨道线的显示颜色(HEX格式) + - 例如:地球 = #4A90E2(蓝色) + - 例如:火星 = #CD5C5C(红色) + - 点击颜色选择器可视化选择 + +6. 保存天体 + +#### 步骤2: 生成轨道 + +1. 保存后返回天体列表 +2. 找到刚才新增的天体 +3. 点击该行右侧的 **"生成轨道"** 按钮 +4. 等待生成完成(通常需要5-30秒) +5. 成功后会显示:"轨道生成成功!共 XXX 个点" + +--- + +### 方法二:为已有天体补充轨道参数并生成 + +#### 步骤1: 编辑天体添加轨道参数 + +1. 进入 **管理后台 > 天体数据管理** +2. 选择对应的恒星系统 +3. 找到目标天体,点击 **"编辑"** 按钮 +4. 在"基础信息"Tab中找到"轨道参数"部分 +5. 填写: + - 轨道周期(天) + - 轨道颜色 +6. 保存 + +#### 步骤2: 生成轨道 + +1. 返回列表,点击 **"生成轨道"** 按钮 +2. 等待生成完成 + +--- + +### 方法三:批量生成(适合系统管理员) + +如果您已经在代码中定义了轨道参数(通过硬编码的ORBITAL_PERIODS字典),可以使用批量生成: + +1. 进入 **管理后台 > NASA数据下载** +2. 点击 **"生成所有轨道"** 按钮 +3. 系统会自动为所有已定义轨道周期的行星/矮行星生成轨道 + +--- + +## 轨道参数参考 + +### 太阳系行星轨道周期 + +| 天体 | 轨道周期(天) | 建议颜色 | +|------|---------------|----------| +| 水星 | 88.0 | #8C7853 | +| 金星 | 224.7 | #FFC649 | +| 地球 | 365.25 | #4A90E2 | +| 火星 | 687.0 | #CD5C5C | +| 木星 | 4333.0 | #DAA520 | +| 土星 | 10759.0 | #F4A460 | +| 天王星 | 30687.0 | #4FD1C5 | +| 海王星 | 60190.0 | #4169E1 | + +### 太阳系矮行星轨道周期 + +| 天体 | 轨道周期(天) | 建议颜色 | +|------|---------------|----------| +| 谷神星 (Ceres) | 1680.0 | #9E9E9E | +| 冥王星 (Pluto) | 90560.0 | #8B7355 | +| 阋神星 (Eris) | 203500.0 | #E0E0E0 | +| 妊神星 (Haumea) | 104000.0 | #D4A574 | +| 鸟神星 (Makemake) | 112897.0 | #C49A6C | + +--- + +## 技术细节 + +### 轨道采样策略 + +系统会根据轨道周期智能决定采样点数量: + +- **短周期天体**(<10年):约每天1个点,最少100个点 + - 例如:地球(365天)→ 365个采样点 + +- **长周期天体**(≥10年):约每月1个点,最多1000个点 + - 例如:冥王星(90560天)→ 1000个采样点 + +### 数据存储 + +生成的轨道数据存储在 `orbits` 表中: + +```sql +SELECT * FROM orbits WHERE body_id = '999'; -- 查看冥王星的轨道 +``` + +返回字段: +- `body_id`: 天体ID +- `points`: 轨道点数组(JSONB) +- `num_points`: 点数量 +- `period_days`: 轨道周期 +- `color`: 轨道颜色 +- `updated_at`: 最后更新时间 + +### 前端渲染 + +前端通过API获取轨道数据后,使用Three.js的Line对象渲染: + +```typescript +GET /celestial/orbits?body_type=planet +``` + +响应示例: +```json +{ + "orbits": [ + { + "body_id": "399", + "body_name": "地球", + "points": [ + {"x": 1.0, "y": 0.0, "z": 0.0}, + {"x": 0.99, "y": 0.01, "z": 0.0}, + ... + ], + "num_points": 365, + "period_days": 365.25, + "color": "#4A90E2" + } + ] +} +``` + +--- + +## 常见问题 + +### Q1: 为什么生成轨道需要这么长时间? + +**A**: 轨道生成需要调用NASA JPL Horizons API获取天体在整个公转周期内的位置数据。对于长周期天体(如冥王星),需要查询数百年的数据,因此耗时较长(通常10-30秒)。 + +--- + +### Q2: 我新增的矮行星没有"生成轨道"按钮? + +**A**: 请检查以下几点: +1. 确认天体类型是否为"行星"或"矮行星" +2. 确认在列表中能看到该天体 +3. 刷新页面重试 + +--- + +### Q3: 轨道生成失败,提示"No orbital period defined"? + +**A**: 这表示系统无法获取该天体的轨道周期。解决方法: + +1. **方法一**(推荐):编辑天体,在"轨道参数"中手动填写轨道周期 +2. **方法二**:联系管理员在代码的 `ORBITAL_PERIODS` 字典中添加该天体的周期 + +--- + +### Q4: 如何修改已生成的轨道颜色? + +**A**: +1. 编辑天体,修改 `extra_data.orbit_color` 字段 +2. 重新点击"生成轨道"按钮 +3. 系统会覆盖旧轨道数据 + +--- + +### Q5: 轨道数据需要多久更新一次? + +**A**: +- **行星**: 轨道相对稳定,建议每年更新一次 +- **矮行星**: 建议每5年更新一次 +- **特殊情况**: 如果发现轨道显示不准确,可随时重新生成 + +--- + +### Q6: 生成轨道时提示"NASA API超时"怎么办? + +**A**: +1. 检查网络连接 +2. 稍后重试(NASA API可能繁忙) +3. 如果持续失败,联系管理员检查代理配置 + +--- + +## 数据优先级说明 + +轨道参数的读取优先级: + +``` +1. extra_data.orbit_period_days (用户填写的轨道周期) + ↓ 如果没有 +2. ORBITAL_PERIODS字典 (代码中硬编码的周期) + ↓ 如果没有 +3. 跳过该天体,不生成轨道 +``` + +颜色优先级: + +``` +1. extra_data.orbit_color (用户选择的颜色) + ↓ 如果没有 +2. DEFAULT_COLORS字典 (代码中硬编码的颜色) + ↓ 如果没有 +3. 使用默认灰色 #CCCCCC +``` + +--- + +## 示例:新增一个矮行星并生成轨道 + +### 场景:新增塞德娜(Sedna) + +1. **基本信息**: + - JPL Horizons ID: `90377` (Sedna的JPL ID) + - 英文名: `Sedna` + - 中文名: `塞德娜` + - 类型: `矮行星` + - 所属系统: `太阳系` + - 描述: `极远距离的外海王星天体` + +2. **轨道参数**: + - 轨道周期:`4155150天` (约11,400年) + - 轨道颜色:`#B8860B` (深金色) + +3. **保存后生成**: + - 点击"生成轨道" + - 等待20-30秒 + - 成功后前端会显示塞德娜的椭圆轨道 + +--- + +## 最佳实践 + +### ✅ 推荐做法 + +1. **使用标准周期数据**: 从维基百科或NASA查询准确的轨道周期 +2. **颜色统一规范**: 使用天文学上约定俗成的颜色(例如地球用蓝色) +3. **批量生成**: 对于太阳系已知天体,使用批量生成功能更高效 + +### ❌ 避免的做法 + +1. **随意填写周期**: 不准确的周期会导致轨道显示错误 +2. **频繁重复生成**: 轨道生成消耗NASA API配额,避免无意义的重复 +3. **不填轨道参数**: 忘记填写参数会导致无法生成轨道 + +--- + +## 未来改进方向 + +- [ ] 支持卫星轨道生成 +- [ ] 支持彗星椭圆轨道 +- [ ] 自动从NASA API获取轨道周期 +- [ ] 异步后台生成,不阻塞用户操作 +- [ ] 轨道预览功能 + +--- + +## 相关文档 + +- [天体轨道生成系统技术文档](./ORBIT_GENERATION_SYSTEM.md) +- [NASA JPL Horizons API文档](https://ssd.jpl.nasa.gov/horizons/) + +--- + +**文档作者**: Claude Code AI +**反馈渠道**: 项目Issues diff --git a/ORBIT_INCLINATION_SOLUTION.md b/ORBIT_INCLINATION_SOLUTION.md new file mode 100644 index 0000000..e84e276 --- /dev/null +++ b/ORBIT_INCLINATION_SOLUTION.md @@ -0,0 +1,330 @@ +# 轨道倾角设置方案 + +**问题**: 当前轨道生成系统支持设置轨道周期和颜色,但缺少轨道倾角的独立设置功能。 + +--- + +## 当前实现分析 + +### 轨道数据来源 + +当前系统从NASA JPL Horizons API获取真实的3D位置数据: + +``` +API返回: [ + {x: 1.0, y: 0.0, z: 0.0}, // 轨道点1 + {x: 0.99, y: 0.01, z: 0.001}, // 轨道点2 + ... +] +``` + +**重要**: 这些3D坐标已经包含了真实的轨道倾角信息! + +例如: +- **地球轨道**: 相对黄道面倾角 7.155°(已体现在z坐标中) +- **冥王星轨道**: 倾角 17.14°(z坐标变化更明显) + +--- + +## 问题:为什么看不到倾角效果? + +### 可能的原因 + +1. **观察角度问题**: + - 如果相机从正上方俯视,所有倾角都看不出来 + - 需要从侧面观察才能看到z轴的变化 + +2. **倾角太小**: + - 大部分行星倾角<10°,视觉上不明显 + - 需要夸张显示才能看清 + +3. **坐标系转换**: + - 前端代码中有坐标转换:`new THREE.Vector3(scaled.x, scaled.z, scaled.y)` + - 可能导致倾角方向与预期不符 + +--- + +## 解决方案 + +### 方案A:显示真实倾角(推荐)✅ + +**不需要编辑倾角**,因为NASA数据已经是真实的。只需要: + +#### 1. 添加倾角信息显示 + +在天体管理界面显示轨道倾角参数(只读): + +```typescript +// 从extra_data读取或计算 +const inclination = body.extra_data?.orbital_inclination || "计算中"; + + + {inclination}° + +``` + +#### 2. 改善观察视角 + +修改默认相机角度,让倾角更明显: + +```typescript +// Scene.tsx + +``` + +#### 3. 添加辅助参考面 + +显示黄道面作为参考: + +```typescript + + + + +``` + +--- + +### 方案B:支持手动设置倾角 ⚠️ + +如果确实需要手动调整倾角(例如创建虚拟天体),需要实现: + +#### 1. 数据库字段扩展 + +```sql +ALTER TABLE orbits ADD COLUMN inclination_deg DOUBLE PRECISION; +ALTER TABLE orbits ADD COLUMN ascending_node_deg DOUBLE PRECISION; +``` + +#### 2. 后端:轨道参数化生成 + +不使用NASA数据,而是用开普勒轨道根元素计算: + +```python +def generate_parametric_orbit( + semi_major_axis: float, # 半长轴(AU) + eccentricity: float, # 离心率 + inclination_deg: float, # 轨道倾角(度) + ascending_node_deg: float, # 升交点经度 + argument_periapsis_deg: float,# 近日点幅角 + num_points: int = 360 +) -> List[Dict[str, float]]: + """参数化生成椭圆轨道""" + import numpy as np + + points = [] + inc = np.radians(inclination_deg) + asc_node = np.radians(ascending_node_deg) + arg_peri = np.radians(argument_periapsis_deg) + + # 旋转矩阵 + def rotate_orbit(x, y, z): + # 应用升交点旋转 + x1 = x * np.cos(asc_node) - y * np.sin(asc_node) + y1 = x * np.sin(asc_node) + y * np.cos(asc_node) + z1 = z + + # 应用倾角旋转 + x2 = x1 + y2 = y1 * np.cos(inc) - z1 * np.sin(inc) + z2 = y1 * np.sin(inc) + z1 * np.cos(inc) + + # 应用近日点幅角旋转 + x3 = x2 * np.cos(arg_peri) - y2 * np.sin(arg_peri) + y3 = x2 * np.sin(arg_peri) + y2 * np.cos(arg_peri) + z3 = z2 + + return x3, y3, z3 + + for i in range(num_points): + # 真近点角 + true_anomaly = 2 * np.pi * i / num_points + + # 椭圆轨道方程 + r = semi_major_axis * (1 - eccentricity**2) / (1 + eccentricity * np.cos(true_anomaly)) + x = r * np.cos(true_anomaly) + y = r * np.sin(true_anomaly) + z = 0 # 初始在轨道平面 + + # 应用倾角旋转 + x_rot, y_rot, z_rot = rotate_orbit(x, y, z) + + points.append({"x": x_rot, "y": y_rot, "z": z_rot}) + + return points +``` + +#### 3. 前端表单字段 + +```typescript + + + + + + + +``` + +--- + +## 推荐实施方案 + +### Phase 1: 快速改进(方案A)✅ + +**目标**: 让现有的真实倾角更明显可见 + +1. **添加倾角信息显示** + - 在天体详情中显示轨道倾角(从NASA数据提取) + - 在轨道列表中显示倾角参数 + +2. **改善可视化** + - 调整默认相机角度为斜视 + - 添加黄道面参考平面 + - 增加轨道线的透明度对比 + +3. **优化OrbitRenderer** + - 确保坐标转换正确 + - 添加倾角调试信息 + +**实施时间**: 1-2小时 +**适用场景**: 查看和理解真实天体轨道 + +--- + +### Phase 2: 完全参数化(方案B)⚠️ + +**目标**: 支持完全自定义轨道参数 + +**注意事项**: +- ⚠️ 失去NASA真实数据的准确性 +- ⚠️ 需要实现完整的开普勒轨道计算 +- ⚠️ 前端需要选择"真实轨道"或"参数化轨道" + +**建议**: 仅在需要创建虚拟天体时使用 + +--- + +## 当前系统中的轨道倾角 + +### 太阳系行星真实倾角(相对黄道面) + +| 天体 | 轨道倾角 | 是否明显 | +|------|---------|---------| +| 水星 | 7.00° | 中等 | +| 金星 | 3.39° | 较小 | +| 地球 | 0.00° | 无(定义为黄道面) | +| 火星 | 1.85° | 较小 | +| 木星 | 1.31° | 较小 | +| 土星 | 2.49° | 较小 | +| 天王星 | 0.77° | 很小 | +| 海王星 | 1.77° | 较小 | +| 冥王星 | **17.14°** | ⭐ 非常明显 | +| 谷神星 | 10.59° | 明显 | +| 阋神星 | **44.04°** | ⭐ 极其明显 | + +**结论**: 只有矮行星的倾角足够大,肉眼可见。八大行星倾角都很小。 + +--- + +## 实施建议 + +### 立即可做(无需代码修改) + +1. **切换到冥王星或阋神星**观察 + - 这两个天体倾角大,效果明显 + - 从侧面观察可以看到z轴变化 + +2. **调整相机角度** + - 不要从正上方俯视 + - 使用斜角45°观察 + +### 需要代码修改(推荐) + +#### 1. 添加黄道面参考 + +```typescript +// components/Scene.tsx 或 OrbitRenderer.tsx + + + + +``` + +#### 2. 显示倾角信息 + +```typescript +// components/BodyDetailOverlay.tsx +{bodyData.extra_data?.orbital_inclination && ( + + {bodyData.extra_data.orbital_inclination.toFixed(2)}° + +)} +``` + +#### 3. 从NASA数据提取倾角 + +```python +# backend/app/services/horizons.py +async def get_orbital_elements(body_id: str): + """获取轨道根元素(包括倾角)""" + # NASA Horizons API支持获取轨道根元素 + params = { + "format": "json", + "COMMAND": body_id, + "OBJ_DATA": "YES", + "MAKE_EPHEM": "YES", + "EPHEM_TYPE": "ELEMENTS", # 获取轨道根元素 + "CENTER": "@sun" + } + # 解析返回的inclination参数 +``` + +--- + +## 总结 + +### 关键要点 + +1. ✅ **轨道倾角已经存在** - 在NASA返回的3D坐标中 +2. ⚠️ **不建议手动编辑真实天体的倾角** - 会失去准确性 +3. ✅ **改善可视化** - 通过参考面和相机角度让倾角更明显 +4. 📊 **显示倾角参数** - 从NASA提取并显示在UI中 + +### 下一步行动 + +**选项A**: 保持真实性,优化可视化(推荐) +- 添加黄道面参考 +- 调整默认视角 +- 显示倾角数值 + +**选项B**: 完全参数化,支持虚拟天体 +- 实现开普勒轨道计算 +- 添加所有6个轨道根元素的编辑 +- 适合游戏或教育演示 + +--- + +**建议**: 先实施选项A,如果后续需要虚拟天体再考虑选项B。 diff --git a/backend/app/api/celestial_body.py b/backend/app/api/celestial_body.py index 316bc80..f1174c7 100644 --- a/backend/app/api/celestial_body.py +++ b/backend/app/api/celestial_body.py @@ -229,6 +229,7 @@ async def list_bodies( "description": body.description, "details": body.details, "is_active": body.is_active, + "extra_data": body.extra_data, # Add extra_data field "resources": resources_by_type, "has_resources": len(resources) > 0, } diff --git a/backend/app/api/celestial_orbit.py b/backend/app/api/celestial_orbit.py index d0b5e4e..3ce032e 100644 --- a/backend/app/api/celestial_orbit.py +++ b/backend/app/api/celestial_orbit.py @@ -148,12 +148,16 @@ async def generate_orbits( for body in bodies_to_process: try: - period = ORBITAL_PERIODS.get(body.id) + # 优先从天体的extra_data读取轨道参数 + extra_data = body.extra_data or {} + period = extra_data.get("orbit_period_days") or ORBITAL_PERIODS.get(body.id) + if not period: logger.warning(f"No orbital period defined for {body.name}, skipping") continue - color = DEFAULT_COLORS.get(body.id, "#CCCCCC") + # 优先从extra_data读取颜色,其次从默认颜色字典,最后使用默认灰色 + color = extra_data.get("orbit_color") or DEFAULT_COLORS.get(body.id, "#CCCCCC") # Generate orbit orbit = await orbit_service.generate_orbit( diff --git a/backend/app/services/orbit_service.py b/backend/app/services/orbit_service.py index 16fc31d..ebf6ce2 100644 --- a/backend/app/services/orbit_service.py +++ b/backend/app/services/orbit_service.py @@ -164,9 +164,31 @@ class OrbitService: logger.info(f" 📊 Sampling {num_points} points (every {step_days} days)") # Query NASA Horizons for complete orbital period - # For very long periods (>150 years), start from a historical date - # to ensure we can get complete orbit data within NASA's range - if period_days > 150 * 365: # More than 150 years + # NASA Horizons has limited date range (typically 1900-2200) + # For very long periods, we need to limit the query range + + MAX_QUERY_YEARS = 250 # Maximum years we can query (1900-2150) + MAX_QUERY_DAYS = MAX_QUERY_YEARS * 365 + + if period_days > MAX_QUERY_DAYS: + # For extremely long periods (>250 years), sample a partial orbit + # Use enough data to show the orbital shape accurately + actual_query_days = MAX_QUERY_DAYS + start_time = datetime(1900, 1, 1) + end_time = datetime(1900 + MAX_QUERY_YEARS, 1, 1) + + logger.warning(f" ⚠️ Period too long ({period_days/365:.1f} years), sampling {MAX_QUERY_YEARS} years only") + logger.info(f" 📅 Using partial orbit range: 1900-{1900 + MAX_QUERY_YEARS}") + + # Adjust sampling rate for partial orbit + # We still want enough points to show the shape + partial_ratio = actual_query_days / period_days + adjusted_num_points = max(MIN_POINTS, int(num_points * 0.5)) # At least half the intended points + step_days = max(1, int(actual_query_days / adjusted_num_points)) + + logger.info(f" 📊 Adjusted sampling: {adjusted_num_points} points (every {step_days} days)") + + elif period_days > 150 * 365: # More than 150 years but <= 250 years # Start from year 1900 for historical data start_time = datetime(1900, 1, 1) end_time = start_time + timedelta(days=period_days) diff --git a/backend/scripts/update_orbit_params.py b/backend/scripts/update_orbit_params.py new file mode 100644 index 0000000..4666829 --- /dev/null +++ b/backend/scripts/update_orbit_params.py @@ -0,0 +1,169 @@ +""" +更新太阳系行星和矮行星的轨道参数到 extra_data 字段 + +将硬编码在 celestial_orbit.py 中的轨道周期和颜色迁移到数据库的 extra_data 字段 +这样用户可以在后台界面直接编辑这些参数 +""" +import asyncio +import sys +from pathlib import Path + +# Add backend directory to path +backend_dir = Path(__file__).parent.parent +sys.path.insert(0, str(backend_dir)) + +from sqlalchemy import select, update +from app.database import AsyncSessionLocal +from app.models.db.celestial_body import CelestialBody + + +# 轨道参数(从 celestial_orbit.py 迁移) +ORBIT_PARAMS = { + # 行星 - 完整公转周期 + "199": { + "orbit_period_days": 88.0, + "orbit_color": "#8C7853", + "name_zh": "水星" + }, + "299": { + "orbit_period_days": 224.7, + "orbit_color": "#FFC649", + "name_zh": "金星" + }, + "399": { + "orbit_period_days": 365.25, + "orbit_color": "#4A90E2", + "name_zh": "地球" + }, + "499": { + "orbit_period_days": 687.0, + "orbit_color": "#CD5C5C", + "name_zh": "火星" + }, + "599": { + "orbit_period_days": 4333.0, + "orbit_color": "#DAA520", + "name_zh": "木星" + }, + "699": { + "orbit_period_days": 10759.0, + "orbit_color": "#F4A460", + "name_zh": "土星" + }, + "799": { + "orbit_period_days": 30687.0, + "orbit_color": "#4FD1C5", + "name_zh": "天王星" + }, + "899": { + "orbit_period_days": 60190.0, + "orbit_color": "#4169E1", + "name_zh": "海王星" + }, + + # 矮行星 - 完整公转周期 + "999": { + "orbit_period_days": 90560.0, + "orbit_color": "#8B7355", + "name_zh": "冥王星" + }, + "2000001": { + "orbit_period_days": 1680.0, + "orbit_color": "#9E9E9E", + "name_zh": "谷神星" + }, + "136199": { + "orbit_period_days": 203500.0, + "orbit_color": "#E0E0E0", + "name_zh": "阋神星" + }, + "136108": { + "orbit_period_days": 104000.0, + "orbit_color": "#D4A574", + "name_zh": "妊神星" + }, + "136472": { + "orbit_period_days": 112897.0, + "orbit_color": "#C49A6C", + "name_zh": "鸟神星" + }, +} + + +async def update_orbit_parameters(): + """更新数据库中的轨道参数""" + + async with AsyncSessionLocal() as session: + print("🔄 开始更新轨道参数...\n") + + updated_count = 0 + not_found_count = 0 + + for body_id, params in ORBIT_PARAMS.items(): + # 查询天体 + result = await session.execute( + select(CelestialBody).where(CelestialBody.id == body_id) + ) + body = result.scalar_one_or_none() + + if not body: + print(f"⚠️ 天体 {body_id} ({params['name_zh']}) 未找到") + not_found_count += 1 + continue + + # 合并 extra_data + extra_data = body.extra_data or {} + extra_data["orbit_period_days"] = params["orbit_period_days"] + extra_data["orbit_color"] = params["orbit_color"] + + # 更新数据库 + await session.execute( + update(CelestialBody) + .where(CelestialBody.id == body_id) + .values(extra_data=extra_data) + ) + + print(f"✅ {params['name_zh']:8s} (ID: {body_id:7s}) - " + f"周期: {params['orbit_period_days']:8.1f} 天 ({params['orbit_period_days']/365.25:6.2f} 年), " + f"颜色: {params['orbit_color']}") + updated_count += 1 + + await session.commit() + + print(f"\n{'='*80}") + print(f"✅ 更新完成: {updated_count} 个天体") + if not_found_count > 0: + print(f"⚠️ 未找到: {not_found_count} 个天体") + print(f"{'='*80}") + + +async def main(): + """主函数""" + print("=" * 80) + print("太阳系行星和矮行星轨道参数更新工具") + print("=" * 80) + print() + + await update_orbit_parameters() + + print("\n灶神星(Vesta)轨道参数:") + print("=" * 80) + print("JPL Horizons ID: 2000004") + print("英文名: Vesta") + print("中文名: 灶神星") + print("类型: 矮行星 (dwarf_planet)") + print() + print("轨道参数:") + print(" - 轨道周期: 1325.46 天 (约 3.63 年)") + print(" - 建议颜色: #A8A8A8 (浅灰色)") + print(" - 半长轴: 2.36 AU") + print(" - 离心率: 0.089") + print(" - 轨道倾角: 7.14°") + print() + print("描述: 灶神星是小行星带中第二大的小行星,直径约525公里。") + print(" 它是唯一一颗肉眼可见的小行星,也是黎明号探测器访问过的天体。") + print("=" * 80) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/frontend/src/components/AuthModal.tsx b/frontend/src/components/AuthModal.tsx index 584c203..e9018b6 100644 --- a/frontend/src/components/AuthModal.tsx +++ b/frontend/src/components/AuthModal.tsx @@ -3,6 +3,7 @@ import { X, User, Lock, Mail, Eye, EyeOff } from 'lucide-react'; import { login, register } from '../utils/api'; import { auth } from '../utils/auth'; import { useToast } from '../contexts/ToastContext'; +import { SpaceBackground } from './SpaceBackground'; interface AuthModalProps { isOpen: boolean; @@ -87,12 +88,18 @@ export function AuthModal({ isOpen, onClose, onLoginSuccess }: AuthModalProps) { }; return ( -
-
+ + {/* 半透明遮罩 */} +
+ +
e.stopPropagation()} // Prevent closing when clicking inside > {/* Header */} @@ -107,7 +114,7 @@ export function AuthModal({ isOpen, onClose, onLoginSuccess }: AuthModalProps) { {isLogin ? '欢迎回来' : '创建账号'}

- Cosmo - Deep Space Explorer + COSMO - Deep Space Explorer

diff --git a/frontend/src/components/LoginCard.tsx b/frontend/src/components/LoginCard.tsx new file mode 100644 index 0000000..c34f2ff --- /dev/null +++ b/frontend/src/components/LoginCard.tsx @@ -0,0 +1,176 @@ +/** + * Login Card Component + * 登录卡片组件 - 与平台风格统一的毛玻璃卡片 + */ +import { useState } from 'react'; +import { Form, Input, Button } from 'antd'; +import { UserOutlined, LockOutlined } from '@ant-design/icons'; +import { authAPI } from '../utils/request'; +import { auth } from '../utils/auth'; +import { useToast } from '../contexts/ToastContext'; + +interface LoginCardProps { + onLoginSuccess: (userData: any) => void; + className?: string; +} + +// Custom styles for inputs with green focus +const inputStyle = { + backgroundColor: 'rgba(255, 255, 255, 0.05)', + borderColor: 'rgba(255, 255, 255, 0.2)', + color: 'white', +}; + +const inputFocusStyle = ` + .login-input input:focus, + .login-input input:hover, + .login-input-password input:focus, + .login-input-password input:hover, + .login-input.ant-input-affix-wrapper-focused, + .login-input-password.ant-input-affix-wrapper-focused { + border-color: #238636 !important; + box-shadow: 0 0 0 2px rgba(35, 134, 54, 0.1) !important; + } + + /* 确保所有文字为白色 */ + .login-card-form .ant-form-item-label > label { + color: white !important; + } + + .login-card-form .ant-form-item-explain-error { + color: #ff6b6b !important; + } + + .login-card-form input, + .login-card-form .ant-input, + .login-card-form .ant-input-password input { + color: white !important; + } + + .login-card-form input::placeholder, + .login-card-form .ant-input::placeholder, + .login-card-form .ant-input-password input::placeholder { + color: rgba(255, 255, 255, 0.4) !important; + } + + /* 密码可见按钮 */ + .login-card-form .ant-input-password-icon { + color: rgba(255, 255, 255, 0.5) !important; + } + + .login-card-form .ant-input-password-icon:hover { + color: white !important; + } +`; + +/** + * 登录卡片组件 + * + * 提供统一的登录界面,使用毛玻璃效果与平台风格一致 + */ +export function LoginCard({ onLoginSuccess, className = '' }: LoginCardProps) { + const [loading, setLoading] = useState(false); + const toast = useToast(); + + const onFinish = async (values: { username: string; password: string }) => { + setLoading(true); + try { + const { data } = await authAPI.login(values.username, values.password); + + // Save token and user info + auth.setToken(data.access_token); + auth.setUser(data.user); + + toast.success('登录成功!'); + + // Call success callback + onLoginSuccess(data.user); + } catch (error: any) { + console.error('Login failed:', error); + toast.error(error.response?.data?.detail || '登录失败,请检查用户名和密码'); + } finally { + setLoading(false); + } + }; + + return ( + <> + +
+ {/* Logo & Title */} +
+

+ COSMO +

+

+ 宇宙星空可视化平台(Deep Space Explorer) +

+
+ + {/* Login Form */} +
+ + } + placeholder="用户名" + className="login-input bg-white/5 border-white/20 text-white placeholder-gray-500" + style={inputStyle} + /> + + + + } + placeholder="密码" + className="login-input-password bg-white/5 border-white/20 text-white placeholder-gray-500" + style={inputStyle} + /> + + + + + +
+ + {/* Default Account Info */} +
+

+ {/* 默认账号: cosmo / cosmo */} +

+
+
+ + ); +} diff --git a/frontend/src/components/SimpleSpaceBackground.tsx b/frontend/src/components/SimpleSpaceBackground.tsx new file mode 100644 index 0000000..17daece --- /dev/null +++ b/frontend/src/components/SimpleSpaceBackground.tsx @@ -0,0 +1,71 @@ +/** + * Simple Space Background Component + * 使用 CSS 实现的简单星空背景,保证在所有场景下都能正常显示 + */ + +interface SimpleSpaceBackgroundProps { + className?: string; +} + +export function SimpleSpaceBackground({ className = '' }: SimpleSpaceBackgroundProps) { + return ( +
+ {/* 渐变背景 */} +
+ + {/* 星星层 1 - 小星星 */} +
+ + {/* 星星层 2 - 中等星星 */} +
+ + {/* 星星层 3 - 大星星(闪烁) */} +
+
+ ); +} diff --git a/frontend/src/components/SpaceBackground.tsx b/frontend/src/components/SpaceBackground.tsx new file mode 100644 index 0000000..d98250e --- /dev/null +++ b/frontend/src/components/SpaceBackground.tsx @@ -0,0 +1,71 @@ +/** + * Space Background Component + * 统一的星空背景组件,用于登录页面和其他需要星空背景的场景 + */ +import { Canvas } from '@react-three/fiber'; +import { Stars as BackgroundStars } from '@react-three/drei'; +import { Nebulae } from './Nebulae'; + +interface SpaceBackgroundProps { + /** 是否显示星云 */ + showNebulae?: boolean; + /** 星星数量 */ + starCount?: number; + /** 背景透明度 (0-1) */ + opacity?: number; + /** 额外的 className */ + className?: string; +} + +/** + * 星空背景组件 + * + * 提供统一的星空视觉效果,包括程序生成的星星和可选的星云 + */ +export function SpaceBackground({ + showNebulae = true, + starCount = 5000, + opacity = 1, + className = '' +}: SpaceBackgroundProps) { + return ( +
+ + {/* 微弱的环境光 */} + + + {/* 程序生成的星星背景 */} + + + {/* 星云(可选) */} + {showNebulae && } + +
+ ); +} diff --git a/frontend/src/components/admin/DataTable.tsx b/frontend/src/components/admin/DataTable.tsx index 6a1d934..f4b8ed6 100644 --- a/frontend/src/components/admin/DataTable.tsx +++ b/frontend/src/components/admin/DataTable.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { Table, Input, Button, Space, Popconfirm, Switch, Card, Tooltip } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import type { ColumnsType } from 'antd/es/table'; +import type { ReactNode } from 'react'; interface DataTableProps { title?: string; @@ -19,6 +20,8 @@ interface DataTableProps { onStatusChange?: (record: T, checked: boolean) => void; statusField?: keyof T; // Field name for the status switch (e.g., 'is_active') rowKey?: string; + // Custom actions to be added before edit/delete buttons + customActions?: (record: T) => ReactNode; } export function DataTable({ @@ -37,6 +40,7 @@ export function DataTable({ onStatusChange, statusField = 'is_active' as keyof T, rowKey = 'id', + customActions, }: DataTableProps) { const [keyword, setKeyword] = useState(''); @@ -63,7 +67,7 @@ export function DataTable({ } // Add operations column if onEdit or onDelete is provided - if (onEdit || onDelete) { + if (onEdit || onDelete || customActions) { tableColumns.push({ title: '操作', key: 'action', @@ -71,6 +75,7 @@ export function DataTable({ fixed: 'right', render: (_, record) => ( + {customActions && customActions(record)} {onEdit && ( - - - -
-

默认账号: cosmo / cosmo

-
- + 返回首页 + +
); } diff --git a/frontend/src/pages/admin/CelestialBodies.tsx b/frontend/src/pages/admin/CelestialBodies.tsx index e096e7c..f983e1f 100644 --- a/frontend/src/pages/admin/CelestialBodies.tsx +++ b/frontend/src/pages/admin/CelestialBodies.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { Modal, Form, Input, Select, Switch, InputNumber, Tag, Badge, Descriptions, Button, Space, Alert, Upload, Popconfirm, Row, Col, Tabs, Card } from 'antd'; +import { Modal, Form, Input, Select, Switch, InputNumber, Tag, Badge, Descriptions, Button, Space, Alert, Upload, Popconfirm, Row, Col, Tabs, Card, Tooltip } from 'antd'; import { CheckCircleOutlined, CloseCircleOutlined, SearchOutlined, UploadOutlined, DeleteOutlined, StarOutlined } from '@ant-design/icons'; import type { UploadFile } from 'antd/es/upload/interface'; import type { ColumnsType } from 'antd/es/table'; @@ -22,6 +22,11 @@ interface CelestialBody { description: string; details?: string; // Added details field is_active: boolean; + extra_data?: { + orbit_period_days?: number; + orbit_color?: string; + [key: string]: any; // Allow other extra data + }; resources?: { [key: string]: Array<{ id: number; @@ -205,7 +210,26 @@ export function CelestialBodies() { // Edit handler const handleEdit = (record: CelestialBody) => { setEditingRecord(record); - form.setFieldsValue(record); + + // Parse extra_data if it's a string (from backend JSON field) + let extraData = record.extra_data; + if (typeof extraData === 'string') { + try { + extraData = JSON.parse(extraData); + } catch (e) { + console.error('Failed to parse extra_data:', e); + extraData = {}; + } + } + + // Properly set form values including nested extra_data + const formValues = { + ...record, + extra_data: extraData || {}, // Ensure extra_data is an object + }; + + form.setFieldsValue(formValues); + setActiveTabKey('basic'); // Reset to basic tab setIsModalOpen(true); }; @@ -297,7 +321,8 @@ export function CelestialBodies() { ); toast.success(`${response.data.message} (上传到 ${response.data.upload_directory} 目录)`); - setRefreshResources(prev => prev + 1); // Trigger reload + // Trigger a refresh of resources + setRefreshResources(prev => prev + 1); return false; // Prevent default upload behavior } catch (error: any) { toast.error(error.response?.data?.detail || '上传失败'); @@ -307,6 +332,34 @@ export function CelestialBodies() { } }; + // Generate orbit for a celestial body + const handleGenerateOrbit = async (record: CelestialBody) => { + if (!['planet', 'dwarf_planet'].includes(record.type)) { + toast.warning('只有行星和矮行星可以生成轨道'); + return; + } + + setLoading(true); + try { + const response = await request.post( + `/celestial/admin/orbits/generate?body_ids=${record.id}` + ); + + if (response.data.results && response.data.results.length > 0) { + const result = response.data.results[0]; + if (result.status === 'success') { + toast.success(`轨道生成成功!共 ${result.num_points} 个点`); + } else { + toast.error(`轨道生成失败:${result.error}`); + } + } + } catch (error: any) { + toast.error(error.response?.data?.detail || '轨道生成失败'); + } finally { + setLoading(false); + } + }; + // Handle resource delete const handleResourceDelete = async (resourceId: number) => { try { @@ -452,6 +505,33 @@ export function CelestialBodies() { statusField="is_active" rowKey="id" pageSize={10} + customActions={(record) => { + // Show "Generate Orbit" button for all types, but disable for non-planets + const canGenerateOrbit = ['planet', 'dwarf_planet'].includes(record.type); + + return ( + handleGenerateOrbit(record)} + okText="确认" + cancelText="取消" + disabled={!canGenerateOrbit} + > + + + + + ); + }} /> + {/* Orbit parameters for planets and dwarf planets */} + prevValues.type !== currentValues.type}> + {({ getFieldValue }) => { + const bodyType = getFieldValue('type'); + if (!['planet', 'dwarf_planet'].includes(bodyType)) { + return null; + } + + return ( + + + + + + + + + + + + + } + type="info" + style={{ marginBottom: 16 }} + /> + ); + }} + + {editingRecord && (