import React, { useState, useEffect, useCallback } from "react"; import { View, Text, StyleSheet, FlatList, Image, TouchableOpacity, TextInput, SafeAreaView, StatusBar, ActivityIndicator, KeyboardAvoidingView, Platform, Keyboard, ScrollView, } 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'; // 图标组件 - 使用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; }; // 产品项组件 - 使用React.memo优化渲染 const ProductItem = React.memo( ({ product, onPress, t, }: { product: Product; onPress: (product: Product) => void; t: any; }) => ( onPress(product)} activeOpacity={0.7} > {product.product_image_urls[0] ? ( ) : ( product picture )} {/* 价格 */} {product?.min_price?.toFixed(2)} {/* 产品标题 */} {product.subject} {/* 销售量 */} {t('monthlySales')}: {product.sold_out} ) ); export const SearchResultScreen = () => { const { t } = useTranslation(); const navigation = useNavigation>(); const route = useRoute, string>>(); const [searchText, setSearchText] = useState(""); const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [hasMore, setHasMore] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [minPrice, setMinPrice] = useState(""); const [maxPrice, setMaxPrice] = useState(""); 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 = React.useRef(null); const [searchParams, setSearchParams] = useState({ keyword: route.params?.keyword || "", page: 1, page_size: 20, sort_order: "desc", max_price: null, min_price: null, category_id: null, sort_by: "create_date", }); // 初始化搜索关键字 useEffect(() => { if (route.params?.keyword) { setSearchText(route.params.keyword); const newParams = { ...searchParams, keyword: route.params.keyword, }; setSearchParams(newParams); searchProducts(newParams); } }, [route.params?.keyword]); // 搜索产品的API调用 const searchProducts = useCallback( async (params: ProductParams, isLoadMore = false) => { if (!isLoadMore) setLoading(true); try { const res = await productApi.getSearchProducts(params); if (isLoadMore) { setProducts((prev) => [...prev, ...res.products]); } else { setProducts(res.products); } // 如果返回的数据少于页面大小,说明没有更多数据了 setHasMore(res.products.length === params.page_size); } catch (error) { console.error("Error searching products:", error); } finally { setLoading(false); setLoadingMore(false); } }, [] ); // 处理搜索提交 const handleSearch = useCallback(() => { if (searchText.trim()) { // 重置排序状态 setSortField("price"); setSortOrder(null); const newParams = { ...searchParams, keyword: searchText.trim(), page: 1, // 重置到第一页 }; setSearchParams(newParams); searchProducts(newParams); } }, [searchText, searchParams, searchProducts]); // 处理价格筛选 const handlePriceFilter = useCallback(() => { Keyboard.dismiss(); const newParams = { ...searchParams }; if (minPrice.trim()) { newParams.min_price = parseFloat(minPrice); } else { newParams.min_price = null; } if (maxPrice.trim()) { newParams.max_price = parseFloat(maxPrice); } else { newParams.max_price = null; } newParams.page = 1; // 重置到第一页 setSearchParams(newParams); searchProducts(newParams); console.log(newParams); }, [minPrice, maxPrice, searchParams, searchProducts]); // 重置价格筛选 const resetPriceFilter = useCallback(() => { setMinPrice(""); setMaxPrice(""); const newParams = { ...searchParams, min_price: null, max_price: null, page: 1, }; setSearchParams(newParams); searchProducts(newParams); }, [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 renderProductItem = useCallback( ({ item }: { item: Product }) => ( ), [handleProductPress, t] ); // 处理排序 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(() => { if (loading || !hasMore || loadingMore) return; setLoadingMore(true); const newParams = { ...searchParams, page: searchParams.page + 1, }; setSearchParams(newParams); searchProducts(newParams, true); }, [loading, hasMore, loadingMore, searchParams, searchProducts]); // 渲染底部加载指示器 const renderFooter = useCallback(() => { if (!hasMore) return ( {t('noMoreData')} ); if (loadingMore) return ( {t('loadingMore')} ); return ; }, [loadingMore, hasMore, 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 }); }, []); return ( {/* 搜索栏 */} {searchText.length > 0 && ( setSearchText("")}> )} {/* 搜索结果 */} {/* 价格筛选器 */} {isFilterVisible && ( {t('priceRange')}: - {t('reset')} {t('apply')} )} {/* 搜索结果标题栏和排序选项 */} {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 ? ( ) : ( <> String(item.offer_id)} numColumns={2} contentContainerStyle={styles.productGrid} ListEmptyComponent={renderEmptyList} ListFooterComponent={renderFooter} showsVerticalScrollIndicator={false} initialNumToRender={8} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} onEndReached={handleLoadMore} onEndReachedThreshold={0.2} onScroll={handleScroll} scrollEventThrottle={16} /> {showBackToTop && ( )} )} ); }; const styles = StyleSheet.create({ safeArea: { flex: 1, backgroundColor: "#ffffff", }, container: { flex: 1, backgroundColor: "#f5f5f5", }, searchHeader: { flexDirection: "row", alignItems: "center", paddingHorizontal: 15, paddingVertical: 10, backgroundColor: "#fff", borderBottomWidth: 1, borderBottomColor: "#f0f0f0", }, backButton: { marginRight: 10, padding: 5, }, searchBar: { flex: 1, flexDirection: "row", alignItems: "center", backgroundColor: "#f5f5f5", borderRadius: 20, paddingHorizontal: 15, height: 40, }, searchInput: { flex: 1, marginLeft: 8, fontSize: 16, color: "#333", height: 40, }, resultsContainer: { flex: 1, }, resultsHeader: { backgroundColor: "#fff", borderBottomWidth: 1, borderBottomColor: "#f0f0f0", paddingVertical: 10, }, resultsTitle: { fontSize: 16, fontWeight: "bold", color: "#333", flex: 1, }, resultsCount: { fontSize: 14, color: "#999", }, loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center", }, productGrid: { padding: 8, }, productCard: { flex: 1, margin: 8, backgroundColor: "#fff", borderRadius: 8, overflow: "hidden", elevation: 2, // Android shadow shadowColor: "#000", // iOS shadow shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 2, }, productImageContainer: { height: 150, backgroundColor: "#f9f9f9", alignItems: "center", justifyContent: "center", }, productImage: { width: "100%", height: "100%", }, placeholderText: { color: "#999", fontSize: 16, }, productInfo: { padding: 10, }, productPrice: { fontSize: 18, fontWeight: "bold", color: "#ff6600", marginBottom: 5, }, productTitle: { fontSize: 14, color: "#333", marginBottom: 5, }, productSales: { fontSize: 12, color: "#999", }, emptyContainer: { flex: 1, minHeight: 300, justifyContent: "center", alignItems: "center", padding: 20, }, emptyText: { fontSize: 16, fontWeight: "bold", color: "#333", marginTop: 15, marginBottom: 8, }, emptySubtext: { fontSize: 14, color: "#999", textAlign: "center", }, filterButton: { marginLeft: 10, padding: 5, }, filterContainer: { backgroundColor: "#fff", paddingHorizontal: 15, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: "#f0f0f0", }, priceFilterRow: { flexDirection: "row", alignItems: "center", marginBottom: 10, }, filterLabel: { fontSize: 14, fontWeight: "bold", color: "#333", marginRight: 10, }, priceInputContainer: { flex: 1, flexDirection: "row", alignItems: "center", }, priceInput: { flex: 1, height: 36, backgroundColor: "#f5f5f5", borderRadius: 4, paddingHorizontal: 10, fontSize: 14, color: "#333", }, priceDivider: { marginHorizontal: 8, color: "#333", }, filterButtons: { flexDirection: "row", justifyContent: "flex-end", }, resetButton: { paddingHorizontal: 15, paddingVertical: 6, borderRadius: 4, backgroundColor: "#f5f5f5", marginRight: 10, }, resetButtonText: { fontSize: 14, color: "#666", }, applyButton: { paddingHorizontal: 15, paddingVertical: 6, borderRadius: 4, backgroundColor: "#0066FF", }, applyButtonText: { fontSize: 14, color: "#fff", fontWeight: "bold", }, sortScrollView: { flexGrow: 0, }, sortGroup: { flexDirection: "row", alignItems: "center", paddingHorizontal: 15, }, sortLabel: { fontSize: 14, color: "#666", marginRight: 8, }, sortButtons: { flexDirection: "row", alignItems: "center", }, sortButton: { flexDirection: "row", alignItems: "center", paddingVertical: 4, paddingHorizontal: 8, borderRadius: 4, marginLeft: 6, borderWidth: 1, borderColor: "#e0e0e0", }, sortButtonActive: { borderColor: "#ff6600", backgroundColor: "#fff8f5", }, sortButtonText: { fontSize: 12, color: "#666", }, sortButtonTextActive: { color: "#ff6600", fontWeight: "bold", }, sortDivider: { width: 1, height: 20, backgroundColor: "#e0e0e0", marginHorizontal: 15, }, footerContainer: { padding: 15, alignItems: "center", flexDirection: "row", justifyContent: "center", }, footerText: { fontSize: 14, color: "#666", marginLeft: 8, }, footerSpace: { height: 20, }, backToTopButton: { position: "absolute", bottom: 20, right: 20, width: 44, height: 44, borderRadius: 22, backgroundColor: "#0066FF", justifyContent: "center", alignItems: "center", elevation: 5, shadowColor: "#000", shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.3, shadowRadius: 3, }, });