# StarGalaxy 组件实现计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 在 square 页面「星河」tab 渲染一个 3D 倾斜椭圆轨道 + 9 item 顺时针旋转 + TOP 1-3 颁奖台的排行榜组件 **Architecture:** 三层拆分 — `index.vue`(容器/数据/装饰)+ `PodiumCard.vue`(TOP 1-3 大卡)+ `ScatteredRanks.vue`(TOP 4-12 9 个散落 item)+ `config.js`(9 slot 位置公式)。单组 `@keyframes orbit` 配合 9 个不同 `animation-delay` 实现旋转。复用现有 `getHotRankingApi` 和 `getAssetCoverRealUrl`。 **Tech Stack:** Vue 3 Composition API + uni-app + CSS keyframes(uni-app 跨端支持 `transform`)。**无单元测试框架**(项目 package.json 只有 vuex),通过 H5 端 `npm run dev:h5` 可视化验证。 **Spec:** `docs/superpowers/specs/2026-06-10-square-stargalaxy-component-design.md` --- ## 文件结构 | 路径 | 类型 | 职责 | |----|----|----| | `frontend/pages/square/components/StarGalaxy/index.vue` | 新建 | 容器组件:数据加载、装饰层、3D 椭圆轨道 SVG、TOP 1-3 颁奖台 + ScatteredRanks 编排 | | `frontend/pages/square/components/StarGalaxy/PodiumCard.vue` | 新建 | TOP 1-3 大卡(钻石渐变外框 + cover + 下方 TOP N 标签 + 可选皇冠) | | `frontend/pages/square/components/StarGalaxy/ScatteredRanks.vue` | 新建 | TOP 4-12 9 个散落 item(cover + 上方 TOP N 标签) | | `frontend/pages/square/components/StarGalaxy/config.js` | 新建 | 9 slot 位置/translate/scale 公式 + KEYFRAMES 常量 | | `frontend/pages/square/square.vue` | 修改 | 在「星河」tab 分支渲染 ``,并设置 onShow 重置 activeContentTab 为 "xinghe" | 每个新文件职责单一,文件之间通过 props/emit 通信。 --- ## Task 1: 创建 config.js **Files:** - Create: `frontend/pages/square/components/StarGalaxy/config.js` - [ ] **Step 1.1: 创建目录** ```bash mkdir -p frontend/pages/square/components/StarGalaxy ``` - [ ] **Step 1.2: 写入 config.js** ```js // StarGalaxy 组件配置常量 // 9 个散落 item 沿 65° 倾斜椭圆轨道排列 // slot 0 = 最前(底部,TOP 4 起始位置),slot 4-5 = 最后(顶部) export const RING = { cx: 187, // 椭圆圆心 x cy: 510, // 椭圆圆心 y rx: 130, // 水平半径 ry: 55, // 垂直半径(cos(65°) ≈ 0.423,模拟向后倾 65°) startAngle: 180, // 起始角:slot 0 在正下方 step: 40, // 间隔角(顺时针 = 负方向 → step = -40 在 CSS 中) } // item 固定尺寸(label 在 cover 上方) export const ITEM = { width: 46, // cover + label 宽度 labelHeight: 14, // 顶部 label 高度 coverHeight: 56, // 底部 cover 高度 gap: 2, // label 与 cover 之间的间距 } // total: 14 + 2 + 56 = 72 // TOP 6 / TOP 11 推到边缘,避免与 TOP 5/7、TOP 10/12 重叠 // TOP 6 推到屏幕右侧 (321, 488),TOP 11 推到屏幕左侧 (8, 488) export const OVERRIDES = { 6: { x: 321, y: 488 }, 11: { x: 8, y: 488 }, } // 计算 y 在椭圆前/后位置的比例(0 = 最后, 1 = 最前) function yFactor(y) { // y=458 (back) → 0; y=565 (front) → 1 return Math.max(0, Math.min(1, (y - 458) / 107)) } // 生成 9 个 item 的位置配置 export function generateRingPositions() { return Array.from({ length: 9 }, (_, i) => { const rank = i + 4 const alpha = (RING.startAngle + i * RING.step) * Math.PI / 180 const baseX = RING.cx + RING.rx * Math.sin(alpha) - ITEM.width / 2 const baseY = RING.cy - RING.ry * Math.cos(alpha) - (ITEM.labelHeight + ITEM.gap + ITEM.coverHeight) / 2 const ovr = OVERRIDES[rank] const y = ovr?.y ?? baseY const x = ovr?.x ?? baseX const f = yFactor(y) return { rank, x, y, scale: 0.75 + 0.40 * f, // 0.75 → 1.15 zIndex: Math.round(f * 10), // 0 → 10 } }) } // 单组 @keyframes(CSS 模板字符串) // translate 值是相对 slot 0 中心 (164, 530) 的偏移 export const ORBIT_KEYFRAMES = ` @keyframes orbit { 0% { transform: translate(0,0) scale(1.15); } 11.11% { transform: translate(84px,-13px) scale(1.05); } 22.22% { transform: translate(157px,-43px) scale(0.95); } 33.33% { transform: translate(113px,-83px) scale(0.85); } 44.44% { transform: translate(45px,-107px) scale(0.75); } 55.55% { transform: translate(-45px,-107px) scale(0.75); } 66.66% { transform: translate(-113px,-83px) scale(0.85); } 77.77% { transform: translate(-156px,-43px) scale(0.95); } 88.88% { transform: translate(-84px,-13px) scale(1.05); } 100% { transform: translate(0,0) scale(1.15); } } @keyframes crownPulse { 0%, 100% { transform: translateX(-50%) scale(1); } 50% { transform: translateX(-50%) scale(1.15); } } ` // 各 slot 对应 ring-item 类的 r0..r8 的 delay(负值让 item 起始位置 = slot) export const RING_DELAYS = [0, -4, -8, -12, -16, -20, -24, -28, -32] ``` - [ ] **Step 1.3: 验证文件创建** ```bash ls -la frontend/pages/square/components/StarGalaxy/config.js ``` Expected: 文件存在,~80 行 - [ ] **Step 1.4: 提交** ```bash cd frontend git add pages/square/components/StarGalaxy/config.js git commit -m "feat(stargalaxy): add ring position config and orbit keyframes" ``` --- ## Task 2: 创建 PodiumCard.vue **Files:** - Create: `frontend/pages/square/components/StarGalaxy/PodiumCard.vue` - [ ] **Step 2.1: 写入 PodiumCard.vue** ```vue ``` - [ ] **Step 2.2: 验证文件创建** ```bash ls -la frontend/pages/square/components/StarGalaxy/PodiumCard.vue wc -l frontend/pages/square/components/StarGalaxy/PodiumCard.vue ``` Expected: ~150 行 - [ ] **Step 2.3: 提交** ```bash cd frontend git add pages/square/components/StarGalaxy/PodiumCard.vue git commit -m "feat(stargalaxy): add PodiumCard for TOP 1-3 with gold/silver/bronze labels" ``` --- ## Task 3: 创建 ScatteredRanks.vue **Files:** - Create: `frontend/pages/square/components/StarGalaxy/ScatteredRanks.vue` - [ ] **Step 3.1: 写入 ScatteredRanks.vue** ```vue ``` - [ ] **Step 3.2: 验证** ```bash ls -la frontend/pages/square/components/StarGalaxy/ScatteredRanks.vue ``` - [ ] **Step 3.3: 提交** ```bash cd frontend git add pages/square/components/StarGalaxy/ScatteredRanks.vue git commit -m "feat(stargalaxy): add ScatteredRanks with 9 ring items + 36s orbit animation" ``` --- ## Task 4: 创建 StarGalaxy/index.vue 容器 **Files:** - Create: `frontend/pages/square/components/StarGalaxy/index.vue` - [ ] **Step 4.1: 写入 index.vue** ```vue ``` - [ ] **Step 4.2: 验证文件结构** ```bash ls -la frontend/pages/square/components/StarGalaxy/ ``` Expected: 4 files (config.js, PodiumCard.vue, ScatteredRanks.vue, index.vue) - [ ] **Step 4.3: 提交** ```bash cd frontend git add pages/square/components/StarGalaxy/index.vue git commit -m "feat(stargalaxy): add container with data loading, decoration, podium + scattered orchestration" ``` --- ## Task 5: 在 square.vue 中集成 StarGalaxy **Files:** - Modify: `frontend/pages/square/square.vue:42-49`(添加星河分支) - [ ] **Step 5.1: 导入 StarGalaxy 组件** 在 `square.vue` 顶部 import 区域(约第 71 行附近)添加: ```js import StarGalaxy from "./components/StarGalaxy/index.vue"; ``` - [ ] **Step 5.2: 添加星河分支** 在 `` 之前,添加新的星河分支(替换原 `` 注释块): ```vue ``` - [ ] **Step 5.3: 验证 square.vue 语法** ```bash cd frontend npx --no-install eslint pages/square/square.vue 2>/dev/null || echo "无 eslint,跳过" ``` - [ ] **Step 5.4: 提交** ```bash cd frontend git add pages/square/square.vue git commit -m "feat(square): wire StarGalaxy component into 星河 tab" ``` --- ## Task 6: H5 端可视化验证 **Files:** 无(仅验证) - [ ] **Step 6.1: 启动 H5 调试服务** ```bash cd frontend npm run dev:h5 ``` Expected: 浏览器打开 `http://localhost:8080`(或类似端口),square 页面正常加载。 - [ ] **Step 6.2: 进入「星河」tab 视觉验证** 手动检查清单: - [ ] 顶部「★ 星河 ★」标题可见 - [ ] 3 张 TOP 1-3 颁奖台卡片显示,TOP 1 在中央最大,TOP 2 在左上,TOP 3 在右上 - [ ] TOP 1 顶部有皇冠,cover 下方有金渐变「TOP 1」标签 - [ ] TOP 2 下方有银渐变「TOP 2」标签 - [ ] TOP 3 下方有铜渐变「TOP 3」标签 - [ ] 下方椭圆轨道上有 9 个 item(TOP 4-12)排成圆环 - [ ] TOP 4 在最前(最大),TOP 8/9 在最后(最小) - [ ] 9 个 item 顺时针缓慢旋转(36s 一圈) - [ ] 点击任一 item 跳到 asset-detail 页 - [ ] **Step 6.3: 检查动画** - [ ] 打开 DevTools 观察 .ring-item 的 transform 在变化 - [ ] 旋转应该是匀速、连续、无卡顿 - [ ] 前后 item 的大小差异明显(TOP 4 比 TOP 8 大约 1.5 倍) - [ ] **Step 6.4: 检查错误状态** 在 DevTools 中断网(Network → Offline),刷新页面,应看到「加载失败,点击重试」+ 重试按钮。恢复网络点击重试,数据应正常加载。 - [ ] **Step 6.5: 提交验证记录(如有问题修复)** ```bash cd frontend git status # 如有修改: git add -A git commit -m "fix(stargalaxy): visual verification fixes" || echo "无修改需提交" ``` --- ## Task 7: 跨端兼容性烟测 **Files:** 无(仅验证) - [ ] **Step 7.1: 验证关键 CSS 兼容性** 确认以下 CSS 属性能在 uni-app 跨端运行(小程序、H5、APP): - `transform: scale()` + `transform-origin: center` — ✅ uni-app 全支持 - `filter: blur()` — ✅ H5 + APP 支持,小程序需 `enable-backdrop-filter` - `radial-gradient` — ✅ H5 + APP 支持,小程序有限制 - `@keyframes` + `animation` — ✅ uni-app 全支持 如果小程序有问题,对应的 @media 块需要适配或使用条件编译。 - [ ] **Step 7.2: 文档记录** 在 `frontend/pages/square/components/StarGalaxy/README.md` 写组件使用说明: ```markdown # StarGalaxy 组件 square 页「星河」tab 的排行榜组件。 ## 视觉特征 - 65° 倾斜椭圆轨道 - TOP 1-3 颁奖台(cover + 下方标签 + 钻石外框) - TOP 4-12 9 个散落 item(cover + 上方标签) - 9 item 顺时针 36s 旋转一圈 - 近大远小(scale 0.75→1.15) ## 数据来源 `getHotRankingApi("displaying", null, 1, 12)` — 与「星榜」共用 API,取前 12 条。 ## 文件 - `index.vue` — 容器 - `PodiumCard.vue` — TOP 1-3 大卡 - `ScatteredRanks.vue` — TOP 4-12 9 散落 item - `config.js` — 位置公式和 keyframes ## 可调参数 - 椭圆倾斜角(config.js: RING.ry)— 改 ry 调整倾斜度 - 旋转周期(ScatteredRanks.vue: animation duration)— 改 36s 调整速度 - 近大远小范围(keyframes scale 0.75→1.15)— 改 0.75/1.15 调整 ## 可访问性 - `prefers-reduced-motion: reduce` 时关闭旋转动画 - label 文字使用高对比度的 #FFFABD + 红色 text-shadow ``` ```bash cd frontend git add pages/square/components/StarGalaxy/README.md git commit -m "docs(stargalaxy): add component README" ``` --- ## 验收清单(最终) - [ ] Task 1-5 全部完成并提交 - [ ] Task 6.2 视觉验证清单全部 ✅ - [ ] Task 6.3 动画流畅 - [ ] Task 6.4 错误状态可重试 - [ ] Task 7 README 文档完成 - [ ] 跨端验证(至少 H5 通过;小程序/APP 在后续 PR 验证) - [ ] 代码无 ESLint 错误(如有 ESLint 配置) - [ ] 所有提交信息遵循 `feat/fix/docs/...` Conventional Commits 规范 --- ## 风险与回退 | 风险 | 回退方案 | |----|----| | 旋转动画在低端机卡顿 | 在 keyframes 中加 `transform: translateZ(0)` 强制 GPU 加速 | | 小程序不支持 `radial-gradient` | 改用 `background-image: linear-gradient` 模拟或接受色彩降级 | | 椭圆轨道 SVG 在小程序不显示 | 用纯 CSS border 模拟椭圆装饰 | | TOP 6/11 推到边缘与不同屏幕宽度冲突 | 在 config.js 用百分比 + 媒体查询自适应 | --- ## 后续 PR(v2 增强,不在本计划范围) - 缩略图懒加载优化(``) - 9 item 悬停暂停动画 - 减少运动偏好支持进一步增强 - 双指捏合缩放查看详情