diff --git a/src/api/draw.ts b/src/api/draw.ts index cbc1b7f..2c861f2 100644 --- a/src/api/draw.ts +++ b/src/api/draw.ts @@ -1,14 +1,14 @@ import { lotteryRequest } from "@/lib/lottery-http"; import { API_V1_PREFIX } from "@/api/paths"; -import type { DrawCurrentPayload } from "@/types/api/draw-current"; +import type { DrawCurrentResponse } from "@/types/api/draw-current"; import type { DrawResultDetailPayload, DrawResultsListPayload, } from "@/types/api/draw-results"; /** `GET /api/v1/draw/current`(无需登录;无当前期时 `data` 为 `null`) */ -export function getDrawCurrent(): Promise { - return lotteryRequest.get( +export function getDrawCurrent(): Promise { + return lotteryRequest.get( `${API_V1_PREFIX}/draw/current`, ); } diff --git a/src/features/hall/hall-draw-panel.tsx b/src/features/hall/hall-draw-panel.tsx index 5ea3577..527edeb 100644 --- a/src/features/hall/hall-draw-panel.tsx +++ b/src/features/hall/hall-draw-panel.tsx @@ -1,6 +1,7 @@ "use client"; import { Hourglass, Landmark, TimerReset } from "lucide-react"; +import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; @@ -38,24 +39,40 @@ function CurrentTime({ payload }: { payload: DrawCurrentPayload }) { } function CloseTime({ + serverNowMs, hud, payload, }: { + serverNowMs: number; hud: ReturnType; payload: DrawCurrentPayload; }) { const { t } = useTranslation("player"); const sealedCountdown = isHallSealedCountdownUi(payload.status); + const [nowMs, setNowMs] = useState(serverNowMs); + + useEffect(() => { + setNowMs(serverNowMs); + }, [serverNowMs]); + + useEffect(() => { + const intervalId = window.setInterval(() => { + setNowMs((current) => current + 1000); + }, 1000); + + return () => window.clearInterval(intervalId); + }, []); + let seconds = 0; let label = t("draw.closesIn"); if (hud.countdownKind === "close") { - seconds = Math.max(0, payload.seconds_to_close); + seconds = Math.max(0, Math.ceil(((payload.close_time ? Date.parse(payload.close_time) : 0) - nowMs) / 1000)); } else if (hud.countdownKind === "draw") { - seconds = Math.max(0, payload.seconds_to_draw); + seconds = Math.max(0, Math.ceil(((payload.draw_time ? Date.parse(payload.draw_time) : 0) - nowMs) / 1000)); label = sealedCountdown ? t("draw.drawsIn") : t("draw.closesIn"); } else if (hud.countdownKind === "cooldown") { - seconds = Math.max(0, payload.seconds_remaining_in_cooldown ?? 0); + seconds = Math.max(0, Math.ceil(((payload.cooling_end_time ? Date.parse(payload.cooling_end_time) : 0) - nowMs) / 1000)); label = t("draw.coolDown"); } @@ -70,7 +87,7 @@ function CloseTime({ } export function HallDrawPanel({ drawLive }: { drawLive: HallDrawLiveSnapshot }) { - const { raw, display, error, reload } = drawLive; + const { raw, display, serverNowMs, error, reload } = drawLive; const { t } = useTranslation("player"); if (error) { @@ -112,7 +129,6 @@ export function HallDrawPanel({ drawLive }: { drawLive: HallDrawLiveSnapshot }) const hud = drawStatusHud(display.status); const sealedUi = isHallSealedCountdownUi(display.status); - return (
- + Promise; isBettable: boolean; @@ -48,6 +49,7 @@ function applySnapshotDrift( */ export function useHallDrawLive(): HallDrawLiveSnapshot { const [raw, setRaw] = useState(undefined); + const [serverNowMs, setServerNowMs] = useState(() => Date.now()); const [emittedAtMs, setEmittedAtMs] = useState(() => Date.now()); const [nowMs, setNowMs] = useState(() => Date.now()); const [error, setError] = useState(null); @@ -72,17 +74,22 @@ export function useHallDrawLive(): HallDrawLiveSnapshot { setEmittedAtMs(evt.emitted_at_ms ?? Date.now()); }, []); + const updateFromResponse = useCallback((resp: DrawCurrentResponse) => { + setServerNowMs(resp.server_now_ms); + setRaw(resp.data); + setEmittedAtMs(resp.server_now_ms); + }, []); + const load = useCallback(async () => { try { setError(null); const d = await getDrawCurrent(); - setRaw(d); - setEmittedAtMs(Date.now()); + updateFromResponse(d); } catch { setError("draw.loadFailedRefresh"); setRaw(undefined); } - }, []); + }, [updateFromResponse]); // 初始加载 useEffect(() => { @@ -228,5 +235,5 @@ export function useHallDrawLive(): HallDrawLiveSnapshot { const isBettable = display != null && display.status === "open"; - return { raw, display, error, reload: load, isBettable }; + return { raw, display, serverNowMs, error, reload: load, isBettable }; } diff --git a/src/types/api/draw-current.ts b/src/types/api/draw-current.ts index 9b57f03..724e932 100644 --- a/src/types/api/draw-current.ts +++ b/src/types/api/draw-current.ts @@ -36,3 +36,9 @@ export type DrawCurrentPayload = { result_version?: number; result_source?: string | null; }; + +/** `GET /api/v1/draw/current` 的完整响应 */ +export type DrawCurrentResponse = { + server_now_ms: number; + data: DrawCurrentPayload | null; +};