refactor: 重构整体页面布局与样式,统一UI设计风格
- 重构PlayerAppShell,移除冗余头部导航与国际化依赖,统一页面背景与内边距 - 新增通用页面容器组件PlayerPanel,统一页面头部布局与样式 - 重构底部导航栏,调整图标、文案与样式,新增激活状态指示器 - 重构所有页面组件:大厅页、注单页、结果页、开奖面板等,统一使用新的UI组件与设计风格 - 优化状态标签、卡片、按钮等组件的视觉样式,统一配色与圆角规范 - 移除冗余依赖与注释代码,整理代码结构
This commit is contained in:
@@ -32,7 +32,7 @@ export function StatusDot({
|
||||
ring?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<span className="inline-flex shrink-0 items-center gap-1.5 rounded-full border border-[#e5edf8] bg-[#f8fbff] px-2 py-1 text-xs font-bold text-slate-600">
|
||||
<span
|
||||
className={cn(
|
||||
"inline-block size-2 shrink-0 rounded-full",
|
||||
@@ -41,7 +41,7 @@ export function StatusDot({
|
||||
)}
|
||||
aria-hidden
|
||||
/>
|
||||
<span className="text-foreground">{label}</span>
|
||||
<span>{label}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,23 +5,15 @@ import { useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { getTicketItems } from "@/api/ticket-items";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { PlayerPanel } from "@/components/layout/player-panel";
|
||||
import { StatusDot, ticketStatusDisplay } from "@/features/orders/ticket-item-status";
|
||||
import { formatLotteryInstant } from "@/lib/player-datetime";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import { formatLotteryInstant } from "@/lib/player-datetime";
|
||||
import { playLabelZh } from "@/lib/play-labels";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { TicketItemListRow } from "@/types/api/ticket-items";
|
||||
|
||||
/** 界面文档 §4.7 我的注单 */
|
||||
export function TicketOrdersListScreen() {
|
||||
const searchParams = useSearchParams();
|
||||
const drawNoFilter = useMemo(
|
||||
@@ -75,122 +67,132 @@ export function TicketOrdersListScreen() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base">我的注单</CardTitle>
|
||||
<CardDescription>
|
||||
<PlayerPanel title="My Bets" subtitle="Recent ticket records" eyebrow="N lotto">
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-xl border border-[#e6edf8] bg-[#f8fbff] p-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-bold text-[#32518d]">
|
||||
{drawNoFilter ? "Filtered Issue" : "Total Records"}
|
||||
</p>
|
||||
<p className="mt-1 truncate font-mono text-lg font-black text-[#0b3f96]">
|
||||
{drawNoFilter || total}
|
||||
</p>
|
||||
</div>
|
||||
{drawNoFilter ? (
|
||||
<>
|
||||
当前筛选期号{" "}
|
||||
<span className="font-mono text-foreground">{drawNoFilter}</span>
|
||||
{" · "}
|
||||
<Link href="/orders" className="text-primary underline-offset-4 hover:underline">
|
||||
清除筛选
|
||||
</Link>
|
||||
</>
|
||||
<Link
|
||||
href="/orders"
|
||||
className="shrink-0 rounded-full border border-[#dce7f7] bg-white px-3 py-1.5 text-xs font-bold text-[#0b56b7]"
|
||||
>
|
||||
Clear
|
||||
</Link>
|
||||
) : (
|
||||
"最近下注记录"
|
||||
<Link
|
||||
href="/hall"
|
||||
className="shrink-0 rounded-full bg-[#e5002c] px-3 py-1.5 text-xs font-bold text-white"
|
||||
>
|
||||
Bet Now
|
||||
</Link>
|
||||
)}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
{loading ? (
|
||||
<div className="space-y-3">
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Skeleton key={i} className="h-24 w-full rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
) : error ? (
|
||||
<Card className="border-destructive/40">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">注单</CardTitle>
|
||||
<CardDescription>{error}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button type="button" size="sm" onClick={() => void fetchPage(1, false)}>
|
||||
重试
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : items.length === 0 ? (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">还没有下注记录</CardTitle>
|
||||
<CardDescription>去下注大厅试试手气吧</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Link href="/hall" className={cn(buttonVariants({ size: "sm" }))}>
|
||||
去下注
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-xs text-muted-foreground">共 {total} 条</p>
|
||||
<div className="flex flex-col gap-3">
|
||||
{items.map((row) => {
|
||||
const cur = row.currency_code ?? "NPR";
|
||||
const st = ticketStatusDisplay(
|
||||
row.status,
|
||||
row.win_amount,
|
||||
row.jackpot_win_amount,
|
||||
);
|
||||
const totalWin = row.win_amount + row.jackpot_win_amount;
|
||||
return (
|
||||
<Link key={row.ticket_no} href={`/orders/${encodeURIComponent(row.ticket_no)}`}>
|
||||
<Card className="transition-colors hover:border-primary/30">
|
||||
<CardHeader className="space-y-1 pb-2">
|
||||
<div className="flex flex-wrap items-start justify-between gap-2">
|
||||
<span className="font-mono text-sm font-semibold text-foreground">
|
||||
{row.draw_no ?? "—"}
|
||||
</span>
|
||||
<StatusDot label={st.label} dotClass={st.dotClass} ring={st.ring} />
|
||||
</div>
|
||||
<CardDescription className="font-mono text-xs leading-relaxed">
|
||||
号码 {row.original_number ?? row.play_code} · 玩法 {playLabelZh(row.play_code)}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-1 pt-0 text-xs">
|
||||
<p className="text-muted-foreground">
|
||||
金额 {formatMinorAsCurrency(row.total_bet_amount, cur)} · 实扣{" "}
|
||||
{formatMinorAsCurrency(row.actual_deduct_amount, cur)}
|
||||
</p>
|
||||
{totalWin > 0 && row.status === "settled_win" ? (
|
||||
<p className="font-medium text-emerald-700 dark:text-emerald-400">
|
||||
中奖 {formatMinorAsCurrency(totalWin, cur)}
|
||||
{row.jackpot_win_amount > 0 ? (
|
||||
<span className="text-muted-foreground">
|
||||
{" "}
|
||||
(含 Jackpot {formatMinorAsCurrency(row.jackpot_win_amount, cur)})
|
||||
</span>
|
||||
) : null}
|
||||
</p>
|
||||
) : null}
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{formatLotteryInstant(row.placed_at ?? null)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{page < lastPage ? (
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="space-y-3">
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Skeleton key={i} className="h-28 w-full rounded-xl" />
|
||||
))}
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="rounded-xl border border-red-200 bg-red-50 px-4 py-4 text-sm text-red-700">
|
||||
<p>{error}</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
disabled={loadingMore}
|
||||
onClick={() => loadMore()}
|
||||
className="mt-3 bg-[#e5002c] text-white hover:bg-[#d10028]"
|
||||
onClick={() => void fetchPage(1, false)}
|
||||
>
|
||||
{loadingMore ? "加载中…" : "加载更多"}
|
||||
Retry
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : items.length === 0 ? (
|
||||
<div className="rounded-xl border border-dashed border-[#dce7f7] bg-[#f8fbff] px-4 py-10 text-center">
|
||||
<p className="text-sm font-bold text-slate-700">No bet records yet.</p>
|
||||
<Link
|
||||
href="/hall"
|
||||
className="mt-4 inline-flex h-9 items-center rounded-lg bg-[#e5002c] px-4 text-sm font-bold text-white"
|
||||
>
|
||||
Submit Bet
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="space-y-3">
|
||||
{items.map((row) => {
|
||||
const cur = row.currency_code ?? "NPR";
|
||||
const st = ticketStatusDisplay(
|
||||
row.status,
|
||||
row.win_amount,
|
||||
row.jackpot_win_amount,
|
||||
);
|
||||
const totalWin = row.win_amount + row.jackpot_win_amount;
|
||||
return (
|
||||
<Link
|
||||
key={row.ticket_no}
|
||||
href={`/orders/${encodeURIComponent(row.ticket_no)}`}
|
||||
className="block rounded-xl border border-[#e5edf8] bg-white p-3 shadow-[0_8px_24px_rgba(15,23,42,0.05)] transition-colors hover:border-[#b9ccf6]"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<p className="truncate font-mono text-sm font-black text-[#0b3f96]">
|
||||
{row.draw_no ?? "—"}
|
||||
</p>
|
||||
<p className="mt-1 truncate text-xs text-slate-500">
|
||||
{playLabelZh(row.play_code)} · {row.original_number ?? row.play_code}
|
||||
</p>
|
||||
</div>
|
||||
<StatusDot label={st.label} dotClass={st.dotClass} ring={st.ring} />
|
||||
</div>
|
||||
<div className="mt-3 grid grid-cols-2 gap-2">
|
||||
<div className="rounded-lg bg-[#f8fbff] px-3 py-2">
|
||||
<p className="text-[10px] font-bold uppercase text-[#7890b8]">Stake</p>
|
||||
<p className="mt-1 text-sm font-black text-slate-900">
|
||||
{formatMinorAsCurrency(row.total_bet_amount, cur)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-[#f8fbff] px-3 py-2">
|
||||
<p className="text-[10px] font-bold uppercase text-[#7890b8]">Deduction</p>
|
||||
<p className="mt-1 text-sm font-black text-[#0b3f96]">
|
||||
{formatMinorAsCurrency(row.actual_deduct_amount, cur)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{totalWin > 0 && row.status === "settled_win" ? (
|
||||
<p className="mt-2 text-xs font-bold text-emerald-600">
|
||||
Win {formatMinorAsCurrency(totalWin, cur)}
|
||||
</p>
|
||||
) : null}
|
||||
<p className="mt-2 text-[11px] text-slate-500">
|
||||
{formatLotteryInstant(row.placed_at ?? null)}
|
||||
</p>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{page < lastPage ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
className="h-10 w-full rounded-lg bg-[#07459f] text-white hover:bg-[#063b88]"
|
||||
disabled={loadingMore}
|
||||
onClick={() => loadMore()}
|
||||
>
|
||||
{loadingMore ? "Loading..." : "Load More"}
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</PlayerPanel>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user