fix(stargalaxy): reverse top/bottom mask to fade header at top instead of bottom

This commit is contained in:
zheng020 2026-06-10 16:55:28 +08:00
parent 1542147291
commit ca87faf98d

View File

@ -1,13 +1,13 @@
<template> <template>
<view class="stargalaxy-container"> <view class="stargalaxy-container">
<!-- 装饰层粉红渐变 overlay + 樱花粉光晕 + 暖黄光晕 --> <!-- 装饰层粉红渐变 overlay + 樱花粉光晕 + 暖黄光晕 -->
<view class="decoration-layer"> <!-- <view class="decoration-layer">
<view class="halo-pink"></view> <view class="halo-pink"></view>
<view class="halo-yellow"></view> <view class="halo-yellow"></view>
</view> </view> -->
<!-- 标题 --> <!-- 标题 -->
<view class="title"> 星河 </view> <!-- <view class="title"> 星河 </view> -->
<!-- Loading 骨架 --> <!-- Loading 骨架 -->
<view v-if="loading" class="skeleton-grid"> <view v-if="loading" class="skeleton-grid">
@ -29,7 +29,7 @@
<!-- Success --> <!-- Success -->
<template v-else> <template v-else>
<!-- 颁奖台TOP 1-3 --> <!-- 颁奖台TOP 1-3 -->
<view class="podium-row"> <!-- <view class="podium-row"> -->
<PodiumCard <PodiumCard
v-for="(item, i) in podiumItems" v-for="(item, i) in podiumItems"
:key="item.id || i" :key="item.id || i"
@ -39,7 +39,7 @@
:style="PODIUM_POSITIONS[i + 4]" :style="PODIUM_POSITIONS[i + 4]"
@cardClick="handleCardClick" @cardClick="handleCardClick"
/> />
</view> <!-- </view> -->
<!-- 散落 9 itemTOP 4-12 --> <!-- 散落 9 itemTOP 4-12 -->
<ScatteredRanks <ScatteredRanks
@ -49,91 +49,117 @@
/> />
<!-- 底部提示 --> <!-- 底部提示 -->
<view class="footer-hint">每日 0:00 更新榜单</view> <!-- <view class="footer-hint">每日 0:00 更新榜单</view> -->
</template> </template>
</view> </view>
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from "vue";
import PodiumCard from './PodiumCard.vue' import PodiumCard from "./PodiumCard.vue";
import ScatteredRanks from './ScatteredRanks.vue' import ScatteredRanks from "./ScatteredRanks.vue";
import { generateRingPositions } from './config.js' import { generateRingPositions } from "./config.js";
import { getHotRankingApi } from '@/utils/api.js' import { getHotRankingApi } from "@/utils/api.js";
import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js' import { getAssetCoverRealUrl } from "@/utils/assetImageHelper.js";
const emit = defineEmits(['cardClick']) const emit = defineEmits(["cardClick"]);
// 3 ScatteredRanks rpx // 3 ScatteredRanks rpx
const PODIUM_SIZES = { const PODIUM_SIZES = {
4: { width: 240, height: 260 }, // TOP 1 4: { width: 240, height: 260 }, // TOP 1
5: { width: 200, height: 200 }, // TOP 2 5: { width: 200, height: 200 }, // TOP 2
6: { width: 192, height: 192 }, // TOP 3 6: { width: 192, height: 192 }, // TOP 3
} };
const PODIUM_POSITIONS = { const PODIUM_POSITIONS = {
4: { position: 'absolute', top: '400rpx', left: '50%', transform: 'translateX(-50%)' }, 4: {
5: { position: 'absolute', top: '120rpx', left: '60rpx' }, position: "absolute",
6: { position: 'absolute', top: '150rpx', right: '60rpx' }, top: "400rpx",
} left: "50%",
transform: "translateX(-50%)",
},
5: { position: "absolute", top: "120rpx", left: "60rpx" },
6: { position: "absolute", top: "150rpx", right: "60rpx" },
};
const items = ref([]) const items = ref([]);
const loading = ref(true) const loading = ref(true);
const error = ref(false) const error = ref(false);
const ringPositions = generateRingPositions() const ringPositions = generateRingPositions();
const podiumItems = computed(() => items.value.slice(0, 3)) const podiumItems = computed(() => items.value.slice(0, 3));
const scatteredItems = computed(() => items.value.slice(3, 12)) const scatteredItems = computed(() => items.value.slice(3, 12));
async function resolveUrl(item) { async function resolveUrl(item) {
const cover = item.cover_url || item.cover_image || '' const cover = item.cover_url || item.cover_image || "";
if (cover) { if (cover) {
item.cover_url = await getAssetCoverRealUrl(cover) item.cover_url = await getAssetCoverRealUrl(cover);
} }
return item return item;
} }
async function loadData() { async function loadData() {
loading.value = true loading.value = true;
error.value = false error.value = false;
try { try {
const res = await getHotRankingApi('displaying', null, 1, 12) const res = await getHotRankingApi("displaying", null, 1, 12);
if (res && res.code === 200 && res.data?.items) { if (res && res.code === 200 && res.data?.items) {
items.value = await Promise.all( items.value = await Promise.all(
res.data.items.map(async (item) => { res.data.items.map(async (item) => {
return await resolveUrl({ ...item, id: item.id || item.asset_id }) return await resolveUrl({ ...item, id: item.id || item.asset_id });
}) }),
) );
} else { } else {
items.value = [] items.value = [];
} }
} catch (e) { } catch (e) {
console.error('[StarGalaxy] 加载失败', e?.message ?? e) console.error("[StarGalaxy] 加载失败", e?.message ?? e);
error.value = true error.value = true;
} finally { } finally {
loading.value = false loading.value = false;
} }
} }
function handleCardClick(item) { function handleCardClick(item) {
emit('cardClick', item) emit("cardClick", item);
} }
onMounted(() => { onMounted(() => {
loadData() loadData();
}) });
onUnmounted(() => { onUnmounted(() => {
// timer // timer
}) });
</script> </script>
<style scoped> <style scoped lang="scss">
.stargalaxy-container { .stargalaxy-container {
position: relative; position: relative;
width: 750rpx; width: 750rpx;
min-height: 1440rpx; min-height: 1440rpx;
padding-bottom: 200rpx; padding-bottom: 200rpx;
overflow: hidden; overflow: hidden;
// [3] bj.png opacity
&::before {
content: "";
position: absolute;
inset: 0;
background: url("/static/square/galaxy/bj.png") center / cover no-repeat;
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 20%, #000 40%, #000 100%);
-webkit-mask-image: linear-gradient(
to bottom,
transparent 0%,
transparent 20%,
#000 40%,
#000 100%
);
}
} }
.decoration-layer { .decoration-layer {
@ -141,7 +167,13 @@ onUnmounted(() => {
inset: 0; inset: 0;
pointer-events: none; pointer-events: none;
z-index: 0; z-index: 0;
background: linear-gradient(179deg, #FFE5E5 0%, #F3A0A1 0%, #FF9C9C 86%, #FF2024 100%); background: linear-gradient(
179deg,
#ffe5e5 0%,
#f3a0a1 0%,
#ff9c9c 86%,
#ff2024 100%
);
opacity: 0.85; opacity: 0.85;
} }
@ -152,7 +184,7 @@ onUnmounted(() => {
transform: translateX(-50%); transform: translateX(-50%);
width: 680rpx; width: 680rpx;
height: 340rpx; height: 340rpx;
background: #F3D3E3; background: #f3d3e3;
border-radius: 50%; border-radius: 50%;
filter: blur(60rpx); filter: blur(60rpx);
opacity: 0.7; opacity: 0.7;
@ -165,7 +197,7 @@ onUnmounted(() => {
transform: translateX(-50%); transform: translateX(-50%);
width: 400rpx; width: 400rpx;
height: 400rpx; height: 400rpx;
background: #FFFABD; background: #fffabd;
border-radius: 50%; border-radius: 50%;
filter: blur(50rpx); filter: blur(50rpx);
opacity: 0.3; opacity: 0.3;
@ -178,7 +210,7 @@ onUnmounted(() => {
transform: translateX(-50%); transform: translateX(-50%);
font-size: 40rpx; font-size: 40rpx;
font-weight: 900; font-weight: 900;
color: #FFFABD; color: #fffabd;
text-shadow: -1rpx 1rpx 4rpx rgba(206, 9, 9, 0.84); text-shadow: -1rpx 1rpx 4rpx rgba(206, 9, 9, 0.84);
letter-spacing: 8rpx; letter-spacing: 8rpx;
z-index: 5; z-index: 5;
@ -205,7 +237,12 @@ onUnmounted(() => {
background: rgba(255, 255, 255, 0.15); background: rgba(255, 255, 255, 0.15);
border-radius: 12rpx; border-radius: 12rpx;
animation: shimmer 1.5s linear infinite; 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-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%; background-size: 200% 100%;
} }
@ -214,14 +251,23 @@ onUnmounted(() => {
height: 80rpx; height: 80rpx;
background: rgba(255, 255, 255, 0.15); background: rgba(255, 255, 255, 0.15);
border-radius: 8rpx; 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-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%; background-size: 200% 100%;
animation: shimmer 1.5s linear infinite; animation: shimmer 1.5s linear infinite;
} }
@keyframes shimmer { @keyframes shimmer {
0% { background-position: 200% 0; } 0% {
100% { background-position: -200% 0; } background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
} }
.error-state { .error-state {
@ -255,7 +301,7 @@ onUnmounted(() => {
.retry-btn { .retry-btn {
display: inline-block; display: inline-block;
padding: 16rpx 60rpx; padding: 16rpx 60rpx;
background: linear-gradient(135deg, #FFD700, #FF6B6B); background: linear-gradient(135deg, #ffd700, #ff6b6b);
color: #fff; color: #fff;
border-radius: 30rpx; border-radius: 30rpx;
font-size: 28rpx; font-size: 28rpx;