| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- import { useRouter } from 'expo-router';
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
- import {
- ActivityIndicator,
- FlatList,
- ImageBackground,
- RefreshControl,
- StatusBar,
- StyleSheet,
- Text,
- View,
- } from 'react-native';
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
- import { Banner } from '@/components/home/Banner';
- import { GoodsCard } from '@/components/home/GoodsCard';
- import { IPFilter } from '@/components/home/IPFilter';
- import { QuickEntry } from '@/components/home/QuickEntry';
- import { SearchBar } from '@/components/home/SearchBar';
- import { Images } from '@/constants/images';
- // API 服务
- import { getIPList, IPItem } from '@/services/award';
- import { BannerItem, getPageConfig, TabItem } from '@/services/base';
- import { getGoodsList, GoodsItem, GoodsListParams } from '@/services/mall';
- export default function HomeScreen() {
- const insets = useSafeAreaInsets();
- const router = useRouter();
- const [refreshing, setRefreshing] = useState(false);
- const [loading, setLoading] = useState(true);
- const [listLoading, setListLoading] = useState(false);
- // 数据状态
- const [goods, setGoods] = useState<GoodsItem[]>([]);
- const [banners, setBanners] = useState<BannerItem[]>([]);
- const [tabs, setTabs] = useState<TabItem[]>([]);
- const [ipList, setIpList] = useState<IPItem[]>([]);
- const [ipIndex, setIpIndex] = useState(0);
- // 搜索参数
- const [searchParams, setSearchParams] = useState<GoodsListParams>({
- current: 1,
- size: 15,
- keyword: '',
- worksId: '',
- });
- // 加载商品列表
- const loadGoods = useCallback(async (params: GoodsListParams, append = false) => {
- try {
- if (!append) setListLoading(true);
- const data = await getGoodsList(params);
- if (append) {
- setGoods((prev) => [...prev, ...data]);
- } else {
- setGoods(data);
- }
- } catch (error) {
- console.error('加载商品失败:', error);
- } finally {
- if (!append) setListLoading(false);
- }
- }, []);
- // 加载 IP 列表
- const loadIPList = useCallback(async () => {
- try {
- const data = await getIPList();
- const allIP: IPItem = { id: '', name: '所有IP' };
- setIpList([allIP, ...data.filter((item) => item !== null)]);
- } catch (error) {
- console.error('加载IP列表失败:', error);
- }
- }, []);
- // 加载页面配置
- const loadPageConfig = useCallback(async () => {
- try {
- const bannerConfig = await getPageConfig('index_banner');
- if (bannerConfig?.components?.[0]?.elements) {
- setBanners(bannerConfig.components[0].elements);
- }
- const iconConfig = await getPageConfig('index_icon');
- if (iconConfig?.components?.[0]?.elements) {
- setTabs(iconConfig.components[0].elements);
- }
- } catch (error) {
- console.error('加载页面配置失败:', error);
- }
- }, []);
- // 初始化数据
- const initData = useCallback(async () => {
- setLoading(true);
- await Promise.all([loadGoods(searchParams), loadIPList(), loadPageConfig()]);
- setLoading(false);
- }, []);
- useEffect(() => {
- initData();
- }, []);
- // 下拉刷新
- const onRefresh = async () => {
- setRefreshing(true);
- const newParams = { ...searchParams, current: 1 };
- setSearchParams(newParams);
- await Promise.all([loadGoods(newParams), loadIPList(), loadPageConfig()]);
- setRefreshing(false);
- };
- // 搜索
- const handleSearch = async (keyword: string) => {
- const newParams = { ...searchParams, keyword, current: 1 };
- setSearchParams(newParams);
- await loadGoods(newParams);
- };
- // Banner 点击
- const handleBannerPress = (item: BannerItem) => {
- console.log('Banner pressed:', item);
- };
- // 功能入口点击
- const handleQuickEntryPress = (item: TabItem) => {
- console.log('Quick entry pressed:', item);
- };
- // IP 筛选
- const handleIPSelect = (item: IPItem, index: number) => {
- // 立即更新 UI,不等待网络请求
- setIpIndex(index);
- setGoods([]); // 清空列表,给予用户切换反馈 (或者可以保留旧数据直到新数据到来,取决于需求,清空通常感觉更"快"因为有反馈)
-
- // 异步加载数据
- requestAnimationFrame(async () => {
- const newParams = { ...searchParams, worksId: item.id, current: 1 };
- setSearchParams(newParams);
- await loadGoods(newParams);
- });
- };
- // 商品点击
- const handleGoodsPress = (item: GoodsItem) => {
- router.push(`/product/${item.id}` as any);
- };
- // 列表头部组件
- const ListHeader = useMemo(() => {
- return (
- <>
- {/* 搜索栏 */}
- <SearchBar onSearch={handleSearch} />
- {/* Banner 轮播 */}
- {banners.length > 0 && <Banner data={banners} onPress={handleBannerPress} />}
- {/* 功能入口 */}
- <View style={styles.section}>
- {tabs.length > 0 && <QuickEntry data={tabs} onPress={handleQuickEntryPress} />}
- {/* IP 分类筛选 */}
- {ipList.length > 0 && (
- <IPFilter data={ipList} activeIndex={ipIndex} onSelect={handleIPSelect} />
- )}
- </View>
- </>
- );
- }, [banners, tabs, ipList, ipIndex]); // 依赖项
- const renderItem = useCallback(({ item }: { item: GoodsItem }) => {
- return <GoodsCard data={item} onPress={handleGoodsPress} />;
- }, []);
- const ListEmptyComponent = useMemo(() => {
- if (listLoading) {
- return (
- <View style={styles.loadingListContainer}>
- <ActivityIndicator size="small" color="#aaa" />
- <Text style={styles.loadingText}>加载商品中...</Text>
- </View>
- );
- }
- if (!loading && goods.length === 0) {
- return (
- <View style={styles.emptyContainer}>
- <Text style={styles.emptyText}>暂无商品</Text>
- </View>
- )
- }
- return null;
- }, [listLoading, loading, goods.length]);
- if (loading) {
- return (
- <View style={styles.loadingContainer}>
- <ActivityIndicator size="large" color="#fff" />
- <Text style={styles.loadingText}>加载中...</Text>
- </View>
- );
- }
- return (
- <View style={styles.container}>
- <StatusBar barStyle="light-content" />
- <ImageBackground
- source={{ uri: Images.common.indexBg }}
- style={styles.background}
- resizeMode="cover"
- >
- <FlatList
- data={goods}
- renderItem={renderItem}
- keyExtractor={(item) => item.id}
- ListHeaderComponent={ListHeader}
- numColumns={2}
- columnWrapperStyle={styles.columnWrapper}
- contentContainerStyle={{ paddingTop: insets.top + 10, paddingBottom: 20 }}
- showsVerticalScrollIndicator={false}
- refreshControl={
- <RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#fff" />
- }
- ListEmptyComponent={ListEmptyComponent}
- onEndReachedThreshold={0.5}
- // onEndReached={() => { /* Implement pagination if needed */ }}
- />
- </ImageBackground>
- </View>
- );
- }
- const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#000',
- },
- background: {
- flex: 1,
- },
- section: {
- paddingHorizontal: 10,
- },
- loadingContainer: {
- flex: 1,
- backgroundColor: '#1a1a2e',
- justifyContent: 'center',
- alignItems: 'center',
- },
- loadingListContainer: {
- padding: 20,
- alignItems: 'center',
- },
- loadingText: {
- color: '#fff',
- marginTop: 10,
- fontSize: 14,
- },
- columnWrapper: {
- justifyContent: 'space-between',
- paddingHorizontal: 10,
- },
- emptyContainer: {
- padding: 50,
- alignItems: 'center',
- },
- emptyText: {
- color: '#999',
- fontSize: 14,
- },
- });
|