diff --git a/src/components/NTRPTestEntryCard/index.tsx b/src/components/NTRPTestEntryCard/index.tsx
index ece7e53..e433ab6 100644
--- a/src/components/NTRPTestEntryCard/index.tsx
+++ b/src/components/NTRPTestEntryCard/index.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from "react";
+import React, { useState, useEffect, useCallback, memo } from "react";
import { View, Image, Text } from "@tarojs/components";
import Taro from "@tarojs/taro";
import { useUserInfo, useUserActions } from "@/store/userStore";
@@ -26,82 +26,87 @@ function NTRPTestEntryCard(props: {
console.log(userInfo);
useEffect(() => {
- fetchUserInfo();
+ if (!userInfo.id) {
+ fetchUserInfo();
+ }
evaluateService.getLastResult().then((res) => {
setTestFlag(res.code === 0 && res.data.has_ntrp_level);
});
- }, []);
+ }, [userInfo.id]);
- function handleTest() {
- switch (type) {
- case EvaluateScene.list:
- setCallback({
- type,
- next: () => {
- Taro.redirectTo({ url: "/game_pages/list/index" });
- },
- onCancel: () => {
- // Taro.redirectTo({ url: "/game_pages/list/index" });
- Taro.navigateBack();
- },
- });
- break;
- case EvaluateScene.share:
- setCallback({
- type,
- next: () => {
- Taro.redirectTo({ url: "/main_pages/index" });
- },
- onCancel: () => {
- Taro.redirectTo({ url: "/main_pages/index" });
- },
- });
- break;
- case EvaluateScene.detail:
- case EvaluateScene.publish:
- setCallback(evaluateCallback as EvaluateCallback);
- break;
- case EvaluateScene.user:
- setCallback({
- type,
- next: () => {
- Taro.redirectTo({ url: "/main_pages/index" });
- },
- onCancel: () => {
- // Taro.redirectTo({ url: "/user_pages/myself/index" });
- Taro.navigateBack();
- },
- });
- break;
- case EvaluateScene.userEdit:
- setCallback({
- type,
- next: () => {
- Taro.redirectTo({ url: "/game_pages/list/index" });
- },
- onCancel: () => {
- // Taro.redirectTo({ url: "/user_pages/edit/index" });
- Taro.navigateBack();
- },
- });
- break;
- default:
- setCallback({
- type,
- next: () => {
- Taro.redirectTo({ url: "/main_pages/index" });
- },
- onCancel: () => {
- Taro.redirectTo({ url: "/main_pages/index" });
- },
- });
- }
- Taro.navigateTo({
- url: `/other_pages/ntrp-evaluate/index?stage=${
- testFlag ? StageType.INTRO : StageType.TEST
- }`,
- });
- }
+ const handleTest = useCallback(
+ function () {
+ switch (type) {
+ case EvaluateScene.list:
+ setCallback({
+ type,
+ next: () => {
+ Taro.redirectTo({ url: "/game_pages/list/index" });
+ },
+ onCancel: () => {
+ // Taro.redirectTo({ url: "/game_pages/list/index" });
+ Taro.navigateBack();
+ },
+ });
+ break;
+ case EvaluateScene.share:
+ setCallback({
+ type,
+ next: () => {
+ Taro.redirectTo({ url: "/main_pages/index" });
+ },
+ onCancel: () => {
+ Taro.redirectTo({ url: "/main_pages/index" });
+ },
+ });
+ break;
+ case EvaluateScene.detail:
+ case EvaluateScene.publish:
+ setCallback(evaluateCallback as EvaluateCallback);
+ break;
+ case EvaluateScene.user:
+ setCallback({
+ type,
+ next: () => {
+ Taro.redirectTo({ url: "/main_pages/index" });
+ },
+ onCancel: () => {
+ // Taro.redirectTo({ url: "/user_pages/myself/index" });
+ Taro.navigateBack();
+ },
+ });
+ break;
+ case EvaluateScene.userEdit:
+ setCallback({
+ type,
+ next: () => {
+ Taro.redirectTo({ url: "/game_pages/list/index" });
+ },
+ onCancel: () => {
+ // Taro.redirectTo({ url: "/user_pages/edit/index" });
+ Taro.navigateBack();
+ },
+ });
+ break;
+ default:
+ setCallback({
+ type,
+ next: () => {
+ Taro.redirectTo({ url: "/main_pages/index" });
+ },
+ onCancel: () => {
+ Taro.redirectTo({ url: "/main_pages/index" });
+ },
+ });
+ }
+ Taro.navigateTo({
+ url: `/other_pages/ntrp-evaluate/index?stage=${
+ testFlag ? StageType.INTRO : StageType.TEST
+ }`,
+ });
+ },
+ [setCallback]
+ );
return type === EvaluateScene.list ? (
@@ -169,4 +174,5 @@ function NTRPTestEntryCard(props: {
);
}
-export default NTRPTestEntryCard;
+export default memo(NTRPTestEntryCard);
+// export default NTRPTestEntryCard;
diff --git a/src/container/listContainer/index.tsx b/src/container/listContainer/index.tsx
index 6d1439c..cdcf4cf 100644
--- a/src/container/listContainer/index.tsx
+++ b/src/container/listContainer/index.tsx
@@ -8,7 +8,7 @@ import { setStorage, getStorage } from "@/store/storage";
import { NTRPTestEntryCard } from "@/components";
import { EvaluateScene } from "@/store/evaluateStore";
import "./index.scss";
-import { useRef, useEffect, useState } from "react";
+import { useRef, useEffect, useState, useMemo } from "react";
const ListContainer = (props) => {
const {
@@ -26,6 +26,7 @@ const ListContainer = (props) => {
style,
collapse = false,
defaultShowNum,
+ evaluateFlag,
} = props;
const timerRef = useRef(null);
const loadingStartTimeRef = useRef(null);
@@ -47,19 +48,17 @@ const ListContainer = (props) => {
});
useEffect(() => {
- setShowNumber(
- () => {
- return defaultShowNum === undefined ? data?.length : defaultShowNum
-
- })
- }, [data])
+ setShowNumber(() => {
+ return defaultShowNum === undefined ? data?.length : defaultShowNum;
+ });
+ }, [data]);
// 控制骨架屏显示逻辑
useEffect(() => {
if (loading) {
// 开始加载时记录时间
loadingStartTimeRef.current = Date.now();
-
+
// 延迟 300ms 后再显示骨架屏
skeletonTimerRef.current = setTimeout(() => {
setShowSkeleton(true);
@@ -102,6 +101,7 @@ const ListContainer = (props) => {
// 对于没有ntrp等级的用户每个月展示一次, 插在第三个位置
function insertEvaluateCard(list) {
+ if (!evaluateFlag) return list;
if (!list || list.length === 0) {
return list;
}
@@ -122,6 +122,11 @@ const ListContainer = (props) => {
return [item1, item2, item3, { type: "evaluateCard" }, ...rest];
}
+ const memoizedList = useMemo(
+ () => insertEvaluateCard(data),
+ [evaluateFlag, data, userInfo.ntrp_level]
+ );
+
// 渲染列表
const renderList = (list) => {
// 请求数据为空
@@ -137,12 +142,12 @@ const ListContainer = (props) => {
);
}
- showNumber !== undefined && (list = list.slice(0, showNumber))
+ showNumber !== undefined && (list = list.slice(0, showNumber));
// 渲染数据
return (
<>
- {insertEvaluateCard(list).map((match, index) => {
+ {memoizedList.map((match, index) => {
if (match.type === "evaluateCard") {
return (
@@ -164,20 +169,33 @@ const ListContainer = (props) => {
{renderList(recommendList)} */}
{/* 到底了 */}
- {collapse ?
- data?.length > defaultShowNum ?
- data?.length > showNumber ?
- { setShowNumber(data?.length) }}>
+ {collapse ? (
+ data?.length > defaultShowNum ? (
+ data?.length > showNumber ? (
+ {
+ setShowNumber(data?.length);
+ }}
+ >
更多球局
- :
- { setShowNumber(defaultShowNum) }}>
+
+ ) : (
+ {
+ setShowNumber(defaultShowNum);
+ }}
+ >
收起
- :
- null
- : data?.length > 0 && 到底了}
+ )
+ ) : null
+ ) : (
+ data?.length > 0 && 到底了
+ )}
);
};
diff --git a/src/game_pages/list/index.tsx b/src/game_pages/list/index.tsx
index 502ff3a..1acc70e 100644
--- a/src/game_pages/list/index.tsx
+++ b/src/game_pages/list/index.tsx
@@ -66,7 +66,7 @@ const ListPage = () => {
const scrollViewRef = useRef(null); // ScrollView 的 ref
const scrollTimeoutRef = useRef(null);
const lastScrollTopRef = useRef(0);
- const scrollDirectionRef = useRef<'up' | 'down' | null>(null);
+ const scrollDirectionRef = useRef<"up" | "down" | null>(null);
const lastScrollTimeRef = useRef(Date.now());
const loadingMoreRef = useRef(false); // 防止重复加载更多
const scrollStartPositionRef = useRef(0); // 记录开始滚动的位置
@@ -74,45 +74,54 @@ const ListPage = () => {
const [scrollTop, setScrollTop] = useState(0); // 控制 ScrollView 滚动位置
// 动态控制 GuideBar 的 z-index
- const [guideBarZIndex, setGuideBarZIndex] = useState<'low' | 'high'>('high');
+ const [guideBarZIndex, setGuideBarZIndex] = useState<"low" | "high">("high");
const [isPublishMenuVisible, setIsPublishMenuVisible] = useState(false);
const [isDistanceFilterVisible, setIsDistanceFilterVisible] = useState(false);
const [isCityPickerVisible, setIsCityPickerVisible] = useState(false);
-
+
// 处理 PublishMenu 显示/隐藏
const handlePublishMenuVisibleChange = useCallback((visible: boolean) => {
setIsPublishMenuVisible(visible);
}, []);
-
+
// 处理 DistanceQuickFilter 显示/隐藏
const handleDistanceFilterVisibleChange = useCallback((visible: boolean) => {
setIsDistanceFilterVisible(visible);
}, []);
-
+
// 处理 CityPicker 显示/隐藏
const handleCityPickerVisibleChange = useCallback((visible: boolean) => {
setIsCityPickerVisible(visible);
}, []);
-
+
// 滚动到顶部的方法
const scrollToTop = useCallback(() => {
// 使用一个唯一值触发 scrollTop 更新,确保每次都能滚动到顶部
- setScrollTop(prev => prev === 0 ? 0.1 : 0);
+ setScrollTop((prev) => (prev === 0 ? 0.1 : 0));
}, []);
-
+
// 监听所有弹窗和菜单的状态,动态调整 GuideBar 的 z-index
useEffect(() => {
if (isPublishMenuVisible) {
// PublishMenu 展开时,GuideBar 保持高层级
- setGuideBarZIndex('high');
- } else if (isShowFilterPopup || isDistanceFilterVisible || isCityPickerVisible) {
+ setGuideBarZIndex("high");
+ } else if (
+ isShowFilterPopup ||
+ isDistanceFilterVisible ||
+ isCityPickerVisible
+ ) {
// 任何筛选组件或选择器展开时,GuideBar 降低层级
- setGuideBarZIndex('low');
+ setGuideBarZIndex("low");
} else {
// 都关闭时,GuideBar 保持高层级
- setGuideBarZIndex('high');
+ setGuideBarZIndex("high");
}
- }, [isShowFilterPopup, isPublishMenuVisible, isDistanceFilterVisible, isCityPickerVisible]);
+ }, [
+ isShowFilterPopup,
+ isPublishMenuVisible,
+ isDistanceFilterVisible,
+ isCityPickerVisible,
+ ]);
// ScrollView 滚动处理函数
const handleScrollViewScroll = useCallback(
@@ -133,28 +142,34 @@ const ListPage = () => {
if (Math.abs(scrollDiff) > 15) {
if (scrollDiff > 0) {
// 方向改变时,记录新的起始位置
- if (newDirection !== 'up') {
+ if (newDirection !== "up") {
scrollStartPositionRef.current = lastScrollTop;
}
- newDirection = 'up';
+ newDirection = "up";
} else {
// 方向改变时,记录新的起始位置
- if (newDirection !== 'down') {
+ if (newDirection !== "down") {
scrollStartPositionRef.current = lastScrollTop;
}
- newDirection = 'down';
+ newDirection = "down";
}
scrollDirectionRef.current = newDirection;
}
// 计算从开始滚动到现在的累计距离
- const totalScrollDistance = Math.abs(currentScrollTop - scrollStartPositionRef.current);
+ const totalScrollDistance = Math.abs(
+ currentScrollTop - scrollStartPositionRef.current
+ );
// 滚动阈值
const positionThreshold = 120; // 需要滚动到距离顶部120px
const distanceThreshold = 80; // 需要连续滚动80px才触发
- if (newDirection === 'up' && currentScrollTop > positionThreshold && totalScrollDistance > distanceThreshold) {
+ if (
+ newDirection === "up" &&
+ currentScrollTop > positionThreshold &&
+ totalScrollDistance > distanceThreshold
+ ) {
// 上滑超过阈值,且连续滚动距离足够,隐藏搜索框
if (showSearchBar || !isShowInputCustomerNavBar) {
setShowSearchBar(false);
@@ -164,7 +179,10 @@ const ListPage = () => {
// 重置起始位置
scrollStartPositionRef.current = currentScrollTop;
}
- } else if ((newDirection === 'down' && totalScrollDistance > distanceThreshold) || currentScrollTop <= positionThreshold) {
+ } else if (
+ (newDirection === "down" && totalScrollDistance > distanceThreshold) ||
+ currentScrollTop <= positionThreshold
+ ) {
// 下滑且连续滚动距离足够,或者回到顶部附近,显示搜索框
if (!showSearchBar || isShowInputCustomerNavBar) {
setShowSearchBar(true);
@@ -296,7 +314,7 @@ const ListPage = () => {
updateFilterOptions(params);
};
- const handleSearchChange = () => { };
+ const handleSearchChange = () => {};
// 距离筛选
const handleDistanceOrQuickChange = (name, value) => {
@@ -433,7 +451,6 @@ const ListPage = () => {
return (
<>
-
{/* 自定义导航 */}
{
{/* 固定在顶部的搜索框和筛选 */}
{/* 搜索框 - 可隐藏 */}
-
+
0}
@@ -507,7 +528,11 @@ const ListPage = () => {
lowerThreshold={100}
onScrollToLower={async () => {
// 防止重复调用,检查 loading 状态和是否正在加载更多
- if (!loading && !loadingMoreRef.current && listPageState?.isHasMoreData) {
+ if (
+ !loading &&
+ !loadingMoreRef.current &&
+ listPageState?.isHasMoreData
+ ) {
loadingMoreRef.current = true;
try {
await loadMoreMatches();
@@ -529,14 +554,19 @@ const ListPage = () => {
error={error}
reload={refreshMatches}
loadMoreMatches={loadMoreMatches}
+ evaluateFlag
/>
)}
-
>
diff --git a/src/main_pages/components/ListPageContent.tsx b/src/main_pages/components/ListPageContent.tsx
index d0fc120..dd0a10a 100644
--- a/src/main_pages/components/ListPageContent.tsx
+++ b/src/main_pages/components/ListPageContent.tsx
@@ -36,9 +36,10 @@ const ListPageContent: React.FC = ({
}) => {
const store = useListStore() || {};
const { fetchUserInfo } = useUserActions();
- const { statusNavbarHeightInfo, getCurrentLocationInfo } = useGlobalState() || {};
+ const { statusNavbarHeightInfo, getCurrentLocationInfo } =
+ useGlobalState() || {};
const { totalHeight = 98 } = statusNavbarHeightInfo || {};
-
+
const {
listPageState,
loading,
@@ -77,7 +78,7 @@ const ListPageContent: React.FC = ({
const scrollViewRef = useRef(null);
const scrollTimeoutRef = useRef(null);
const lastScrollTopRef = useRef(0);
- const scrollDirectionRef = useRef<'up' | 'down' | null>(null);
+ const scrollDirectionRef = useRef<"up" | "down" | null>(null);
const lastScrollTimeRef = useRef(Date.now());
const loadingMoreRef = useRef(false);
const scrollStartPositionRef = useRef(0);
@@ -86,17 +87,20 @@ const ListPageContent: React.FC = ({
const [refreshing, setRefreshing] = useState(false);
// 处理距离筛选显示/隐藏
- const handleDistanceFilterVisibleChange = useCallback((visible: boolean) => {
- onDistanceFilterVisibleChange?.(visible);
- onNavStateChange?.({ isDistanceFilterVisible: visible });
- }, [onDistanceFilterVisibleChange, onNavStateChange]);
+ 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);
+ setScrollTop((prev) => (prev === 0 ? 0.1 : 0));
}, []);
// 监听外部滚动触发
@@ -120,24 +124,30 @@ const ListPageContent: React.FC = ({
let newDirection = scrollDirectionRef.current;
if (Math.abs(scrollDiff) > 15) {
if (scrollDiff > 0) {
- if (newDirection !== 'up') {
+ if (newDirection !== "up") {
scrollStartPositionRef.current = lastScrollTop;
}
- newDirection = 'up';
+ newDirection = "up";
} else {
- if (newDirection !== 'down') {
+ if (newDirection !== "down") {
scrollStartPositionRef.current = lastScrollTop;
}
- newDirection = 'down';
+ newDirection = "down";
}
scrollDirectionRef.current = newDirection;
}
- const totalScrollDistance = Math.abs(currentScrollTop - scrollStartPositionRef.current);
+ const totalScrollDistance = Math.abs(
+ currentScrollTop - scrollStartPositionRef.current
+ );
const positionThreshold = 120;
const distanceThreshold = 80;
- if (newDirection === 'up' && currentScrollTop > positionThreshold && totalScrollDistance > distanceThreshold) {
+ if (
+ newDirection === "up" &&
+ currentScrollTop > positionThreshold &&
+ totalScrollDistance > distanceThreshold
+ ) {
if (showSearchBar || !isShowInputCustomerNavBar) {
setShowSearchBar(false);
updateListPageState({
@@ -146,7 +156,10 @@ const ListPageContent: React.FC = ({
onNavStateChange?.({ isShowInputCustomerNavBar: true });
scrollStartPositionRef.current = currentScrollTop;
}
- } else if ((newDirection === 'down' && totalScrollDistance > distanceThreshold) || currentScrollTop <= positionThreshold) {
+ } else if (
+ (newDirection === "down" && totalScrollDistance > distanceThreshold) ||
+ currentScrollTop <= positionThreshold
+ ) {
if (!showSearchBar || isShowInputCustomerNavBar) {
setShowSearchBar(true);
updateListPageState({
@@ -160,7 +173,12 @@ const ListPageContent: React.FC = ({
lastScrollTopRef.current = currentScrollTop;
lastScrollTimeRef.current = currentTime;
},
- [showSearchBar, isShowInputCustomerNavBar, updateListPageState, onNavStateChange]
+ [
+ showSearchBar,
+ isShowInputCustomerNavBar,
+ updateListPageState,
+ onNavStateChange,
+ ]
);
useEffect(() => {
@@ -253,7 +271,7 @@ const ListPageContent: React.FC = ({
updateFilterOptions(params);
};
- const handleSearchChange = () => { };
+ const handleSearchChange = () => {};
const handleDistanceOrQuickChange = (name, value) => {
updateDistanceQuickFilter({
@@ -341,7 +359,11 @@ const ListPageContent: React.FC = ({
)}
-
+
0}
@@ -378,7 +400,11 @@ const ListPageContent: React.FC = ({
onRefresherRefresh={handleRefresh}
lowerThreshold={100}
onScrollToLower={async () => {
- if (!loading && !loadingMoreRef.current && listPageState?.isHasMoreData) {
+ if (
+ !loading &&
+ !loadingMoreRef.current &&
+ listPageState?.isHasMoreData
+ ) {
loadingMoreRef.current = true;
try {
await loadMoreMatches();
@@ -399,6 +425,7 @@ const ListPageContent: React.FC = ({
error={error}
reload={refreshMatches}
loadMoreMatches={loadMoreMatches}
+ evaluateFlag
/>
@@ -409,4 +436,3 @@ const ListPageContent: React.FC = ({
};
export default ListPageContent;
-
diff --git a/src/main_pages/components/MyselfPageContent.tsx b/src/main_pages/components/MyselfPageContent.tsx
index 0cc724c..3e52662 100644
--- a/src/main_pages/components/MyselfPageContent.tsx
+++ b/src/main_pages/components/MyselfPageContent.tsx
@@ -16,7 +16,7 @@ const MyselfPageContent: React.FC = () => {
const pickerOption = usePickerOption();
const { statusNavbarHeightInfo } = useGlobalState() || {};
const { totalHeight = 98 } = statusNavbarHeightInfo || {};
-
+
const instance = (Taro as any).getCurrentInstance();
const user_id = instance.router?.params?.userid || "";
const is_current_user = !user_id;
@@ -26,7 +26,9 @@ const MyselfPageContent: React.FC = () => {
const [ended_game_records, setEndedGameRecords] = useState([]);
const [loading] = useState(false);
const [is_following, setIsFollowing] = useState(false);
- const [active_tab, setActiveTab] = useState<"hosted" | "participated">("hosted");
+ const [active_tab, setActiveTab] = useState<"hosted" | "participated">(
+ "hosted"
+ );
useEffect(() => {
pickerOption.getCities();
@@ -66,7 +68,7 @@ const MyselfPageContent: React.FC = () => {
const load_game_data = async () => {
try {
- if (!user_info || !('id' in user_info)) {
+ if (!user_info || !("id" in user_info)) {
return;
}
let games_data;
@@ -136,7 +138,10 @@ const MyselfPageContent: React.FC = () => {
return (
-
+
{
};
export default MyselfPageContent;
-
diff --git a/src/store/evaluateStore.ts b/src/store/evaluateStore.ts
index f7e28cf..b08e264 100644
--- a/src/store/evaluateStore.ts
+++ b/src/store/evaluateStore.ts
@@ -10,21 +10,25 @@ export enum EvaluateScene {
}
export interface EvaluateCallback {
- type: EvaluateScene | ''
+ type: EvaluateScene | "";
// flag是用来区分跳转ntrp测试后的操作和直接修改ntrp水平成功后的操作
// score是用在加入球局前判断是否满足球局要求的返回值,限定为必传
// next有两个地方调用:ntrp结果页handleGoon、ntrp弹窗(NTRPEvaluatePopup)直接修改点击保存按钮时
- next: ({ flag, score }: { flag?: boolean, score: string }) => void,
- onCancel: () => void,
+ next: ({ flag, score }: { flag?: boolean; score: string }) => void;
+ onCancel: () => void;
}
export interface EvaluateCallbackType extends EvaluateCallback {
- setCallback: (options: { type: EvaluateScene | '', next: () => void, onCancel: () => void }) => void,
- clear: () => void,
+ setCallback: (options: {
+ type: EvaluateScene | "";
+ next: ({ flag, score }: { flag?: boolean; score: string }) => void;
+ onCancel: () => void;
+ }) => void;
+ clear: () => void;
}
export const useEvaluateCallback = create()((set) => ({
- type: '',
+ type: "",
next: () => { },
onCancel: () => { },
setCallback: ({ type, next, onCancel }) => {
@@ -32,15 +36,18 @@ export const useEvaluateCallback = create()((set) => ({
type,
next,
onCancel,
- })
+ });
+ },
+ clear: () => {
+ set({ type: "", next: () => { }, onCancel: () => { } });
},
- clear: () => { set({ type: '', next: () => { }, onCancel: () => { } }) }
}));
-export const useEvaluate = () => useEvaluateCallback(({ type, next, onCancel, setCallback, clear }) => ({
- type,
- next,
- onCancel,
- setCallback,
- clear,
-}))
+export const useEvaluate = () =>
+ useEvaluateCallback(({ type, next, onCancel, setCallback, clear }) => ({
+ type,
+ next,
+ onCancel,
+ setCallback,
+ clear,
+ }));