1
This commit is contained in:
412
src/main_pages/components/ListPageContent.tsx
Normal file
412
src/main_pages/components/ListPageContent.tsx
Normal file
@@ -0,0 +1,412 @@
|
||||
import SearchBar from "@/components/SearchBar";
|
||||
import FilterPopup from "@/components/FilterPopup";
|
||||
import styles from "@/game_pages/list/index.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<ListPageContentProps> = ({
|
||||
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<NodeJS.Timeout | null>(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<string, any>) => {
|
||||
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 (
|
||||
<View className={styles.cqContainer}>
|
||||
{item ? (
|
||||
<View className={styles.wrapper}>
|
||||
<View className={styles.tips}>
|
||||
<Text className={styles.tip1}>当前城市暂无球局</Text>
|
||||
<Text className={styles.tip2}>
|
||||
加入城市球友群,获得最新球局消息
|
||||
</Text>
|
||||
</View>
|
||||
<View className={styles.qrcodeWrappper}>
|
||||
<Image
|
||||
className={styles.qrcode}
|
||||
src={item.qr_code_url}
|
||||
mode="widthFix"
|
||||
onClick={() => {
|
||||
saveImage(item.qr_code_url);
|
||||
}}
|
||||
/>
|
||||
<Text className={styles.qrcodeTip}>
|
||||
点击图片保存,使用微信扫码加入群聊
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<View>
|
||||
<Text>当前城市暂无球局, 敬请期待</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{area_city !== "上海" ? (
|
||||
renderCityQrcode()
|
||||
) : (
|
||||
<View ref={scrollContextRef}>
|
||||
<View className={styles.listPage} style={{ paddingTop: totalHeight }}>
|
||||
{isShowFilterPopup && (
|
||||
<View>
|
||||
<FilterPopup
|
||||
loading={loading}
|
||||
onCancel={toggleShowPopup}
|
||||
onConfirm={handleFilterConfirm}
|
||||
onChange={handleUpdateFilterOptions}
|
||||
filterOptions={filterOptions}
|
||||
onClear={clearFilterOptions}
|
||||
visible={isShowFilterPopup}
|
||||
onClose={toggleShowPopup}
|
||||
statusNavbarHeigh={statusNavbarHeightInfo?.totalHeight}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View className={styles.fixedHeader}>
|
||||
<View className={`${styles.listTopSearchWrapper} ${showSearchBar ? styles.show : styles.hide}`}>
|
||||
<SearchBar
|
||||
handleFilterIcon={toggleShowPopup}
|
||||
isSelect={filterCount > 0}
|
||||
filterCount={filterCount}
|
||||
onChange={handleSearchChange}
|
||||
value={searchValue}
|
||||
onInputClick={handleSearchClick}
|
||||
/>
|
||||
</View>
|
||||
<View className={styles.listTopFilterWrapper}>
|
||||
<DistanceQuickFilter
|
||||
cityOptions={distanceData}
|
||||
quickOptions={quickFilterData}
|
||||
onChange={handleDistanceOrQuickChange}
|
||||
cityName="distanceFilter"
|
||||
quickName="order"
|
||||
cityValue={distanceQuickFilter?.distanceFilter}
|
||||
quickValue={distanceQuickFilter?.order}
|
||||
onMenuVisibleChange={handleDistanceFilterVisibleChange}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
scrollY
|
||||
scrollTop={scrollTop}
|
||||
className={styles.listScrollView}
|
||||
scrollWithAnimation
|
||||
enhanced
|
||||
showScrollbar={false}
|
||||
refresherEnabled={true}
|
||||
refresherTriggered={refreshing}
|
||||
onRefresherRefresh={handleRefresh}
|
||||
lowerThreshold={100}
|
||||
onScrollToLower={async () => {
|
||||
if (!loading && !loadingMoreRef.current && listPageState?.isHasMoreData) {
|
||||
loadingMoreRef.current = true;
|
||||
try {
|
||||
await loadMoreMatches();
|
||||
} catch (error) {
|
||||
console.error("加载更多失败:", error);
|
||||
} finally {
|
||||
loadingMoreRef.current = false;
|
||||
}
|
||||
}
|
||||
}}
|
||||
onScroll={handleScrollViewScroll}
|
||||
>
|
||||
<ListContainer
|
||||
data={matches}
|
||||
recommendList={recommendList}
|
||||
loading={loading}
|
||||
isShowNoData={isShowNoData}
|
||||
error={error}
|
||||
reload={refreshMatches}
|
||||
loadMoreMatches={loadMoreMatches}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListPageContent;
|
||||
|
||||
Reference in New Issue
Block a user