diff --git a/app/navigation/AppNavigator.tsx b/app/navigation/AppNavigator.tsx index f16ae40..444cec1 100644 --- a/app/navigation/AppNavigator.tsx +++ b/app/navigation/AppNavigator.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { NavigationContainer } from "@react-navigation/native"; +import React, { useRef } from 'react'; +import { NavigationContainer, NavigationState, PartialState } from "@react-navigation/native"; import { createNativeStackNavigator } from "@react-navigation/native-stack"; import { RootStackParamList } from './types'; import * as Screens from './screens'; @@ -7,9 +7,84 @@ import Toast from "react-native-toast-message"; const Stack = createNativeStackNavigator(); +// 获取当前路由信息的工具函数 +// [DEBUG-ROUTER-LOGGER] 路由跟踪函数 - 生产环境可删除 +const getActiveRouteName = (state: NavigationState | PartialState | undefined): string => { + if (!state || !state.routes) return ''; + + const route = state.routes[state.index || 0]; + + // 检查是否存在嵌套导航 + if (route.state && route.state.routes) { + return getActiveRouteName(route.state); + } + + return route.name; +}; + +// 获取路由的完整路径 +// [DEBUG-ROUTER-LOGGER] 路由跟踪函数 - 生产环境可删除 +const getRoutePath = (state: NavigationState | PartialState | undefined): string[] => { + if (!state || !state.routes) return []; + + const route = state.routes[state.index || 0]; + const currentPath = [route.name]; + + // 检查是否存在嵌套导航 + if (route.state && route.state.routes) { + return [...currentPath, ...getRoutePath(route.state)]; + } + + return currentPath; +}; + export const AppNavigator = () => { + // [DEBUG-ROUTER-LOGGER] 路由跟踪引用 - 生产环境可删除 + const navigationRef = useRef(null); + const routeNameRef = useRef(); + return ( - + { + // [DEBUG-ROUTER-LOGGER] 初始路由日志 - 生产环境可删除 - 开始 + routeNameRef.current = getActiveRouteName(navigationRef.current?.getRootState()); + console.log('[DEBUG-ROUTER] 初始路由:', routeNameRef.current); + + // 打印组件信息 + const componentInfo = Screens[routeNameRef.current as keyof typeof Screens]; + console.log('[DEBUG-ROUTER] 组件信息:', componentInfo ? componentInfo.name || '未命名组件' : '未找到组件'); + // [DEBUG-ROUTER-LOGGER] 初始路由日志 - 生产环境可删除 - 结束 + }} + onStateChange={(state) => { + // [DEBUG-ROUTER-LOGGER] 路由变化日志 - 生产环境可删除 - 开始 + const previousRouteName = routeNameRef.current; + const currentRouteName = getActiveRouteName(state); + + if (previousRouteName !== currentRouteName) { + // 记录路由变化 + console.log(`[DEBUG-ROUTER] 路由变化: ${previousRouteName} -> ${currentRouteName}`); + + // 打印完整路径 + const fullPath = getRoutePath(state); + console.log('[DEBUG-ROUTER] 路由完整路径:', fullPath.join(' -> ')); + + // 打印组件信息 + const componentInfo = Screens[currentRouteName as keyof typeof Screens]; + console.log('[DEBUG-ROUTER] 组件信息:', componentInfo ? componentInfo.name || '未命名组件' : '未找到组件'); + + // 打印路由参数信息 + const currentRoute = state?.routes?.[state.index || 0]; + if (currentRoute && currentRoute.params) { + console.log('[DEBUG-ROUTER] 路由参数:', JSON.stringify(currentRoute.params, null, 2)); + } + + // 更新当前路由名称引用 + routeNameRef.current = currentRouteName; + } + // [DEBUG-ROUTER-LOGGER] 路由变化日志 - 生产环境可删除 - 结束 + }} + > { const [showCategoryModal, setShowCategoryModal] = useState(false); const [showImagePickerModal, setShowImagePickerModal] = useState(false); const [selectedCategory, setSelectedCategory] = useState("Bijoux"); - const [selectedHorizontalCategory, setSelectedHorizontalCategory] = - useState("Tous"); - const [refreshing, setRefreshing] = useState(false); - const [loading, setLoading] = useState(true); // 添加加载状态 - const horizontalScrollRef = useRef(null); + const [selectedHorizontalCategory, setSelectedHorizontalCategory] = useState("Tous"); const userStore = useUserStore(); - const [searchParams, setSearchParams] = useState({ - keyword: "pen", - page: 1, - page_size: 10, - sort_order: "desc", - sort_by: "default", - language: "en", - }); - const [products, setProducts] = useState([]); - const [hasMore, setHasMore] = useState(true); - const [isLoadingMore, setIsLoadingMore] = useState(false); - const [totalProducts, setTotalProducts] = useState(0); - const [initialLoadComplete, setInitialLoadComplete] = useState(false); - const [currentPage, setCurrentPage] = useState(1); // 添加当前页码状态 + const { country, currency } = useGlobalStore(); const flatListRef = useRef(null); + const horizontalScrollRef = useRef(null); const data = [ { imgUrl: require("../../assets/img/banner en (5)_compressed.png"), @@ -351,95 +336,221 @@ export const HomeScreen = () => { add: "CompanyScreen", }, ]; - const [galleryUsed, setGalleryUsed] = useState(false); // 标记是否使用过相册 - const [loadingPlaceholders, setLoadingPlaceholders] = useState(0); // 添加占位符数量状态 - const getProductData = async (isLoadMore = false) => { - if (isLoadMore) { - setIsLoadingMore(true); - } else { - setLoading(true); - } + const [galleryUsed, setGalleryUsed] = useState(false); + const [hotTerms, setHotTerms] = useState([]); + const [isLoadingHotTerms, setIsLoadingHotTerms] = useState(false); + + // 直接在组件中实现分页加载逻辑 + const [products, setProducts] = useState([]); + const [loading, setLoading] = useState(true); + const [loadingMore, setLoadingMore] = useState(false); + const [hasMore, setHasMore] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const [loadingPlaceholders, setLoadingPlaceholders] = useState(0); + const [totalItems, setTotalItems] = useState(0); + const [params, setParams] = useState({ + keyword: "pen", // 初始关键词,将在获取热门关键词后更新 + sort_order: "desc", + sort_by: "default", + language: getCurrentLanguage(), + page: 1, + page_size: 10, + ...(userStore.user?.user_id ? { user_id: userStore.user.user_id } : {}), + }); + + // 获取热门关键词并初始化产品列表 + useEffect(() => { + const initApp = async () => { + try { + // 获取热门关键词 + const response = await productApi.getHotTerms(); + const terms = response.terms || []; + setHotTerms(terms); + + // 如果获取到了热门关键词,使用随机关键词 + if (terms.length > 0) { + const randomIndex = Math.floor(Math.random() * terms.length); + const randomKeyword = terms[randomIndex]; + + // 更新参数 + setParams(prev => ({ + ...prev, + keyword: randomKeyword + })); + + // 获取第一页数据 + await fetchInitialProducts(randomKeyword); + } else { + // 如果没有热门关键词,使用默认关键词"pen" + await fetchInitialProducts("pen"); + } + } catch (error) { + console.error("初始化失败:", error); + // 出错时使用默认关键词 + await fetchInitialProducts("pen"); + } + }; + + initApp(); + }, []); + + // 获取随机关键词 + const getRandomKeyword = useCallback(() => { + if (hotTerms.length === 0) return "pen"; + const randomIndex = Math.floor(Math.random() * hotTerms.length); + const keyword = hotTerms[randomIndex]; + console.log("获取随机关键词:", keyword); + return keyword; + }, [hotTerms]); + + // 获取初始产品数据(第一页及额外的三页) + const fetchInitialProducts = useCallback(async (keyword: string) => { + setLoading(true); try { - const currentParams = { - ...searchParams, - page: isLoadMore ? currentPage + 1 : 1, - ...(userStore.user?.user_id ? { user_id: userStore.user.user_id } : {}), + // 第一页请求参数 + const initialParams = { + ...params, + keyword, + page: 1, + page_size: 10 }; - const res = await productApi.getSearchProducts(currentParams); - setTotalProducts(res.total || 0); + // 获取第一页数据 + const firstPageRes = await productApi.getSearchProducts(initialParams); + setProducts(firstPageRes.products); + setTotalItems(firstPageRes.total || 0); - if (isLoadMore) { - setProducts(prev => [...prev, ...res.products]); - setCurrentPage(prev => prev + 1); - } else { - setProducts(res.products); - setCurrentPage(1); - } - - const currentTotal = isLoadMore ? products.length + res.products.length : res.products.length; - setHasMore(currentTotal < (res.total || 0)); - - if (!isLoadMore && !initialLoadComplete) { - setInitialLoadComplete(true); - const remainingParams = { - ...currentParams, - page: 2, - page_size: 30 + if (hotTerms.length > 0) { + // 存储已使用的关键词,避免重复 + const usedKeywords = new Set([keyword]); + + // 创建获取唯一关键词的函数 + const getUniqueKeyword = () => { + // 如果热门关键词数量不足,或者已经用完所有关键词,返回随机关键词 + if (hotTerms.length <= usedKeywords.size || hotTerms.length <= 1) { + return hotTerms[Math.floor(Math.random() * hotTerms.length)]; + } + + // 尝试获取未使用过的关键词 + let attempts = 0; + while (attempts < 10) { // 最多尝试10次 + const randomIndex = Math.floor(Math.random() * hotTerms.length); + const candidateKeyword = hotTerms[randomIndex]; + + if (!usedKeywords.has(candidateKeyword)) { + usedKeywords.add(candidateKeyword); + return candidateKeyword; + } + + attempts++; + } + + // 如果无法找到唯一关键词,返回随机关键词 + return hotTerms[Math.floor(Math.random() * hotTerms.length)]; }; - const remainingRes = await productApi.getSearchProducts(remainingParams); - setProducts(prev => { - const newProducts = [...prev, ...remainingRes.products]; - setHasMore(newProducts.length < (res.total || 0)); - return newProducts; + + // 使用不同关键词加载额外的3页数据 + const remainingRequests = Array.from({ length: 3 }, async (_, index) => { + // 获取唯一的随机关键词 + const pageKeyword = getUniqueKeyword(); + const pageParams = { + ...params, + keyword: pageKeyword, + page: index + 2, + page_size: 10 + }; + return productApi.getSearchProducts(pageParams); }); - setCurrentPage(2); + + // 并行获取额外数据 + const additionalResults = await Promise.all(remainingRequests); + const additionalProducts = additionalResults.flatMap(result => result.products); + + // 合并所有产品 + setProducts(prev => [...prev, ...additionalProducts]); + setCurrentPage(4); + setHasMore(firstPageRes.products.length + additionalProducts.length < (firstPageRes.total || 0)); + } else { + // 如果没有热门关键词,只使用第一页数据 + setCurrentPage(1); + setHasMore(firstPageRes.products.length < (firstPageRes.total || 0)); } + } catch (error) { + console.error("获取产品数据失败:", error); + } finally { + setLoading(false); + } + }, [params, hotTerms]); + + // 加载更多产品 + const handleLoadMore = useCallback(() => { + if (!hasMore || loadingMore || hotTerms.length === 0) return; + + setLoadingMore(true); + setLoadingPlaceholders(10); + + // 使用新的随机关键词 + const newKeyword = getRandomKeyword(); + + // 准备请求参数 + const loadMoreParams = { + ...params, + keyword: newKeyword, + page: currentPage + 1, + page_size: 10 + }; + + // 获取下一页数据 + productApi.getSearchProducts(loadMoreParams) + .then(res => { + setProducts(prev => [...prev, ...res.products]); + setCurrentPage(prev => prev + 1); + setHasMore((products.length + res.products.length) < (res.total || 0)); + }) + .catch(error => { + console.error("加载更多失败:", error); + }) + .finally(() => { + setLoadingMore(false); + setLoadingPlaceholders(0); + }); + }, [hasMore, loadingMore, hotTerms, getRandomKeyword, params, currentPage, products.length]); + + // 刷新产品列表 + const handleRefresh = useCallback(async () => { + if (hotTerms.length === 0) return; + + setRefreshing(true); + + try { + // 使用新的随机关键词 + const refreshKeyword = getRandomKeyword(); + console.log("刷新,使用关键词:", refreshKeyword); + // 重新获取初始数据 + await fetchInitialProducts(refreshKeyword); } catch (error) { - console.error("Error fetching products:", error); + console.error("刷新失败:", error); } finally { - if (isLoadMore) { - setIsLoadingMore(false); - setLoadingPlaceholders(0); // 清除占位符 - } else { - setTimeout(() => { - setLoading(false); - }, 300); - } + setRefreshing(false); } - }; + }, [hotTerms, getRandomKeyword, fetchInitialProducts]); + const handleProductPress = useCallback( (item: Product) => { InteractionManager.runAfterInteractions(() => { navigation.navigate("ProductDetail", { offer_id: item.offer_id, - searchKeyword: searchParams.keyword, + searchKeyword: params.keyword, price: item.min_price, }); }); }, [navigation] ); - const onRefresh = useCallback(async () => { - setRefreshing(true); - setInitialLoadComplete(false); // 重置初始加载标记 - setCurrentPage(1); // 重置页码 - setSearchParams(prev => ({ ...prev, page_size: 10 })); // 只重置每页数量 - try { - await getProductData(); - } catch (error) { - console.error("Error fetching products:", error); - } finally { - setRefreshing(false); - } - }, [searchParams]); - const { country, currency, language } = useGlobalStore(); - useEffect(() => { - console.log("userStore.user", userStore.user); - getProductData(); - }, [userStore.user, country, currency, language]); + const categories = [ "Tous", "Bijoux", @@ -453,6 +564,7 @@ export const HomeScreen = () => { "Hygiène et Soins pour le corps", "Maquillage", ]; + const defaultSubcategories: SubcategoryItem[] = [ { id: 1, title: "Jewelry", icon: "diamond-outline" }, { id: 2, title: "Earrings", icon: "ear-outline" }, @@ -461,6 +573,7 @@ export const HomeScreen = () => { { id: 5, title: "Earrings", icon: "ear-outline" }, { id: 6, title: "Bracelet", icon: "watch-outline" }, ]; + const categoryContent: CategoryContentType = { Tous: [], Bijoux: defaultSubcategories, @@ -474,38 +587,37 @@ export const HomeScreen = () => { "Hygiène et Soins pour le corps": defaultSubcategories, Maquillage: defaultSubcategories, }; + useEffect(() => { - // Ensure the content is available when category changes if (!categoryContent[selectedHorizontalCategory]) { setSelectedHorizontalCategory("Tous"); } }, [selectedHorizontalCategory]); - // 导航到搜索页面 - 使用useCallback优化函数引用 + const navigateToSearch = useCallback(() => { - // 使用InteractionManager延迟执行导航操作,确保当前交互和动画已完成 InteractionManager.runAfterInteractions(() => { navigation.navigate("Search"); }); }, [navigation]); + const navigateToShippingDetails = useCallback(() => { InteractionManager.runAfterInteractions(() => { navigation.navigate("ShippingDetailsSection"); }); }, [navigation]); + const navigateToInquiry = useCallback(() => { InteractionManager.runAfterInteractions(() => { navigation.navigate("InquiryScreen"); }); }, [navigation]); + const scrollToCategory = (category: string) => { const categoryIndex = categories.findIndex((c) => c === category); if (categoryIndex !== -1 && horizontalScrollRef.current) { - const firstFourKeys = Object.keys(categoryContent).slice( - 0, - categoryIndex - 1 - ); + const firstFourKeys = Object.keys(categoryContent).slice(0, categoryIndex - 1); let str = ""; - firstFourKeys.forEach((key, index) => { + firstFourKeys.forEach((key) => { str += key; }); horizontalScrollRef.current.scrollTo({ @@ -514,7 +626,7 @@ export const HomeScreen = () => { }); } }; - // 渲染产品列表项 + const renderProductItem = ({ item }: { item: Product }) => ( handleProductPress(item)} @@ -538,19 +650,13 @@ export const HomeScreen = () => { VIP - - {userStore.user?.vip_level} - + {userStore.user?.vip_level} )} - + {getSubjectTransLanguage(item)} @@ -562,27 +668,18 @@ export const HomeScreen = () => { )} - - {item.min_price || "0"} - - - {item.currency || "FCFA"} - + {item.min_price || "0"} + {item.currency || "FCFA"} - - {item.sold_out || "0"}+ ventes - + {item.sold_out || "0"}+ ventes ); - // 渲染骨架屏网格 const renderSkeletonGrid = useCallback(() => { - // 创建骨架屏数组 const skeletonArray = Array(8).fill(null); - return ( { ); }, []); - // 清理expo-image-picker临时文件 const cleanupImagePickerCache = async () => { try { - // Skip cache cleanup on web platform if (Platform.OS === 'web') { console.log('Cache cleanup skipped on web platform'); setGalleryUsed(false); return; } - // 相册选择后清理临时缓存 const cacheDir = `${FileSystem.cacheDirectory}ImagePicker`; await FileSystem.deleteAsync(cacheDir, { idempotent: true }); console.log("已清理ImagePicker缓存"); - - // 立即重置状态,无需用户干预 setGalleryUsed(false); } catch (error) { console.log("清理缓存错误", error); - // Even if cleanup fails, reset the state setGalleryUsed(false); } }; - // 将图片URI转换为FormData - const uriToFormData = async (uri: string) => { - try { - // 创建FormData对象 - const formData = new FormData(); - - // 获取文件名 - const filename = uri.split("/").pop() || "image.jpg"; - - // 判断文件类型(mime type) - const match = /\.(\w+)$/.exec(filename); - const type = match ? `image/${match[1]}` : "image/jpeg"; - - // 处理iOS路径前缀 - const imageUri = Platform.OS === "ios" ? uri.replace("file://", "") : uri; - - // 将图片转换为Blob - const imageFetchResponse = await fetch(imageUri); - const imageBlob = await imageFetchResponse.blob(); - - // 添加图片到FormData - formData.append("image", imageBlob, filename); - - console.log("FormData 详情:"); - console.log("- 图片URI:", uri); - console.log("- 文件名:", filename); - console.log("- 文件类型:", type); - - return formData; - } catch (error) { - console.error("创建FormData错误:", error); - throw error; - } - }; - - // 处理从相册选择 const handleChooseFromGallery = useCallback(async () => { - console.log("handleChooseFromGallery"); setShowImagePickerModal(false); - - // 等待模态窗关闭后再执行 setTimeout(async () => { try { - // 请求相册权限 - const permissionResult = - await ImagePicker.requestMediaLibraryPermissionsAsync(); + const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (permissionResult.status !== "granted") { console.log("相册权限被拒绝"); return; } - // 打开相册 const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: true, @@ -682,32 +731,24 @@ export const HomeScreen = () => { }); if (!result.canceled && result.assets && result.assets.length > 0) { - console.log("相册选择成功:", result.assets[0].uri); - await cleanupImagePickerCache(); navigation.navigate("ImageSearchResultScreen", { image: result.assets[0].uri, type: 1, }); } - } catch (error: any) { + } catch (error) { console.error("相册错误:", error); - // 出错时也清理缓存 await cleanupImagePickerCache(); } }, 500); - }, [userStore.user]); + }, []); - // 处理相机拍照 - 简化版本,不再需要处理galleryUsed const handleTakePhoto = useCallback(async () => { - console.log("handleTakePhoto"); setShowImagePickerModal(false); - - // 等待模态窗关闭后再执行 setTimeout(async () => { try { - const permissionResult = - await ImagePicker.requestCameraPermissionsAsync(); + const permissionResult = await ImagePicker.requestCameraPermissionsAsync(); if (permissionResult.status !== "granted") { console.log("相机权限被拒绝"); return; @@ -721,68 +762,34 @@ export const HomeScreen = () => { }); if (!result.canceled && result.assets && result.assets.length > 0) { - console.log("拍照成功:", result.assets[0].uri); - - // 使用后清理缓存 await cleanupImagePickerCache(); - // 将图片URI转换为FormData navigation.navigate("ImageSearchResultScreen", { image: result.assets[0].uri, type: 1, }); } - } catch (error: any) { + } catch (error) { console.error("相机错误:", error); - // 出错时也清理缓存 await cleanupImagePickerCache(); } }, 500); - }, [userStore.user]); + }, []); - // 重置应用状态函数 const resetAppState = useCallback(() => { - // 重置标记 setGalleryUsed(false); - - // 清理缓存 cleanupImagePickerCache(); - - // 提示用户 Alert.alert("已重置", "现在您可以使用相机功能了"); }, []); - // 修改加载更多函数 - const loadMore = () => { - if (!hasMore || isLoadingMore) { - console.log('不加载更多:', { hasMore, isLoadingMore }); - return; - } - - console.log('开始加载更多, 当前页码:', currentPage); - setLoadingPlaceholders(10); // 设置10个占位符 - getProductData(true); - }; - - // 修改滚动到底部处理函数 - const handleEndReached = () => { - console.log('触发加载更多'); - loadMore(); - }; - - // 修改渲染函数 const renderItem = ({ item, index }: { item: Product; index: number }) => { - // 如果是占位符 if (index >= products.length && index < products.length + loadingPlaceholders) { return ; } - return renderProductItem({ item }); }; - // 渲染列表头部内容 const renderHeader = () => ( <> - {/* 轮播图 */} { )} /> - {/* 自定义指示器 */} - {/* - {data.map((_, index) => ( - - ))} - */} - {/* 搜索栏 - 定位在轮播图上方 */} { - {/* 内容区域 */} - {/* 左侧区域 - 上下两个 */} { /> - {/* 右侧区域 - 一个 */} { key={index} style={[ styles.categoryItem, - selectedHorizontalCategory === category && - styles.categoryItemActive, + selectedHorizontalCategory === category && styles.categoryItemActive, ]} onPress={() => setSelectedHorizontalCategory(category)} > {t(`homePage.${category.toLowerCase()}`)} @@ -924,7 +913,6 @@ export const HomeScreen = () => { - {/* Subcategory Content */} {selectedHorizontalCategory && categoryContent[selectedHorizontalCategory] && categoryContent[selectedHorizontalCategory].length > 0 ? ( @@ -983,7 +971,7 @@ export const HomeScreen = () => { backgroundColor: "transparent", }} ListHeaderComponent={renderHeader} - onEndReached={handleEndReached} + onEndReached={handleLoadMore} onEndReachedThreshold={3} ListFooterComponent={() => ( !hasMore && !loadingPlaceholders ? ( @@ -995,7 +983,7 @@ export const HomeScreen = () => { refreshControl={ { /> )} - {/* Categories Modal */} { {category} @@ -1062,7 +1048,6 @@ export const HomeScreen = () => { - {/* Image Picker Modal */} { > {!galleryUsed ? ( - // 正常状态,显示相机选项 { {t("homePage.takePhoto")} ) : ( - // 已使用相册状态,显示重置选项 ( - - - - - - - +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( ({ @@ -127,10 +202,9 @@ const ProductItem = React.memo( style={styles.productCard} onPress={() => onPress(product)} activeOpacity={0.7} - key={product.offer_id} > - {product.product_image_urls[0] ? ( + {product.product_image_urls && product.product_image_urls[0] ? ( { if (route.params?.keyword) { @@ -217,7 +294,28 @@ export const SearchResultScreen = ({ route, navigation }: SearchResultScreenProp keyword: route.params.keyword, }; setSearchParams(newParams); - searchProducts(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); @@ -226,29 +324,79 @@ export const SearchResultScreen = ({ route, navigation }: SearchResultScreenProp category_id: route.params.category_id, }; setSearchParams(newParams); - searchProducts(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]); + }, [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) => [...prev, ...res.products]); + // 使用回调方式更新,确保获取最新状态 + 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,防止继续加载 @@ -258,19 +406,23 @@ export const SearchResultScreen = ({ route, navigation }: SearchResultScreenProp setProducts([]); setOriginalProducts([]); } + throw error; } finally { - setLoading(false); - setLoadingMore(false); - - // Add a short delay before hiding skeletons for smoother transition - if (!isLoadMore) { + if (isLoadMore) { + // 延迟清除加载状态,让视觉过渡更平滑 + setTimeout(() => { + setLoadingMore(false); + }, 300); + } else { + setLoading(false); + // 添加延迟以使骨架屏过渡更平滑 setTimeout(() => { setShowSkeleton(false); }, 300); } } }, - [] + [loading, loadingMore] ); // 处理搜索提交 const handleSearch = useCallback(() => { @@ -324,23 +476,34 @@ export const SearchResultScreen = ({ route, navigation }: SearchResultScreenProp ), [searchText, t] ); - // 渲染产品项 - const renderProductItem = useCallback( - ({ item }: { item: Product }) => ( + // 渲染商品项 + const renderItem = useCallback(({ item, index }: { item: any; index: number }) => { + // 处理骨架屏项 + if (item && item.isLoadingSkeleton) { + return ; + } + + // 处理空白占位项 + if (!item) { + // 显示透明的占位视图 + return ; + } + + // 渲染正常商品项 + return ( - ), - [handleProductPress, t, userStore] - ); + ); + }, [handleProductPress, t, userStore]); // 创建产品列表项的key提取器 - const keyExtractor = useCallback( - (item: Product, index: number) => `${item.offer_id}-${index}`, - [] - ); + 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") => { @@ -370,35 +533,121 @@ export const SearchResultScreen = ({ route, navigation }: SearchResultScreenProp ); // 处理加载更多 const handleLoadMore = useCallback(() => { - if (loading || !hasMore || loadingMore) return; + // 简化条件判断,直接仿照HomeScreen的处理方式 + if (!hasMore || loadingMore) { + return; + } + + // 添加时间防抖,避免频繁触发 + const now = Date.now(); + if (now - lastLoadTime.current < 500) { // 500ms内不重复触发 + return; + } + lastLoadTime.current = now; + + // 标记为加载中 setLoadingMore(true); - const newParams = { + + // 请求参数 + 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: searchParams.page + 1, + page: 1, }; - setSearchParams(newParams); - searchProducts(newParams, true); - }, [loading, hasMore, loadingMore, searchParams, searchProducts]); + + // 直接调用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 (!hasMore) + // 加载中状态显示骨架屏 + if (loadingMore) { return ( - - {t("noMoreData")} + + + + + + + ); - if (loadingMore) + } + + // 没有更多数据时显示提示 + if (!hasMore) { return ( - - {t("loadingMore")} + {t("noMoreData")} ); + } + return ; - }, [loadingMore, hasMore, t]); + }, [hasMore, loadingMore, t]); // 处理滚动事件 const handleScroll = useCallback((event: any) => { const offsetY = event.nativeEvent.contentOffset.y; + // 当滚动超过屏幕高度的一半时显示回到顶部按钮 setShowBackToTop(offsetY > 300); }, []); @@ -443,7 +692,7 @@ export const SearchResultScreen = ({ route, navigation }: SearchResultScreenProp ); // 渲染骨架屏网格 const renderSkeletonGrid = useCallback(() => { - // Create an array of items for the skeleton grid + // 创建一个骨架屏数组 const skeletonArray = Array(8).fill(null); return ( @@ -459,6 +708,20 @@ export const SearchResultScreen = ({ route, navigation }: SearchResultScreenProp ); }, []); + // 确保产品列表包含偶数条目,防止最后一个产品占满整行 + 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 ( @@ -699,23 +962,36 @@ export const SearchResultScreen = ({ route, navigation }: SearchResultScreenProp <> + } /> {showBackToTop && ( { return apiService.get('/api/search/', params); }, + // 获取热门搜索词 + getHotTerms: () => { + return apiService.get('/api/search/hot-terms/'); + }, // 获取商品详情 getProductDetail: (offer_id: string, user_id?: number) => { const url = user_id ? `/api/products/${offer_id}/?user_id=${user_id}` : `/api/products/${offer_id}/`; diff --git a/yarn.lock b/yarn.lock index 77d71b5..3db9f37 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9960,4 +9960,4 @@ yocto-queue@^0.1.0: zustand@^5.0.4: version "5.0.4" resolved "https://registry.npmmirror.com/zustand/-/zustand-5.0.4.tgz" - integrity sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ== + integrity sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ== \ No newline at end of file