style(stargalaxy): remove shared podium size — each .podium-N controls its own width/height
3
.gitignore
vendored
@ -46,3 +46,6 @@ hookify.*.local.md
|
||||
# statisticService runtime logs (created by logger when running tests)
|
||||
backend/services/statisticService/**/logs/
|
||||
backend/services/statisticService/**/logs/*.log
|
||||
|
||||
# superpowers
|
||||
.superpowers
|
||||
1021
docs/superpowers/plans/2026-06-10-square-stargalaxy-component.md
Normal file
@ -0,0 +1,276 @@
|
||||
# 星河(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 三端视觉一致
|
||||
@ -42,8 +42,8 @@
|
||||
:key="item.id || index"
|
||||
class="grid-card"
|
||||
:class="{
|
||||
'grid-card-top': index < 3,
|
||||
[`grid-card-top-${index + 1}`]: index < 3,
|
||||
[`grid-card-top-${index + 1}`]: index < 5,
|
||||
'grid-card-top-other': index >= 5,
|
||||
}"
|
||||
@click="handleCardClick(item)"
|
||||
>
|
||||
@ -147,9 +147,33 @@ const tabs = [
|
||||
{
|
||||
key: "hot",
|
||||
label: "热度榜",
|
||||
icon: "/static/square/rementubiao.png",
|
||||
iconWidth: 32,
|
||||
iconHeight: 40,
|
||||
icon: "/static/square/galaxy/dianzanbang.png",
|
||||
iconWidth: 64,
|
||||
iconHeight: 72,
|
||||
fetch: () => getHotRankingApi("displaying", null, 1, 11),
|
||||
},
|
||||
{
|
||||
key: "new",
|
||||
label: "活跃榜",
|
||||
icon: "/static/square/galaxy/huoyuebang.png",
|
||||
iconWidth: 64,
|
||||
iconHeight: 72,
|
||||
fetch: () => getHotRankingApi("displaying", null, 1, 11),
|
||||
},
|
||||
{
|
||||
key: "trending",
|
||||
label: "曝光榜",
|
||||
icon: "/static/square/galaxy/baoguangbang.png",
|
||||
iconWidth: 64,
|
||||
iconHeight: 72,
|
||||
fetch: () => getHotRankingApi("displaying", null, 1, 11),
|
||||
},
|
||||
{
|
||||
key: "trending",
|
||||
label: "同城榜",
|
||||
icon: "/static/square/galaxy/tongchengbang.png",
|
||||
iconWidth: 64,
|
||||
iconHeight: 72,
|
||||
fetch: () => getHotRankingApi("displaying", null, 1, 11),
|
||||
},
|
||||
];
|
||||
@ -279,7 +303,7 @@ onUnmounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.hot-category-block {
|
||||
padding: 0 9.5rpx;
|
||||
border-radius: 24rpx;
|
||||
@ -289,11 +313,12 @@ onUnmounted(() => {
|
||||
/* Tab 栏 */
|
||||
.ranking-tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 16rpx;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
top: 16rpx;
|
||||
transform: translate(-50%);
|
||||
opacity: 0.8;
|
||||
border-top-left-radius: 14px;
|
||||
@ -302,20 +327,22 @@ onUnmounted(() => {
|
||||
border-bottom-left-radius: 7px;
|
||||
z-index: 1; /* 在内容网格之下 */
|
||||
width: 480rpx;
|
||||
height: 80rpx;
|
||||
height: 96rpx;
|
||||
background: linear-gradient(183.58deg, #ff5a5d -36.55%, #c2ebff 121.2%);
|
||||
backdrop-filter: blur(11.699999809265137px);
|
||||
}
|
||||
|
||||
.ranking-tab-item {
|
||||
height: 80rpx;
|
||||
width: 88rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
transition: all 0.25s ease;
|
||||
position: absolute;
|
||||
top: 24rpx;
|
||||
/* 保持 position: relative,让 .ranking-tab-icon 的 position: absolute 能以本元素为定位基准。
|
||||
注意:不能写 position: absolute,否则 4 个 tab 会从 flex 流中抽离并全部堆在 left:0 重叠。 */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ranking-tab-item.active {
|
||||
@ -326,7 +353,11 @@ onUnmounted(() => {
|
||||
);
|
||||
backdrop-filter: blur(1.7999999523162842px);
|
||||
height: 144rpx;
|
||||
width: 88rpx;
|
||||
top: 40rpx;
|
||||
}
|
||||
|
||||
.ranking-tab-item.active .ranking-tab-icon {
|
||||
top: -24rpx;
|
||||
}
|
||||
|
||||
.ranking-tab-label {
|
||||
@ -341,7 +372,6 @@ onUnmounted(() => {
|
||||
.ranking-tab-icon {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
}
|
||||
|
||||
.ranking-tab-item.active .ranking-tab-label {
|
||||
@ -451,7 +481,6 @@ onUnmounted(() => {
|
||||
position: relative;
|
||||
width: 90rpx;
|
||||
height: 120rpx;
|
||||
|
||||
}
|
||||
.card-image {
|
||||
width: 100%;
|
||||
@ -475,17 +504,57 @@ onUnmounted(() => {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* 前 3 名专属:卡片整体突出 */
|
||||
.grid-card-top {
|
||||
}
|
||||
|
||||
.grid-card-top-1 {
|
||||
background: url("/static/square/galaxy/TOP.png") no-repeat center;
|
||||
}
|
||||
|
||||
.grid-card-top-2 {
|
||||
background: url("/static/square/galaxy/TOP2.png") no-repeat center;
|
||||
}
|
||||
|
||||
.grid-card-top-3 {
|
||||
background: url("/static/square/galaxy/TOP3.png") no-repeat center;
|
||||
}
|
||||
.grid-card-top-4 {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
// [方案3] 伪元素承载 bj.png,对图片单独设 opacity
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: url("/static/dashboard/bj.png") center / cover no-repeat;
|
||||
opacity: 0.8; // ⬅ 调这个数控制图片透明度(0=完全透明,1=完全不透明)
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
.grid-card-top-5 {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
// [方案3] 伪元素承载 bj.png,对图片单独设 opacity
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: url("/static/dashboard/bj.png") center / cover no-repeat;
|
||||
opacity: 0.8; // ⬅ 调这个数控制图片透明度(0=完全透明,1=完全不透明)
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-card-top-other {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: url("/static/square/top/bj.png") center / cover no-repeat;
|
||||
opacity: 0.26;
|
||||
}
|
||||
}
|
||||
|
||||
/* 前 3 名专属:包裹藏品图的边框图(叠加在 card-image 之上) */
|
||||
|
||||
@ -39,12 +39,7 @@ function handleClick() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// 公共背景图(不随 rank 变化,所有 podium 卡片都显示)
|
||||
// 放在 .podium-N 之前以确保不被任何具体 rank 的样式覆盖
|
||||
background-image: url("/static/square/galaxy/topbj3.png");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
// 宽高交给各 .podium-N 自由控制(不设默认值)
|
||||
}
|
||||
|
||||
/* TOP 1: 中央大卡(最大) */
|
||||
@ -52,27 +47,84 @@ function handleClick() {
|
||||
top: 400rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 96rpx;
|
||||
height: 128rpx;
|
||||
width: 240rpx;
|
||||
height: 320rpx;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("/static/square/galaxy/topbj1.png");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0.95;
|
||||
transform: scale(1.15);
|
||||
}
|
||||
.cover-wrap {
|
||||
width: 144rpx;
|
||||
height: 240rpx;
|
||||
bottom: 136rpx;
|
||||
left: 32rpx;
|
||||
.podium-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TOP 2: 右上 */
|
||||
/* TOP 2: 左上 */
|
||||
.podium-2 {
|
||||
top: 150rpx;
|
||||
right: 60rpx;
|
||||
width: 96rpx;
|
||||
height: 128rpx;
|
||||
top: 184rpx;
|
||||
left: -96rpx;
|
||||
width: 200rpx;
|
||||
height: 280rpx;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("/static/square/galaxy/topbj2.png");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0.95;
|
||||
transform: rotate(-14deg) scale(0.85);
|
||||
}
|
||||
.cover-wrap {
|
||||
width: 144rpx;
|
||||
height: 240rpx;
|
||||
bottom: 72rpx;
|
||||
.podium-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TOP 3: 左上 */
|
||||
/* TOP 3: 右上 */
|
||||
.podium-3 {
|
||||
top: 152rpx;
|
||||
left: 96rpx;
|
||||
width: 160rpx;
|
||||
height: 208rpx;
|
||||
.podium-frame {
|
||||
width: 160rpx;
|
||||
height: 208rpx;
|
||||
right: -96rpx;
|
||||
width: 220rpx;
|
||||
height: 260rpx;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("/static/square/galaxy/topbj3.png");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
opacity: 0.95;
|
||||
transform: scale(0.85);
|
||||
}
|
||||
.cover-wrap {
|
||||
width: 176rpx;
|
||||
height: 180rpx;
|
||||
bottom: 72rpx;
|
||||
.podium-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,8 +139,7 @@ function handleClick() {
|
||||
}
|
||||
|
||||
.cover-wrap {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@ -96,13 +147,12 @@ function handleClick() {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.top-label {
|
||||
width: 64rpx;
|
||||
position: absolute;
|
||||
bottom: -64rpx;
|
||||
bottom: 48rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: #fffabd;
|
||||
|
||||
@ -28,15 +28,11 @@
|
||||
@click="handleClick(items[i])"
|
||||
>
|
||||
<!-- 相框(最底层) -->
|
||||
<image
|
||||
class="ring-frame"
|
||||
:src="ringFrameSrc(p.rank)"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<image class="ring-frame" :src="ringFrameSrc(p.rank)" mode="aspectFit" />
|
||||
<view class="top-label">{{ formatLabel(p.rank) }}</view>
|
||||
<image
|
||||
class="cover-image"
|
||||
:src="(items[i]?.cover_url) || (items[i]?.cover_image) || ''"
|
||||
:src="items[i]?.cover_url || items[i]?.cover_image || ''"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
@ -47,65 +43,64 @@
|
||||
// Note: @keyframes orbit is inlined below (not imported from config.js ORBIT_KEYFRAMES)
|
||||
// because Vue <style> blocks cannot interpolate JS string constants.
|
||||
// config.js ORBIT_KEYFRAMES is kept as documentation/source-of-truth.
|
||||
import { RING_DELAYS } from './config.js'
|
||||
import { RING_DELAYS } from "./config.js";
|
||||
|
||||
const props = defineProps({
|
||||
items: { type: Array, required: true }, // length 9
|
||||
positions: { type: Array, required: true }, // from generateRingPositions()
|
||||
})
|
||||
items: { type: Array, required: true }, // length 9
|
||||
positions: { type: Array, required: true }, // from generateRingPositions()
|
||||
});
|
||||
|
||||
const emit = defineEmits(['cardClick'])
|
||||
const emit = defineEmits(["cardClick"]);
|
||||
|
||||
// 静态 base 位置:slot 0 中心 (187, 565),item 46×72,top-left = (164, 530)
|
||||
const BASE_X = 164
|
||||
const BASE_Y = 530
|
||||
const BASE_X = 164;
|
||||
const BASE_Y = 530;
|
||||
|
||||
// Each item's static fallback position = position at its slot in the orbit keyframe cycle.
|
||||
// When animation runs, the keyframe overrides these. When animation doesn't run (e.g. prefers-reduced-motion),
|
||||
// items stay at their slot positions instead of stacking at (BASE_X, BASE_Y).
|
||||
const SLOT_TRANSFORMS = [
|
||||
'translate(0, 0) scale(1.15)', // slot 0: TOP 4 (front center)
|
||||
'translate(84px, -13px) scale(1.05)', // slot 1: TOP 5
|
||||
'translate(157px, -43px) scale(0.95)', // slot 2: TOP 6
|
||||
'translate(113px, -83px) scale(0.85)', // slot 3: TOP 7
|
||||
'translate(45px, -107px) scale(0.75)', // slot 4: TOP 8
|
||||
'translate(-45px, -107px) scale(0.75)', // slot 5: TOP 9
|
||||
'translate(-113px, -83px) scale(0.85)', // slot 6: TOP 10
|
||||
'translate(-156px, -43px) scale(0.95)', // slot 7: TOP 11
|
||||
'translate(-84px, -13px) scale(1.05)', // slot 8: TOP 12
|
||||
]
|
||||
"translate(0, 0) scale(1.15)", // slot 0: TOP 4 (front center)
|
||||
"translate(84px, -13px) scale(1.05)", // slot 1: TOP 5
|
||||
"translate(157px, -43px) scale(0.95)", // slot 2: TOP 6
|
||||
"translate(113px, -83px) scale(0.85)", // slot 3: TOP 7
|
||||
"translate(45px, -107px) scale(0.75)", // slot 4: TOP 8
|
||||
"translate(-45px, -107px) scale(0.75)", // slot 5: TOP 9
|
||||
"translate(-113px, -83px) scale(0.85)", // slot 6: TOP 10
|
||||
"translate(-156px, -43px) scale(0.95)", // slot 7: TOP 11
|
||||
"translate(-84px, -13px) scale(1.05)", // slot 8: TOP 12
|
||||
];
|
||||
|
||||
function ringItemStyle(p) {
|
||||
return {
|
||||
left: BASE_X + 'rpx',
|
||||
top: BASE_Y + 'rpx',
|
||||
left: BASE_X + "rpx",
|
||||
top: BASE_Y + "rpx",
|
||||
zIndex: p.zIndex,
|
||||
transform: SLOT_TRANSFORMS[p.rank - 4],
|
||||
animationDelay: RING_DELAYS[p.rank - 4] + 's',
|
||||
}
|
||||
animationDelay: RING_DELAYS[p.rank - 4] + "s",
|
||||
};
|
||||
}
|
||||
|
||||
function formatLabel(rank) {
|
||||
return 'TOP ' + rank
|
||||
return "TOP " + rank;
|
||||
}
|
||||
|
||||
function ringFrameSrc(rank) {
|
||||
// rank 4 → LV4 (TOP 4), rank 5 → LV5 (TOP 5), ..., rank 12 → LV12 (TOP 12)
|
||||
// ScatteredRanks rank == display rank (no offset, unlike PodiumCard which uses rank - 3)
|
||||
return `/static/square/galaxy/LV${rank}.png`
|
||||
return `/static/square/galaxy/LV${rank}.png`;
|
||||
}
|
||||
|
||||
function handleClick(item) {
|
||||
if (item) emit('cardClick', item)
|
||||
if (item) emit("cardClick", item);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scattered-ranks {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
top: 22%;
|
||||
left: 22%;
|
||||
width: 750rpx;
|
||||
height: 720rpx;
|
||||
pointer-events: none;
|
||||
@ -123,79 +118,100 @@ function handleClick(item) {
|
||||
|
||||
.ring-item {
|
||||
position: absolute;
|
||||
width: 46rpx;
|
||||
height: 72rpx; /* 14 label + 2 gap + 56 cover */
|
||||
width: 84rpx;
|
||||
height: 104rpx; /* 14 label + 2 gap + 56 cover */
|
||||
transform-origin: center;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
/* display: flex; */
|
||||
flex-direction: column;
|
||||
animation: orbit 36s linear infinite;
|
||||
}
|
||||
|
||||
.ring-frame {
|
||||
position: absolute;
|
||||
inset: -2rpx; /* extend slightly outside the item bounds */
|
||||
width: calc(100% + 4rpx);
|
||||
height: calc(100% + 4rpx);
|
||||
z-index: 0;
|
||||
inset: 0; /* extend slightly outside the item bounds */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.top-label {
|
||||
width: 46rpx;
|
||||
height: 14rpx;
|
||||
background: radial-gradient(ellipse, #C8E6FF, #fff 50%, #4D9AF8);
|
||||
border-radius: 7rpx;
|
||||
width: 80rpx;
|
||||
position: relative;
|
||||
bottom: 16rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: #fffabd;
|
||||
font-size: 20rpx;
|
||||
font-weight: 800;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 7rpx;
|
||||
font-weight: 900;
|
||||
color: #FFFABD;
|
||||
text-shadow: -1rpx 1rpx 2rpx rgba(206, 9, 9, 0.84);
|
||||
z-index: 2; /* above frame */
|
||||
}
|
||||
|
||||
/* TOP 4 label 是金渐变(最显眼) */
|
||||
.r0 .top-label {
|
||||
background: radial-gradient(ellipse, #FFFFFF, #FFFABD 30%, #4D9AF8 100%);
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 250, 189, 0.55);
|
||||
font-size: 8rpx;
|
||||
text-shadow: -1px 1px 4px #ce0909d6;
|
||||
background: linear-gradient(
|
||||
93.1deg,
|
||||
rgba(224, 180, 247, 0.71) -12.06%,
|
||||
rgba(178, 246, 204, 0.71) 52.09%,
|
||||
rgba(98, 178, 244, 0.71) 163.5%
|
||||
);
|
||||
backdrop-filter: blur(11.699999809265137px);
|
||||
z-index: 7;
|
||||
}
|
||||
|
||||
.cover-image {
|
||||
margin-top: 2rpx;
|
||||
width: 46rpx;
|
||||
height: 56rpx;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 5rpx;
|
||||
box-shadow: 3rpx 3rpx 6rpx rgba(198, 13, 13, 0.45);
|
||||
z-index: 1; /* between frame and label */
|
||||
}
|
||||
|
||||
.r0 .cover-image {
|
||||
box-shadow: 0 12rpx 28rpx rgba(255, 32, 36, 0.5), 0 0 24rpx rgba(255, 250, 189, 0.55);
|
||||
z-index: 1; /* between frame and label */
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 关键帧:放在非 scoped 块中,让所有 ring-item 共享 */
|
||||
@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); }
|
||||
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); }
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(-50%) scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(-50%) scale(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
/* 可访问性:减少动画 */
|
||||
|
||||
@ -118,7 +118,7 @@ onUnmounted(() => {
|
||||
position: relative;
|
||||
width: 750rpx;
|
||||
min-height: 1440rpx;
|
||||
padding-bottom: 200rpx;
|
||||
padding-bottom: 392rpx;
|
||||
top: -128rpx;
|
||||
// [方案3] 伪元素承载 bj.png,对图片单独设 opacity
|
||||
&::before {
|
||||
|
||||
@ -8,41 +8,72 @@
|
||||
></image> -->
|
||||
|
||||
<!-- Header组件 -->
|
||||
<Header :showGuideIcon="true" :showTaskIcon="true" :showStarActivityIcon="true" backIconColor="#e6e6e6" />
|
||||
<Header
|
||||
:showGuideIcon="true"
|
||||
:showTaskIcon="true"
|
||||
:showStarActivityIcon="true"
|
||||
backIconColor="#e6e6e6"
|
||||
/>
|
||||
|
||||
<!-- 蒙层 - 导航栏展开时显示 -->
|
||||
<view v-if="navExpanded" class="nav-mask" @click="navExpanded = false"></view>
|
||||
<view
|
||||
v-if="navExpanded"
|
||||
class="nav-mask"
|
||||
@click="navExpanded = false"
|
||||
></view>
|
||||
|
||||
<!-- 排行榜弹窗 -->
|
||||
<RankingModal :visible="showRankingModal" :parent-active="true" :star-id="currentStarId"
|
||||
@update:visible="handleRankingModalClose" @visit="handleRankingVisit" />
|
||||
<RankingModal
|
||||
:visible="showRankingModal"
|
||||
:parent-active="true"
|
||||
:star-id="currentStarId"
|
||||
@update:visible="handleRankingModalClose"
|
||||
@visit="handleRankingVisit"
|
||||
/>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<BottomNav :activeTab="4" :isExpanded="navExpanded" @update:activeTab="handleTabChange"
|
||||
@update:isExpanded="navExpanded = $event" />
|
||||
<BottomNav
|
||||
:activeTab="4"
|
||||
:isExpanded="navExpanded"
|
||||
@update:activeTab="handleTabChange"
|
||||
@update:isExpanded="navExpanded = $event"
|
||||
/>
|
||||
|
||||
<!-- 全局引导遮罩 -->
|
||||
<GuideOverlay />
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<scroll-view class="content-wrapper" :class="{ 'spotlight-ready': isH5 }" scroll-y :show-scrollbar="false"
|
||||
:bounce="false" @scroll="onScroll" @touchstart="onTouchStart" @touchmove="onTouchMove"
|
||||
@scrolltolower="handleScrollToLower">
|
||||
<scroll-view
|
||||
class="content-wrapper"
|
||||
:class="{ 'spotlight-ready': isH5 }"
|
||||
scroll-y
|
||||
:show-scrollbar="false"
|
||||
:bounce="false"
|
||||
@scroll="onScroll"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@scrolltolower="handleScrollToLower"
|
||||
>
|
||||
<!-- 区域一:顶部运营轮播图 -->
|
||||
<view ref="bannerSectionRef" class="banner-section">
|
||||
<BannerCarousel :bannerActivities="bannerActivities" :banners="banners"
|
||||
@activityClick="handleActivityClick" @top3Click="showRankingModal = true"
|
||||
@bannerClick="handleBannerClick" />
|
||||
<BannerCarousel
|
||||
:bannerActivities="bannerActivities"
|
||||
:banners="banners"
|
||||
@activityClick="handleActivityClick"
|
||||
@top3Click="showRankingModal = true"
|
||||
@bannerClick="handleBannerClick"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<ContentTabs ref="contentTabsRef" class="tabs" :modelValue="activeContentTab"
|
||||
@update:modelValue="activeContentTab = $event" />
|
||||
<ContentTabs
|
||||
ref="contentTabsRef"
|
||||
class="tabs"
|
||||
:modelValue="activeContentTab"
|
||||
@update:modelValue="activeContentTab = $event"
|
||||
/>
|
||||
|
||||
<!-- 星河区块 - 仅在 星河 时显示 -->
|
||||
<view
|
||||
v-if="activeContentTab === 'xinghe'"
|
||||
class="star-galaxy-wrapper"
|
||||
>
|
||||
<view v-if="activeContentTab === 'xinghe'" class="star-galaxy-wrapper">
|
||||
<StarGalaxy @cardClick="handleCardClick" />
|
||||
</view>
|
||||
|
||||
@ -111,32 +142,38 @@ const allSpotlightRefs = () => {
|
||||
// hotCategoryRef.value,
|
||||
creationGridRef.value?.mainTabsRef?.value,
|
||||
creationGridRef.value?.categoryRef?.value,
|
||||
].filter(Boolean)
|
||||
].filter(Boolean);
|
||||
|
||||
const cards = creationGridRef.value?.getCardRefs?.() || []
|
||||
return [...sections, ...cards]
|
||||
}
|
||||
const cards = creationGridRef.value?.getCardRefs?.() || [];
|
||||
return [...sections, ...cards];
|
||||
};
|
||||
|
||||
const { update, bindScroll, start: startSpotlight, stop: stopSpotlight, isH5 } = useSpotlight({
|
||||
const {
|
||||
update,
|
||||
bindScroll,
|
||||
start: startSpotlight,
|
||||
stop: stopSpotlight,
|
||||
isH5,
|
||||
} = useSpotlight({
|
||||
getElements: allSpotlightRefs,
|
||||
})
|
||||
});
|
||||
|
||||
// 统一 scroll 处理:spotlight + 驱动 CreationGrid 切 fixed
|
||||
const onScroll = (e) => {
|
||||
bindScroll() // 兼容路径:scroll 事件触发时也跑一次(主驱动是 rAF 轮询)
|
||||
const scrollTop = (e && e.detail && e.detail.scrollTop) || 0
|
||||
bindScroll(); // 兼容路径:scroll 事件触发时也跑一次(主驱动是 rAF 轮询)
|
||||
const scrollTop = (e && e.detail && e.detail.scrollTop) || 0;
|
||||
// 通知 CreationGrid:根据 scrollTop 决定分类标签是否切到 fixed
|
||||
creationGridRef.value?.updateScroll?.(scrollTop)
|
||||
}
|
||||
creationGridRef.value?.updateScroll?.(scrollTop);
|
||||
};
|
||||
|
||||
// 移动端 rAF 在用户滚动时会被节流,opacity 跟不上。
|
||||
// 用 touchstart / touchmove 同步触发 update(),绕开 rAF 节流。
|
||||
const onTouchStart = () => {
|
||||
update()
|
||||
}
|
||||
update();
|
||||
};
|
||||
const onTouchMove = () => {
|
||||
update()
|
||||
}
|
||||
update();
|
||||
};
|
||||
|
||||
// tab 变化 → 新内容可能进入视口,重做一次 spotlight 计算
|
||||
watch(activeContentTab, () => {
|
||||
@ -151,7 +188,8 @@ const onGridLoaded = (count) => {
|
||||
};
|
||||
|
||||
// ========== Composables ==========
|
||||
const { bannerActivities, banners, loadBannerActivities, loadBanners } = useBanner();
|
||||
const { bannerActivities, banners, loadBannerActivities, loadBanners } =
|
||||
useBanner();
|
||||
|
||||
// ========== Handlers ==========
|
||||
|
||||
@ -199,17 +237,17 @@ const handleActivityClick = (item) => {
|
||||
|
||||
// 运营 banner 点击(铸造活动)
|
||||
const handleBannerClick = (banner) => {
|
||||
console.log('[square] banner click', banner);
|
||||
console.log("[square] banner click", banner);
|
||||
// 优先使用自定义 route
|
||||
if (banner.route) {
|
||||
return uni.navigateTo({ url: banner.route });
|
||||
}
|
||||
if (banner.link_type === 'activity') {
|
||||
if (banner.link_type === "activity") {
|
||||
return uni.navigateTo({
|
||||
url: `/pages/castlove/detail?id=${banner.link_value}`,
|
||||
});
|
||||
}
|
||||
if (banner.link_type === 'topic') {
|
||||
if (banner.link_type === "topic") {
|
||||
return uni.navigateTo({
|
||||
url: `/pages/topic/detail?id=${banner.link_value}`,
|
||||
});
|
||||
@ -254,10 +292,10 @@ const handleTabChange = (newTab) => {
|
||||
// 主Tab点击 / 分类切换 已在 CreationGrid 内部处理,不再冒泡
|
||||
|
||||
// ========== Tile Change Callback ==========
|
||||
const handleTileChange = () => { };
|
||||
const handleTileChange = () => {};
|
||||
|
||||
// ========== Reset Square ==========
|
||||
const resetSquare = async () => { };
|
||||
const resetSquare = async () => {};
|
||||
|
||||
// ========== Lifecycle ==========
|
||||
onMounted(() => {
|
||||
@ -320,7 +358,13 @@ onUnmounted(() => {
|
||||
<style lang="scss" scoped>
|
||||
.square-container {
|
||||
position: relative;
|
||||
background: linear-gradient(179.98deg, rgba(255, 229, 229, 0.25) -32.49%, rgba(243, 160, 161, 0.25) -32.49%, rgba(255, 156, 156, 0.25) 86.46%, rgba(255, 32, 36, 0.25) 180.79%);
|
||||
background: linear-gradient(
|
||||
179.98deg,
|
||||
rgba(255, 229, 229, 0.25) -32.49%,
|
||||
rgba(243, 160, 161, 0.25) -32.49%,
|
||||
rgba(255, 156, 156, 0.25) 86.46%,
|
||||
rgba(255, 32, 36, 0.25) 180.79%
|
||||
);
|
||||
backdrop-filter: blur(4px);
|
||||
|
||||
width: 100vw;
|
||||
@ -356,7 +400,7 @@ onUnmounted(() => {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
/* margin-top: 160rpx; */
|
||||
padding: 208rpx 16rpx 0;
|
||||
padding: 208rpx 0 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@ -466,7 +510,6 @@ onUnmounted(() => {
|
||||
|
||||
/* 动效减弱的可访问性兜底 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
.spotlight,
|
||||
.spotlight-ready .banner-section,
|
||||
.spotlight-ready .tabs,
|
||||
|
||||
BIN
frontend/static/square/galaxy/LV1.png
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
frontend/static/square/galaxy/LV10.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
frontend/static/square/galaxy/LV11.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
frontend/static/square/galaxy/LV12.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
frontend/static/square/galaxy/LV2.png
Normal file
|
After Width: | Height: | Size: 244 KiB |
BIN
frontend/static/square/galaxy/LV3.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
frontend/static/square/galaxy/LV4.png
Normal file
|
After Width: | Height: | Size: 189 KiB |
BIN
frontend/static/square/galaxy/LV5.png
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
frontend/static/square/galaxy/LV6.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
frontend/static/square/galaxy/LV7.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
frontend/static/square/galaxy/LV8.png
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
frontend/static/square/galaxy/LV9.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
frontend/static/square/galaxy/TOP.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
frontend/static/square/galaxy/TOP2.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
frontend/static/square/galaxy/TOP3.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
frontend/static/square/galaxy/baoguangbang.png
Normal file
|
After Width: | Height: | Size: 260 KiB |
BIN
frontend/static/square/galaxy/bj.png
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
BIN
frontend/static/square/galaxy/dianzanbang.png
Normal file
|
After Width: | Height: | Size: 344 KiB |
BIN
frontend/static/square/galaxy/dizuo1.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
frontend/static/square/galaxy/dizuo2.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
frontend/static/square/galaxy/dizuo3.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
frontend/static/square/galaxy/huoyuebang.png
Normal file
|
After Width: | Height: | Size: 272 KiB |
BIN
frontend/static/square/galaxy/tongchengbang.png
Normal file
|
After Width: | Height: | Size: 308 KiB |
BIN
frontend/static/square/galaxy/topbj1.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
frontend/static/square/galaxy/topbj2.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
frontend/static/square/galaxy/topbj3.png
Normal file
|
After Width: | Height: | Size: 920 KiB |
BIN
frontend/static/square/top/bj.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |