topfans/frontend/pages/square/components/StarGalaxy/index.vue

310 lines
6.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="stargalaxy-container">
<!-- 装饰层粉红渐变 overlay + 樱花粉光晕 + 暖黄光晕 -->
<!-- <view class="decoration-layer">
<view class="halo-pink"></view>
<view class="halo-yellow"></view>
</view> -->
<!-- 标题 -->
<!-- <view class="title"> 星河 </view> -->
<!-- Loading 骨架 -->
<view v-if="loading" class="skeleton-grid">
<view v-for="i in 3" :key="'p' + i" class="skeleton-podium"></view>
<view v-for="i in 9" :key="'s' + i" class="skeleton-ring"></view>
</view>
<!-- Error -->
<view v-else-if="error" class="error-state">
<text class="error-text">加载失败,点击重试</text>
<view class="retry-btn" @click="loadData">重试</view>
</view>
<!-- Empty数据为空 -->
<view v-else-if="items.length === 0" class="empty-state">
<text class="empty-text">暂无星河数据</text>
</view>
<!-- Success -->
<template v-else>
<!-- 颁奖台TOP 1-3 -->
<!-- <view class="podium-row"> -->
<PodiumCard
v-for="(item, i) in podiumItems"
:key="item.id || i"
:item="item"
:rank="i + 1"
@cardClick="handleCardClick"
/>
<!-- </view> -->
<!-- 散落 9 itemTOP 4-12 -->
<ScatteredRanks
:items="scatteredItems"
:positions="ringPositions"
@cardClick="handleCardClick"
/>
<!-- 底部提示 -->
<!-- <view class="footer-hint">每日 0:00 更新榜单</view> -->
</template>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from "vue";
import PodiumCard from "./PodiumCard.vue";
import ScatteredRanks from "./ScatteredRanks.vue";
import { generateRingPositions } from "./config.js";
import { getHotRankingApi } from "@/utils/api.js";
import { getAssetCoverRealUrl } from "@/utils/assetImageHelper.js";
const emit = defineEmits(["cardClick"]);
const items = ref([]);
const loading = ref(true);
const error = ref(false);
const ringPositions = generateRingPositions();
const podiumItems = computed(() => items.value.slice(0, 3));
const scatteredItems = computed(() => items.value.slice(3, 12));
async function resolveUrl(item) {
const cover = item.cover_url || item.cover_image || "";
if (cover) {
item.cover_url = await getAssetCoverRealUrl(cover);
}
return item;
}
async function loadData() {
loading.value = true;
error.value = false;
try {
const res = await getHotRankingApi("displaying", null, 1, 12);
if (res && res.code === 200 && res.data?.items) {
items.value = await Promise.all(
res.data.items.map(async (item) => {
return await resolveUrl({ ...item, id: item.id || item.asset_id });
}),
);
} else {
items.value = [];
}
} catch (e) {
console.error("[StarGalaxy] 加载失败", e?.message ?? e);
error.value = true;
} finally {
loading.value = false;
}
}
function handleCardClick(item) {
emit("cardClick", item);
}
onMounted(() => {
loadData();
});
onUnmounted(() => {
// 清理(如有 timer
});
</script>
<style scoped lang="scss">
.stargalaxy-container {
position: relative;
width: 750rpx;
min-height: 1440rpx;
padding-bottom: 200rpx;
top: -128rpx;
// [方案3] 伪元素承载 bj.png对图片单独设 opacity
&::before {
content: "";
position: absolute;
inset: 0;
background: url("/static/square/galaxy/bj.png") center no-repeat;
background-size: 115% 100%;
pointer-events: none;
z-index: 0;
bottom: 512rpx;
// 顶部渐隐蒙版 StarGalaxy 顶部 20% 渐变到透明
// 上方 header/页面 bj.png 从顶部自然透出形成柔和衔接
// 700rpx 0-20%(0-140rpx) 完全透明20-40%(140-280rpx) 渐变到不透明
mask-image: linear-gradient(
to bottom,
transparent 0%,
transparent 0%,
#000 14%,
#000 100%
);
-webkit-mask-image: linear-gradient(
to bottom,
transparent 0%,
transparent 0%,
#000 14%,
#000 100%
);
}
}
.decoration-layer {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 0;
background: linear-gradient(
179deg,
#ffe5e5 0%,
#f3a0a1 0%,
#ff9c9c 86%,
#ff2024 100%
);
opacity: 0.85;
}
.halo-pink {
position: absolute;
top: 580rpx;
left: 50%;
transform: translateX(-50%);
width: 680rpx;
height: 340rpx;
background: #f3d3e3;
border-radius: 50%;
filter: blur(60rpx);
opacity: 0.7;
}
.halo-yellow {
position: absolute;
top: 100rpx;
left: 50%;
transform: translateX(-50%);
width: 400rpx;
height: 400rpx;
background: #fffabd;
border-radius: 50%;
filter: blur(50rpx);
opacity: 0.3;
}
.title {
position: absolute;
top: 28rpx;
left: 50%;
transform: translateX(-50%);
font-size: 40rpx;
font-weight: 900;
color: #fffabd;
text-shadow: -1rpx 1rpx 4rpx rgba(206, 9, 9, 0.84);
letter-spacing: 8rpx;
z-index: 5;
}
.podium-row {
position: relative;
z-index: 2;
}
.skeleton-grid {
position: relative;
z-index: 2;
padding: 200rpx 60rpx;
display: flex;
flex-wrap: wrap;
gap: 40rpx;
justify-content: center;
}
.skeleton-podium {
width: 200rpx;
height: 200rpx;
background: rgba(255, 255, 255, 0.15);
border-radius: 12rpx;
animation: shimmer 1.5s linear infinite;
background-image: linear-gradient(
90deg,
rgba(255, 255, 255, 0.05) 0%,
rgba(255, 255, 255, 0.25) 50%,
rgba(255, 255, 255, 0.05) 100%
);
background-size: 200% 100%;
}
.skeleton-ring {
width: 60rpx;
height: 80rpx;
background: rgba(255, 255, 255, 0.15);
border-radius: 8rpx;
background-image: linear-gradient(
90deg,
rgba(255, 255, 255, 0.05) 0%,
rgba(255, 255, 255, 0.25) 50%,
rgba(255, 255, 255, 0.05) 100%
);
background-size: 200% 100%;
animation: shimmer 1.5s linear infinite;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.error-state {
position: relative;
z-index: 2;
padding: 600rpx 0 0;
text-align: center;
}
.error-text {
display: block;
font-size: 28rpx;
color: #fff;
margin-bottom: 30rpx;
}
.empty-state {
position: relative;
z-index: 2;
padding: 600rpx 0 0;
text-align: center;
}
.empty-text {
display: block;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.85);
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.3);
}
.retry-btn {
display: inline-block;
padding: 16rpx 60rpx;
background: linear-gradient(135deg, #ffd700, #ff6b6b);
color: #fff;
border-radius: 30rpx;
font-size: 28rpx;
font-weight: 600;
}
.footer-hint {
position: absolute;
bottom: 16rpx;
left: 50%;
transform: translateX(-50%);
font-size: 20rpx;
color: rgba(255, 255, 255, 0.7);
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.3);
z-index: 3;
}
</style>