"use client"; import { useMemo } from "react"; import Top3Podium from "@/components/ranking/Top3Podium"; import RankingRow from "@/components/ranking/RankingRow"; import DebutLineDivider from "@/components/ranking/DebutLineDivider"; import VoteModal from "@/components/VoteModal"; import LiveBadge from "@/components/LiveBadge"; import { sortArtists } from "@/lib/mock-data"; import { useVoteStore } from "@/lib/store"; import { useVoteAction } from "@/hooks/useVoteAction"; import { useRanking } from "@/hooks/useRanking"; import type { Artist } from "@/types/artist"; export default function RankingPage() { const storeArtists = useVoteStore((s) => s.artists); const { target, remaining, dailyQuota, openVote, closeVote, confirmVote } = useVoteAction(); const live = useRanking({ pollInterval: 30_000 }); // 数据同步:本地乐观投票 + 服务端最新票数取 max(避免 API 落后覆盖本地新票, // 也避免本地缺其他用户的票数)。合并后按 votes desc + no asc 重新排序并赋 rank。 const sorted = useMemo(() => { const apiVotes = new Map(); if (live.data?.list) { for (const row of live.data.list) apiVotes.set(row.id, row.voteCount); } const merged = storeArtists.map((a) => { const apiV = apiVotes.get(a.id) ?? 0; return apiV > a.votes ? { ...a, votes: apiV } : a; }); const ranked = sortArtists(merged, "votes"); // 重新按合并后的排名赋 rank(store 自带的 rank 仅来自本地 vote 后的 rank()) return ranked.map((a, i) => ({ ...a, rank: i + 1 })); }, [storeArtists, live.data]); // 仅有票的人能"进榜单",0 票不参与排名兜底 const ranked = sorted.filter((a) => a.votes > 0); const zeros = sorted.filter((a) => a.votes === 0); // 只要有 1 人有票就显示领奖台(#2/#3 缺位会自动以"虚位以待"占位); // 至少 12 人有票才有"出道线 / 复活位"概念 const podiumReady = ranked.length >= 1; const debutReady = ranked.length >= 12; const top3 = podiumReady ? ranked.slice(0, 3) : []; // 出道线上方(top3 之后到出道线之间) // - Top12 满员:经典 9 位(rank 4-12) // - 已有领奖台但 Top12 未满:podium 之后的所有有票 // - 连领奖台都没满:所有有票的人都从这里开始 const aboveLine = debutReady ? ranked.slice(3, 12) : podiumReady ? ranked.slice(3) : ranked; // 出道线下方 // - Top12 满员:ranked[12..] + 全部 0 票 // - 否则:仅 0 票 const belowLine = debutReady ? [...ranked.slice(12), ...zeros] : zeros; const debutCutoff = debutReady ? ranked[11].votes : 0; return ( <>
{/* Top 3 领奖台 */} {/* 实时刷新标识 */}
{/* 列表头部 */}
排名 头像 艺人 票数 距上一名 操作
{aboveLine.map((a, idx) => { const prev = idx === 0 ? podiumReady ? top3[2] : undefined : aboveLine[idx - 1]; const gap = prev ? prev.votes - a.votes : undefined; return ( ); })}
{/* 仅 Top12 满员才显示"出道线"分隔 */} {debutReady && }
{belowLine.map((a, idx) => { const prev = idx === 0 ? aboveLine[aboveLine.length - 1] : belowLine[idx - 1]; const gap = prev ? prev.votes - a.votes : undefined; // 仅 Top12 满员时第一位才是"复活位" const isRescue = debutReady && idx === 0; const gapToDebut = isRescue ? debutCutoff - a.votes + 1 : undefined; return ( ); })}
); }