feat(player): PC 端列表/布局适配与分页
玩家端桌面:全宽居中 Logo、圆角侧栏与主内容面板;开奖结果/注单改 shadcn Table+分页(移动仍无限滚动),注单组默认折叠;信用/钱包筛选与分页;详情页与奖号网格密度优化。
This commit is contained in:
@@ -3,11 +3,13 @@
|
||||
import { useMemo, type Ref } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { PlayerListPagination } from "@/components/layout/player-list-pagination";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { formatLotteryInstantInTimeZone, formatPlayerInstant } from "@/lib/player-datetime";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import type { WalletLogItem, WalletLogsData } from "@/types/api/wallet-logs";
|
||||
import { getWalletLogsLastPage } from "@/types/api/wallet-logs";
|
||||
|
||||
/** 与 §4.9 筛选一致;接口 `type` 查询参数 */
|
||||
export const WALLET_FLOW_FILTERS: { value: string; labelKey: string }[] = [
|
||||
@@ -101,6 +103,8 @@ type WalletLogsBlockProps = {
|
||||
hasMore?: boolean;
|
||||
onLoadMore?: () => void;
|
||||
loadMoreRef?: Ref<HTMLDivElement>;
|
||||
/** PC:翻页(替换无限滚动) */
|
||||
onPageChange?: (page: number) => void;
|
||||
filter: string;
|
||||
onFilterChange: (value: string) => void;
|
||||
currency: string;
|
||||
@@ -119,6 +123,7 @@ export function WalletLogsBlock({
|
||||
hasMore = false,
|
||||
onLoadMore,
|
||||
loadMoreRef,
|
||||
onPageChange,
|
||||
filter,
|
||||
onFilterChange,
|
||||
currency,
|
||||
@@ -142,29 +147,32 @@ export function WalletLogsBlock({
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="space-y-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<section className="space-y-3 lg:space-y-4">
|
||||
<div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between lg:gap-4">
|
||||
<div>
|
||||
<h2 className="text-sm font-black text-[#0b3f96]">{resolvedTitle}</h2>
|
||||
<h2 className="text-sm font-black text-[#0b3f96] lg:text-base">{resolvedTitle}</h2>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{filters.map((f) => (
|
||||
<Button
|
||||
key={f.value || "all"}
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={filter === f.value ? "default" : "outline"}
|
||||
className={
|
||||
filter === f.value
|
||||
? "h-8 rounded-full bg-[#07459f] px-3 text-xs font-bold text-white hover:bg-[#063b88]"
|
||||
: "h-8 rounded-full border-[#dce7f7] bg-white px-3 text-xs font-bold text-[#32518d] hover:bg-[#f8fbff]"
|
||||
}
|
||||
disabled={logsLoading && filter === f.value}
|
||||
onClick={() => onFilterChange(f.value)}
|
||||
>
|
||||
{f.label}
|
||||
</Button>
|
||||
))}
|
||||
{/* 移动端:小胶囊;PC:大厅 4D/3D 同款分段控件 */}
|
||||
<div className="flex flex-wrap gap-1.5 lg:inline-flex lg:max-w-full lg:flex-nowrap lg:items-center lg:gap-1 lg:overflow-x-auto lg:rounded-lg lg:bg-[#f3f6fb] lg:p-1">
|
||||
{filters.map((f) => {
|
||||
const active = filter === f.value;
|
||||
return (
|
||||
<button
|
||||
key={f.value || "all"}
|
||||
type="button"
|
||||
disabled={logsLoading && active}
|
||||
onClick={() => onFilterChange(f.value)}
|
||||
aria-pressed={active}
|
||||
className={
|
||||
active
|
||||
? "inline-flex h-8 items-center justify-center rounded-full bg-[#07459f] px-3 text-xs font-bold text-white transition-colors hover:bg-[#063b88] disabled:opacity-60 lg:h-auto lg:min-w-[4.5rem] lg:rounded-lg lg:bg-[#2d63e2] lg:px-3.5 lg:py-2 lg:text-sm lg:shadow-[0_4px_12px_rgba(45,99,226,0.28)] lg:hover:bg-[#2556c7]"
|
||||
: "inline-flex h-8 items-center justify-center rounded-full border border-[#dce7f7] bg-white px-3 text-xs font-bold text-[#32518d] transition-colors hover:bg-[#f8fbff] lg:h-auto lg:min-w-[4.5rem] lg:rounded-lg lg:border-0 lg:bg-transparent lg:px-3.5 lg:py-2 lg:text-sm lg:text-[#5b7fbf] lg:hover:bg-[#f3f7ff] lg:hover:text-[#2d63e2]"
|
||||
}
|
||||
>
|
||||
{f.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -174,30 +182,68 @@ export function WalletLogsBlock({
|
||||
|
||||
{logs ? (
|
||||
<>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{/* PC 分页底栏已含总数,避免重复 */}
|
||||
<p className="text-xs text-muted-foreground lg:hidden">
|
||||
{t("wallet.totalRecords", { total: logs.total })}
|
||||
</p>
|
||||
<ul className={logsLoading ? "space-y-2 opacity-60" : "space-y-2"}>
|
||||
<div className={logsLoading ? "opacity-60" : undefined}>
|
||||
{logs.items.length === 0 ? (
|
||||
<li className="rounded-lg border border-dashed py-8 text-center text-sm text-muted-foreground">
|
||||
<div className="rounded-lg border border-dashed py-8 text-center text-sm text-muted-foreground lg:py-12">
|
||||
{t(creditMode ? "wallet.emptyCreditLogs" : "wallet.emptyLogs", {
|
||||
defaultValue: creditMode ? "暂无信用流水" : "暂无流水",
|
||||
})}
|
||||
</li>
|
||||
</div>
|
||||
) : (
|
||||
logs.items.map((row) => (
|
||||
<LogRow
|
||||
key={row.log_id}
|
||||
item={row}
|
||||
currency={currency}
|
||||
creditMode={creditMode}
|
||||
settlementTimeZone={settlementTimeZone}
|
||||
/>
|
||||
))
|
||||
<>
|
||||
<div className="lg:overflow-hidden lg:rounded-xl lg:border lg:border-[#e5edf8] lg:bg-white lg:shadow-[0_8px_24px_rgba(15,23,42,0.05)]">
|
||||
<div className="hidden lg:grid lg:grid-cols-[minmax(10rem,1.2fr)_minmax(9rem,1fr)_minmax(8rem,0.9fr)_minmax(7rem,0.8fr)_minmax(7rem,0.8fr)_minmax(6rem,0.7fr)] lg:items-center lg:gap-3 lg:border-b lg:border-[#e9eff8] lg:bg-[#f8fbff] lg:px-5 lg:py-3">
|
||||
<p className="text-xs font-bold text-[#59739f]">
|
||||
{t("wallet.logType", { defaultValue: "类型" })}
|
||||
</p>
|
||||
<p className="text-xs font-bold text-[#59739f]">
|
||||
{t("wallet.logTime", { defaultValue: "时间" })}
|
||||
</p>
|
||||
<p className="text-xs font-bold text-[#59739f]">
|
||||
{t("wallet.logRef", { defaultValue: "关联单号" })}
|
||||
</p>
|
||||
<p className="text-right text-xs font-bold text-[#59739f]">
|
||||
{t("wallet.logAmount", { defaultValue: "金额" })}
|
||||
</p>
|
||||
<p className="text-right text-xs font-bold text-[#59739f]">
|
||||
{creditMode
|
||||
? t("wallet.creditAvailableAfter")
|
||||
: t("wallet.balanceAfter")}
|
||||
</p>
|
||||
<p className="text-right text-xs font-bold text-[#59739f]">
|
||||
{t("orders.status")}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="space-y-2 lg:space-y-0">
|
||||
{logs.items.map((row) => (
|
||||
<LogRow
|
||||
key={row.log_id}
|
||||
item={row}
|
||||
currency={currency}
|
||||
creditMode={creditMode}
|
||||
settlementTimeZone={settlementTimeZone}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
{onPageChange ? (
|
||||
<PlayerListPagination
|
||||
page={logs.page}
|
||||
lastPage={getWalletLogsLastPage(logs)}
|
||||
total={logs.total}
|
||||
loading={logsLoading}
|
||||
onPageChange={onPageChange}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
{logs.items.length > 0 ? (
|
||||
<>
|
||||
<div className="lg:hidden">
|
||||
<div ref={loadMoreRef} className="min-h-1" />
|
||||
{hasMore ? (
|
||||
<Button
|
||||
@@ -216,7 +262,7 @@ export function WalletLogsBlock({
|
||||
{t("wallet.noMoreLogs")}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
@@ -258,36 +304,52 @@ export function LogRow({
|
||||
? "border-slate-200 bg-slate-50 text-slate-600"
|
||||
: "border-blue-200 bg-blue-50 text-blue-700";
|
||||
|
||||
const timeLabel = creditMode && settlementTimeZone
|
||||
? formatLotteryInstantInTimeZone(item.created_at, settlementTimeZone)
|
||||
: formatPlayerInstant(item.created_at);
|
||||
|
||||
const amountLabel = isCreditSettlementConfirm
|
||||
? t("wallet.creditReleasedAmount", {
|
||||
defaultValue: "释额 {{amount}}",
|
||||
amount: formatMinorAsCurrency(item.amount_abs, ccy),
|
||||
})
|
||||
: `${isIn ? "+" : "−"}${formatMinorAsCurrency(item.amount_abs, ccy)}`;
|
||||
|
||||
const balanceLabel = item.balance_after != null
|
||||
? formatMinorAsCurrency(item.balance_after, ccy)
|
||||
: creditMode && item.affects_available_credit === false
|
||||
? t("wallet.noCreditChange", { defaultValue: "不改变可用信用" })
|
||||
: "—";
|
||||
|
||||
const balanceCaption = creditMode
|
||||
? item.affects_available_credit === false
|
||||
? t("wallet.creditBillOnly", { defaultValue: "账期记录" })
|
||||
: t("wallet.creditAvailableAfter")
|
||||
: t("wallet.balanceAfter");
|
||||
|
||||
return (
|
||||
<li className="rounded-2xl border border-[#e1eaf6] bg-white px-3 py-3 text-sm shadow-[0_10px_28px_rgba(15,23,42,0.06)]">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<li className="rounded-2xl border border-[#e1eaf6] bg-white px-3 py-3 text-sm shadow-[0_10px_28px_rgba(15,23,42,0.06)] lg:grid lg:grid-cols-[minmax(10rem,1.2fr)_minmax(9rem,1fr)_minmax(8rem,0.9fr)_minmax(7rem,0.8fr)_minmax(7rem,0.8fr)_minmax(6rem,0.7fr)] lg:items-center lg:gap-3 lg:rounded-none lg:border-x-0 lg:border-t-0 lg:border-b-[#edf2f8] lg:px-5 lg:py-4 lg:shadow-none lg:last:border-b-0 lg:hover:bg-[#fbfdff]">
|
||||
<div className="flex items-start justify-between gap-3 lg:contents">
|
||||
<div className="min-w-0 flex-1 lg:min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`size-2.5 rounded-full ${dotTone}`} aria-hidden />
|
||||
<p className="truncate text-base font-black leading-tight text-[#101a33]">
|
||||
<span className={`size-2.5 shrink-0 rounded-full ${dotTone}`} aria-hidden />
|
||||
<p className="truncate text-base font-black leading-tight text-[#101a33] lg:text-sm">
|
||||
{logTypeLabel(item.type, t, creditMode, item.biz_type)}
|
||||
</p>
|
||||
</div>
|
||||
<p className="mt-1.5 text-xs font-medium text-slate-500">
|
||||
{creditMode && settlementTimeZone
|
||||
? formatLotteryInstantInTimeZone(item.created_at, settlementTimeZone)
|
||||
: formatPlayerInstant(item.created_at)}
|
||||
<p className="mt-1.5 text-xs font-medium text-slate-500 lg:hidden">
|
||||
{timeLabel}
|
||||
</p>
|
||||
{item.ref_id ? (
|
||||
<p className="mt-1 truncate font-mono text-[11px] text-slate-400">
|
||||
<p className="mt-1 truncate font-mono text-[11px] text-slate-400 lg:hidden">
|
||||
{item.ref_id}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 flex-col items-end gap-2 text-right">
|
||||
<div className="flex shrink-0 flex-col items-end gap-2 text-right lg:hidden">
|
||||
<p className={`text-lg font-black tabular-nums ${amountTone}`}>
|
||||
{isCreditSettlementConfirm
|
||||
? t("wallet.creditReleasedAmount", {
|
||||
defaultValue: "释额 {{amount}}",
|
||||
amount: formatMinorAsCurrency(item.amount_abs, ccy),
|
||||
})
|
||||
: `${isIn ? "+" : "−"}${formatMinorAsCurrency(item.amount_abs, ccy)}`}
|
||||
{amountLabel}
|
||||
</p>
|
||||
<span className={`inline-flex items-center rounded-full border px-2.5 py-1 text-[11px] font-black ${statusTone}`}>
|
||||
{txnStatusLabel(item.status, t)}
|
||||
@@ -295,20 +357,28 @@ export function LogRow({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex items-center justify-between rounded-xl bg-[#f8fbff] px-3 py-2 text-xs">
|
||||
<p className="hidden text-sm text-slate-600 lg:block">{timeLabel}</p>
|
||||
<p className="hidden truncate font-mono text-xs text-slate-400 lg:block">
|
||||
{item.ref_id || "—"}
|
||||
</p>
|
||||
<p className={`hidden text-right text-sm font-black tabular-nums lg:block ${amountTone}`}>
|
||||
{amountLabel}
|
||||
</p>
|
||||
<p className="hidden text-right font-mono text-sm font-black tabular-nums text-[#32518d] lg:block">
|
||||
{balanceLabel}
|
||||
</p>
|
||||
<div className="hidden justify-end lg:flex">
|
||||
<span className={`inline-flex items-center rounded-full border px-2.5 py-1 text-[11px] font-black ${statusTone}`}>
|
||||
{txnStatusLabel(item.status, t)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex items-center justify-between rounded-xl bg-[#f8fbff] px-3 py-2 text-xs lg:hidden">
|
||||
<span className="font-semibold text-slate-500">
|
||||
{creditMode
|
||||
? item.affects_available_credit === false
|
||||
? t("wallet.creditBillOnly", { defaultValue: "账期记录" })
|
||||
: t("wallet.creditAvailableAfter")
|
||||
: t("wallet.balanceAfter")}
|
||||
{balanceCaption}
|
||||
</span>
|
||||
<span className="font-mono font-black tabular-nums text-[#32518d]">
|
||||
{item.balance_after != null
|
||||
? formatMinorAsCurrency(item.balance_after, ccy)
|
||||
: creditMode && item.affects_available_credit === false
|
||||
? t("wallet.noCreditChange", { defaultValue: "不改变可用信用" })
|
||||
: "—"}
|
||||
{balanceLabel}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { PlayerMoneyDisplay } from "@/components/player-money-display";
|
||||
import { WalletLogsBlock } from "@/features/wallet/wallet-logs-block";
|
||||
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { isCreditFundingPlayer } from "@/lib/player-funding-mode";
|
||||
import { formatMinorAsCurrency } from "@/lib/money";
|
||||
import { formatLotteryInstantInTimeZone, formatPlayerInstant } from "@/lib/player-datetime";
|
||||
@@ -102,22 +103,22 @@ function CreditSettlementBillsBlock({
|
||||
const netDirection = netPending >= 0 ? "receivable" : "payable";
|
||||
|
||||
return (
|
||||
<section className="rounded-2xl border border-[#dce7f7] bg-white px-3 py-3 shadow-[0_10px_28px_rgba(15,23,42,0.06)]">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h2 className="text-sm font-black text-[#0b3f96]">
|
||||
<section className="flex h-full flex-col rounded-2xl border border-[#dce7f7] bg-white px-3 py-3 shadow-[0_10px_28px_rgba(15,23,42,0.06)] lg:px-4 lg:py-4">
|
||||
<div className="flex shrink-0 items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-sm font-black text-[#0b3f96] lg:text-base">
|
||||
{t("wallet.settlementTitle", { defaultValue: "我的账期账单" })}
|
||||
</h2>
|
||||
<p className="mt-1 text-xs text-slate-500">
|
||||
<p className="mt-1 text-xs text-slate-500 lg:line-clamp-2">
|
||||
{t("wallet.settlementHint", { defaultValue: "只显示未结清账单,中奖超出授信的部分在这里结算。" })}
|
||||
</p>
|
||||
</div>
|
||||
<span className="rounded-full bg-[#f1f6ff] px-2.5 py-1 text-xs font-black text-[#32518d]">
|
||||
<span className="shrink-0 rounded-full bg-[#f1f6ff] px-2.5 py-1 text-xs font-black text-[#32518d]">
|
||||
{t("wallet.settlementPendingCount", { defaultValue: "{{count}}笔", count: pendingCount })}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 grid grid-cols-2 gap-2">
|
||||
<div className="mt-3 grid shrink-0 grid-cols-2 gap-2">
|
||||
<div className="rounded-xl bg-emerald-50 px-3 py-2">
|
||||
<p className="text-xs font-bold text-emerald-700">
|
||||
{t("wallet.pendingReceivable", { defaultValue: "待收" })}
|
||||
@@ -134,27 +135,26 @@ function CreditSettlementBillsBlock({
|
||||
{formatMinorAsCurrency(summary?.pending_payable ?? 0, currency)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 rounded-xl bg-[#f8fbff] px-3 py-2 text-xs">
|
||||
<span className="font-semibold text-slate-500">
|
||||
{netDirection === "receivable"
|
||||
? t("wallet.netReceivable", { defaultValue: "净待收" })
|
||||
: t("wallet.netPayable", { defaultValue: "净待付" })}
|
||||
</span>
|
||||
<span className={netDirection === "receivable" ? "ml-2 font-mono font-black text-emerald-700" : "ml-2 font-mono font-black text-red-700"}>
|
||||
{formatMinorAsCurrency(Math.abs(netPending), currency)}
|
||||
</span>
|
||||
<div className="col-span-2 rounded-xl bg-[#f8fbff] px-3 py-2 text-xs">
|
||||
<span className="font-semibold text-slate-500">
|
||||
{netDirection === "receivable"
|
||||
? t("wallet.netReceivable", { defaultValue: "净待收" })
|
||||
: t("wallet.netPayable", { defaultValue: "净待付" })}
|
||||
</span>
|
||||
<span className={netDirection === "receivable" ? "ml-2 font-mono font-black text-emerald-700" : "ml-2 font-mono font-black text-red-700"}>
|
||||
{formatMinorAsCurrency(Math.abs(netPending), currency)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data === null ? (
|
||||
<Skeleton className="mt-3 h-20 w-full rounded-xl" />
|
||||
<Skeleton className="mt-3 min-h-20 w-full flex-1 rounded-xl" />
|
||||
) : data.items.length === 0 ? (
|
||||
<p className="mt-3 rounded-xl border border-dashed border-[#dce7f7] py-5 text-center text-sm text-slate-500">
|
||||
<p className="mt-3 flex min-h-20 flex-1 items-center justify-center rounded-xl border border-dashed border-[#dce7f7] px-3 text-center text-sm text-slate-500">
|
||||
{t("wallet.noSettlementBills", { defaultValue: "暂无待结算账单" })}
|
||||
</p>
|
||||
) : (
|
||||
<ul className="mt-3 space-y-2">
|
||||
<ul className="mt-3 min-h-0 max-h-[16rem] flex-1 space-y-2 overflow-y-auto lg:max-h-none">
|
||||
{data.items.map((item) => {
|
||||
const receivable = item.direction === "receivable";
|
||||
return (
|
||||
@@ -218,6 +218,7 @@ export function WalletScreen() {
|
||||
const fundingModeView = resolveFundingModeView(balance, profile);
|
||||
const fundingModeKnown = fundingModeView !== "unknown";
|
||||
const isCreditPlayer = fundingModeView === "credit";
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
useEffect(() => {
|
||||
if (actionDeepLinkHandledRef.current || loading) return;
|
||||
@@ -414,7 +415,22 @@ export function WalletScreen() {
|
||||
});
|
||||
}, [hasMore, loadLogs, loadingMore, logs, t]);
|
||||
|
||||
const handleLogsPageChange = useCallback((nextPage: number) => {
|
||||
if (!logs || nextPage === logs.page || logsLoading) return;
|
||||
setError(null);
|
||||
setLogsLoading(true);
|
||||
void loadLogs(nextPage, false)
|
||||
.catch((e) => {
|
||||
setError(formatWalletClientError(e, t));
|
||||
})
|
||||
.finally(() => {
|
||||
setLogsLoading(false);
|
||||
});
|
||||
}, [loadLogs, logs, logsLoading, t]);
|
||||
|
||||
useEffect(() => {
|
||||
// 仅移动端无限滚动;PC 用分页
|
||||
if (!isMobile) return;
|
||||
const target = loadMoreRef.current;
|
||||
if (!target || loading || logsLoading || loadingMore || !hasMore) return;
|
||||
|
||||
@@ -429,13 +445,13 @@ export function WalletScreen() {
|
||||
|
||||
observer.observe(target);
|
||||
return () => observer.disconnect();
|
||||
}, [hasMore, loadMore, loading, loadingMore, logsLoading]);
|
||||
}, [hasMore, isMobile, loadMore, loading, loadingMore, logsLoading]);
|
||||
|
||||
return (
|
||||
<PlayerPanel title={panelTitle}>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-3 lg:space-y-4">
|
||||
{error ? (
|
||||
<div className="rounded-xl border border-red-200 bg-red-50 px-3 py-3 text-sm text-red-700">
|
||||
<div className="rounded-xl border border-red-200 bg-red-50 px-3 py-3 text-sm text-red-700 lg:px-5 lg:py-4">
|
||||
<p>{error}</p>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -447,98 +463,152 @@ export function WalletScreen() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<section className="relative overflow-hidden rounded-xl bg-[#e5002c] px-3 py-4 text-white shadow-[0_10px_28px_rgba(229,0,44,0.25)]">
|
||||
<Image
|
||||
src="/entry/image5.png"
|
||||
alt=""
|
||||
fill
|
||||
className="pointer-events-none object-cover object-center"
|
||||
aria-hidden
|
||||
/>
|
||||
<div className="relative flex items-center gap-3">
|
||||
<div className="flex size-14 shrink-0 items-center justify-center rounded-full bg-white text-[#d81435] shadow-sm">
|
||||
<Wallet className="size-7" aria-hidden />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-semibold text-white/90">
|
||||
{fundingModeKnown && isCreditPlayer
|
||||
? t("wallet.creditAvailable", { defaultValue: "可用信用" })
|
||||
: fundingModeKnown
|
||||
? t("wallet.balance")
|
||||
: t("wallet.loadingBalance", { defaultValue: "正在加载" })}
|
||||
</p>
|
||||
{loading || !fundingModeKnown ? (
|
||||
<Skeleton className="mt-2 h-8 w-44 rounded-md bg-white/25" />
|
||||
) : (
|
||||
<PlayerMoneyDisplay
|
||||
amountMinor={displayMinor}
|
||||
currency={currency}
|
||||
className="mt-1 text-white"
|
||||
/>
|
||||
)}
|
||||
<p className="mt-2 text-xs text-white/75">
|
||||
{!fundingModeKnown
|
||||
? t("wallet.loadingAccountMode", { defaultValue: "正在确认资金模式…" })
|
||||
: isCreditPlayer
|
||||
? t("wallet.creditSummary", {
|
||||
defaultValue: "授信 {{limit}} · 已用 {{used}}",
|
||||
limit: formatMinorAsCurrency(balance?.credit_limit ?? 0, currency),
|
||||
used: formatMinorAsCurrency(balance?.used_credit ?? 0, currency),
|
||||
})
|
||||
: t("wallet.available", {
|
||||
amount: formatMinorAsCurrency(balance?.available_balance ?? 0, currency),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{fundingModeKnown && isCreditPlayer ? (
|
||||
<CreditSettlementBillsBlock
|
||||
data={settlementBills}
|
||||
currency={currency}
|
||||
settlementTimeZone={settlementTimeZone}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{fundingModeKnown && !isCreditPlayer ? (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<TransferInDialog
|
||||
idPrefix="wallet-"
|
||||
currency={currency}
|
||||
lotteryMinor={Number(balance?.balance ?? 0)}
|
||||
mainMinor={
|
||||
balance?.main_balance === null || balance?.main_balance === undefined
|
||||
? null
|
||||
: Number(balance.main_balance)
|
||||
}
|
||||
onSuccess={refreshAll}
|
||||
triggerVariant="hall"
|
||||
triggerLabel={t("wallet.transferIn", { defaultValue: "Transfer In" })}
|
||||
triggerClassName="h-14 rounded-2xl text-base font-black"
|
||||
open={transferInOpen}
|
||||
onOpenChange={setTransferInOpen}
|
||||
/>
|
||||
<TransferOutDialog
|
||||
idPrefix="wallet-"
|
||||
currency={currency}
|
||||
availableMinor={Number(balance?.available_balance ?? 0)}
|
||||
onSuccess={refreshAll}
|
||||
triggerVariant="hall"
|
||||
triggerLabel={t("wallet.transferOut", { defaultValue: "Transfer Out" })}
|
||||
triggerClassName="h-14 rounded-2xl text-base font-black"
|
||||
open={transferOutOpen}
|
||||
onOpenChange={setTransferOutOpen}
|
||||
/>
|
||||
<div className="lg:grid lg:grid-cols-[minmax(0,1.2fr)_minmax(16rem,0.8fr)] lg:items-stretch lg:gap-4">
|
||||
<section className="relative overflow-hidden rounded-xl bg-[#e5002c] px-3 py-4 text-white shadow-[0_10px_28px_rgba(229,0,44,0.25)] lg:px-6 lg:py-6">
|
||||
<Image
|
||||
src="/entry/image5.png"
|
||||
alt=""
|
||||
fill
|
||||
className="pointer-events-none object-cover object-center"
|
||||
aria-hidden
|
||||
/>
|
||||
<div className="relative flex items-center gap-3 lg:gap-5">
|
||||
<div className="flex size-14 shrink-0 items-center justify-center rounded-full bg-white text-[#d81435] shadow-sm lg:size-16">
|
||||
<Wallet className="size-7 lg:size-8" aria-hidden />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-semibold text-white/90 lg:text-base">
|
||||
{t("wallet.balance")}
|
||||
</p>
|
||||
{loading ? (
|
||||
<Skeleton className="mt-2 h-8 w-44 rounded-md bg-white/25 lg:h-10 lg:w-56" />
|
||||
) : (
|
||||
<PlayerMoneyDisplay
|
||||
amountMinor={displayMinor}
|
||||
currency={currency}
|
||||
className="mt-1 text-white lg:text-[1.75rem]"
|
||||
/>
|
||||
)}
|
||||
<p className="mt-2 text-xs text-white/75 lg:text-sm">
|
||||
{t("wallet.available", {
|
||||
amount: formatMinorAsCurrency(balance?.available_balance ?? 0, currency),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div className="mt-3 grid grid-cols-2 gap-3 lg:mt-0 lg:grid-cols-1 lg:gap-3">
|
||||
<TransferInDialog
|
||||
idPrefix="wallet-"
|
||||
currency={currency}
|
||||
lotteryMinor={Number(balance?.balance ?? 0)}
|
||||
mainMinor={
|
||||
balance?.main_balance === null || balance?.main_balance === undefined
|
||||
? null
|
||||
: Number(balance.main_balance)
|
||||
}
|
||||
onSuccess={refreshAll}
|
||||
triggerVariant="hall"
|
||||
triggerLabel={t("wallet.transferIn", { defaultValue: "Transfer In" })}
|
||||
triggerClassName="h-14 rounded-2xl text-base font-black lg:h-full lg:min-h-[4.5rem]"
|
||||
open={transferInOpen}
|
||||
onOpenChange={setTransferInOpen}
|
||||
/>
|
||||
<TransferOutDialog
|
||||
idPrefix="wallet-"
|
||||
currency={currency}
|
||||
availableMinor={Number(balance?.available_balance ?? 0)}
|
||||
onSuccess={refreshAll}
|
||||
triggerVariant="hall"
|
||||
triggerLabel={t("wallet.transferOut", { defaultValue: "Transfer Out" })}
|
||||
triggerClassName="h-14 rounded-2xl text-base font-black lg:h-full lg:min-h-[4.5rem]"
|
||||
open={transferOutOpen}
|
||||
onOpenChange={setTransferOutOpen}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
) : (
|
||||
<div className="space-y-3 lg:grid lg:grid-cols-[minmax(0,1.1fr)_minmax(20rem,0.9fr)] lg:items-stretch lg:gap-4 lg:space-y-0">
|
||||
<section className="relative flex h-full flex-col overflow-hidden rounded-xl bg-[#e5002c] px-3 py-4 text-white shadow-[0_10px_28px_rgba(229,0,44,0.25)] lg:px-6 lg:py-5">
|
||||
<Image
|
||||
src="/entry/image5.png"
|
||||
alt=""
|
||||
fill
|
||||
className="pointer-events-none object-cover object-center"
|
||||
aria-hidden
|
||||
/>
|
||||
<div className="relative flex flex-1 items-center gap-3 lg:gap-5">
|
||||
<div className="flex size-14 shrink-0 items-center justify-center rounded-full bg-white text-[#d81435] shadow-sm lg:size-16">
|
||||
<Wallet className="size-7 lg:size-8" aria-hidden />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-semibold text-white/90 lg:text-base">
|
||||
{fundingModeKnown
|
||||
? t("wallet.creditAvailable", { defaultValue: "可用信用" })
|
||||
: t("wallet.loadingBalance", { defaultValue: "正在加载" })}
|
||||
</p>
|
||||
{loading || !fundingModeKnown ? (
|
||||
<Skeleton className="mt-2 h-8 w-44 rounded-md bg-white/25 lg:h-10 lg:w-56" />
|
||||
) : (
|
||||
<PlayerMoneyDisplay
|
||||
amountMinor={displayMinor}
|
||||
currency={currency}
|
||||
className="mt-1 text-white lg:text-[1.75rem]"
|
||||
/>
|
||||
)}
|
||||
<p className="mt-2 text-xs text-white/75 lg:text-sm">
|
||||
{!fundingModeKnown
|
||||
? t("wallet.loadingAccountMode", { defaultValue: "正在确认资金模式…" })
|
||||
: t("wallet.creditSummary", {
|
||||
defaultValue: "授信 {{limit}} · 已用 {{used}}",
|
||||
limit: formatMinorAsCurrency(balance?.credit_limit ?? 0, currency),
|
||||
used: formatMinorAsCurrency(balance?.used_credit ?? 0, currency),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{fundingModeKnown ? (
|
||||
<div className="relative mt-4 grid shrink-0 grid-cols-2 gap-2 lg:mt-auto lg:gap-3">
|
||||
<div className="rounded-xl bg-white/15 px-3 py-2.5 backdrop-blur-[2px]">
|
||||
<p className="text-[11px] font-bold uppercase tracking-wide text-white/75">
|
||||
{t("wallet.creditLimit", { defaultValue: "授信额度" })}
|
||||
</p>
|
||||
<p className="mt-1 font-mono text-sm font-black tabular-nums text-white">
|
||||
{formatMinorAsCurrency(balance?.credit_limit ?? 0, currency)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-xl bg-white/15 px-3 py-2.5 backdrop-blur-[2px]">
|
||||
<p className="text-[11px] font-bold uppercase tracking-wide text-white/75">
|
||||
{t("wallet.usedCredit", { defaultValue: "已用信用" })}
|
||||
</p>
|
||||
<p className="mt-1 font-mono text-sm font-black tabular-nums text-white">
|
||||
{formatMinorAsCurrency(balance?.used_credit ?? 0, currency)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
{fundingModeKnown ? (
|
||||
<CreditSettlementBillsBlock
|
||||
data={settlementBills}
|
||||
currency={currency}
|
||||
settlementTimeZone={settlementTimeZone}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton className="h-full min-h-40 w-full rounded-2xl" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{fundingModeKnown && !isCreditPlayer && (logs?.pending_reconcile?.length ?? 0) > 0 ? (
|
||||
<section
|
||||
id="wallet-pending"
|
||||
className="scroll-mt-3 rounded-2xl border border-amber-200 bg-amber-50 px-3 py-3 text-sm text-amber-900"
|
||||
className="scroll-mt-3 rounded-2xl border border-amber-200 bg-amber-50 px-3 py-3 text-sm text-amber-900 lg:px-5 lg:py-4"
|
||||
>
|
||||
<h2 className="text-sm font-black text-amber-800">
|
||||
<h2 className="text-sm font-black text-amber-800 lg:text-base">
|
||||
{t("wallet.pendingSectionTitle", { defaultValue: "待对账划转" })}
|
||||
</h2>
|
||||
<p className="mt-1 text-xs text-amber-700">
|
||||
@@ -546,11 +616,11 @@ export function WalletScreen() {
|
||||
defaultValue: "以下划转仍在与主站对账,请稍后刷新查看结果。",
|
||||
})}
|
||||
</p>
|
||||
<ul className="mt-3 space-y-2">
|
||||
<ul className="mt-3 grid gap-2 lg:grid-cols-2 lg:gap-3">
|
||||
{logs?.pending_reconcile.map((item) => (
|
||||
<li
|
||||
key={item.transfer_no}
|
||||
className="rounded-xl border border-amber-200 bg-white px-3 py-2"
|
||||
className="rounded-xl border border-amber-200 bg-white px-3 py-2 lg:px-4 lg:py-3"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="font-semibold">
|
||||
@@ -579,6 +649,7 @@ export function WalletScreen() {
|
||||
hasMore={hasMore}
|
||||
onLoadMore={loadMore}
|
||||
loadMoreRef={loadMoreRef}
|
||||
onPageChange={handleLogsPageChange}
|
||||
filter={filter}
|
||||
onFilterChange={handleFilterChange}
|
||||
currency={currency}
|
||||
|
||||
Reference in New Issue
Block a user