feat: 增强结果展示与用户交互
- 在 PlayerBottomNav 中新增注单导航选项 - 在 DrawResultDetailScreen 中添加高亮显示用户命中号码的功能,并显示个人派彩信息 - 在 DrawResultsListScreen 中引入 JackpotResultsStrip 组件以展示奖池信息 - 在 TwentyThreeResultsGrid 中实现命中号码的高亮效果,提升用户体验
This commit is contained in:
@@ -4,6 +4,7 @@ import Link from "next/link";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { getDrawResultByNo } from "@/api/draw";
|
||||
import { getTicketDrawMyMatch } from "@/api/ticket-items";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -13,8 +14,12 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { JackpotResultsStrip } from "@/features/results/jackpot-results-strip";
|
||||
import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-grid";
|
||||
import { getPlayerBearerTokenPayload } from "@/lib/lottery-auth";
|
||||
import { formatLotteryInstant } from "@/lib/player-datetime";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import { norm4d } from "@/lib/norm-4d";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { DrawResultDetailPayload } from "@/types/api/draw-results";
|
||||
|
||||
@@ -22,11 +27,17 @@ type DrawResultDetailScreenProps = {
|
||||
drawNo: string;
|
||||
};
|
||||
|
||||
/** §4.6 开奖结果详情:23 分区 + [< >] 切换 */
|
||||
/** §4.6 开奖结果详情:23 分区 + [< >] 切换 + 本人命中高亮 + Jackpot */
|
||||
export function DrawResultDetailScreen({ drawNo }: DrawResultDetailScreenProps) {
|
||||
const [data, setData] = useState<DrawResultDetailPayload | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [highlightSet, setHighlightSet] = useState<ReadonlySet<string> | null>(null);
|
||||
const [myTotals, setMyTotals] = useState<{
|
||||
win: number;
|
||||
jackpot: number;
|
||||
hasBets: boolean;
|
||||
} | null>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -48,6 +59,43 @@ export function DrawResultDetailScreen({ drawNo }: DrawResultDetailScreenProps)
|
||||
});
|
||||
}, [load]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
queueMicrotask(() => {
|
||||
if (!data) {
|
||||
setHighlightSet(null);
|
||||
setMyTotals(null);
|
||||
return;
|
||||
}
|
||||
const token = getPlayerBearerTokenPayload();
|
||||
if (!token) {
|
||||
setHighlightSet(new Set());
|
||||
setMyTotals(null);
|
||||
return;
|
||||
}
|
||||
void (async () => {
|
||||
try {
|
||||
const m = await getTicketDrawMyMatch(data.draw_no);
|
||||
if (cancelled) return;
|
||||
setHighlightSet(new Set(m.hit_numbers_4d.map((n) => norm4d(n))));
|
||||
setMyTotals({
|
||||
win: m.total_win_minor,
|
||||
jackpot: m.total_jackpot_win_minor,
|
||||
hasBets: m.has_bets,
|
||||
});
|
||||
} catch {
|
||||
if (!cancelled) {
|
||||
setHighlightSet(new Set());
|
||||
setMyTotals(null);
|
||||
}
|
||||
}
|
||||
})();
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [data]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Card>
|
||||
@@ -81,8 +129,23 @@ export function DrawResultDetailScreen({ drawNo }: DrawResultDetailScreenProps)
|
||||
);
|
||||
}
|
||||
|
||||
const currency = "NPR";
|
||||
const showMyPayout =
|
||||
myTotals &&
|
||||
myTotals.hasBets &&
|
||||
(myTotals.win > 0 || myTotals.jackpot > 0);
|
||||
const showHitOnly =
|
||||
myTotals?.hasBets &&
|
||||
highlightSet &&
|
||||
highlightSet.size > 0 &&
|
||||
myTotals &&
|
||||
myTotals.win === 0 &&
|
||||
myTotals.jackpot === 0;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<JackpotResultsStrip currencyCode={currency} />
|
||||
|
||||
<Card>
|
||||
<CardHeader className="space-y-3 pb-2">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
@@ -120,11 +183,45 @@ export function DrawResultDetailScreen({ drawNo }: DrawResultDetailScreenProps)
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-2">
|
||||
<TwentyThreeResultsGrid numbers={data.results} />
|
||||
<p className="mt-4 text-xs text-muted-foreground">
|
||||
中奖号码高亮、「查看我的中奖情况」跳转注单并按该期筛选:见实施计划 docs/06 §11.7、§14.3「承接阶段
|
||||
3」(界面 §4.6)。
|
||||
</p>
|
||||
<TwentyThreeResultsGrid
|
||||
numbers={data.results}
|
||||
highlighted4d={highlightSet ?? undefined}
|
||||
/>
|
||||
|
||||
{showMyPayout && myTotals ? (
|
||||
<div className="mt-4 rounded-md border border-emerald-500/25 bg-emerald-500/5 px-3 py-2 text-sm">
|
||||
<p className="font-medium text-emerald-900 dark:text-emerald-100">本期我的派彩</p>
|
||||
<p className="mt-1 font-mono text-xs tabular-nums text-muted-foreground">
|
||||
常规:{formatMinorAsCurrency(myTotals.win, currency)}
|
||||
{myTotals.jackpot > 0 ? (
|
||||
<>
|
||||
{" "}
|
||||
· Jackpot:{formatMinorAsCurrency(myTotals.jackpot, currency)}
|
||||
</>
|
||||
) : null}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
{showHitOnly ? (
|
||||
<p className="mt-3 text-xs text-amber-900/90 dark:text-amber-100/90">
|
||||
您的注单已命中本期开奖号码中的格子;派彩完成后将显示金额汇总。
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="mt-4 flex flex-col gap-3">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
如果您中奖,与注单匹配的号码将以金色高亮显示(需登录)。
|
||||
</p>
|
||||
<Link
|
||||
href={`/orders?draw_no=${encodeURIComponent(data.draw_no)}`}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "default", size: "sm" }),
|
||||
"w-full sm:w-auto sm:self-start",
|
||||
)}
|
||||
>
|
||||
查看我的中奖情况
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user