11 KiB
星河(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 分支中渲染 <StarGalaxy @cardClick="handleCardClick" />,复用现有 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:
<view class="ring-item">
<view class="top-label">TOP 4</view> <!-- 14rpx,label 在 cover 上方 -->
<image class="cover" src="..." /> <!-- 56rpx,cover 在 label 下方 -->
</view>
- 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 皇冠脉冲
@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 可访问性
@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
<image lazy-load>(与现有 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. 验收标准
- ✅ square 页点击「星河」tab,显示 12 个 item 的 3D 排行榜
- ✅ TOP 1-3 在上方颁奖台位置,cover 下方有金/银/铜 TOP N 标签
- ✅ TOP 4-12 在下方椭圆轨道,9 个 item 等大 46×70,标签在 cover 上方
- ✅ 9 item 顺时针连续旋转(36s 一圈)
- ✅ 近大远小(scale 0.75→1.15)表达 3D 透视
- ✅ 点击任一卡片跳 asset-detail
- ✅ 双击点赞 + 波纹动画
- ✅ Loading / Empty / Error 状态都正常显示
- ✅
prefers-reduced-motion: reduce时关闭旋转 - ✅ H5 + 小程序 + APP 三端视觉一致