You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
156 lines
3.9 KiB
156 lines
3.9 KiB
/** |
|
* 轮播图组件 |
|
* |
|
* 展示首页轮播图,支持自动播放和手动滑动 |
|
* 使用真实数据 |
|
*/ |
|
|
|
import React, { useState, useEffect, useRef, useCallback } from 'react'; |
|
import { |
|
View, |
|
Image, |
|
TouchableOpacity, |
|
ScrollView, |
|
NativeScrollEvent, |
|
NativeSyntheticEvent, |
|
ActivityIndicator, |
|
Dimensions, |
|
Alert, |
|
} from 'react-native'; |
|
import Colors from '@/constants/Colors'; |
|
// import type { Banner } from '@/types/home'; |
|
import { styles } from './styles'; |
|
import useMsgStore from '@/stores/msgStore'; |
|
|
|
|
|
interface BannerSwiperProps { |
|
theme: 'light' | 'dark'; |
|
} |
|
|
|
const { width } = Dimensions.get('window'); |
|
|
|
/** |
|
* 轮播图组件 |
|
*/ |
|
export default function BannerSwiper({ theme }: BannerSwiperProps) { |
|
const s = styles[theme]; |
|
const [currentIndex, setCurrentIndex] = useState(0); |
|
const [loading, setLoading] = useState(true); |
|
const scrollViewRef = useRef<ScrollView>(null); |
|
const autoPlayTimerRef = useRef<any>(null); |
|
const { homeBanner } = useMsgStore(); |
|
|
|
|
|
// 加载轮播图数据 |
|
useEffect(() => { |
|
// 如果有传入的 banners 数据,直接使用 |
|
if (homeBanner.length > 0) { |
|
setLoading(false); |
|
return; |
|
} |
|
// 如果没有数据,保持 loading 状态显示骨架屏 |
|
}, [homeBanner]); |
|
|
|
// 处理 Banner 点击 |
|
const onBannerPress = useCallback((banner: Record<string, any>) => { |
|
Alert.alert('轮播图', `点击了: ${banner.title || banner.id}`); |
|
// 这里可以添加导航逻辑 |
|
}, []); |
|
|
|
// 处理滚动事件 |
|
const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => { |
|
const contentOffsetX = event.nativeEvent.contentOffset.x; |
|
const index = Math.round(contentOffsetX / (width - 24)); |
|
setCurrentIndex(Math.min(index, homeBanner.length - 1)); |
|
}; |
|
|
|
// 启动自动播放 |
|
const startAutoPlay = useCallback(() => { |
|
if (homeBanner.length <= 1) return; |
|
autoPlayTimerRef.current = setInterval(() => { |
|
setCurrentIndex((prev) => { |
|
const nextIndex = (prev + 1) % homeBanner.length; |
|
scrollViewRef.current?.scrollTo({ |
|
x: nextIndex * (width - 24), |
|
animated: true, |
|
}); |
|
return nextIndex; |
|
}); |
|
}, 5000); |
|
}, [homeBanner.length]); |
|
|
|
// 停止自动播放 |
|
const stopAutoPlay = useCallback(() => { |
|
if (autoPlayTimerRef.current) { |
|
clearInterval(autoPlayTimerRef.current); |
|
} |
|
}, []); |
|
|
|
// 自动播放 |
|
useEffect(() => { |
|
if (!loading && homeBanner.length > 0) { |
|
startAutoPlay(); |
|
} |
|
return () => stopAutoPlay(); |
|
}, [loading, homeBanner.length, startAutoPlay, stopAutoPlay]); |
|
|
|
// 骨架屏 - 加载中显示占位符 |
|
if (loading || homeBanner.length === 0) { |
|
return ( |
|
<View style={s.container}> |
|
<View |
|
style={[ |
|
s.image, |
|
{ |
|
backgroundColor: theme === 'dark' ? '#333' : '#e0e0e0', |
|
}, |
|
]} |
|
/> |
|
</View> |
|
); |
|
} |
|
|
|
return ( |
|
<View style={s.container}> |
|
<ScrollView |
|
ref={scrollViewRef} |
|
style={s.scrollView} |
|
horizontal |
|
pagingEnabled |
|
scrollEventThrottle={16} |
|
onScroll={handleScroll} |
|
showsHorizontalScrollIndicator={false} |
|
onMomentumScrollBegin={stopAutoPlay} |
|
onMomentumScrollEnd={startAutoPlay} |
|
> |
|
{homeBanner.map((banner) => ( |
|
<TouchableOpacity |
|
key={banner.id} |
|
onPress={() => onBannerPress(banner)} |
|
activeOpacity={0.9} |
|
> |
|
<Image |
|
source={{ uri: banner.subject }} |
|
style={s.image} |
|
resizeMode="cover" |
|
/> |
|
</TouchableOpacity> |
|
))} |
|
</ScrollView> |
|
|
|
{/* 指示器 */} |
|
<View style={s.indicatorContainer}> |
|
{homeBanner.map((_, index) => ( |
|
<View |
|
key={index} |
|
style={[ |
|
s.indicator, |
|
index === currentIndex && s.indicatorActive, |
|
]} |
|
/> |
|
))} |
|
</View> |
|
</View> |
|
); |
|
} |
|
|
|
|