277 lines
11 KiB
Markdown
277 lines
11 KiB
Markdown
# 星河(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:
|
||
|
||
```vue
|
||
<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 皇冠脉冲
|
||
|
||
```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 `<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. 验收标准
|
||
|
||
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 三端视觉一致
|