UI-UX/src/components/me/QuotaCard.tsx
iye 71a2672ff6 fix(data,ranking,ui): real dynamic ranking + data sync hardening
数据准确性
- 票数初始为 0,不再用 Math.sqrt 公式造假票
- 排序 tiebreaker 统一为 votes desc + no asc,确保稳定
- store.rank() 与 sortArtists() 行为对齐

Top12 / 出道位
- Top12Bar 仅收纳真正有票的人(votes > 0),0 票时显示"出道位尚未产生"空态
- ArtistCard / RankingRow / SearchModal / MyFanSupport / RankCard 的 inTop12 高亮全部加 votes > 0 守卫
- ArtistFilters 新增"实时排名 / 编号顺序"分段切换 + 首页 sortKey 状态

领奖台 (Top3Podium)
- 1 人有票即可显示领奖台(此前要求 >= 3 人才显示)
- 缺位的 #2/#3 用"虚位以待"占位卡片填充,与正式卡片同 3:4 比例对齐
- 全员 0 票时三张全部显示虚位以待
- 卡片间距拉大到 gap-8 sm:gap-12

排行榜页 (/ranking)
- API 票数与本地乐观投票取 max() 合并,避免 API 落后覆盖本地新票
- 合并后重新排序与赋 rank
- 仅 >= 12 人有票才显示出道线分隔与复活位标记
- 复活位 gapToDebut 计算修正

跨日额度
- selectRemaining 按 quotaDate 判断是否跨日,跨日自动回满额(此前会卡在昨日剩余值)

搜索 (SearchModal)
- 改为订阅 store 拿活的票数,投票后立即反映
- 默认"热门 Top12"只在真正有票时显示,否则降级为"推荐艺人 · 编号顺序"
- 票数显示统一走 formatVotes(0 票不再显示 0.0w)

人物详情
- 36 人真实数据接入,移除全部静态数据(slogan/birthday/cv/themeColor)
- 接入人物小传 docx 数据(年龄/身高/性格/口头禅/技能/赛道/座右铭/长简介)
- 视频区与版心同宽 + 首帧自动作为封面 + 整区点击播放/暂停 + 可拖拽进度条
- 表演图片改为三张氛围图竖向 3:4,左对齐
- 移除分享按钮,投票按钮全宽

个人页 (/me)
- 移除等级/邀请好友/签到/编辑资料等静态数据
- 退出登录按钮在移动端 icon-only 显示(此前 sm:hidden 导致移动端无法登出)
- 我的应援 list 基于真实 myVotesByArtist 派生,凯之类的投票真正同步过去

导航
- 余票徽章未登录态显示 0/0,已登录显示 N/10
- 登录/注册按钮样式与登录后头像胶囊保持一致(紫色实心)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:56:42 +08:00

76 lines
2.8 KiB
TypeScript

"use client";
interface QuotaCardProps {
/** 今日剩余票数 */
remaining: number;
/** 每日总额度(用于「重置为 X 票」展示) */
dailyQuota: number;
}
export default function QuotaCard({ remaining, dailyQuota }: QuotaCardProps) {
return (
<div className="relative overflow-hidden rounded-2xl border border-purple-500/30 bg-gradient-to-br from-purple-500/[0.12] via-purple-500/[0.04] to-transparent shadow-[0_8px_32px_rgba(0,0,0,0.55),0_0_28px_rgba(139,92,246,0.2)]">
{/* 装饰:右侧紫色光晕 */}
<div
aria-hidden
className="absolute right-0 top-0 bottom-0 w-1/2 pointer-events-none"
style={{
background:
"radial-gradient(circle at 70% 50%, rgba(139,92,246,0.45) 0%, transparent 60%)",
}}
/>
{/* 装饰:右侧"水晶"占位(无素材时用 CSS 渲染的辉光六边形) */}
<CrystalDecoration />
<div className="relative p-6 sm:p-8">
<p className="text-xs text-white/70 tracking-wider"></p>
<div className="mt-2 flex items-baseline gap-1">
<span className="font-display text-5xl sm:text-6xl text-white tabular-nums leading-none">
{remaining}
</span>
<span className="text-xl text-white/85 ml-1"></span>
</div>
<p className="text-[11px] text-white/55 mt-3">
00:00 {" "}
<span className="text-purple-300 font-display tabular-nums">
{dailyQuota}
</span>{" "}
</p>
</div>
</div>
);
}
/** 右侧装饰:用 CSS 渲染的简易"紫色水晶"效果,作为 3D 素材到位前的占位。 */
function CrystalDecoration() {
return (
<div
aria-hidden
className="absolute right-4 sm:right-8 top-1/2 -translate-y-1/2 w-32 h-32 sm:w-40 sm:h-40 pointer-events-none"
style={{ perspective: "600px" }}
>
<div
className="absolute inset-0 rounded-2xl border border-purple-300/40"
style={{
background:
"linear-gradient(135deg, rgba(196,181,253,0.4) 0%, rgba(139,92,246,0.25) 60%, rgba(91,33,182,0.4) 100%)",
transform: "rotate(45deg) skewY(8deg)",
boxShadow:
"0 0 24px rgba(196,181,253,0.5), inset 0 0 24px rgba(255,255,255,0.15)",
}}
/>
<div
className="absolute inset-3 rounded-xl"
style={{
background:
"linear-gradient(135deg, rgba(255,255,255,0.18) 0%, transparent 60%)",
transform: "rotate(45deg) skewY(8deg)",
}}
/>
<div className="absolute top-1/4 left-1/4 w-2 h-2 rounded-full bg-white/85 shadow-[0_0_8px_rgba(255,255,255,0.9)]" />
<div className="absolute bottom-1/4 right-1/4 w-1 h-1 rounded-full bg-purple-200 shadow-[0_0_6px_rgba(196,181,253,0.9)]" />
</div>
);
}