269 lines
5.6 KiB
Vue
269 lines
5.6 KiB
Vue
<template>
|
||
<view class="banner-carousel" @click.stop>
|
||
<swiper
|
||
class="banner-swiper"
|
||
:autoplay="true"
|
||
:interval="4000"
|
||
:duration="400"
|
||
:circular="true"
|
||
:indicator-dots="false"
|
||
@change="onSwiperChange"
|
||
>
|
||
<swiper-item @click.stop="$emit('top3Click')">
|
||
<BannerTop3 @dataLoaded="onTop3DataLoaded" />
|
||
</swiper-item>
|
||
<swiper-item
|
||
v-for="item in bannerActivities"
|
||
:key="item.id"
|
||
@click.stop="$emit('activityClick', item)"
|
||
>
|
||
<image
|
||
class="banner-activity-img"
|
||
:src="item.cover_image || '/static/avatar/1.jpeg'"
|
||
mode="aspectFill"
|
||
/>
|
||
</swiper-item>
|
||
</swiper>
|
||
|
||
<!-- 卡片层跟随第一个 swiper-item,切走时隐藏 -->
|
||
<view
|
||
class="cards-overlay"
|
||
:style="{ opacity: currentIndex === 0 ? 1 : 0, pointerEvents: currentIndex === 0 ? 'auto' : 'none' }"
|
||
@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 } from 'vue'
|
||
import BannerTop3 from '../../components/BannerTop3.vue'
|
||
|
||
defineProps({
|
||
bannerActivities: { type: Array, default: () => [] }
|
||
})
|
||
defineEmits(['activityClick', 'top3Click'])
|
||
|
||
const top3Items = ref([])
|
||
const loading = ref(true)
|
||
const currentIndex = ref(0)
|
||
|
||
const onSwiperChange = (e) => {
|
||
currentIndex.value = e.detail.current
|
||
}
|
||
|
||
const onTop3DataLoaded = (items) => {
|
||
const list = items.slice(0, 3)
|
||
while (list.length < 3) list.push({ asset_id: `placeholder-${list.length}` })
|
||
top3Items.value = list
|
||
loading.value = false
|
||
}
|
||
|
||
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)
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.banner-carousel {
|
||
position: relative;
|
||
width: 100%;
|
||
z-index: 100;
|
||
padding: 0 8rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.banner-swiper {
|
||
width: 100%;
|
||
height: 360rpx;
|
||
border-radius: 24rpx;
|
||
}
|
||
|
||
.banner-activity-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
}
|
||
|
||
/* 卡片层:绝对定位叠在 swiper 上,可自由溢出 */
|
||
.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-bg-frame {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.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>
|