From 646c13969792a48b2b04e73ec8de9d4c1c57f7f6 Mon Sep 17 00:00:00 2001 From: kang Date: Wed, 1 Jul 2026 15:35:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(wallet):=20=E6=96=B0=E5=A2=9E=E4=BF=A1?= =?UTF-8?q?=E7=94=A8=E7=9B=98=E7=8E=A9=E5=AE=B6=E5=BE=85=E7=BB=93=E7=AE=97?= =?UTF-8?q?=E8=B4=A6=E5=8D=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增接口 getWalletSettlementBills 获取信用盘玩家账期账单数据 - 在钱包日志块中添加业务类型标签支持,增强日志类型显示 - WalletScreen 中增加账期账单数据的加载和展示逻辑 - 新增 CreditSettlementBillsBlock 组件用于展示账期账单摘要和详情列表 - 支持账单状态显示和账期区间格式化 - 仅信用盘玩家显示账期账单模块,其他类型玩家隐藏 - 新增 wallet-settlement-bills.ts 类型定义文件,完善账期账单数据结构 --- src/api/wallet.ts | 13 ++ src/features/wallet/wallet-logs-block.tsx | 34 ++++- src/features/wallet/wallet-screen.tsx | 149 +++++++++++++++++++++- src/types/api/wallet-settlement-bills.ts | 28 ++++ 4 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 src/types/api/wallet-settlement-bills.ts diff --git a/src/api/wallet.ts b/src/api/wallet.ts index 10475d8..3dc4af2 100644 --- a/src/api/wallet.ts +++ b/src/api/wallet.ts @@ -5,6 +5,7 @@ import type { WalletTransferResultData, } from "@/types/api/wallet-transfer"; import type { GetWalletLogsParams, WalletLogsData } from "@/types/api/wallet-logs"; +import type { WalletSettlementBillsData } from "@/types/api/wallet-settlement-bills"; export type GetWalletBalanceParams = { /** Query `currency`,不传则用玩家默认币种 */ @@ -31,6 +32,13 @@ export function getWalletLogs( ); } +/** `GET /api/v1/wallet/settlement-bills`(信用盘玩家待收/待付账单) */ +export function getWalletSettlementBills(): Promise { + return lotteryRequest.get( + `/wallet/settlement-bills`, + ); +} + /** `POST /api/v1/wallet/transfer-in`(主站 → 彩票) */ export function postWalletTransferIn( body: WalletTransferBody, @@ -58,3 +66,8 @@ export type { WalletLogsData, WalletPendingTransfer, } from "@/types/api/wallet-logs"; +export type { + WalletSettlementBillItem, + WalletSettlementBillsData, + WalletSettlementBillsSummary, +} from "@/types/api/wallet-settlement-bills"; diff --git a/src/features/wallet/wallet-logs-block.tsx b/src/features/wallet/wallet-logs-block.tsx index 4a8a39e..71df835 100644 --- a/src/features/wallet/wallet-logs-block.tsx +++ b/src/features/wallet/wallet-logs-block.tsx @@ -48,11 +48,33 @@ const FLOW_LABEL_FALLBACKS: Record = { "wallet.creditFlow.reversal": "退本释额", }; +const CREDIT_BIZ_LABEL_FALLBACKS: Record = { + bet_hold: "下注占用", + bet_hold_release: "退本释额", + game_settlement_loss: "亏损入账", + game_settlement_win: "中奖释额", + game_settlement_reversal: "结算冲正", + settlement_confirm: "账期收款", + settlement_payout: "账期付款", + rebate_accrued: "账期回水", + rebate_in_bill: "回水已入账单", + rebate_settled: "回水已结清", +}; + export function logTypeLabel( type: string, t?: (key: string, options?: { defaultValue?: string }) => string, creditMode = false, + bizType?: string, ): string { + if (creditMode && bizType) { + const bizKey = `wallet.creditBiz.${bizType}`; + const fallback = CREDIT_BIZ_LABEL_FALLBACKS[bizType]; + if (fallback) { + return t?.(bizKey, { defaultValue: fallback }) ?? fallback; + } + } + const prefix = creditMode ? "wallet.creditFlow." : "wallet.flow."; const key = `${prefix}${type}`; return t?.(key, { defaultValue: FLOW_LABEL_FALLBACKS[key] ?? type }) ?? type; @@ -227,7 +249,7 @@ export function LogRow({

- {logTypeLabel(item.type, t, creditMode)} + {logTypeLabel(item.type, t, creditMode, item.biz_type)}

@@ -253,12 +275,18 @@ export function LogRow({

- {creditMode ? t("wallet.creditAvailableAfter") : t("wallet.balanceAfter")} + {creditMode + ? item.affects_available_credit === false + ? t("wallet.creditBillOnly", { defaultValue: "账期记录" }) + : t("wallet.creditAvailableAfter") + : t("wallet.balanceAfter")} {item.balance_after != null ? formatMinorAsCurrency(item.balance_after, ccy) - : "—"} + : creditMode && item.affects_available_credit === false + ? t("wallet.noCreditChange", { defaultValue: "不改变可用信用" }) + : "—"}
diff --git a/src/features/wallet/wallet-screen.tsx b/src/features/wallet/wallet-screen.tsx index 39801f6..8b811db 100644 --- a/src/features/wallet/wallet-screen.tsx +++ b/src/features/wallet/wallet-screen.tsx @@ -1,12 +1,12 @@ "use client"; -import { Wallet } from "lucide-react"; +import { ArrowDownLeft, ArrowUpRight, Wallet } from "lucide-react"; import Image from "next/image"; import { useRouter, useSearchParams } from "next/navigation"; import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { getWalletBalance, getWalletLogs } from "@/api/wallet"; +import { getWalletBalance, getWalletLogs, getWalletSettlementBills } from "@/api/wallet"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { PlayerPanel } from "@/components/layout/player-panel"; @@ -19,11 +19,16 @@ import { WalletLogsBlock } from "@/features/wallet/wallet-logs-block"; import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency"; import { isCreditFundingPlayer } from "@/lib/player-funding-mode"; import { formatMinorAsCurrency } from "@/lib/money"; +import { formatPlayerInstant } from "@/lib/player-datetime"; import { PLAYER_CURRENCY_CHANGE_EVENT } from "@/lib/player-currency-preference"; import { formatWalletClientError } from "@/lib/wallet-api-error"; import { usePlayerSessionStore } from "@/stores/player-session-store"; import type { WalletBalanceData } from "@/types/api/wallet-balance"; import { getWalletLogsLastPage, type WalletLogsData } from "@/types/api/wallet-logs"; +import type { + WalletSettlementBillItem, + WalletSettlementBillsData, +} from "@/types/api/wallet-settlement-bills"; import type { PlayerMeData } from "@/types/api/player-me"; const WALLET_LOGS_PAGE_SIZE = 10; @@ -49,6 +54,128 @@ function resolveFundingModeView( return "unknown"; } +function settlementStatusLabel( + status: string, + t: (key: string, options?: { defaultValue?: string }) => string, +): string { + const fallback: Record = { + pending_confirm: "待确认", + confirmed: "待收付", + partial_paid: "部分收付", + overdue: "已逾期", + settled: "已结清", + }; + + return t(`wallet.settlementStatus.${status}`, { defaultValue: fallback[status] ?? status }); +} + +function formatPeriodRange(item: WalletSettlementBillItem): string { + const start = item.period_start ? formatPlayerInstant(item.period_start).slice(0, 10) : "—"; + const end = item.period_end ? formatPlayerInstant(item.period_end).slice(0, 10) : "—"; + return `${start} / ${end}`; +} + +function CreditSettlementBillsBlock({ + data, + currency, +}: { + data: WalletSettlementBillsData | null; + currency: string; +}) { + const { t } = useTranslation("player"); + const summary = data?.summary; + const netPending = Number(summary?.net_pending ?? 0); + const pendingCount = Number(summary?.pending_count ?? 0); + const netDirection = netPending >= 0 ? "receivable" : "payable"; + + return ( +
+
+
+

+ {t("wallet.settlementTitle", { defaultValue: "我的账期账单" })} +

+

+ {t("wallet.settlementHint", { defaultValue: "只显示未结清账单,中奖超出授信的部分在这里结算。" })} +

+
+ + {t("wallet.settlementPendingCount", { defaultValue: "{{count}}笔", count: pendingCount })} + +
+ +
+
+

+ {t("wallet.pendingReceivable", { defaultValue: "待收" })} +

+

+ {formatMinorAsCurrency(summary?.pending_receivable ?? 0, currency)} +

+
+
+

+ {t("wallet.pendingPayable", { defaultValue: "待付" })} +

+

+ {formatMinorAsCurrency(summary?.pending_payable ?? 0, currency)} +

+
+
+ +
+ + {netDirection === "receivable" + ? t("wallet.netReceivable", { defaultValue: "净待收" }) + : t("wallet.netPayable", { defaultValue: "净待付" })} + + + {formatMinorAsCurrency(Math.abs(netPending), currency)} + +
+ + {data === null ? ( + + ) : data.items.length === 0 ? ( +

+ {t("wallet.noSettlementBills", { defaultValue: "暂无待结算账单" })} +

+ ) : ( +
    + {data.items.map((item) => { + const receivable = item.direction === "receivable"; + return ( +
  • +
    +
    + + {receivable ? : } + +
    +

    + {receivable + ? t("wallet.agentPaysPlayer", { defaultValue: "代理应付玩家" }) + : t("wallet.playerPaysAgent", { defaultValue: "玩家应付代理" })} +

    +

    {formatPeriodRange(item)}

    +
    +
    +
    +

    + {formatMinorAsCurrency(item.unpaid_amount, currency)} +

    +

    {settlementStatusLabel(item.status, t)}

    +
    +
    +
  • + ); + })} +
+ )} +
+ ); +} + export function WalletScreen() { const router = useRouter(); const searchParams = useSearchParams(); @@ -58,6 +185,7 @@ export function WalletScreen() { const [transferOutOpen, setTransferOutOpen] = useState(false); const [balance, setBalance] = useState(null); const [logs, setLogs] = useState(null); + const [settlementBills, setSettlementBills] = useState(null); const [filter, setFilter] = useState(""); const [loading, setLoading] = useState(true); const [logsLoading, setLogsLoading] = useState(false); @@ -141,6 +269,12 @@ export function WalletScreen() { if (cancelled) return; setBalance(b); setLogs(nextLogs); + if (isCreditFundingPlayer(b) || isCreditFundingPlayer(profile)) { + const bills = await getWalletSettlementBills(); + if (!cancelled) setSettlementBills(bills); + } else { + setSettlementBills(null); + } } catch (e) { if (!cancelled) { setError(formatWalletClientError(e, t)); @@ -220,13 +354,18 @@ export function WalletScreen() { loadLogs(1, false), ]); setBalance(b); + if (isCreditFundingPlayer(b) || isCreditFundingPlayer(profile)) { + setSettlementBills(await getWalletSettlementBills()); + } else { + setSettlementBills(null); + } } catch (e) { setError(formatWalletClientError(e, t)); } finally { setLogsLoading(false); setLoading(false); } - }, [currency, loadLogs, t]); + }, [currency, loadLogs, profile, t]); useEffect(() => { const onCurrencyChange = () => void refreshAll(); @@ -338,6 +477,10 @@ export function WalletScreen() { + {fundingModeKnown && isCreditPlayer ? ( + + ) : null} + {fundingModeKnown && !isCreditPlayer ? (