import SearchBar from "@/components/SearchBar"; import FilterPopup from "@/components/FilterPopup"; import styles from "./ListPageContent.module.scss"; import { useEffect, useRef, useCallback, useState } from "react"; import Taro from "@tarojs/taro"; import { useListStore } from "@/store/listStore"; import { useGlobalState } from "@/store/global"; import { View, Image, Text, ScrollView } from "@tarojs/components"; import ListContainer from "@/container/listContainer"; import DistanceQuickFilter from "@/components/DistanceQuickFilter"; import { updateUserLocation } from "@/services/userService"; import { useUserActions } from "@/store/userStore"; import { useDictionaryStore } from "@/store/dictionaryStore"; import { saveImage, navigateTo } from "@/utils"; export interface ListPageContentProps { onNavStateChange?: (state: { isShowInputCustomerNavBar?: boolean; isDistanceFilterVisible?: boolean; isCityPickerVisible?: boolean; }) => void; onScrollToTop?: () => void; // 外部滚动到顶部方法(由主容器提供) scrollToTopTrigger?: number; // 触发滚动的计数器 onDistanceFilterVisibleChange?: (visible: boolean) => void; onCityPickerVisibleChange?: (visible: boolean) => void; // 保留接口,但由主容器直接处理 onFilterPopupVisibleChange?: (visible: boolean) => void; // 筛选弹窗显示/隐藏回调 } const ListPageContent: React.FC = ({ onNavStateChange, onScrollToTop: _onScrollToTop, scrollToTopTrigger, onDistanceFilterVisibleChange, onCityPickerVisibleChange: _onCityPickerVisibleChange, onFilterPopupVisibleChange, }) => { const store = useListStore() || {}; const { fetchUserInfo } = useUserActions(); const { statusNavbarHeightInfo, getCurrentLocationInfo } = useGlobalState() || {}; const { totalHeight = 98 } = statusNavbarHeightInfo || {}; const { listPageState, loading, error, searchValue, distanceData, quickFilterData, getMatchesData, updateState, updateListPageState, updateFilterOptions, clearFilterOptions, initialFilterSearch, loadMoreMatches, fetchGetGamesCount, updateDistanceQuickFilter, getCities, getCityQrCode, area, cityQrCode, } = store; const { isShowFilterPopup, data: matches, recommendList, filterCount, filterOptions, distanceQuickFilter, isShowInputCustomerNavBar, pageOption, isShowNoData, } = listPageState || {}; const scrollContextRef = useRef(null); const scrollViewRef = useRef(null); const scrollTimeoutRef = useRef(null); const lastScrollTopRef = useRef(0); const scrollDirectionRef = useRef<"up" | "down" | null>(null); const lastScrollTimeRef = useRef(Date.now()); const loadingMoreRef = useRef(false); const scrollStartPositionRef = useRef(0); const [showSearchBar, setShowSearchBar] = useState(true); const [scrollTop, setScrollTop] = useState(0); const [refreshing, setRefreshing] = useState(false); // 处理距离筛选显示/隐藏 const handleDistanceFilterVisibleChange = useCallback( (visible: boolean) => { onDistanceFilterVisibleChange?.(visible); onNavStateChange?.({ isDistanceFilterVisible: visible }); }, [onDistanceFilterVisibleChange, onNavStateChange] ); // 处理城市选择器显示/隐藏(由主容器统一管理,通过 onNavStateChange 通知) // 注意:CustomerNavBar 的 onCityPickerVisibleChange 由主容器直接处理 // 滚动到顶部(用于 ScrollView 内部滚动) const scrollToTopInternal = useCallback(() => { setScrollTop((prev) => (prev === 0 ? 0.1 : 0)); }, []); // 监听外部滚动触发 useEffect(() => { if (scrollToTopTrigger && scrollToTopTrigger > 0) { scrollToTopInternal(); } }, [scrollToTopTrigger, scrollToTopInternal]); // ScrollView 滚动处理 const handleScrollViewScroll = useCallback( (e: any) => { const currentScrollTop = e?.detail?.scrollTop || 0; const lastScrollTop = lastScrollTopRef.current; const currentTime = Date.now(); const timeDiff = currentTime - lastScrollTimeRef.current; if (timeDiff < 100) return; const scrollDiff = currentScrollTop - lastScrollTop; let newDirection = scrollDirectionRef.current; if (Math.abs(scrollDiff) > 15) { if (scrollDiff > 0) { if (newDirection !== "up") { scrollStartPositionRef.current = lastScrollTop; } newDirection = "up"; } else { if (newDirection !== "down") { scrollStartPositionRef.current = lastScrollTop; } newDirection = "down"; } scrollDirectionRef.current = newDirection; } const totalScrollDistance = Math.abs( currentScrollTop - scrollStartPositionRef.current ); const positionThreshold = 120; const distanceThreshold = 80; if ( newDirection === "up" && currentScrollTop > positionThreshold && totalScrollDistance > distanceThreshold ) { if (showSearchBar || !isShowInputCustomerNavBar) { setShowSearchBar(false); updateListPageState({ isShowInputCustomerNavBar: true, }); onNavStateChange?.({ isShowInputCustomerNavBar: true }); scrollStartPositionRef.current = currentScrollTop; } } else if ( (newDirection === "down" && totalScrollDistance > distanceThreshold) || currentScrollTop <= positionThreshold ) { if (!showSearchBar || isShowInputCustomerNavBar) { setShowSearchBar(true); updateListPageState({ isShowInputCustomerNavBar: false, }); onNavStateChange?.({ isShowInputCustomerNavBar: false }); scrollStartPositionRef.current = currentScrollTop; } } lastScrollTopRef.current = currentScrollTop; lastScrollTimeRef.current = currentTime; }, [ showSearchBar, isShowInputCustomerNavBar, updateListPageState, onNavStateChange, ] ); useEffect(() => { getLocation(); fetchUserInfo(); getCities(); getCityQrCode(); }, []); useEffect(() => { if (pageOption?.page === 1 && matches?.length > 0) { setShowSearchBar(true); updateListPageState({ isShowInputCustomerNavBar: false, }); onNavStateChange?.({ isShowInputCustomerNavBar: false }); } }, [matches, pageOption?.page, updateListPageState, onNavStateChange]); useEffect(() => { return () => { if (scrollTimeoutRef.current) { clearTimeout(scrollTimeoutRef.current); } }; }, []); const getLocation = async () => { const location = await getCurrentLocationInfo(); updateState({ location }); if (location && location.latitude && location.longitude) { try { await updateUserLocation(location.latitude, location.longitude); } catch (error) { console.error("更新用户位置失败:", error); } } fetchGetGamesCount(); getMatchesData(); return location; }; const refreshMatches = async () => { await initialFilterSearch(true); }; const handleRefresh = async () => { setRefreshing(true); try { await refreshMatches(); } catch (error) { (Taro as any).showToast({ title: "刷新失败,请重试", icon: "error", duration: 1000, }); } finally { setTimeout(() => { setRefreshing(false); }, 0); } }; const handleFilterConfirm = () => { toggleShowPopup(); getMatchesData(); }; const toggleShowPopup = () => { const newVisible = !isShowFilterPopup; // 先通知父组件筛选弹窗状态变化(设置 z-index) onFilterPopupVisibleChange?.(newVisible); // 然后更新本地状态显示/隐藏弹窗 // 使用 setTimeout 确保 z-index 先设置,再显示弹窗 if (newVisible) { setTimeout(() => { updateListPageState({ isShowFilterPopup: newVisible, }); }, 50); } else { // 关闭时直接更新状态 updateListPageState({ isShowFilterPopup: newVisible, }); } }; const handleUpdateFilterOptions = (params: Record) => { updateFilterOptions(params); }; const handleSearchChange = () => {}; const handleDistanceOrQuickChange = (name, value) => { updateDistanceQuickFilter({ [name]: value, }); }; const handleSearchClick = () => { navigateTo({ url: "/game_pages/search/index", }); }; const initDictionaryData = async () => { try { const { fetchDictionary } = useDictionaryStore.getState(); await fetchDictionary(); } catch (error) { console.error("初始化字典数据失败:", error); } }; useEffect(() => { initDictionaryData(); }, []); const area_city = area?.at(-2) || "上海"; function renderCityQrcode() { let item = cityQrCode.find((item) => item.city_name === area_city); if (!item) item = cityQrCode.find((item) => item.city_name === "其他"); return ( {item ? ( 当前城市暂无球局 加入城市球友群,获得最新球局消息 { saveImage(item.qr_code_url); }} /> 点击图片保存,使用微信扫码加入群聊 ) : ( 当前城市暂无球局, 敬请期待 )} ); } return ( <> {area_city !== "上海" ? ( renderCityQrcode() ) : ( {isShowFilterPopup && ( )} 0} filterCount={filterCount} onChange={handleSearchChange} value={searchValue} onInputClick={handleSearchClick} /> { if ( !loading && !loadingMoreRef.current && listPageState?.isHasMoreData ) { loadingMoreRef.current = true; try { await loadMoreMatches(); } catch (error) { console.error("加载更多失败:", error); } finally { loadingMoreRef.current = false; } } }} onScroll={handleScrollViewScroll} > )} ); }; export default ListPageContent;