topfans/frontend/pages/components/BannerTop3.vue
2026-05-11 17:56:16 +08:00

255 lines
5.1 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>
<!-- 只渲染背景图卡片由父组件 BannerCarousel 渲染在 swiper 外层 -->
<view class="banner-top3-bg" >
<image class="banner-bg" src="/static/square/paihangbang.png" mode="aspectFill" />
<!-- 卡片层 -->
<view class="cards-overlay" @click.stop="$emit('top3Click')">
<view
v-for="(item, index) in top3Items"
:key="item.asset_id || index"
class="card-wrapper"
:class="`card-pos-${index}`"
>
<view class="card-frame">
<view class="card-image-wrap">
<image
class="card-image"
:src="item.cover_url || '/static/avatar/1.jpeg'"
mode="aspectFill"
/>
<!-- 点赞数叠在图片底部 -->
<view class="card-footer">
<image class="card-heart" src="/static/icon/heart-icon.png" mode="aspectFit" />
<view class="card-likes-wrap">
<text class="card-likes">{{ formatLikes(item.like_count) }}</text>
</view>
</view>
</view>
<!-- 边框叠在整张卡片上 -->
<image class="card-frame-border" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill" />
</view>
</view>
<!-- 骨架屏 -->
<view v-if="loading" class="skeleton-wrap">
<view v-for="i in 3" :key="i" class="skeleton-card" :class="`skeleton-pos-${i - 1}`" />
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { getHotRankingApi } from '@/utils/api.js';
const emit = defineEmits(['dataLoaded', 'top3Click']);
const top3Items = ref([]);
const loading = ref(true);
const resolveOssUrl = async (fileName, type) => {
if (!fileName) return '';
// 直接使用后端返回的图片URL
return fileName;
};
const formatLikes = (n) => {
if (!n || isNaN(n)) return '0';
if (n >= 10000) return (n / 10000).toFixed(1) + 'w';
if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
return String(n);
};
const loadTop3 = async () => {
try {
const res = await getHotRankingApi('total', null, 1, 3);
if (res.code === 200 && res.data?.items) {
const items = res.data.items.slice(0, 3);
const resolved = items.map(item => {
return { ...item, cover_url: item.cover_url || '', avatar_url: item.avatar_url || '' };
});
top3Items.value = resolved;
loading.value = false;
emit('dataLoaded', resolved);
}
} catch (e) {
console.error('[BannerTop3] 加载失败', e?.message ?? e);
loading.value = false;
}
};
// const onBannerTap = () => {
// uni.navigateTo({ url: '/pages/profile/hisWorks?userId=1&nickname=用户' });
// };
onMounted(loadTop3);
// 监听身份切换事件,切换后刷新排行榜数据
uni.$on('userInfoUpdated', () => {
loadTop3();
});
onUnmounted(() => {
uni.$off('userInfoUpdated');
});
defineExpose({ reload: loadTop3 });
</script>
<style scoped>
.banner-top3-bg {
width: 100%;
height: 360rpx;
position: relative;
/* overflow: hidden; */
border-radius: 24rpx;
}
.banner-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
/* 卡片层 */
.cards-overlay {
position: absolute;
top: 0;
right: 8rpx;
width: 420rpx;
height: 360rpx;
pointer-events: auto;
z-index: 10;
}
.card-wrapper {
position: absolute;
width: 148rpx;
height: 220rpx;
}
.card-pos-0 {
left: 50rpx;
top: 40rpx;
transform: rotate(-6deg);
z-index: 3;
}
.card-pos-1 {
left: 140rpx;
top: -8rpx;
transform: rotate(6deg);
z-index: 4;
}
.card-pos-2 {
left: 240rpx;
top: 106rpx;
transform: rotate(16deg);
z-index: 5;
}
.card-frame {
width: 100%;
height: 100%;
border-radius: 16rpx;
position: relative;
overflow: visible;
}
.card-image-wrap {
width: 90%;
height: 92%;
position: relative;
border-radius: 16rpx;
overflow: hidden;
z-index: 5;
padding: 8rpx;
}
.card-image {
width: 100%;
height: 100%;
border-radius: 16rpx;
}
.card-frame-border {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
z-index: 2;
pointer-events: none;
}
.card-footer {
position: absolute;
bottom: 8rpx;
left: 0;
right: 64rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 4rpx;
z-index: 3;
}
.card-heart {
width: 22rpx;
height: 22rpx;
flex-shrink: 0;
}
.card-likes-wrap {
background: linear-gradient(to bottom right,
#F0E4B1 0%,
#F08399 50%,
#B94E73 100%
);
border-radius: 8rpx;
padding: 2rpx 8rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow:
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
}
.card-likes {
font-size: 12rpx;
color: #ffffff;
font-weight: 700;
}
/* 骨架屏 */
.skeleton-wrap {
position: absolute;
inset: 0;
z-index: 5;
}
.skeleton-card {
position: absolute;
width: 148rpx;
height: 220rpx;
border-radius: 16rpx;
background: linear-gradient(90deg,
rgba(255,255,255,0.06) 25%,
rgba(255,255,255,0.14) 50%,
rgba(255,255,255,0.06) 75%);
background-size: 200% 100%;
animation: shimmer 1.4s infinite;
}
.skeleton-pos-0 { left: 50rpx; top: 40rpx; transform: rotate(-6deg); }
.skeleton-pos-1 { left: 140rpx; top: -40rpx; transform: rotate(6deg); }
.skeleton-pos-2 { left: 240rpx; top: 10rpx; transform: rotate(16deg); }
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
</style>