feat: enhance draw processing and ticket validation logic

- Added a new function to check if the hall is awaiting draw processing, improving the draw status handling.
- Implemented validation for roll numbers in ticket orders, ensuring compliance with specified formats.
- Enhanced the draft line issue reasoning to provide detailed feedback on invalid ticket entries.
- Updated HallDrawPanel and related components to utilize the new draw processing checks and improve user notifications.
- Added new translations for draw processing and ticket validation messages in multiple languages.
This commit is contained in:
2026-05-25 16:44:00 +08:00
parent 3bcbf7d256
commit 3b83c6627c
10 changed files with 356 additions and 61 deletions

View File

@@ -3,7 +3,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { getDrawCurrent } from "@/api/draw";
import { isHallSealedCountdownUi } from "@/features/draw/draw-status-meta";
import { isHallAwaitingDrawProcessing, isHallSealedCountdownUi } from "@/features/draw/draw-status-meta";
import { getLotteryEcho } from "@/lib/lottery-echo";
import { useNetworkConnectionStore } from "@/stores/network-connection-store";
import type { DrawCurrentPayload, DrawCurrentResponse } from "@/types/api/draw-current";
@@ -13,6 +13,8 @@ export type HallDrawLiveSnapshot = {
raw: DrawCurrentPayload | null | undefined;
display: DrawCurrentPayload | null | undefined;
serverNowMs: number;
/** 与 display 漂移推演一致的本地「当前」毫秒时间戳 */
nowMs: number;
error: string | null;
reload: () => Promise<void>;
isBettable: boolean;
@@ -266,6 +268,13 @@ export function useHallDrawLive(): HallDrawLiveSnapshot {
((display.seconds_remaining_in_cooldown ?? 1) === 0 ||
(coolingEndMs !== null && !Number.isNaN(coolingEndMs) && coolingEndMs <= nowMs));
const awaitingDraw = isHallAwaitingDrawProcessing(
display.status,
display.draw_time,
display.seconds_to_draw,
nowMs,
);
const sealedDone =
isHallSealedCountdownUi(display.status) && (display.seconds_to_draw ?? 1) === 0;
@@ -274,11 +283,13 @@ export function useHallDrawLive(): HallDrawLiveSnapshot {
const trigger = coolingDone
? `${display.draw_no}:cooldown-end`
: sealedDone
? `${display.draw_no}:sealed-end`
: closeDone
? `${display.draw_no}:close-end`
: null;
: awaitingDraw
? `${display.draw_no}:awaiting-draw`
: sealedDone
? `${display.draw_no}:sealed-end`
: closeDone
? `${display.draw_no}:close-end`
: null;
if (trigger && zeroRefreshKeyRef.current !== trigger) {
zeroRefreshKeyRef.current = trigger;
@@ -292,13 +303,34 @@ export function useHallDrawLive(): HallDrawLiveSnapshot {
}
}, [display?.draw_no, display?.status]);
// WebSocket 已连接时的兜底轮询tick 最多 1 分钟延迟时的保险)
const needsFastDrawPoll =
display != null
&& isHallAwaitingDrawProcessing(
display.status,
display.draw_time,
display.seconds_to_draw,
nowMs,
);
// 封盘/待开奖/开奖中:调度推进状态前每 3 秒拉一次,避免 0:00 卡几十秒
useEffect(() => {
if (!needsFastDrawPoll) {
return;
}
const intervalId = window.setInterval(() => {
void load();
}, 45_000);
}, 3000);
return () => window.clearInterval(intervalId);
}, [load]);
}, [needsFastDrawPoll, load]);
return { raw, display, serverNowMs, error, reload: load, isBettable };
// WebSocket 已连接时的兜底轮询tick 延迟时的保险)
useEffect(() => {
const intervalMs = needsFastDrawPoll ? 15_000 : 45_000;
const intervalId = window.setInterval(() => {
void load();
}, intervalMs);
return () => window.clearInterval(intervalId);
}, [load, needsFastDrawPoll]);
return { raw, display, serverNowMs, nowMs, error, reload: load, isBettable };
}