import React, { useState, useEffect, useCallback, useRef } from "react"; import { View, Text, StyleSheet, FlatList, Image, TouchableOpacity, TextInput, SafeAreaView, StatusBar, ActivityIndicator, KeyboardAvoidingView, Platform, ScrollView, RefreshControl, Animated, } from "react-native"; import Ionicons from "@expo/vector-icons/Ionicons"; import { useNavigation, useRoute } from "@react-navigation/native"; import { NativeStackNavigationProp } from "@react-navigation/native-stack"; import { RouteProp } from "@react-navigation/native"; import { productApi, ProductParams, type Product, } from "../services/api/productApi"; import { useTranslation } from "react-i18next"; import isSmallScreen from "../utils/isSmallScreen"; import { Svg, Path } from "react-native-svg"; import SearchIcon from "../components/SearchIcon"; import widthUtils from "../utils/widthUtils"; import fontSize from "../utils/fontsizeUtils"; import useUserStore from "../store/user"; import { getSubjectTransLanguage } from "../utils/languageUtils"; // 图标组件 - 使用React.memo优化渲染 const IconComponent = React.memo( ({ name, size, color }: { name: string; size: number; color: string }) => { const Icon = Ionicons as any; return ; } ); // 路由参数类型 type SearchResultRouteParams = { keyword?: string; category_id?: number; }; // 组件Props类型 type SearchResultScreenProps = { route: RouteProp, string>; navigation: NativeStackNavigationProp; }; // 懒加载图片组件 - 改进版本 const LazyImage = React.memo( ({ uri, style, resizeMode, }: { uri: string; style: any; resizeMode: any; }) => { const [isLoaded, setIsLoaded] = useState(false); const [hasError, setHasError] = useState(false); const onLoad = useCallback(() => { setIsLoaded(true); }, []); const onError = useCallback(() => { setHasError(true); setIsLoaded(true); // Also mark as loaded on error to remove placeholder }, []); return ( {/* Show placeholder while image is loading */} {!isLoaded && !hasError && ( )} {/* Show error state if image failed to load */} {hasError && ( 加载失败 )} {/* Actual image */} ); } ); // 产品骨架屏组件 - 用于加载状态 const ProductSkeleton = React.memo(() => { // 创建动画值 const shimmerAnim = useRef(new Animated.Value(0)).current; // 设置动画效果 useEffect(() => { const shimmerAnimation = Animated.loop( Animated.timing(shimmerAnim, { toValue: 1, duration: 1500, useNativeDriver: true, }) ); shimmerAnimation.start(); return () => { shimmerAnimation.stop(); }; }, []); // 定义动画插值 const shimmerTranslate = shimmerAnim.interpolate({ inputRange: [0, 1], outputRange: [-200, 200], }); return ( ); }); // 产品项组件 - 使用React.memo优化渲染 const ProductItem = React.memo( ({ product, onPress, t, userStore, }: { product: Product; onPress: (product: Product) => void; t: any; userStore: any; }) => ( onPress(product)} activeOpacity={0.7} > {product.product_image_urls && product.product_image_urls[0] ? ( ) : ( {t('productPicture')} )} {userStore.user?.user_id && ( VIP {userStore.user?.vip_level} )} {/* 产品分类 */} {getSubjectTransLanguage(product) || product.subject_trans} {/* 价格信息 */} {userStore.user?.user_id && ( {product.original_min_price || "0"} {product.currency || "FCFA"} )} {product.min_price || "0"} {product.currency || "FCFA"} {/* 销售量 */} {product.sold_out} {product.sold_out === 0 ? "" : "+"}{" "} {t('sales')} ) ); export const SearchResultScreen = ({ route, navigation }: SearchResultScreenProps) => { const { t } = useTranslation(); const [searchText, setSearchText] = useState(""); const [products, setProducts] = useState([]); const [originalProducts, setOriginalProducts] = useState([]); const [loading, setLoading] = useState(true); const [hasMore, setHasMore] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [isFilterVisible, setIsFilterVisible] = useState(false); const [sortOrder, setSortOrder] = useState<"asc" | "desc" | null>(null); const [sortField, setSortField] = useState<"price" | "time">("price"); const [showBackToTop, setShowBackToTop] = useState(false); const flatListRef = useRef(null); const [activeTab, setActiveTab] = useState<"default" | "volume" | "price">( "default" ); const userStore = useUserStore(); const [showSkeleton, setShowSkeleton] = useState(true); const [searchParams, setSearchParams] = useState({ keyword: route.params?.keyword || "", page: 1, page_size: 10, sort_order: "desc", category_id: null, sort_by: "default", language: "en", user_id: userStore.user.user_id, }); const [currentPage, setCurrentPage] = useState(1); const [refreshing, setRefreshing] = useState(false); const lastLoadTime = useRef(0); // 初始化搜索关键字 useEffect(() => { if (route.params?.keyword) { setSearchText(route.params.keyword); setShowSkeleton(true); const newParams = { ...searchParams, keyword: route.params.keyword, }; setSearchParams(newParams); // 直接调用API,避免依赖循环 const fetchData = async () => { try { setLoading(true); const res = await productApi.getSearchProducts(newParams); setProducts(res.products); setOriginalProducts(res.products); setCurrentPage(1); setHasMore(res.products.length === newParams.page_size); } catch (error) { console.error("Error fetching products:", error); setProducts([]); setOriginalProducts([]); setHasMore(false); } finally { setLoading(false); setTimeout(() => { setShowSkeleton(false); }, 300); } }; fetchData(); } if (route.params?.category_id) { setShowSkeleton(true); const newParams = { ...searchParams, category_id: route.params.category_id, }; setSearchParams(newParams); // 直接调用API,避免依赖循环 const fetchData = async () => { try { setLoading(true); const res = await productApi.getSearchProducts(newParams); setProducts(res.products); setOriginalProducts(res.products); setCurrentPage(1); setHasMore(res.products.length === newParams.page_size); } catch (error) { console.error("Error fetching products:", error); setProducts([]); setOriginalProducts([]); setHasMore(false); } finally { setLoading(false); setTimeout(() => { setShowSkeleton(false); }, 300); } }; fetchData(); } }, [route.params?.keyword, route.params?.category_id]); // 搜索产品的API调用 const searchProducts = useCallback( async (params: ProductParams, isLoadMore = false) => { // 防止重复请求 - 只在内部状态已经是加载中时阻止请求 if (isLoadMore && loadingMore) { console.log('阻止重复加载更多请求'); return; } if (!isLoadMore && loading) { console.log('阻止重复初始加载请求'); return; } console.log('发起请求:', isLoadMore ? '加载更多' : '初始加载', params); if (!isLoadMore) { setLoading(true); setShowSkeleton(true); } else { setLoadingMore(true); } try { const res = await productApi.getSearchProducts(params); console.log('请求成功, 获取商品数:', res.products.length); if (isLoadMore) { // 使用回调方式更新,确保获取最新状态 setProducts(prev => { // 过滤掉重复商品,避免闪烁 const newProducts = res.products.filter( newProduct => !prev.some( existingProduct => existingProduct.offer_id === newProduct.offer_id ) ); return [...prev, ...newProducts]; }); setCurrentPage(prev => prev + 1); } else { setProducts(res.products); // 保存原始排序的数据,以便默认排序时恢复 setOriginalProducts(res.products); setCurrentPage(1); } // 如果返回的数据少于页面大小,说明没有更多数据了 setHasMore(res.products.length === params.page_size); return res; } catch (error) { console.error("Error searching products:", error); // 发生错误时,设置hasMore为false,防止继续加载 setHasMore(false); // 如果不是加载更多,清空产品列表 if (!isLoadMore) { setProducts([]); setOriginalProducts([]); } throw error; } finally { if (isLoadMore) { // 延迟清除加载状态,让视觉过渡更平滑 setTimeout(() => { setLoadingMore(false); }, 300); } else { setLoading(false); // 添加延迟以使骨架屏过渡更平滑 setTimeout(() => { setShowSkeleton(false); }, 300); } } }, [loading, loadingMore] ); // 处理搜索提交 const handleSearch = useCallback(() => { if (searchText.trim()) { // 重置排序状态 setSortField("price"); setSortOrder(null); // 重置到默认标签 setActiveTab("default"); // Show skeleton for new search setShowSkeleton(true); const newParams = { ...searchParams, keyword: searchText.trim(), page: 1, // 重置到第一页 }; setSearchParams(newParams); searchProducts(newParams); } }, [searchText, searchParams, searchProducts]); // 切换筛选器显示状态 const toggleFilter = useCallback(() => { setIsFilterVisible(!isFilterVisible); }, [isFilterVisible]); // 处理点击产品 const handleProductPress = useCallback( (product: Product) => { // 导航到产品详情页,并传递产品信息 navigation.navigate("ProductDetail", { offer_id: product.offer_id, searchKeyword: searchText, price: product.min_price, }); }, [navigation, searchText] ); // 返回上一页 const goBack = useCallback(() => { navigation.goBack(); }, [navigation]); // 渲染列表为空时的组件 const renderEmptyList = useCallback( () => ( {t("noResults")} "{searchText}" {t("tryDifferentKeywords")} ), [searchText, t] ); // 渲染商品项 const renderItem = useCallback(({ item, index }: { item: any; index: number }) => { // 处理骨架屏项 if (item && item.isLoadingSkeleton) { return ; } // 处理空白占位项 if (!item) { // 显示透明的占位视图 return ; } // 渲染正常商品项 return ( ); }, [handleProductPress, t, userStore]); // 创建产品列表项的key提取器 const keyExtractor = useCallback((item: any, index: number) => { if (!item) return `empty-${index}`; return `${item.offer_id || 'item'}-${index}`; }, []); // 处理排序 const handleSort = useCallback( (field: "price" | "time", order: "asc" | "desc") => { setSortField(field); setSortOrder(order); // 本地排序,不发送API请求 setProducts((prevProducts) => { const sortedProducts = [...prevProducts]; if (field === "price") { sortedProducts.sort((a, b) => { const priceA = a.min_price || 0; const priceB = b.min_price || 0; return order === "asc" ? priceA - priceB : priceB - priceA; }); } else if (field === "time") { sortedProducts.sort((a, b) => { // 假设产品有create_time字段,如果没有可以使用其他时间相关字段 const timeA = new Date(a.create_date || 0).getTime(); const timeB = new Date(b.create_date || 0).getTime(); return order === "asc" ? timeA - timeB : timeB - timeA; }); } return sortedProducts; }); }, [] ); // 处理加载更多 const handleLoadMore = useCallback(() => { // 简化条件判断,直接仿照HomeScreen的处理方式 if (!hasMore || loadingMore) { return; } // 添加时间防抖,避免频繁触发 const now = Date.now(); if (now - lastLoadTime.current < 500) { // 500ms内不重复触发 return; } lastLoadTime.current = now; // 标记为加载中 setLoadingMore(true); // 请求参数 const loadMoreParams = { ...searchParams, page: currentPage + 1, }; // 获取下一页数据 productApi.getSearchProducts(loadMoreParams) .then(res => { // 使用回调更新,确保获取最新状态 setProducts(prev => { // 过滤掉重复商品 const newProducts = res.products.filter( newProduct => !prev.some( existingProduct => existingProduct.offer_id === newProduct.offer_id ) ); return [...prev, ...newProducts]; }); setCurrentPage(prev => prev + 1); setHasMore(res.products.length === loadMoreParams.page_size); }) .catch(error => { console.error("加载更多失败:", error); }) .finally(() => { // 延迟结束加载状态,给用户更好的体验 setTimeout(() => { setLoadingMore(false); }, 300); }); }, [hasMore, loadingMore, searchParams, currentPage]); // 处理下拉刷新 const handleRefresh = useCallback(() => { if (refreshing) return; setRefreshing(true); const refreshParams = { ...searchParams, page: 1, }; // 直接调用API避免状态冲突 productApi.getSearchProducts(refreshParams) .then(res => { // 保留原有商品列表,先显示再更新 setProducts(res.products); setOriginalProducts(res.products); setCurrentPage(1); setHasMore(res.products.length === refreshParams.page_size); }) .catch(error => { console.error("刷新失败:", error); // 不清空产品列表,保持用户体验 }) .finally(() => { // 延迟结束刷新状态,让过渡更平滑 setTimeout(() => { setRefreshing(false); }, 300); }); }, [refreshing, searchParams]); // 渲染底部加载指示器 const renderFooter = useCallback(() => { // 加载中状态显示骨架屏 if (loadingMore) { return ( ); } // 没有更多数据时显示提示 if (!hasMore) { return ( {t("noMoreData")} ); } return ; }, [hasMore, loadingMore, t]); // 处理滚动事件 const handleScroll = useCallback((event: any) => { const offsetY = event.nativeEvent.contentOffset.y; // 当滚动超过屏幕高度的一半时显示回到顶部按钮 setShowBackToTop(offsetY > 300); }, []); // 回到顶部 const scrollToTop = useCallback(() => { flatListRef.current?.scrollToOffset({ offset: 0, animated: true }); }, []); // 处理标签切换 const handleTabChange = useCallback( (tab: "default" | "volume" | "price") => { // 如果点击的是已经激活的价格标签,则切换排序顺序 if (tab === "price" && activeTab === "price") { // 如果当前是价格升序,则切换为降序;如果是降序或未设置,则切换为升序 const newOrder = sortOrder === "asc" ? "desc" : "asc"; handleSort("price", newOrder); scrollToTop(); } else { setActiveTab(tab); // 根据标签类型设置排序规则 if (tab === "price") { // 默认价格从低到高 handleSort("price", "asc"); scrollToTop(); } else if (tab === "volume") { // 按销量排序 const sortedProducts = [...originalProducts]; sortedProducts.sort((a, b) => { const volumeA = a.sold_out || 0; const volumeB = b.sold_out || 0; return volumeB - volumeA; // 从高到低排序 }); setProducts(sortedProducts); scrollToTop(); } else { // 默认排序 - 恢复到原始数据顺序 setProducts([...originalProducts]); scrollToTop(); } } }, [handleSort, activeTab, sortOrder, originalProducts, scrollToTop] ); // 渲染骨架屏网格 const renderSkeletonGrid = useCallback(() => { // 创建一个骨架屏数组 const skeletonArray = Array(8).fill(null); return ( } keyExtractor={(_, index) => `skeleton-${index}`} numColumns={2} scrollEnabled={false} contentContainerStyle={styles.productGrid} /> ); }, []); // 确保产品列表包含偶数条目,防止最后一个产品占满整行 const ensureEvenItems = useCallback((): (Product | any)[] => { // 如果商品数量为奇数 if (products.length % 2 !== 0) { // 加载更多时使用骨架屏替代空白占位符 if (loadingMore) { return [...products, { isLoadingSkeleton: true, tempId: 'loadingPlaceholder' }]; } // 非加载状态时使用空白占位符 return [...products, null]; } return products; }, [products, loadingMore]); return ( {/* 搜索栏 */} {searchText.length > 0 && ( setSearchText("")} hitSlop={{ top: 10, right: 10, bottom: 10, left: 10 }} > )} {/* 标签筛选 */} handleTabChange("default")} > {t('default')} handleTabChange("volume")} > {t('volume')} handleTabChange("price")} > {t('price')} {activeTab === "price" && ( )} {/* 搜索结果 */} {/* 搜索结果标题栏和排序选项 */} {isFilterVisible && ( {t("price")}: handleSort("price", "asc")} > {t("lowToHigh")} {sortField === "price" && sortOrder === "asc" && ( )} handleSort("price", "desc")} > {t("highToLow")} {sortField === "price" && sortOrder === "desc" && ( )} {t('time')}: handleSort("time", "asc")} > {t("oldest")} {sortField === "time" && sortOrder === "asc" && ( )} handleSort("time", "desc")} > {t("newest")} {sortField === "time" && sortOrder === "desc" && ( )} )} {/* 加载指示器或产品列表 */} {loading && showSkeleton ? ( renderSkeletonGrid() ) : ( <> } /> {showBackToTop && ( )} )} ); }; const styles = StyleSheet.create({ safeArea: { flex: 1, backgroundColor: '#fff', }, safeAreaContent: { flex: 1, paddingTop: Platform.OS === 'android' ? 0 : 0, }, container: { flex: 1, backgroundColor: '#fff', }, searchHeader: { flexDirection: "row", alignItems: "center", paddingHorizontal: 12, paddingVertical: 8, backgroundColor: "#fff", borderBottomWidth: 1, borderBottomColor: "#f0f0f0", }, backButton: { padding: 4, }, searchBar: { flex: 1, flexDirection: "row", alignItems: "center", backgroundColor: "#f5f5f5", borderRadius: 20, paddingHorizontal: 8, height: widthUtils(40, 40).height, marginHorizontal: 8, }, searchInput: { flex: 1, marginLeft: 4, fontSize: isSmallScreen ? 14 : 16, color: "#333", height: widthUtils(40, 40).height, paddingRight: 30, }, clearButton: { position: "absolute", right: 10, top: '50%', marginTop: -10, width: 20, height: 20, alignItems: 'center', justifyContent: 'center', zIndex: 20, }, searchButton: { paddingVertical: 4, paddingHorizontal: 8, }, searchButtonText: { fontSize: isSmallScreen ? 14 : 16, color: "#333", fontWeight: "500", }, tabContainer: { flexDirection: "row", backgroundColor: "#fff", borderBottomWidth: 1, borderBottomColor: "#f0f0f0", }, tabButton: { flex: 1, alignItems: "center", justifyContent: "center", paddingVertical: 12, position: "relative", }, tabButtonContent: { flexDirection: "row", alignItems: "center", justifyContent: "center", }, tabIcon: { marginLeft: 4, }, tabText: { fontSize: fontSize(16), color: "#000", }, activeTabText: { color: "#0933a1", fontWeight: "bold", }, activeTabButton: { // borderBottomColor: "#0933a1", }, resultsContainer: { flex: 1, }, resultsHeader: { backgroundColor: "#fff", borderBottomWidth: 1, borderBottomColor: "#f0f0f0", paddingVertical: 8, }, loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center", }, productGrid: { padding: 8, }, productCard: { flex: 1, margin: 8, backgroundColor: "#fff", borderRadius: 8, overflow: "hidden", }, productImageContainer: { height: widthUtils(190, 190).height, backgroundColor: "#f9f9f9", alignItems: "center", justifyContent: "center", }, productImage: { width: "100%", height: "100%", }, placeholderText: { color: "#999", fontSize: fontSize(14), }, productInfo: { padding: 8, }, categoryText: { fontSize: isSmallScreen ? 12 : 14, color: "#000000", fontWeight: "600", marginBottom: 4, fontFamily: "PingFang SC", letterSpacing: 0, }, priceRow: { alignItems: "baseline", marginBottom: 2, }, currentPrice: { fontSize: fontSize(24), fontWeight: "600", color: "#ff6600", marginRight: 4, }, currency: { fontSize: fontSize(14), fontWeight: "600", fontFamily: "PingFang SC", color: "#ff6600", }, originalPrice: { fontSize: fontSize(14), color: "#999", textDecorationLine: "line-through", }, currencySmall: { fontSize: fontSize(14), color: "#9a9a9a", fontWeight: "600", fontFamily: "PingFang SC", }, productSales: { fontSize: fontSize(14), fontWeight: "600", fontFamily: "PingFang SC", color: "#7c7c7c", }, sortScrollView: { flexGrow: 0, }, sortGroup: { flexDirection: "row", alignItems: "center", paddingHorizontal: 16, }, sortLabel: { fontSize: fontSize(16), color: "#666", marginRight: 8, }, sortButtons: { flexDirection: "row", alignItems: "center", }, sortButton: { flexDirection: "row", alignItems: "center", paddingVertical: 4, paddingHorizontal: 8, borderRadius: 4, marginLeft: 4, borderWidth: 1, borderColor: "#e0e0e0", }, sortButtonActive: { borderColor: "#ff6600", backgroundColor: "#fff8f5", }, sortButtonText: { fontSize: fontSize(14), color: "#666", }, sortButtonTextActive: { color: "#ff6600", fontWeight: "bold", }, sortDivider: { width: 1, height: widthUtils(20, 20).height, backgroundColor: "#e0e0e0", marginHorizontal: 16, }, footerContainer: { padding: 16, alignItems: "center", flexDirection: "row", justifyContent: "center", }, footerText: { fontSize: fontSize(14), color: "#666", marginLeft: 8, }, footerSpace: { height: widthUtils(20, 20).height, }, backToTopButton: { position: "absolute", bottom: 20, right: 20, width: widthUtils(20, 20).width, height: widthUtils(20, 20).height, borderRadius: 22, backgroundColor: "#0066FF", justifyContent: "center", alignItems: "center", shadowColor: "#000", shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, }, emptyContainer: { flex: 1, minHeight: 300, justifyContent: "center", alignItems: "center", padding: 16, }, emptyText: { fontSize: fontSize(14), fontWeight: "bold", color: "#333", marginTop: 16, marginBottom: 8, }, emptySubtext: { fontSize: fontSize(14), color: "#999", textAlign: "center", }, resultsTitle: { fontSize: fontSize(14), fontWeight: "bold", color: "#333", flex: 1, }, resultsCount: { fontSize: fontSize(14), color: "#999", }, filterButton: { marginLeft: 8, padding: 4, }, imagePlaceholder: { backgroundColor: '#EAEAEA', justifyContent: 'center', alignItems: 'center', borderRadius: 8, }, vipIcon: { position: "absolute", top: 0, right: 0, justifyContent: "center", alignItems: "center", backgroundColor: "#3b3b3b", borderRadius: 10, flexDirection: "row", width: widthUtils(30, 66).width, height: widthUtils(30, 66).height, }, vipButtonText: { fontStyle: "italic", fontWeight: "900", fontSize: fontSize(18), color: "#f1c355", }, vipLabelBold: { fontStyle: "italic", fontWeight: "900", fontSize: fontSize(18), color: "#f1c355", }, beautyProductInfoRow: { flexDirection: "row", alignItems: "center", }, flexRowCentered: {}, priceContainer: { flexDirection: "row", }, highlightedText: { fontWeight: "700", fontSize: fontSize(24), color: "#ff5100", }, highlightedText1: { fontWeight: "700", fontSize: fontSize(14), color: "#ff5100", }, priceContainer1: {}, priceLabel1: { fontSize: fontSize(12), fontWeight: "600", color: "#9a9a9a", textDecorationLine: "line-through", }, skeletonText: { backgroundColor: '#EAEAEA', borderRadius: 4, overflow: "hidden", position: "relative", }, shimmer: { width: "30%", height: "100%", backgroundColor: "rgba(255, 255, 255, 0.3)", position: "absolute", top: 0, left: 0, }, productColumnWrapper: { justifyContent: 'space-between', paddingHorizontal: 8, }, });