feat: 增强投注结果弹窗与投注网格功能
更新 HallBetResultDialog:根据成功与失败数量显示不同的结果图标与标题。 优化 HallBettingGrid:新增 place trace ID 管理机制,更好地处理投注提交流程,并防止重复扣费。 增强注单详情页面:针对临时状态中的注单自动刷新,提升用户体验。 新增多语言翻译:补充投注结果与状态相关文案,支持更完善的用户反馈。
This commit is contained in:
@@ -450,6 +450,17 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
number: null,
|
||||
longPress: false,
|
||||
});
|
||||
/** 单次预览→确认共用,重试 place 复用,避免重复扣款 */
|
||||
const placeTraceIdRef = useRef<string | null>(null);
|
||||
|
||||
const newPlaceTraceId = (): string =>
|
||||
typeof crypto !== "undefined" && crypto.randomUUID
|
||||
? crypto.randomUUID()
|
||||
: `pl-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
||||
|
||||
const clearPlaceTraceId = () => {
|
||||
placeTraceIdRef.current = null;
|
||||
};
|
||||
const loadCatalog = useCallback(async () => {
|
||||
setCatalogState((s) => (s.kind === "ok" ? s : { kind: "loading" }));
|
||||
try {
|
||||
@@ -466,7 +477,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
const wallet = await getWalletBalance({ currency: currencyParam });
|
||||
setAvailableMinor(Number(wallet.available_balance ?? 0));
|
||||
} catch {
|
||||
setAvailableMinor(0);
|
||||
// 保留上次可用余额,避免短暂失败导致误报余额不足
|
||||
}
|
||||
}, [currencyParam]);
|
||||
|
||||
@@ -892,16 +903,17 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
return;
|
||||
}
|
||||
|
||||
if (previewLoading || placeLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPreviewLoading(true);
|
||||
try {
|
||||
placeTraceIdRef.current = newPlaceTraceId();
|
||||
const data = await postTicketPreview({
|
||||
draw_id: display.draw_no,
|
||||
currency_code: currencyCode,
|
||||
client_trace_id: `pv-${
|
||||
typeof crypto !== "undefined" && crypto.randomUUID
|
||||
? crypto.randomUUID()
|
||||
: String(Date.now())
|
||||
}`,
|
||||
client_trace_id: placeTraceIdRef.current,
|
||||
lines,
|
||||
});
|
||||
setPreviewData(data);
|
||||
@@ -925,6 +937,9 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
|
||||
const handlePlace = async () => {
|
||||
if (!display || !previewData) return;
|
||||
if (placeLoading) {
|
||||
return;
|
||||
}
|
||||
if (!isBettable) {
|
||||
toast.error(t("hall.closedSubmit"));
|
||||
return;
|
||||
@@ -942,18 +957,19 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
return;
|
||||
}
|
||||
|
||||
const traceId = placeTraceIdRef.current ?? newPlaceTraceId();
|
||||
placeTraceIdRef.current = traceId;
|
||||
|
||||
setPlaceLoading(true);
|
||||
try {
|
||||
const data = await postTicketPlace({
|
||||
draw_id: display.draw_no,
|
||||
currency_code: currencyCode,
|
||||
client_trace_id:
|
||||
typeof crypto !== "undefined" && crypto.randomUUID
|
||||
? crypto.randomUUID()
|
||||
: `pl-${Date.now()}`,
|
||||
client_trace_id: traceId,
|
||||
lines,
|
||||
expected_config_versions: previewData.config_versions,
|
||||
});
|
||||
clearPlaceTraceId();
|
||||
setPreviewOpen(false);
|
||||
setPreviewData(null);
|
||||
setResultData(data);
|
||||
@@ -963,17 +979,26 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
triggerWalletPollingAfterBet();
|
||||
void refreshWallet();
|
||||
void reloadDraw();
|
||||
toast.success(
|
||||
t("hall.placeSuccess", {
|
||||
orderNo: data.order_no,
|
||||
amount: formatMinorAsCurrency(data.summary.total_actual_deduct, currencyCode),
|
||||
}),
|
||||
);
|
||||
if ((data.summary.failure_count ?? 0) > 0) {
|
||||
const failureCount = data.summary.failure_count ?? 0;
|
||||
const successCount = data.summary.success_count ?? 0;
|
||||
if (failureCount > 0 && successCount === 0) {
|
||||
toast.error(
|
||||
t("hall.placeAllFailed", {
|
||||
failed: failureCount,
|
||||
}),
|
||||
);
|
||||
} else if (failureCount > 0) {
|
||||
toast.warning(
|
||||
t("hall.placePartialFailed", {
|
||||
success: data.summary.success_count ?? 0,
|
||||
failed: data.summary.failure_count ?? 0,
|
||||
success: successCount,
|
||||
failed: failureCount,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
toast.success(
|
||||
t("hall.placeSuccess", {
|
||||
orderNo: data.order_no,
|
||||
amount: formatMinorAsCurrency(data.summary.total_actual_deduct, currencyCode),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -1268,7 +1293,12 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
className="w-[4.5rem] min-w-[4.5rem] max-w-[4.5rem] px-0.5 py-2 text-center font-bold"
|
||||
>
|
||||
<span className="block whitespace-nowrap text-[10px] leading-tight">
|
||||
{playColumnHeaderLabel(column.play, activeCategory, column.digitSlot, t)}
|
||||
{playColumnHeaderLabel(
|
||||
column.play,
|
||||
activeCategory as Exclude<HallCategory, "JACKPOT">,
|
||||
column.digitSlot,
|
||||
t,
|
||||
)}
|
||||
</span>
|
||||
<span className="mt-0.5 block text-[9px] font-medium text-[#9aa8bd]">
|
||||
{t("hall.table.amountPlaceholder")}
|
||||
@@ -1451,7 +1481,12 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
open={previewOpen}
|
||||
onOpenChange={(open) => {
|
||||
setPreviewOpen(open);
|
||||
if (!open) setPreviewData(null);
|
||||
if (!open) {
|
||||
setPreviewData(null);
|
||||
if (!placeLoading) {
|
||||
clearPlaceTraceId();
|
||||
}
|
||||
}
|
||||
}}
|
||||
currencyCode={currencyCode}
|
||||
data={previewData}
|
||||
|
||||
Reference in New Issue
Block a user