refactor(hall): 优化投注格子行数与号码输入逻辑
Some checks failed
lotteryfront CI / build (push) Has been cancelled

- 引入默认草稿行数常量及批量生成草稿行函数
- 根据玩法类别调整号码最大输入长度和清洗规则
- 更新号码输入组件以适应动态最大长度和格式规范
- 修正相关回调依赖,确保玩法类别变更时生效
- 改善行状态、占位符和样式传递,增强代码可读性

feat(results): 添加多提供商开奖结果切换功能

- 新增状态管理并展示多提供商按钮切换激活项
- 根据选中的提供商动态展示对应开奖结果数据
- 细化UI显示,支持多提供商名称并列渲染

feat(results): 在结果列表中显示多提供商名称

- 当存在多个提供商结果时展示其名称或代码
- 改善界面信息丰富度,方便用户辨识来源

feat(wallet): 支持账期还款释额类型的流水显示

- 调整账期流水类型文案,精准反映释额含义
- 引入账期时区参数以支持多时区时间格式化
- 自定义信用盘账期释额流水的颜色和文案显示
- 在流水明细和列表中根据时区格式化展示时间

feat(wallet): 支持钱包账期账单区间时区格式化

- 允许传入账期时区参数格式化账单区间时间
- 在账期账单区间显示组件中显示对应时区格式
- 根据玩家配置自动获取并传递账期时区信息
- 条件渲染中信用盘区块支持账期时区参数传递

chore(i18n): 更新中文钱包模块多处文案

- 新增“释额 {{amount}}”等账期释额相关文案
- 修改“账期收款”为“账期还款释额”以符合业务描述

refactor(types): 扩展开奖结果支持多提供商结构

- 新增DrawResultProviderRow类型定义以描述提供商结果
- 在DrawResultListItem中增加provider_results字段支持多结果
- 在玩家信息类型中新增settlement_timezone字段表示账期时区
This commit is contained in:
2026-07-09 15:48:36 +08:00
parent 4a6f021304
commit 56ba12fbfc
8 changed files with 173 additions and 43 deletions

View File

@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { formatPlayerInstant } from "@/lib/player-datetime";
import { formatLotteryInstantInTimeZone, formatPlayerInstant } from "@/lib/player-datetime";
import { formatMinorAsCurrency } from "@/lib/money";
import type { WalletLogItem, WalletLogsData } from "@/types/api/wallet-logs";
@@ -54,7 +54,7 @@ const CREDIT_BIZ_LABEL_FALLBACKS: Record<string, string> = {
game_settlement_loss: "亏损入账",
game_settlement_win: "中奖释额",
game_settlement_reversal: "结算冲正",
settlement_confirm: "账期收款",
settlement_confirm: "账期还款释额",
settlement_payout: "账期付款",
rebate_accrued: "账期回水",
rebate_in_bill: "回水已入账单",
@@ -108,6 +108,7 @@ type WalletLogsBlockProps = {
title?: string;
/** 信用盘:专用筛选项与列表文案 */
creditMode?: boolean;
settlementTimeZone?: string;
};
/** 类型筛选 + 列表(钱包盘待对账见钱包页横幅) */
@@ -123,6 +124,7 @@ export function WalletLogsBlock({
currency,
title,
creditMode = false,
settlementTimeZone,
}: WalletLogsBlockProps) {
const { t } = useTranslation("player");
const resolvedTitle =
@@ -189,6 +191,7 @@ export function WalletLogsBlock({
item={row}
currency={currency}
creditMode={creditMode}
settlementTimeZone={settlementTimeZone}
/>
))
)}
@@ -226,14 +229,27 @@ export function LogRow({
item,
currency,
creditMode = false,
settlementTimeZone,
}: {
item: WalletLogItem;
currency: string;
creditMode?: boolean;
settlementTimeZone?: string;
}) {
const { t } = useTranslation("player");
const ccy = item.currency_code || currency;
const isIn = item.direction === "in";
const isCreditSettlementConfirm = creditMode && item.biz_type === "settlement_confirm";
const amountTone = isCreditSettlementConfirm
? "text-[#32518d]"
: isIn
? "text-emerald-600"
: "text-[#e5002c]";
const dotTone = isCreditSettlementConfirm
? "bg-[#2f6fca]"
: isIn
? "bg-emerald-500"
: "bg-[#e5002c]";
const statusTone = item.status === "posted"
? "border-emerald-200 bg-emerald-50 text-emerald-700"
: item.status === "pending_reconcile"
@@ -247,13 +263,15 @@ export function LogRow({
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className={isIn ? "size-2.5 rounded-full bg-emerald-500" : "size-2.5 rounded-full bg-[#e5002c]"} aria-hidden />
<span className={`size-2.5 rounded-full ${dotTone}`} aria-hidden />
<p className="truncate text-base font-black leading-tight text-[#101a33]">
{logTypeLabel(item.type, t, creditMode, item.biz_type)}
</p>
</div>
<p className="mt-1.5 text-xs font-medium text-slate-500">
{formatPlayerInstant(item.created_at)}
{creditMode && settlementTimeZone
? formatLotteryInstantInTimeZone(item.created_at, settlementTimeZone)
: formatPlayerInstant(item.created_at)}
</p>
{item.ref_id ? (
<p className="mt-1 truncate font-mono text-[11px] text-slate-400">
@@ -263,9 +281,13 @@ export function LogRow({
</div>
<div className="flex shrink-0 flex-col items-end gap-2 text-right">
<p className={isIn ? "text-lg font-black tabular-nums text-emerald-600" : "text-lg font-black tabular-nums text-[#e5002c]"}>
{isIn ? "+" : ""}
{formatMinorAsCurrency(item.amount_abs, ccy)}
<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)}`}
</p>
<span className={`inline-flex items-center rounded-full border px-2.5 py-1 text-[11px] font-black ${statusTone}`}>
{txnStatusLabel(item.status, t)}

View File

@@ -19,7 +19,7 @@ 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 { formatLotteryInstantInTimeZone, 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";
@@ -69,18 +69,31 @@ function settlementStatusLabel(
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) : "—";
function formatPeriodRange(
item: WalletSettlementBillItem,
settlementTimeZone?: string,
): string {
const start = item.period_start
? (settlementTimeZone
? formatLotteryInstantInTimeZone(item.period_start, settlementTimeZone)
: formatPlayerInstant(item.period_start)).slice(0, 10)
: "—";
const end = item.period_end
? (settlementTimeZone
? formatLotteryInstantInTimeZone(item.period_end, settlementTimeZone)
: formatPlayerInstant(item.period_end)).slice(0, 10)
: "—";
return `${start} / ${end}`;
}
function CreditSettlementBillsBlock({
data,
currency,
settlementTimeZone,
}: {
data: WalletSettlementBillsData | null;
currency: string;
settlementTimeZone?: string;
}) {
const { t } = useTranslation("player");
const summary = data?.summary;
@@ -157,7 +170,9 @@ function CreditSettlementBillsBlock({
? t("wallet.agentPaysPlayer", { defaultValue: "代理应付玩家" })
: t("wallet.playerPaysAgent", { defaultValue: "玩家应付代理" })}
</p>
<p className="mt-0.5 text-[11px] text-slate-500">{formatPeriodRange(item)}</p>
<p className="mt-0.5 text-[11px] text-slate-500">
{formatPeriodRange(item, settlementTimeZone)}
</p>
</div>
</div>
<div className="shrink-0 text-right">
@@ -199,6 +214,7 @@ export function WalletScreen() {
const sectionDeepLinkHandledRef = useRef(false);
const profile = usePlayerSessionStore((s) => s.profile);
const settlementTimeZone = profile?.settlement_timezone || "UTC";
const fundingModeView = resolveFundingModeView(balance, profile);
const fundingModeKnown = fundingModeView !== "unknown";
const isCreditPlayer = fundingModeView === "credit";
@@ -478,7 +494,11 @@ export function WalletScreen() {
</section>
{fundingModeKnown && isCreditPlayer ? (
<CreditSettlementBillsBlock data={settlementBills} currency={currency} />
<CreditSettlementBillsBlock
data={settlementBills}
currency={currency}
settlementTimeZone={settlementTimeZone}
/>
) : null}
{fundingModeKnown && !isCreditPlayer ? (
@@ -562,6 +582,7 @@ export function WalletScreen() {
filter={filter}
onFilterChange={handleFilterChange}
currency={currency}
settlementTimeZone={isCreditPlayer ? settlementTimeZone : undefined}
/>
) : (
<Skeleton className="h-40 w-full rounded-lg" />