# 星河(StarGalaxy)组件设计 > **日期**: 2026-06-10 > **状态**: 待评审 > **目的**: 为 square 页面的「星河」tab 设计一个 3D 倾斜椭圆轨道 + 顺时针旋转的排行榜组件 > **参考文档**: `docs/figma-analysis-xiaohongshu-stars.md`(Figma 节点 94:78「星河新版本」) --- ## 1. 背景 square 页面现有 3 个内容 tab(星河 / 星榜 / 广场),其中「星榜」是 12 行的横向列表样式(`HotCategoryBlock.vue`)。本次新增「星河」tab,沿用 Figma 中的双层结构: - **上层**:TOP 1-3 颁奖台(cover + 下方标签 + 钻石渐变外框) - **下层**:TOP 4-12 散落在 65° 倾斜椭圆轨道上,绕中心 3D 旋转 设计追求饭圈甜美梦幻风格(粉红渐变 + 玻璃拟态 + 暖黄光晕 + 渐变描边),不显示用户名/点赞数(保持 cover 纯净)。 --- ## 2. 文件结构 ``` frontend/pages/square/components/StarGalaxy/ ├── index.vue ← 容器:数据加载、装饰层、TOP 1-3 + ScatteredRanks 编排 ├── PodiumCard.vue ← TOP 1-3 颁奖台卡片(cover + 下方 TOP N 标签 + 钻石外框) ├── ScatteredRanks.vue ← TOP 4-12 9 个散落 item(cover + 上方 TOP N 标签) └── config.js ← 9 slot 位置/translate/scale 公式 ``` `square.vue` 改动:在星河 tab 分支中渲染 ``,复用现有 `handleCardClick`(单击跳详情 + 双击点赞)。 --- ## 3. 组件职责 | 组件 | 职责 | 内部状态 | 输入 Props | 事件 | |----|----|----|----|----| | `StarGalaxy/index.vue` | 数据加载(`getHotRankingApi`)、装饰层渲染、3D 椭圆轨道 SVG、布局编排 | `items`、`loading` | — | `cardClick(item)` | | `PodiumCard.vue` | 单张 TOP 1-3 大卡:钻石渐变外框 + cover + 下方 TOP N 标签 + 可选皇冠 | — | `rank: 4\|5\|6` 隐式;`item: HotRankingItem`、`size: {w,h}` | `click(item)` | | `ScatteredRanks.vue` | TOP 4-12 共 9 个 item 散落在椭圆轨道 | — | `items: HotRankingItem[]` (length=9) | `cardClick(item)` | | `config.js` | 9 个 slot 的绝对位置/translate/scale/zIndex 公式 | — | — | — | --- ## 4. 数据流 ``` StarGalaxy/index.vue (onMounted) │ ├── getHotRankingApi("displaying", null, 1, 12) │ │ │ └── res.data.items[0..11] │ ├── Promise.all → getAssetCoverRealUrl(item.cover_url) // 复用 HotCategoryBlock 的模式 │ └── items 拆成两段: ├── items.slice(0,3) → PodiumCard × 3 └── items.slice(3,12) → ScatteredRanks(9 个 item) ``` 事件冒泡:所有 card 点击 → `emit('cardClick', item)` → `square.vue` 的 `handleCardClick`(已有,含单击跳详情 + 双击点赞)。 --- ## 5. 视觉设计 ### 5.1 容器尺寸 - 总宽 750rpx × 高约 1440rpx - 适配 iPhone 标准 (375×812) - 整体分上下两层:上层颁奖台 y=48~700,下层椭圆轨道 y=800~1440 ### 5.2 装饰层(背景 overlay) - 粉红渐变:`linear-gradient(179deg, #FFE5E5 0%, #F3A0A1 0%, #FF9C9C 86%, #FF2024 100%)`(覆盖 square 现有渐变之上) - 樱花粉光晕:圆形 `#F3D3E3` + `filter: blur(60px)`,居中(top 580) - 暖黄光晕:圆形 `#FFFABD` + `filter: blur(50px)`,top 50 - ⚠️ 不动 square.vue 现有渐变背景,星河只画自己的装饰层 ### 5.3 PodiumCard 三个实例 | 排名 | 位置 (left, top) | 卡片尺寸 | 标签尺寸 | 装饰图 | |----|----|----|----|----| | TOP 1 | (50%, 400) translateX(-50%) | 240×260 (cover 240×260) | 192×44 (金渐变) | 钻石外框 + 皇冠 | | TOP 2 | (60, 120) | 200×200 (cover 200×200) | 156×36 (银渐变) | 钻石外框 | | TOP 3 | (470, 150) | 192×192 (cover 192×192) | 156×36 (铜渐变) | 钻石外框 | **PodiumCard 内部层级**(从下到上): ``` ┌─ 钻石渐变外框(filter: blur(8-12px),不规则圆角 8px 22px 8px 19px) │ ├─ 藏品主图(不规则圆角 6px 20px 6px 17px) │ ├─ 青绿色高光 overlay(180deg #53F4D3 → 透明) │ └─ 钻石渐变边框层(4px 渐变描边) └─ TOP N 标签(绝对定位 bottom: -4px,居中) ``` **皇冠**(仅 TOP 1):绝对定位 `top: -44px; left: 50%; transform: translateX(-50%)`,font-size: 44rpx,2s 循环 pulse 动画。 ### 5.4 ScatteredRanks 9 个 slot 位置 9 个 item 在 65° 倾斜椭圆上,等角度 40° 间隔(顺时针),起始角 180°(TOP 4 在正下方 = 最前)。 椭圆参数: - 圆心 (cx, cy) = (187, 510) - 水平半径 rx = 130 - 垂直半径 ry = 55(cos(65°) ≈ 0.423,模拟向后倾斜 65°) - 起始角 startAngle = 180°(slot 0 在正下方) - 间隔角 step = -40°(顺时针 = 负方向) | Slot | Rank | 中心 (x, y) | 渲染尺寸 (w×h) | scale | z-index | |----|----|----|----|----|----| | 0 | TOP 4 | (187, 565) | 46×70 | 1.15 | 10 | | 1 | TOP 5 | (271, 552) | 46×70 | 1.05 | 9 | | 2 | TOP 6 | (321, 488) | 46×70 | 0.95 | 6 | | 3 | TOP 7 | (300, 482) | 46×70 | 0.85 | 3 | | 4 | TOP 8 | (232, 458) | 46×70 | 0.75 | 0 | | 5 | TOP 9 | (142, 458) | 46×70 | 0.75 | 0 | | 6 | TOP 10 | (74, 482) | 46×70 | 0.85 | 3 | | 7 | TOP 11 | (8, 488) | 46×70 | 0.95 | 6 | | 8 | TOP 12 | (80, 518) | 46×70 | 1.05 | 9 | **OVERRIDES**:TOP 6 / TOP 11 推到边缘 (8, 488) 和 (321, 488),避免与 TOP 5/7、TOP 12/10 重叠。 ### 5.5 单个 item 结构(ScatteredRanks) 尺寸 46×70 = label 14 + gap 2 + cover 56: ```vue TOP 4 ``` - **label**(顶部 14rpx): - 背景:`radial-gradient(ellipse, #C8E6FF, #fff 50%, #4D9AF8)`(蓝白钻石) - TOP 4 label 用金渐变:`#FFFFFF, #FFFABD 30%, #4D9AF8 100%` - 圆角 7rpx - 文字:14rpx Baloo Bhai(fallback monospace),色 `#FFFABD`,`text-shadow: -1px 1px 2px rgba(206,9,9,0.84)` - **cover**(下方 56rpx): - 圆角 5rpx - box-shadow:`3px 3px 6px rgba(198,13,13,0.45)`(按 scale 调整) ### 5.6 颜色 / 渐变 / 阴影 | Token | 值 | 用途 | |----|----|----| | 主背景 | `linear-gradient(179deg, #FFE5E5 0%, #F3A0A1 0%, #FF9C9C 86%, #FF2024 100%)` | 装饰层渐变 | | 樱花粉光晕 | `#F3D3E3` + `blur(60px)` | 椭圆中心光晕 | | 暖黄光晕 | `#FFFABD` + `blur(50px)` | 顶部装饰光晕 | | 钻石渐变(外框) | `radial-gradient(ellipse at -10% 5%, #86BEFF, #FF3939 32%, #88FFCE 59%, #4D9AF8)` | PodiumCard 描边 | | 蓝白钻石(标签) | `radial-gradient(ellipse, #C8E6FF, #fff 50%, #4D9AF8)` | 9 item label | | 金渐变(TOP 4 label) | `radial-gradient(ellipse, #FFFFFF, #FFFABD 30%, #4D9AF8 100%)` | TOP 4 label | | 金渐变(TOP 1 标签) | `radial-gradient(ellipse, #FFD700, #FFF6A8 30%, #DAA520 100%)` | TOP 1 标签 | | 银渐变(TOP 2 标签) | `radial-gradient(ellipse, #C0C0C0, #E8E8E8 50%, #7A7A7A)` | TOP 2 标签 | | 铜渐变(TOP 3 标签) | `radial-gradient(ellipse, #CD7F32, #E8A45C 50%, #A0522D)` | TOP 3 标签 | | TOP 数字字色 | `#FFFABD` + `text-shadow: -1px 1px 2px rgba(206,9,9,0.84)` | label 文字 | | 卡片阴影 | `3px 3px 6px rgba(198,13,13,0.45)`(随 scale 微调) | cover 阴影 | ### 5.7 字体 | 字体 | 用途 | Fallback | |----|----|----| | Baloo Bhai | label 数字(TOP 4、TOP 5 等) | monospace | | HYQiHei | 不使用(已移除用户名显示) | PingFang | | C800 | 不使用(已移除 TOP 排名文字) | system bold sans | --- ## 6. 动画设计 ### 6.1 9 item 顺时针旋转 ``` @keyframes orbit { 0% { transform: translate(0,0) scale(1.15); } /* slot 0: front */ 11.11% { transform: translate(84px,-13px) scale(1.05); } /* slot 1 */ 22.22% { transform: translate(157px,-43px) scale(0.95); } /* slot 2 */ 33.33% { transform: translate(113px,-83px) scale(0.85); } /* slot 3 */ 44.44% { transform: translate(45px,-107px) scale(0.75); } /* slot 4 */ 55.55% { transform: translate(-45px,-107px) scale(0.75); } /* slot 5 */ 66.66% { transform: translate(-113px,-83px) scale(0.85); } /* slot 6 */ 77.77% { transform: translate(-156px,-43px) scale(0.95); } /* slot 7 */ 88.88% { transform: translate(-84px,-13px) scale(1.05); } /* slot 8 */ 100% { transform: translate(0,0) scale(1.15); } /* loop */ } ``` - 总周期 36s,4s 一步 - 线性 timing,匀速旋转 - infinite 循环 ### 6.2 9 个 item 的 delay 错开 ``` .ring-item { animation: orbit 36s linear infinite; } .r0 { animation-delay: 0s; } /* TOP 4 起始 slot 0 */ .r1 { animation-delay: -4s; } /* TOP 5 起始 slot 1 */ .r2 { animation-delay: -8s; } /* TOP 6 起始 slot 2 */ ... .r8 { animation-delay: -32s; } /* TOP 12 起始 slot 8 */ ``` 负值 delay 让每个 item 起始位置 = 对应的 slot,9 个 item 静态分布在 9 个 slot 上。 ### 6.3 皇冠脉冲 ```css @keyframes crownPulse { 0%, 100% { transform: translateX(-50%) scale(1); } 50% { transform: translateX(-50%) scale(1.15); } } .crown { animation: crownPulse 2s ease-in-out infinite; } ``` ### 6.4 可访问性 ```css @media (prefers-reduced-motion: reduce) { .ring-item { animation: none; } .crown { animation: none; } /* item 用 inline transform 写到元素上,作为静态位置 */ } ``` --- ## 7. 状态管理 | 状态 | 显示 | |----|----| | Loading | 12 个骨架占位(3 大 + 9 小)+ shimmer 动画 | | Success | 完整布局 | | Empty (items.length < 3) | 「数据加载中」+ 重试按钮 | | Error | 居中提示「加载失败,点击重试」+ 重试按钮 | --- ## 8. 交互细节 - **单击 PodiumCard / ScatteredRank 缩略图**:emit('cardClick', item) → square.vue 已有逻辑 → 跳 `pages/asset-detail/asset-detail` - **双击**:同样走 square.vue 的 `handleCardClick` → `doubleTapLike` + 波纹动画 - **TOP 1 奖牌/皇冠**:CSS keyframes 微脉冲(scale 1.0 → 1.15 → 1.0,2s 循环) --- ## 9. 性能/可访问性 - 图片懒加载用 uni-app ``(与现有 HotCategoryBlock 一致) - `prefers-reduced-motion: reduce` 关闭旋转和脉冲 - 9 item 动画使用 GPU 加速(transform) - 椭圆轨道 SVG 装饰层 `pointer-events: none` --- ## 10. 风险与备选 | 风险 | 缓解 | |----|----| | 旋转动画在低端机上卡顿 | 可降级到 60s 周期;或加 `will-change: transform` | | 椭圆轨道的虚线 SVG 在某些小程序不渲染 | 提供 `v-if` 兜底,仅 H5 显示 | | 数据 < 9 条时 ScatteredRanks 渲染异常 | 用 v-for 配合 length 校验,< 9 时只渲染实际条数 | --- ## 11. 验收标准 1. ✅ square 页点击「星河」tab,显示 12 个 item 的 3D 排行榜 2. ✅ TOP 1-3 在上方颁奖台位置,cover 下方有金/银/铜 TOP N 标签 3. ✅ TOP 4-12 在下方椭圆轨道,9 个 item 等大 46×70,标签在 cover 上方 4. ✅ 9 item 顺时针连续旋转(36s 一圈) 5. ✅ 近大远小(scale 0.75→1.15)表达 3D 透视 6. ✅ 点击任一卡片跳 asset-detail 7. ✅ 双击点赞 + 波纹动画 8. ✅ Loading / Empty / Error 状态都正常显示 9. ✅ `prefers-reduced-motion: reduce` 时关闭旋转 10. ✅ H5 + 小程序 + APP 三端视觉一致