255 lines
6.1 KiB
Vue
255 lines
6.1 KiB
Vue
<template>
|
||
<view class="banner-container">
|
||
<swiper
|
||
class="banner-swiper"
|
||
:circular="true"
|
||
:autoplay="true"
|
||
:interval="4000"
|
||
:duration="500"
|
||
:indicator-dots="false"
|
||
@change="onSwiperChange"
|
||
>
|
||
<swiper-item v-for="(banner, index) in banners" :key="index">
|
||
<view class="banner-content" @click="previewImage(index)">
|
||
<!-- 添加 lazy-load 属性实现懒加载 -->
|
||
<image
|
||
:src="banner.image"
|
||
mode="aspectFill"
|
||
class="banner-image"
|
||
lazy-load
|
||
:show-menu-by-longpress="false"
|
||
@load="onImageLoad(index)"
|
||
@error="onImageError(index)"
|
||
/>
|
||
<view class="banner-overlay"></view>
|
||
<view class="banner-text">
|
||
<text class="banner-title">{{ banner.title }}</text>
|
||
<text class="banner-subtitle">{{ banner.subtitle }}</text>
|
||
<text class="banner-desc">{{ banner.desc }}</text>
|
||
</view>
|
||
<!-- 可选:添加加载占位符 -->
|
||
<view v-if="!imageLoaded[index]" class="image-placeholder">
|
||
<view class="loading-spinner"></view>
|
||
</view>
|
||
</view>
|
||
</swiper-item>
|
||
</swiper>
|
||
<view class="banner-dots">
|
||
<view
|
||
v-for="(_, index) in banners"
|
||
:key="index"
|
||
:class="['dot', { active: currentIndex === index }]"
|
||
></view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from 'vue'
|
||
import { getActiveBanners } from '@/api/main.js'
|
||
|
||
// 默认静态数据(后端无数据时作为兜底)
|
||
const fallbackBanners = [
|
||
{
|
||
image: 'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=800&q=80',
|
||
title: '突破自我',
|
||
subtitle: '超越极限',
|
||
desc: '科学训练 · 遇见更好的自己'
|
||
},
|
||
{
|
||
image: 'https://images.unsplash.com/photo-1517836357463-d25dfeac3438?w=800&q=80',
|
||
title: '专业指导',
|
||
subtitle: '高效训练',
|
||
desc: '私人定制 · 专属健身方案'
|
||
},
|
||
{
|
||
image: 'https://images.unsplash.com/photo-1571019614242-c5c5dee9f50b?w=800&q=80',
|
||
title: '健康生活',
|
||
subtitle: '从这里开始',
|
||
desc: '全方位 · 打造完美体态'
|
||
}
|
||
]
|
||
|
||
// 先用回退数据初始化,确保 swiper 立即可见
|
||
const banners = ref([...fallbackBanners])
|
||
|
||
const currentIndex = ref(0)
|
||
const imageLoaded = ref(fallbackBanners.map(() => false))
|
||
const isFetching = ref(false)
|
||
|
||
async function fetchBanners() {
|
||
isFetching.value = true
|
||
try {
|
||
// 禁用缓存,确保每次获取最新数据
|
||
const res = await getActiveBanners({ cache: false })
|
||
// 处理后端返回数据:可能是数组或 { data: [...] }
|
||
const data = Array.isArray(res) ? res : (res?.data || [])
|
||
|
||
if (data.length > 0) {
|
||
// 映射后端字段到组件字段,替换回退数据
|
||
banners.value = data.map(item => ({
|
||
image: item.imageUrl || '',
|
||
title: item.title || '',
|
||
subtitle: item.subtitle || '',
|
||
desc: item.description || ''
|
||
}))
|
||
// 更新 imageLoaded 状态
|
||
imageLoaded.value = banners.value.map(() => false)
|
||
console.log('[BannerSwiper] 轮播图数据加载成功,共', data.length, '条')
|
||
} else {
|
||
console.log('[BannerSwiper] 后端无轮播图数据,使用回退数据')
|
||
// 保持已有的 fallbackBanners,无需重新赋值
|
||
imageLoaded.value = banners.value.map(() => false)
|
||
}
|
||
} catch (err) {
|
||
console.error('[BannerSwiper] 轮播图数据加载失败,使用回退数据:', err)
|
||
// 保持已有的 fallbackBanners,无需重新赋值
|
||
imageLoaded.value = banners.value.map(() => false)
|
||
} finally {
|
||
isFetching.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchBanners()
|
||
})
|
||
|
||
const onSwiperChange = (e) => {
|
||
currentIndex.value = e.detail.current
|
||
}
|
||
|
||
const previewImage = (index) => {
|
||
const urls = banners.map(banner => banner.image)
|
||
uni.previewImage({
|
||
urls: urls,
|
||
current: urls[index],
|
||
indicator: 'default',
|
||
loop: true
|
||
})
|
||
}
|
||
|
||
const onImageLoad = (index) => {
|
||
imageLoaded.value[index] = true
|
||
console.log(`图片 ${index} 加载完成`)
|
||
}
|
||
|
||
const onImageError = (index) => {
|
||
console.error(`图片 ${index} 加载失败`)
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.banner-container {
|
||
width: 100%;
|
||
padding: 0 24rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.banner-swiper {
|
||
width: 100%;
|
||
height: 360rpx;
|
||
border-radius: 20rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.banner-content {
|
||
width: 100%;
|
||
height: 100%;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.banner-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.image-placeholder {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 100%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1;
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border: 4rpx solid rgba(255, 255, 255, 0.3);
|
||
border-top: 4rpx solid #7AB5CC;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.banner-overlay {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
background: linear-gradient(135deg, rgba(0,0,0,0.15) 0%, rgba(0,0,0,0.05) 50%, rgba(0,0,0,0.25) 100%);
|
||
z-index: 2;
|
||
}
|
||
|
||
.banner-text {
|
||
position: absolute;
|
||
left: 36rpx;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
z-index: 3;
|
||
}
|
||
|
||
.banner-title {
|
||
display: block;
|
||
font-size: 48rpx;
|
||
font-weight: 800;
|
||
color: #ffffff;
|
||
margin-bottom: 8rpx;
|
||
text-shadow: 0 4rpx 16rpx rgba(80, 150, 190, 0.4);
|
||
}
|
||
|
||
.banner-subtitle {
|
||
display: block;
|
||
font-size: 56rpx;
|
||
font-weight: 800;
|
||
color: #E0F0FA;
|
||
margin-bottom: 16rpx;
|
||
text-shadow: 0 4rpx 16rpx rgba(80, 150, 190, 0.4);
|
||
}
|
||
|
||
.banner-desc {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
|
||
.banner-dots {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 16rpx;
|
||
padding: 20rpx 0 8rpx;
|
||
}
|
||
|
||
.dot {
|
||
width: 48rpx;
|
||
height: 8rpx;
|
||
border-radius: 9999rpx;
|
||
background: #D0E4EE;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.dot.active {
|
||
width: 64rpx;
|
||
background: linear-gradient(90deg, #7AB5CC, #9CCFDF);
|
||
}
|
||
</style> |