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/DistanceQuickFilterV2"; import { updateUserLocation } from "@/services/userService"; import { useDictionaryStore } from "@/store/dictionaryStore"; import { saveImage, navigateTo } from "@/utils"; export interface ListPageContentProps { isActive?: boolean; // 是否处于激活状态(当前显示的页面) 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 = ({ isActive = true, onNavStateChange, onScrollToTop: _onScrollToTop, scrollToTopTrigger, onDistanceFilterVisibleChange, onCityPickerVisibleChange: _onCityPickerVisibleChange, onFilterPopupVisibleChange, }) => { const store = useListStore() || {}; const { statusNavbarHeightInfo, getCurrentLocationInfo } = useGlobalState() || {}; const { totalHeight = 98 } = statusNavbarHeightInfo || {}; const { listPageState, loading, error, searchValue, distanceData, quickFilterData, getMatchesData, updateState, updateListPageState, updateFilterOptions, clearFilterOptions, initialFilterSearch, loadMoreMatches, fetchGetGamesCount, refreshBothLists, updateDistanceQuickFilter, getCities, getCityQrCode, getDistricts, area, cityQrCode, districts, gamesNum, // 新增:获取球局数量 } = 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 lastLoadedAreaRef = useRef<[string, string] | null>(null); const prevIsActiveRef = useRef(isActive); // 处理距离筛选显示/隐藏 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]); // 使用 ref 保存最新的状态值,避免依赖项变化导致函数重新创建 const showSearchBarRef = useRef(showSearchBar); const isShowInputCustomerNavBarRef = useRef(isShowInputCustomerNavBar); useEffect(() => { showSearchBarRef.current = showSearchBar; isShowInputCustomerNavBarRef.current = isShowInputCustomerNavBar; }, [showSearchBar, isShowInputCustomerNavBar]); // ScrollView 滚动处理 const handleScrollViewScroll = useCallback( (e: any) => { const currentScrollTop = e?.detail?.scrollTop || 0; const scrollHeight = e?.detail?.scrollHeight || 0; const clientHeight = e?.detail?.clientHeight || 0; const lastScrollTop = lastScrollTopRef.current; const currentTime = Date.now(); const timeDiff = currentTime - lastScrollTimeRef.current; if (timeDiff < 100) return; // 计算距离底部的距离,提前加载(距离底部600px时开始加载) // 注意:如果 scrollHeight 或 clientHeight 不可用,则使用 lowerThreshold 触发 if (scrollHeight > 0 && clientHeight > 0) { const distanceToBottom = scrollHeight - currentScrollTop - clientHeight; const preloadThreshold = 600; // 提前加载阈值 // 如果距离底部小于阈值,且正在向下滚动,且有更多数据,则提前加载 if ( distanceToBottom < preloadThreshold && distanceToBottom > 0 && !loading && !loadingMoreRef.current && listPageState?.isHasMoreData && currentScrollTop > lastScrollTop // 向下滚动 ) { loadingMoreRef.current = true; loadMoreMatches().finally(() => { loadingMoreRef.current = false; }); } } 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; // 使用 ref 获取最新值,避免依赖项变化 const currentShowSearchBar = showSearchBarRef.current; const currentIsShowInputCustomerNavBar = isShowInputCustomerNavBarRef.current; if ( newDirection === "up" && currentScrollTop > positionThreshold && totalScrollDistance > distanceThreshold ) { if (currentShowSearchBar || !currentIsShowInputCustomerNavBar) { setShowSearchBar(false); updateListPageState({ isShowInputCustomerNavBar: true, }); onNavStateChange?.({ isShowInputCustomerNavBar: true }); scrollStartPositionRef.current = currentScrollTop; } } else if ( (newDirection === "down" && totalScrollDistance > distanceThreshold) || currentScrollTop <= positionThreshold ) { if (!currentShowSearchBar || currentIsShowInputCustomerNavBar) { setShowSearchBar(true); updateListPageState({ isShowInputCustomerNavBar: false, }); onNavStateChange?.({ isShowInputCustomerNavBar: false }); scrollStartPositionRef.current = currentScrollTop; } } lastScrollTopRef.current = currentScrollTop; lastScrollTimeRef.current = currentTime; }, [updateListPageState, onNavStateChange, loading, loadMoreMatches, listPageState?.isHasMoreData] // 移除 showSearchBar 和 isShowInputCustomerNavBar 依赖,使用 ref 获取最新值 ); useEffect(() => { // 分批异步执行初始化操作,避免阻塞首屏渲染 // 1. 立即执行:获取城市、二维码和行政区列表(轻量操作) getCities(); getCityQrCode(); getDistricts(); // 新增:获取行政区列表 // 只有当页面激活时才加载位置和列表数据 if (isActive) { getLocation().catch((error) => { console.error('获取位置信息失败:', error); }); } }, [isActive]); // 记录上一次的城市,用于检测城市变化 const prevAreaRef = useRef<[string, string] | null>(null); // 监听城市变化,重新获取行政区列表并清空已选择的行政区 useEffect(() => { if (area && area.length >= 2) { const currentProvince = area[1]; const prevProvince = prevAreaRef.current?.[1]; // 只有当城市真正改变时才执行(避免初始化时也触发) if (prevProvince && prevProvince !== currentProvince) { console.log("城市改变,重新获取行政区列表:", { prevProvince, currentProvince, }); // 城市改变时,重新获取行政区列表 getDistricts(); // 清空已选择的行政区,避免显示错误的行政区 const currentState = useListStore.getState(); const currentPageState = currentState.isSearchResult ? currentState.searchPageState : currentState.listPageState; if (currentPageState?.distanceQuickFilter?.district) { updateDistanceQuickFilter({ district: undefined }); } } // 更新记录的城市 prevAreaRef.current = area as [string, string]; } // eslint-disable-next-line react-hooks/exhaustive-deps }, [area?.[1]]); // 只监听省份(area[1])的变化 // 当页面从非激活状态切换为激活状态时,检查城市是否变化,如果变化则重新加载数据 useEffect(() => { // 如果从非激活状态变为激活状态(切回列表页) if (isActive && !prevIsActiveRef.current) { const currentArea = area; const lastArea = lastLoadedAreaRef.current; // 检查城市是否发生变化(比较省份) const currentProvince = currentArea?.[1] || ""; const lastProvince = lastArea?.[1] || ""; // 如果城市发生变化,或者地址存在但不一致,需要重新加载数据 // 注意:即使 lastArea 为空,只要 currentArea 存在,也应该加载数据 if (currentProvince && (currentProvince !== lastProvince || !lastArea)) { console.log("切回列表页,检测到地址变化或不一致,重新加载数据:", { lastArea, currentArea, lastProvince, currentProvince, }); // 地址发生变化或不一致,重新加载数据和球局数量 // 先调用列表接口,然后在列表接口完成后调用数量接口 (async () => { try { if (refreshBothLists) { await refreshBothLists(); } // 列表接口完成后,再调用数量接口 if (fetchGetGamesCount) { await fetchGetGamesCount(); } // 数据加载完成后,更新记录的城市(记录为上一次在列表页加载数据时的城市) if (currentArea) { lastLoadedAreaRef.current = [...currentArea] as [string, string]; } } catch (error) { console.error("重新加载数据失败:", error); } })(); } } // 如果是首次加载且列表页激活,记录当前城市(用于后续比较) if (isActive && !lastLoadedAreaRef.current && area) { lastLoadedAreaRef.current = [...area] as [string, string]; } // 更新上一次的激活状态 prevIsActiveRef.current = isActive; }, [isActive, area, refreshBothLists, fetchGetGamesCount]); // 监听城市变化(在列表页激活状态下),当城市切换后立即更新记录 // 注意:这个 useEffect 用于处理在列表页激活状态下切换城市的情况 // 当用户在列表页切换城市时,HomeNavbar 的 handleCityChange 已经会调用 refreshBothLists // 这里只需要同步更新 lastLoadedAreaRef,确保后续检测逻辑正确 useEffect(() => { // 如果页面激活且城市发生变化(用户在列表页切换了城市) if (isActive && area) { const currentProvince = area[1] || ""; const lastProvince = lastLoadedAreaRef.current?.[1] || ""; // 如果城市发生变化,立即更新记录(因为 refreshBothLists 已经在 HomeNavbar 中调用) if (currentProvince && currentProvince !== lastProvince) { // 立即更新记录,确保地址显示和使用的地址一致 lastLoadedAreaRef.current = [...area] as [string, string]; } } }, [isActive, area]); useEffect(() => { if (pageOption?.page === 1 && matches?.length > 0) { setShowSearchBar(true); updateListPageState({ isShowInputCustomerNavBar: false, }); onNavStateChange?.({ isShowInputCustomerNavBar: false }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [matches?.length, pageOption?.page]); // 注意:updateListPageState 和 onNavStateChange 是稳定的函数引用,不需要加入依赖项 // 只依赖实际会变化的数据:matches 的长度和 pageOption.page 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); } } // 先调用列表接口 await getMatchesData(); // 列表接口完成后,再调用数量接口 await fetchGetGamesCount(); // 初始数据加载完成后,记录当前城市 if (area && isActive) { lastLoadedAreaRef.current = [...area] as [string, string]; } 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 { // 使用 requestAnimationFrame 替代 setTimeout(0),性能更好 requestAnimationFrame(() => { setRefreshing(false); }); } }; const handleFilterConfirm = () => { toggleShowPopup(); getMatchesData(); }; const toggleShowPopup = () => { const newVisible = !isShowFilterPopup; // 先通知父组件筛选弹窗状态变化(设置 z-index) onFilterPopupVisibleChange?.(newVisible); // 然后更新本地状态显示/隐藏弹窗 // 使用 requestAnimationFrame 确保 z-index 先设置,再显示弹窗 if (newVisible) { // 使用双帧延迟确保 z-index 已生效 requestAnimationFrame(() => { requestAnimationFrame(() => { updateListPageState({ isShowFilterPopup: newVisible, }); }); }); } 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(); }, []); // 获取省份名称(area 格式: ["中国", "省份"]) const province = area?.at(1) || "上海"; function renderCityQrcode() { // 根据省份查找对应的二维码 let item = cityQrCode.find((item) => item.city_name === province); if (!item) item = cityQrCode.find((item) => item.city_name === "其他"); return ( {item ? ( 当前城市暂无球局 加入城市球友群,获得最新球局消息 { saveImage(item.qr_code_url); }} /> 点击图片保存,使用微信扫码加入群聊 ) : ( 当前城市暂无球局, 敬请期待 )} ); } // 判定是否显示"暂无球局"页面 // 条件:省份不是上海 或 (已加载完成且球局数量为0) const shouldShowNoGames = province !== "上海"; return ( <> {shouldShowNoGames ? ( 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;