feat: 首页未开赛默认展示未来 3 天,支持展开更多
- 新增 showAllUpcoming 状态,默认 false,仅渲染未来 3 天(今/明/后天)的未开赛 - 列表头显示「未来3天 N / 共 M 场」计数 - 「显示后续 N 场未开赛」按钮:展开全部已加载的未开赛 - 展开后提供「收起,仅显示未来 3 天」回退 - 切换联赛/状态时自动重置为 3 天视图 - 空态区分「未来3天暂无比赛」与「暂无赛程」 - 已完赛/全部状态不受影响,展示全部 Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
This commit is contained in:
co-authored by
new-provider/LongCat-2.0 <
parent
c586a38b3a
commit
19f17145cc
@@ -194,6 +194,7 @@ export default function Matches() {
|
||||
const [nextCursor, setNextCursor] = useState<string | null>(null)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [showAllUpcoming, setShowAllUpcoming] = useState(false) // 默认仅展示未来 3 天;true 展开全部
|
||||
const [predictingId, setPredictingId] = useState<number | null>(null)
|
||||
const [prediction, setPrediction] = useState<Prediction | null>(null)
|
||||
const [predictionFor, setPredictionFor] = useState<Match | null>(null)
|
||||
@@ -223,6 +224,7 @@ export default function Matches() {
|
||||
const seq = ++loadSeq.current
|
||||
setLoading(true)
|
||||
setLoadingMore(false)
|
||||
setShowAllUpcoming(false) // 切换筛选重置为「未来 3 天」视图
|
||||
setError(null)
|
||||
try {
|
||||
const params = new URLSearchParams({ league, status, limit: '50' })
|
||||
@@ -275,6 +277,21 @@ export default function Matches() {
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
/** 未来 3 天窗口:今天 00:00 → 第 3 天 00:00(即今天/明天/后天) */
|
||||
function addDays(d: Date, n: number): string {
|
||||
const x = new Date(d)
|
||||
x.setFullYear(x.getFullYear(), x.getMonth(), x.getDate() + n)
|
||||
return `${x.getFullYear()}-${String(x.getMonth() + 1).padStart(2, '0')}-${String(x.getDate()).padStart(2, '0')}`
|
||||
}
|
||||
const todayKey = todayStr()
|
||||
const windowEnd = addDays(new Date(), 3) // 不含
|
||||
|
||||
/** 比赛是否在未来 3 天内(用于默认视图过滤) */
|
||||
function withinNext3Days(matchDate: string): boolean {
|
||||
const key = toLocalDateKey(matchDate)
|
||||
return key >= todayKey && key < windowEnd
|
||||
}
|
||||
|
||||
/** UTC ISO → 本地日期 YYYY-MM-DD(用于分组) */
|
||||
function toLocalDateKey(iso: string): string {
|
||||
const d = new Date(iso)
|
||||
@@ -346,6 +363,14 @@ export default function Matches() {
|
||||
|
||||
const leagueName = LEAGUES.find(l => l.code === league)?.name ?? league
|
||||
|
||||
/** 未开赛默认仅展示未来 3 天;其余状态展示全部。showAllUpcoming=true 时展开全部。 */
|
||||
const isScheduledView = status === 'scheduled'
|
||||
const visibleMatches = (!isScheduledView || showAllUpcoming)
|
||||
? matches
|
||||
: matches.filter(m => withinNext3Days(m.match_date))
|
||||
// 是否有被折叠的未开赛比赛(用于显示「展开」按钮)
|
||||
const hasHiddenUpcoming = isScheduledView && !showAllUpcoming && matches.length > visibleMatches.length
|
||||
|
||||
/** 状态/模式一组的文字切换 */
|
||||
const Switch = ({ value, onChange, items }: {
|
||||
value: string
|
||||
@@ -420,7 +445,11 @@ export default function Matches() {
|
||||
</span>
|
||||
|
||||
<span className="ml-auto inline-flex items-center gap-3">
|
||||
<span className="tabular-nums">共 {matches.length} 场</span>
|
||||
<span className="tabular-nums">
|
||||
{isScheduledView && !showAllUpcoming && hasHiddenUpcoming
|
||||
? `未来3天 ${visibleMatches.length} / 共 ${matches.length} 场`
|
||||
: `共 ${visibleMatches.length} 场`}
|
||||
</span>
|
||||
<button onClick={load} disabled={loading} className="btn btn-sm">
|
||||
{loading ? (<><Spinner /> 获取中</>) : '刷新'}
|
||||
</button>
|
||||
@@ -453,17 +482,26 @@ export default function Matches() {
|
||||
<section aria-label="赛程">
|
||||
{loading && <SkeletonRows n={4} />}
|
||||
|
||||
{!loading && matches.length === 0 && (
|
||||
{!loading && visibleMatches.length === 0 && (
|
||||
<div className="empty-state">
|
||||
{matches.length > 0 ? (
|
||||
<>
|
||||
<p className="empty-state-title">未来 3 天暂无 {leagueName} 比赛</p>
|
||||
<p className="empty-state-sub">已导入 {matches.length} 场未开赛,点击下方按钮查看</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="empty-state-title">本版暂无赛程</p>
|
||||
<p className="empty-state-sub">请先通过「数据采集」导入 {leagueName} 的比赛数据</p>
|
||||
<a href="/admin/collection" className="empty-state-action">
|
||||
前往数据采集 <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && groupByDate(matches).map(([dateKey, group]) => (
|
||||
{!loading && groupByDate(visibleMatches).map(([dateKey, group]) => (
|
||||
<div key={dateKey}>
|
||||
{/* 日期分组头:sticky 但 z 低于弹窗 z-50 */}
|
||||
<div className="sticky top-0 z-20 border-b border-ink-900 bg-paper-100 px-3 py-2 text-xs font-medium tracking-wide text-ink-600">
|
||||
@@ -604,8 +642,32 @@ export default function Matches() {
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* 未开赛视图:从「未来 3 天」展开全部已加载的未开赛 */}
|
||||
{!loading && hasHiddenUpcoming && (
|
||||
<div className="flex justify-center pt-4">
|
||||
<button
|
||||
onClick={() => setShowAllUpcoming(true)}
|
||||
className="btn btn-outline min-h-[44px] w-full max-w-xs sm:w-auto"
|
||||
>
|
||||
显示后续 {matches.length - visibleMatches.length} 场未开赛
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 已展开全部但未开赛:提供「收起」回未来 3 天 */}
|
||||
{!loading && isScheduledView && showAllUpcoming && matches.length > 0 && (
|
||||
<div className="flex justify-center pt-2">
|
||||
<button
|
||||
onClick={() => setShowAllUpcoming(false)}
|
||||
className="text-xs text-ink-400 hover:text-ink-700 transition-colors"
|
||||
>
|
||||
收起,仅显示未来 3 天
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!loading && nextCursor && (
|
||||
<div className="flex justify-center pt-5">
|
||||
<div className="flex justify-center pt-4">
|
||||
<button onClick={loadMore} disabled={loadingMore} className="btn min-h-[44px] w-full max-w-xs sm:w-auto">
|
||||
{loadingMore ? (<><Spinner /> 获取中</>) : '载入更多赛程'}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user