Files
mini-programs/src/game_pages/list/index.tsx
2025-09-14 16:37:09 +08:00

304 lines
8.1 KiB
TypeScript

import SearchBar from "@/components/SearchBar";
import FilterPopup from "@/components/FilterPopup";
import styles from "./index.module.scss";
import { useEffect, useRef, useCallback } from "react";
import Taro, { usePageScroll } from "@tarojs/taro";
import { useListStore } from "@/store/listStore";
import { useGlobalState } from "@/store/global";
import { View } from "@tarojs/components";
import CustomerNavBar from "@/container/listCustomNavbar";
import GuideBar from "@/components/GuideBar";
import ListContainer from "@/container/listContainer";
import DistanceQuickFilter from "@/components/DistanceQuickFilter";
import { withAuth } from "@/components";
import { updateUserLocation } from "@/services/userService";
const ListPage = () => {
// 从 store 获取数据和方法
const store = useListStore() || {};
const { statusNavbarHeightInfo, getCurrentLocationInfo } = useGlobalState() || {};
const { totalHeight } = statusNavbarHeightInfo || {};
const {
listPageState,
loading,
error,
searchValue,
distanceData,
quickFilterData,
getMatchesData,
updateState,
updateListPageState,
updateFilterOptions, // 更新筛选条件
clearFilterOptions,
initialFilterSearch,
loadMoreMatches,
fetchGetGamesCount,
updateDistanceQuickFilter,
} = store;
const {
isShowFilterPopup,
data: matches,
recommendList,
filterCount,
filterOptions,
distanceQuickFilter,
isShowInputCustomerNavBar,
pageOption
} = listPageState || {};
// 防抖的滚动处理函数
const handleScroll = useCallback((res) => {
const currentScrollTop = res?.scrollTop || 0;
// 添加缓冲区,避免临界点频繁切换
const buffer = 10; // 10px 缓冲区
const shouldShowInputNav = currentScrollTop >= (totalHeight + buffer);
const shouldHideInputNav = currentScrollTop < (totalHeight - buffer);
// 清除之前的定时器
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
// 防抖处理,避免频繁更新状态
scrollTimeoutRef.current = setTimeout(() => {
// 只有在状态真正需要改变时才更新
if (shouldShowInputNav && !isShowInputCustomerNavBar) {
updateListPageState({
isShowInputCustomerNavBar: true
});
} else if (shouldHideInputNav && isShowInputCustomerNavBar) {
updateListPageState({
isShowInputCustomerNavBar: false
});
}
lastScrollTopRef.current = currentScrollTop;
}, 16); // 约60fps的防抖间隔
}, [totalHeight, isShowInputCustomerNavBar, updateState]);
usePageScroll(handleScroll);
const scrollContextRef = useRef(null)
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null)
const lastScrollTopRef = useRef(0)
useEffect(() => {
getLocation()
}, []);
// 监听数据变化,如果是第一页就滚动到顶部
useEffect(() => {
if (pageOption?.page === 1 && matches?.length > 0) {
Taro.pageScrollTo({
scrollTop: 0,
duration: 300
});
}
}, [matches, pageOption?.page]);
// 清理定时器
useEffect(() => {
return () => {
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
};
}, []);
// 监听距离和排序方式变化,自动调用接口
// useEffect(() => {
// // 只有当 distanceQuickFilter 有值时才调用接口
// if (distanceQuickFilter?.distanceFilter !== undefined || distanceQuickFilter?.order !== undefined) {
// // if (distanceQuickFilter?.quick !== "0") {
// getMatchesData();
// // }
// }
// }, [distanceQuickFilter?.distanceFilter, distanceQuickFilter?.order]);
// 获取位置信息
const getLocation = async () => {
const location = await getCurrentLocationInfo()
// 保存位置到全局状态
updateState({ location });
// 同时更新用户位置到后端和 store
if (location && location.latitude && location.longitude) {
try {
await updateUserLocation(location.latitude, location.longitude);
} catch (error) {
console.error('更新用户位置失败:', error);
}
}
fetchGetGamesCount();
// 页面加载时获取数据
getMatchesData();
return location;
}
const refreshMatches = () => {
initialFilterSearch(true);
};
// const getLoadMoreMatches = () => {
// loadMoreMatches()
// }
// 下拉刷新处理函数 - 使用Taro生命周期钩子
Taro.usePullDownRefresh(async () => {
try {
// 调用刷新方法
await refreshMatches();
// 刷新完成后停止下拉刷新动画
Taro.stopPullDownRefresh();
// 显示刷新成功提示
Taro.showToast({
title: "刷新成功",
icon: "success",
duration: 1000,
});
} catch (error) {
// 刷新失败时也停止动画
Taro.stopPullDownRefresh();
Taro.showToast({
title: "刷新失败,请重试",
icon: "error",
duration: 1000,
});
}
});
/**
* @description 综合筛选确认
* @returns
*/
const handleFilterConfirm = () => {
toggleShowPopup();
getMatchesData();
}
/**
* @description 综合筛选弹框
* @returns
*/
const toggleShowPopup = () => {
updateListPageState({
isShowFilterPopup: !isShowFilterPopup
});
};
/**
* @description 更新筛选条件
* @param {Record<string, any>} params 筛选项
*/
const handleUpdateFilterOptions = (params: Record<string, any>) => {
updateFilterOptions(params);
};
const handleSearchChange = () => { };
// 距离筛选
const handleDistanceOrQuickChange = (name, value) => {
updateDistanceQuickFilter({
[name]: value,
});
// updateListPageState({
// distanceQuickFilter: {
// ...distanceQuickFilter,
// [name]: value,
// },
// });
};
const handleSearchClick = () => {
Taro.navigateTo({
url: "/game_pages/search/index",
});
};
return (
<>
{/* 自定义导航 */}
<CustomerNavBar
config={{
showInput: isShowInputCustomerNavBar,
}}
/>
<View ref={scrollContextRef}>
{/* <ShareCardCanvas /> */}
{/* 列表内容 */}
<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.listTopSearchWrapper}`}
>
<SearchBar
handleFilterIcon={toggleShowPopup}
isSelect={filterCount > 0}
filterCount={filterCount}
onChange={handleSearchChange}
value={searchValue}
onInputClick={handleSearchClick}
/>
</View>
{/* 筛选 */}
<View className={styles.listTopFilterWrapper}
style={{
top: totalHeight - 1,
}}>
<DistanceQuickFilter
cityOptions={distanceData}
quickOptions={quickFilterData}
onChange={handleDistanceOrQuickChange}
cityName="distanceFilter"
quickName="order"
cityValue={distanceQuickFilter?.distanceFilter}
quickValue={distanceQuickFilter?.order}
/>
</View>
{/* 列表内容 */}
<ListContainer
data={matches}
recommendList={recommendList}
loading={loading}
error={error}
reload={refreshMatches}
loadMoreMatches={loadMoreMatches}
/>
</View>
</View>
<GuideBar currentPage="list" />
</>
);
};
export default withAuth(ListPage);