fix(stargalaxy): reverse top/bottom mask to fade header at top instead of bottom
This commit is contained in:
parent
1542147291
commit
ca87faf98d
@ -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 item:TOP 4-12 -->
|
<!-- 散落 9 item:TOP 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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user