From d5415888e69e10ca17ef8ebca0a706804da6b7f1 Mon Sep 17 00:00:00 2001 From: kang Date: Sat, 16 May 2026 09:54:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=B8=8B=E6=B3=A8?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E6=B8=85=E7=90=86=E4=B8=8E=E5=BC=80=E5=A5=96?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E8=AF=A6=E6=83=85=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 支持玩法关闭错误返回 cleanup_lines 时自动清空对应下注格并提示原因 - 调整下注预览与下注结果金额汇总文案,补充金额、回水、合计多语言翻译 - 下注结果弹窗新增注单状态展示 - 重构开奖结果详情页样式,强化前三名、派奖提示与查看中奖入口展示 - 精简底部导航激活态视觉效果 --- src/components/layout/player-bottom-nav.tsx | 3 - src/features/hall/hall-bet-preview-dialog.tsx | 10 +- src/features/hall/hall-bet-result-dialog.tsx | 26 +++- src/features/hall/hall-betting-grid.tsx | 51 +++++++ .../results/draw-result-detail-screen.tsx | 141 +++++++++++------- src/i18n/locales/en/player.json | 3 + src/i18n/locales/ne/player.json | 3 + src/i18n/locales/zh/player.json | 3 + 8 files changed, 176 insertions(+), 64 deletions(-) diff --git a/src/components/layout/player-bottom-nav.tsx b/src/components/layout/player-bottom-nav.tsx index 922b0e2..27f791a 100644 --- a/src/components/layout/player-bottom-nav.tsx +++ b/src/components/layout/player-bottom-nav.tsx @@ -59,9 +59,6 @@ export function PlayerBottomNav() { : "text-[#6b7280] hover:text-[#1f2937] active:text-[#1f2937]", )} > - {active ? ( - - ) : null}
  • - {t("hall.preview.totalBet")}{" "} + {t("hall.preview.amount")}{" "} {formatMinorAsCurrency(summary.total_bet_amount, currencyCode)}
  • - {t("hall.preview.rebateDeduct")}{" "} + {t("hall.preview.rebate")}{" "} {formatMinorAsCurrency(summary.total_rebate_amount, currencyCode)}
  • - {t("hall.preview.actualDeduct")}{" "} + {t("hall.preview.actual")}{" "} {formatMinorAsCurrency(summary.total_actual_deduct, currencyCode)}
  • - {t("hall.preview.estimatedPayout")}{" "} + {t("hall.preview.total")}{" "} - {formatMinorAsCurrency(summary.total_estimated_payout, currencyCode)} + {formatMinorAsCurrency(summary.total_actual_deduct, currencyCode)}
  • diff --git a/src/features/hall/hall-bet-result-dialog.tsx b/src/features/hall/hall-bet-result-dialog.tsx index 677b8c2..014b46c 100644 --- a/src/features/hall/hall-bet-result-dialog.tsx +++ b/src/features/hall/hall-bet-result-dialog.tsx @@ -74,6 +74,12 @@ export function HallBetResultDialog({ {t("hall.result.draw", { defaultValue: "期号" })}{" "} {data.draw.draw_id}

    +

    + {t("hall.result.status", { defaultValue: "注单状态" })}{" "} + + {t("ticketStatus.success", { defaultValue: "待开奖" })} + +

    • @@ -85,7 +91,25 @@ export function HallBetResultDialog({ {totalFailure}
    • - {t("hall.result.totalDeduct", { defaultValue: "成功扣款" })}{" "} + {t("hall.result.amount", { defaultValue: "金额" })}{" "} + + {formatMinorAsCurrency(data.summary.total_bet_amount, currencyCode)} + +
    • +
    • + {t("hall.result.rebate", { defaultValue: "回水" })}{" "} + + {formatMinorAsCurrency(data.summary.total_rebate_amount, currencyCode)} + +
    • +
    • + {t("hall.result.actual", { defaultValue: "实扣" })}{" "} + + {formatMinorAsCurrency(data.summary.total_actual_deduct, currencyCode)} + +
    • +
    • + {t("hall.result.total", { defaultValue: "合计" })}{" "} {formatMinorAsCurrency(data.summary.total_actual_deduct, currencyCode)} diff --git a/src/features/hall/hall-betting-grid.tsx b/src/features/hall/hall-betting-grid.tsx index b5e2597..ea53651 100644 --- a/src/features/hall/hall-betting-grid.tsx +++ b/src/features/hall/hall-betting-grid.tsx @@ -50,6 +50,11 @@ type DraftEntry = { line: TicketLineInput; }; +type ClosedPlayCleanupData = { + cleanup_hint?: string; + cleanup_lines?: Array<{ client_line_no?: number; play_code?: string }>; +}; + type CellRiskState = "open" | "warning" | "sold_out"; type QuickFillState = Record; @@ -569,6 +574,40 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot } const buildLines = (): TicketLineInput[] => collectEntries().map((entry) => entry.line); + const applyClosedPlayCleanup = (data: unknown): boolean => { + const payload = data as ClosedPlayCleanupData | null; + const cleanupLines = Array.isArray(payload?.cleanup_lines) ? payload.cleanup_lines : []; + if (cleanupLines.length === 0) return false; + + const entries = collectEntries(); + const cleanupPairs = new Set(); + cleanupLines.forEach((item) => { + const clientLineNo = Number(item?.client_line_no ?? 0); + const playCode = String(item?.play_code ?? ""); + if (!Number.isInteger(clientLineNo) || clientLineNo <= 0 || playCode.trim() === "") return; + const entry = entries[clientLineNo - 1]; + if (!entry) return; + cleanupPairs.add(`${entry.rowId}::${playCode}`); + }); + + if (cleanupPairs.size === 0) return false; + + setRows((current) => + current.map((row) => { + const nextAmounts = { ...row.amounts }; + let changed = false; + Object.keys(nextAmounts).forEach((playCode) => { + if (!cleanupPairs.has(`${row.id}::${playCode}`)) return; + nextAmounts[playCode] = ""; + changed = true; + }); + return changed ? { ...row, amounts: nextAmounts } : row; + }), + ); + + return true; + }; + const handlePreview = async () => { if (!display) { toast.error(t("hall.noDraw")); @@ -609,6 +648,11 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot } } catch (e) { const code = e instanceof LotteryApiBizError ? e.code : 0; const msg = e instanceof LotteryApiBizError ? e.message : t("hall.previewFailed"); + if (e instanceof LotteryApiBizError && code === 2002 && applyClosedPlayCleanup(e.data)) { + const payload = e.data as ClosedPlayCleanupData; + toast.error(payload.cleanup_hint ?? t("hall.ticketError.2002")); + return; + } toast.error(mapTicketBetError(code, msg, t)); } finally { setPreviewLoading(false); @@ -658,6 +702,13 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot } } catch (e) { const code = e instanceof LotteryApiBizError ? e.code : 0; const msg = e instanceof LotteryApiBizError ? e.message : t("hall.placeFailed"); + if (e instanceof LotteryApiBizError && code === 2002 && applyClosedPlayCleanup(e.data)) { + const payload = e.data as ClosedPlayCleanupData; + toast.error(payload.cleanup_hint ?? t("hall.ticketError.2002")); + setPreviewOpen(false); + setPreviewData(null); + return; + } toast.error(mapTicketBetError(code, msg, t)); } finally { setPlaceLoading(false); diff --git a/src/features/results/draw-result-detail-screen.tsx b/src/features/results/draw-result-detail-screen.tsx index 957197c..aff7adc 100644 --- a/src/features/results/draw-result-detail-screen.tsx +++ b/src/features/results/draw-result-detail-screen.tsx @@ -159,96 +159,127 @@ export function DrawResultDetailScreen({ drawNo }: DrawResultDetailScreenProps)
      - - + +
      {data.previous_draw_no ? ( {t("results.previous")} ) : ( - )} - - {data.draw_no} - +
      + + {data.draw_no} + + + {t("results.drawTime", { + time: formatLotteryInstant(data.draw_time_iso ?? data.draw_time ?? null), + })} + +
      {data.next_draw_no ? ( {t("results.next")} ) : ( - )}
      - - {t("results.drawTime", { - time: formatLotteryInstant(data.draw_time_iso ?? data.draw_time ?? null), - })} -
      - + +
      +
      +

      + {t("results.detailTitle")} +

      + + {t("results.detail")} + +
      +
      + {[ + ["1st", data.results["1st"]], + ["2nd", data.results["2nd"]], + ["3rd", data.results["3rd"]], + ].map(([label, value]) => ( +
      +

      {label}

      +

      + {value} +

      +
      + ))} +
      +
      + - {showMyPayout && myTotals ? ( -
      -

      - {t("results.myPayout")} -

      -

      - {t("results.regular", { - amount: formatMinorAsCurrency(myTotals.win, currency), - })} - {myTotals.jackpot > 0 ? ( - <> - {" "} - ·{" "} - {t("results.jackpot", { - amount: formatMinorAsCurrency(myTotals.jackpot, currency), - })} - - ) : null} -

      -
      - ) : null} - {showHitOnly ? ( -

      - {t("results.hitPending")} -

      - ) : null} + {showMyPayout && myTotals ? ( +
      +

      + {t("results.myPayout")} +

      +

      + {t("results.regular", { + amount: formatMinorAsCurrency(myTotals.win, currency), + })} + {myTotals.jackpot > 0 ? ( + <> + {" "} + ·{" "} + {t("results.jackpot", { + amount: formatMinorAsCurrency(myTotals.jackpot, currency), + })} + + ) : null} +

      +
      + ) : null} -
      -

      - {t("results.hitHint")} -

      - - {t("results.viewMyWinning")} - -
      + {showHitOnly ? ( +
      + {t("results.hitPending")} +
      + ) : null} + +
      +

      + {t("results.hitHint")} +

      + + {t("results.viewMyWinning")} + +
      diff --git a/src/i18n/locales/en/player.json b/src/i18n/locales/en/player.json index 0b84a37..c5033ee 100644 --- a/src/i18n/locales/en/player.json +++ b/src/i18n/locales/en/player.json @@ -143,6 +143,9 @@ "empty": "No preview data", "draw": "Issue", "status": "Status", + "amount": "Amount", + "rebate": "Rebate", + "total": "Total", "totalBet": "Total stake", "rebateDeduct": "Rebate deduction", "actualDeduct": "Actual deduction", diff --git a/src/i18n/locales/ne/player.json b/src/i18n/locales/ne/player.json index 65c7f39..6aaef7b 100644 --- a/src/i18n/locales/ne/player.json +++ b/src/i18n/locales/ne/player.json @@ -143,6 +143,9 @@ "empty": "पूर्वावलोकन डेटा छैन", "draw": "इश्यू", "status": "स्थिति", + "amount": "रकम", + "rebate": "रिबेट", + "total": "जम्मा", "totalBet": "कुल बेट", "rebateDeduct": "रिबेट कट्टा", "actualDeduct": "वास्तविक कट्टा", diff --git a/src/i18n/locales/zh/player.json b/src/i18n/locales/zh/player.json index 3889aec..5a1338f 100644 --- a/src/i18n/locales/zh/player.json +++ b/src/i18n/locales/zh/player.json @@ -143,6 +143,9 @@ "empty": "暂无预览数据", "draw": "期号", "status": "状态", + "amount": "金额", + "rebate": "回水", + "total": "合计", "totalBet": "总下注", "rebateDeduct": "回水抵扣", "actualDeduct": "实扣金额",