| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- import { useRouter } from "expo-router";
- import React, { useCallback, useEffect, useMemo, useState } from "react";
- import {
- ActivityIndicator,
- FlatList,
- 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 { Colors } from "@/constants/Colors"; // Import Colors
- // 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) => {
- // 虚拟 ID 检查,防止爬虫(Obfuscation)
- if (item.id === "99999999") return;
- 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={Colors.neonBlue} />
- <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={Colors.neonBlue} />
- <Text style={styles.loadingText}>系统接入中...</Text>
- </View>
- );
- }
- return (
- <View style={styles.container}>
- <StatusBar barStyle="light-content" />
- {/* Remove ImageBackground or use a dark one */}
- <View style={styles.background}>
- <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={Colors.neonBlue}
- />
- }
- ListEmptyComponent={ListEmptyComponent}
- onEndReachedThreshold={0.5}
- />
- </View>
- </View>
- );
- }
- const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: Colors.darkBg,
- },
- background: {
- flex: 1,
- backgroundColor: Colors.darkBg, // Use solid dark background
- },
- section: {
- paddingHorizontal: 10,
- },
- loadingContainer: {
- flex: 1,
- backgroundColor: Colors.darkBg,
- justifyContent: "center",
- alignItems: "center",
- },
- loadingListContainer: {
- padding: 20,
- alignItems: "center",
- },
- loadingText: {
- color: Colors.textSecondary,
- marginTop: 10,
- fontSize: 14,
- fontFamily: "System", // Keep default for now
- },
- columnWrapper: {
- justifyContent: "space-between",
- paddingHorizontal: 10,
- },
- emptyContainer: {
- padding: 50,
- alignItems: "center",
- },
- emptyText: {
- color: Colors.textTertiary,
- fontSize: 14,
- },
- });
|