286 lines
8.6 KiB
TypeScript
286 lines
8.6 KiB
TypeScript
import { View, Text, Image } from "@tarojs/components";
|
|
import ListCard from "@/components/ListCard";
|
|
import ListLoadError from "@/components/ListLoadError";
|
|
import ListCardSkeleton from "@/components/ListCardSkeleton";
|
|
import { useReachBottom } from "@tarojs/taro";
|
|
import Taro from "@tarojs/taro";
|
|
import { useUserInfo, useUserActions, useLastTestResult } from "@/store/userStore";
|
|
import { NTRPTestEntryCard } from "@/components";
|
|
import { EvaluateScene } from "@/store/evaluateStore";
|
|
import { waitForAuthInit } from "@/utils/authInit";
|
|
import "./index.scss";
|
|
import { useRef, useEffect, useState, useMemo } from "react";
|
|
import { useDictionaryStore } from "@/store/dictionaryStore";
|
|
|
|
const ListContainer = (props) => {
|
|
const {
|
|
loading,
|
|
isShowNoData,
|
|
data = [],
|
|
error,
|
|
reload,
|
|
// recommendList,
|
|
loadMoreMatches,
|
|
errorImg,
|
|
emptyText,
|
|
btnText,
|
|
btnImg,
|
|
style,
|
|
collapse = false,
|
|
defaultShowNum,
|
|
evaluateFlag,
|
|
enableHomeCards = false, // 仅首页需要 banner 和 NTRP 测评卡片
|
|
listLoadErrorWrapperHeight,
|
|
listLoadErrorWidth,
|
|
listLoadErrorHeight,
|
|
listLoadErrorScale,
|
|
} = props;
|
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
|
const loadingStartTimeRef = useRef<number | null>(null);
|
|
const skeletonTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
|
|
const [showNumber, setShowNumber] = useState(0);
|
|
const [showSkeleton, setShowSkeleton] = useState(false);
|
|
|
|
const userInfo = useUserInfo();
|
|
const { fetchUserInfo, fetchLastTestResult } = useUserActions();
|
|
// 使用全局状态中的测试结果,避免重复调用接口
|
|
const lastTestResult = useLastTestResult();
|
|
const { bannerListImage, bannerDetailImage, bannerListIndex = 0 } = useDictionaryStore((s) => s.bannerDict) || {};
|
|
useReachBottom(() => {
|
|
// 加载更多方法
|
|
if (loading) {
|
|
return;
|
|
}
|
|
// timerRef.current = setTimeout(() => {
|
|
loadMoreMatches();
|
|
// }, 500);
|
|
});
|
|
|
|
useEffect(() => {
|
|
setShowNumber(() => {
|
|
return defaultShowNum === undefined ? data?.length : defaultShowNum;
|
|
});
|
|
}, [data]);
|
|
|
|
// 控制骨架屏显示逻辑
|
|
useEffect(() => {
|
|
if (loading) {
|
|
// 开始加载时记录时间
|
|
loadingStartTimeRef.current = Date.now();
|
|
|
|
// 延迟 300ms 后再显示骨架屏
|
|
skeletonTimerRef.current = setTimeout(() => {
|
|
setShowSkeleton(true);
|
|
}, 600);
|
|
} else {
|
|
// 加载完成,清除定时器并隐藏骨架屏
|
|
if (skeletonTimerRef.current) {
|
|
clearTimeout(skeletonTimerRef.current);
|
|
skeletonTimerRef.current = null;
|
|
}
|
|
setShowSkeleton(false);
|
|
loadingStartTimeRef.current = null;
|
|
}
|
|
}, [loading]);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
if (timerRef.current) {
|
|
clearTimeout(timerRef.current);
|
|
}
|
|
if (skeletonTimerRef.current) {
|
|
clearTimeout(skeletonTimerRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
// 获取测试结果,判断最近一个月是否有测试记录(仅首页需要)
|
|
useEffect(() => {
|
|
const init = async () => {
|
|
if (!evaluateFlag || !enableHomeCards) return;
|
|
// 先等待静默登录完成
|
|
await waitForAuthInit();
|
|
// 然后再获取用户信息
|
|
const userInfoId = userInfo && 'id' in userInfo ? userInfo.id : null;
|
|
if (!userInfoId) {
|
|
await fetchUserInfo();
|
|
return; // 等待下一次 useEffect 触发(此时 userInfo.id 已有值)
|
|
}
|
|
// 如果全局状态中没有测试结果,则调用接口(使用请求锁,多个组件同时调用时只会请求一次)
|
|
if (!lastTestResult) {
|
|
await fetchLastTestResult();
|
|
}
|
|
};
|
|
init();
|
|
}, [evaluateFlag, enableHomeCards, userInfo, lastTestResult, fetchLastTestResult]);
|
|
|
|
// 从全局状态中获取测试状态
|
|
const hasTestInLastMonth = lastTestResult?.has_test_in_last_month || false;
|
|
|
|
if (error) {
|
|
return <ListLoadError reload={reload} />;
|
|
}
|
|
|
|
const renderSkeleton = () => {
|
|
return (
|
|
<>
|
|
{new Array(10).fill(0).map(() => {
|
|
return <ListCardSkeleton />;
|
|
})}
|
|
</>
|
|
);
|
|
};
|
|
|
|
// 插入 banner 卡片
|
|
function insertBannerCard(list) {
|
|
if (!bannerListImage) return list;
|
|
if (!list || !Array.isArray(list)) return list ?? [];
|
|
return [
|
|
...list.slice(0, Number(bannerListIndex)),
|
|
{ type: "banner", banner_image_url: bannerListImage, banner_detail_url: bannerDetailImage },
|
|
...list.slice(Number(bannerListIndex))
|
|
];
|
|
}
|
|
|
|
// 对于没有ntrp等级的用户每个月展示一次, 插在第二个位置后面
|
|
// insertBannerCard 需在最后统一执行,否则前面分支直接 return 时 banner 不会被插入
|
|
function insertEvaluateCard(list) {
|
|
let result: any[];
|
|
|
|
if (!evaluateFlag) {
|
|
result = showNumber !== undefined ? list.slice(0, showNumber) : list;
|
|
} else if (!list || list.length === 0) {
|
|
result = list;
|
|
} else if (hasTestInLastMonth) {
|
|
result = showNumber !== undefined ? list.slice(0, showNumber) : list;
|
|
} else if (list.length <= 2) {
|
|
result = [...list, { type: "evaluateCard" }];
|
|
} else {
|
|
const [item1, item2, ...rest] = list;
|
|
result = [
|
|
item1,
|
|
item2,
|
|
{ type: "evaluateCard" },
|
|
...(showNumber !== undefined ? rest.slice(0, showNumber - 3) : rest),
|
|
];
|
|
}
|
|
|
|
return insertBannerCard(result);
|
|
}
|
|
|
|
const memoizedList = useMemo(
|
|
() => (enableHomeCards ? insertEvaluateCard(data) : data),
|
|
[enableHomeCards, evaluateFlag, data, hasTestInLastMonth, showNumber, bannerListImage, bannerDetailImage, bannerListIndex]
|
|
);
|
|
|
|
// 渲染 banner 卡片
|
|
const renderBanner = (item, index) => {
|
|
if (!item?.banner_image_url) {
|
|
return null;
|
|
}
|
|
return (
|
|
<View
|
|
key={item.id || `banner-${index}`}
|
|
onClick={() => {
|
|
const target = item.banner_detail_url;
|
|
if (target) {
|
|
(Taro as any).navigateTo({
|
|
url: `/other_pages/bannerDetail/index?img=${encodeURIComponent(target)}`,
|
|
});
|
|
}
|
|
}}
|
|
style={{
|
|
height: "100px",
|
|
overflow: "hidden",
|
|
borderRadius: "12px",
|
|
backgroundImage: `url(${item.banner_image_url})`,
|
|
backgroundSize: "cover",
|
|
backgroundPosition: "center",
|
|
backgroundRepeat: "no-repeat",
|
|
}}
|
|
>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
// 渲染列表
|
|
const renderList = () => {
|
|
// 请求数据为空
|
|
if (isShowNoData) {
|
|
return (
|
|
<ListLoadError
|
|
reload={reload}
|
|
errorImg={errorImg}
|
|
btnText={btnText}
|
|
btnImg={btnImg}
|
|
text={emptyText || "暂无数据"}
|
|
wrapperHeight={listLoadErrorWrapperHeight || ""}
|
|
width={listLoadErrorWidth || ""}
|
|
height={listLoadErrorHeight || ""}
|
|
scale={listLoadErrorScale || ""}
|
|
/>
|
|
);
|
|
}
|
|
|
|
// 渲染数据
|
|
return (
|
|
<>
|
|
{memoizedList.map((match, index) => {
|
|
if (enableHomeCards && match?.type === "banner") {
|
|
return renderBanner(match, index);
|
|
}
|
|
if (enableHomeCards && match?.type === "evaluateCard") {
|
|
return (
|
|
<NTRPTestEntryCard key={`evaluate-${index}`} type={EvaluateScene.list} />
|
|
);
|
|
}
|
|
return <ListCard key={match?.id || index} {...match} />;
|
|
})}
|
|
</>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<View className="listContentWrapper" style={style}>
|
|
{renderList()}
|
|
{/* 显示骨架屏 - 只有在 loading 超过 300ms 时才显示 */}
|
|
{loading && showSkeleton && renderSkeleton()}
|
|
{/* <View className="recommendTextWrapper">
|
|
<Text className="recommendText">搜索结果较少,已为你推荐其他内容</Text>
|
|
</View>
|
|
{renderList(recommendList)} */}
|
|
{/* 到底了 */}
|
|
{collapse ? (
|
|
data?.length > defaultShowNum ? (
|
|
data?.length > showNumber ? (
|
|
<View
|
|
className="collapse-btn fold"
|
|
onClick={() => {
|
|
setShowNumber(data?.length);
|
|
}}
|
|
>
|
|
<Text>更多球局</Text>
|
|
<Image src={require("@/static/userInfo/fold.svg")}></Image>
|
|
</View>
|
|
) : (
|
|
<View
|
|
className="collapse-btn"
|
|
onClick={() => {
|
|
setShowNumber(defaultShowNum);
|
|
}}
|
|
>
|
|
<Text>收起</Text>
|
|
<Image src={require("@/static/userInfo/fold.svg")}></Image>
|
|
</View>
|
|
)
|
|
) : null
|
|
) : (
|
|
data?.length > 0 && <View className="bottomTextWrapper">到底了</View>
|
|
)}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default ListContainer;
|