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(null); const loadingStartTimeRef = useRef(null); const skeletonTimerRef = useRef(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 ; } const renderSkeleton = () => { return ( <> {new Array(10).fill(0).map(() => { return ; })} ); }; // showNumber 为 0 表示尚未同步,不参与截断;截断时只限制「数据条数」,插卡不占数据条数 const shouldLimitByShowNumber = showNumber > 0; // 插入 banner 卡片(在 bannerListIndex 位置插入,不替换数据) function insertBannerCard(list) { if (!bannerListImage) return list; if (!list || !Array.isArray(list)) { list = []; } const idx = Number(bannerListIndex); return [ ...list.slice(0, idx), { type: "banner", banner_image_url: bannerListImage, banner_detail_url: bannerDetailImage, }, ...list.slice(idx), ]; } // 对于没有 ntrp 等级的用户每个月展示一次,插在第 2 条数据后面;插卡是插入不替换,保留全部 showNumber 条数据 function insertEvaluateCard(list) { if (!list || !Array.isArray(list)) return insertBannerCard(list ?? []); const limitedList = shouldLimitByShowNumber ? list.slice(0, showNumber) : list; if (!evaluateFlag || hasTestInLastMonth) { return insertBannerCard(limitedList); } if (limitedList.length <= 2) { return insertBannerCard([...limitedList, { type: "evaluateCard" }]); } const [item1, item2, ...rest] = limitedList; const result = [item1, item2, { type: "evaluateCard" }, ...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 ( { 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", }} > ); }; const showNoData = isShowNoData && !loading && memoizedList?.length === 0; // 渲染列表 const renderList = () => { // 请求数据为空 if (showNoData) { return ( ); } // 渲染数据 return ( <> {memoizedList.map((match, index) => { if (enableHomeCards && match?.type === "banner") { return renderBanner(match, index); } if (enableHomeCards && match?.type === "evaluateCard") { return ( ); } return ; })} ); }; return ( {renderList()} {/* 显示骨架屏 - 只有在 loading 超过 300ms 时才显示 */} {loading && showSkeleton && renderSkeleton()} {/* 搜索结果较少,已为你推荐其他内容 {renderList(recommendList)} */} {/* 到底了 */} {collapse ? ( data?.length > defaultShowNum ? ( data?.length > showNumber ? ( { setShowNumber(data?.length); }} > 更多球局 ) : ( { setShowNumber(defaultShowNum); }} > 收起 ) ) : null ) : ( data?.length > 0 && 到底了 )} ); }; export default ListContainer;