feat(hall): 支持全保金额整除校验与规则提示,优化桌面端转账按钮布局
Some checks failed
lotteryfront CI / build (push) Has been cancelled

This commit is contained in:
2026-07-15 13:40:19 +08:00
parent 295cfb97cd
commit 406ebb7671
14 changed files with 510 additions and 74 deletions

View File

@@ -1,9 +1,10 @@
"use client";
import { ChevronDown, ChevronUp, Lock, Ticket, Trash2, Star } from "lucide-react";
import { ChevronDown, ChevronUp, CircleHelp, Lock, Ticket, Trash2, Star } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState, memo } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Tooltip } from "@base-ui/react/tooltip";
import { getBetProviders } from "@/api/bet-providers";
import { getPlayEffective } from "@/api/play";
@@ -34,6 +35,7 @@ import {
type DraftLineIssueReason,
} from "@/features/hall/hall-bet-rules";
import {
isFullCoverAmountDivisible,
isHighCostSelectionType,
resolveSelectionTotalBet,
selectionCombinationCount,
@@ -1111,6 +1113,18 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
playCode: column.play.play_code,
reason,
});
return;
}
const comboCount = selectionCombinationCount(row.number, row.selectionType);
if (
row.selectionType === "full_cover"
&& !isFullCoverAmountDivisible(amount, comboCount)
) {
issues.push({
rowNo: rowIndex + 1,
playCode: column.play.play_code,
reason: "full_cover_amount_not_divisible",
});
}
});
});
@@ -2257,7 +2271,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
) : null}
</div>
) : null}
<DialogFooter className="gap-2 border-t border-[#eef2f8] px-5 py-4 sm:justify-end">
<DialogFooter className="mx-0 mb-0 gap-2 rounded-b-2xl border-t border-[#eef2f8] bg-white px-5 py-4 sm:justify-end">
<Button
type="button"
variant="outline"
@@ -2341,6 +2355,10 @@ const DraftRowItem = memo(function DraftRowItem({
}) {
const displayNumber = sanitizeNumber(row.number, activeCategory);
const comboCount = selectionCombinationCount(displayNumber, row.selectionType);
const hasFullCoverAmountError = row.selectionType === "full_cover" && playColumns.some((column) => {
const amount = parseDecimalInputToMinor(row.amounts[column.key] ?? "", currencyCode);
return amount !== null && amount > 0 && !isFullCoverAmountDivisible(amount, comboCount);
});
const rowTotalMinor =
playColumns.reduce((total, column) => {
if (draftLineIssueReason(column.play.play_code, row.number, column.digitSlot) !== null) {
@@ -2359,7 +2377,7 @@ const DraftRowItem = memo(function DraftRowItem({
>
<td
className={cn(
"sticky left-0 z-20 px-0.5 py-1.5 text-center font-black text-[#17408d] shadow-[2px_0_6px_rgba(15,23,42,0.04)]",
"sticky left-0 z-20 align-top px-0.5 py-1.5 text-center font-black text-[#17408d] shadow-[2px_0_6px_rgba(15,23,42,0.04)]",
indexColClass,
rowActive ? "bg-[#f5f9ff]" : "bg-white",
)}
@@ -2368,7 +2386,7 @@ const DraftRowItem = memo(function DraftRowItem({
</td>
<td
className={cn(
"sticky z-20 px-1 py-1.5 shadow-[2px_0_6px_rgba(15,23,42,0.04)]",
"sticky z-20 align-top px-1 py-1.5 shadow-[2px_0_6px_rgba(15,23,42,0.04)]",
stickyNumberLeftClass,
numberColClass,
rowActive ? "bg-[#f5f9ff]" : "bg-white",
@@ -2455,7 +2473,7 @@ const DraftRowItem = memo(function DraftRowItem({
);
})}
{showSelectionTypeColumn ? (
<td className={cn(selectionTypeColClass, "px-1 py-1.5 text-center", rowActive && "bg-[#f5f9ff]")}>
<td className={cn(selectionTypeColClass, "align-top px-1 py-1.5 text-center", rowActive && "bg-[#f5f9ff]")}>
<select
value={row.selectionType}
disabled={tableDisabled}
@@ -2470,8 +2488,33 @@ const DraftRowItem = memo(function DraftRowItem({
))}
</select>
{comboCount > 1 && displayNumber.length >= 2 ? (
<p className="mt-0.5 text-[9px] font-bold leading-none text-[#0b56b7]">
{t("hall.table.comboCount", { count: comboCount })}
<div className="mt-0.5 flex items-center justify-center gap-0.5 text-[9px] font-bold leading-none text-[#0b56b7]">
<span>{t("hall.table.comboCount", { count: comboCount })}</span>
{row.selectionType === "full_cover" || row.selectionType === "half_play" ? (
<Tooltip.Root>
<Tooltip.Trigger
className="inline-flex size-3 items-center justify-center rounded-full text-[#5378b5] outline-none hover:text-[#0b56b7] focus-visible:ring-1 focus-visible:ring-[#0b56b7]"
aria-label={t("hall.table.selectionTypeRule")}
title={t("hall.table.selectionTypeRule")}
>
<CircleHelp className="size-3" aria-hidden />
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Positioner side="bottom" sideOffset={6}>
<Tooltip.Popup className="z-[70] max-w-52 rounded-md bg-slate-900 px-2 py-1.5 text-left text-[11px] font-medium leading-snug text-white shadow-lg">
{row.selectionType === "full_cover"
? t("hall.table.fullCoverSplitHint", { count: comboCount })
: t("hall.table.halfPlayHint")}
</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Portal>
</Tooltip.Root>
) : null}
</div>
) : null}
{row.selectionType === "full_cover" && comboCount > 1 && hasFullCoverAmountError ? (
<p className="mt-0.5 text-[9px] font-medium leading-tight text-red-600">
{t("hall.table.fullCoverDivisibilityError", { count: comboCount })}
</p>
) : null}
</td>