feat: enhance ticket order detail and status display

- Updated ticket status display logic to handle "failed" status and improve "settled_win" condition.
- Refactored TicketOrderDetailScreen to utilize search parameters for dynamic navigation and grouping of tickets.
- Enhanced TicketOrdersListScreen to group ticket items and improve rendering of order details.
- Added new translations for order-related terms in multiple languages to support enhanced user experience.
This commit is contained in:
2026-05-25 16:02:23 +08:00
parent ca3a1db770
commit 3bcbf7d256
10 changed files with 446 additions and 30 deletions

View File

@@ -0,0 +1,10 @@
import { TicketOrderGroupScreen } from "@/features/orders/ticket-order-group-screen";
type PageProps = {
params: Promise<{ groupKey: string }>;
};
export default async function OrderGroupPage({ params }: PageProps) {
const { groupKey } = await params;
return <TicketOrderGroupScreen groupKey={groupKey} />;
}

View File

@@ -0,0 +1,131 @@
import type { TicketItemListRow } from "@/types/api/ticket-items";
export type TicketItemGroup = {
key: string;
order_no: string | null;
draw_no: string | null;
placed_at: string | null;
currency_code: string | null;
status: string;
items: TicketItemListRow[];
total_bet_amount: number;
actual_deduct_amount: number;
win_amount: number;
jackpot_win_amount: number;
};
export const ORDER_GROUP_STORAGE_PREFIX = "lottery:order-group:v1:";
/** 分组键:有 order_no 则按订单;否则 draw_no + placed_at + currency + status 兜底 */
export function getTicketItemGroupKey(row: TicketItemListRow): string {
const orderNo = (row.order_no ?? "").trim();
if (orderNo) {
return `order:${orderNo}`;
}
const drawNo = row.draw_no ?? "";
const placedAt = row.placed_at ?? "";
const currency = row.currency_code ?? "";
const status = row.status ?? "";
return `fallback:${drawNo}|${placedAt}|${currency}|${status}`;
}
export function sortTicketGroupItems(items: TicketItemListRow[]): TicketItemListRow[] {
return [...items].sort((a, b) => {
const byPlay = a.play_code.localeCompare(b.play_code);
if (byPlay !== 0) return byPlay;
return (a.original_number ?? "").localeCompare(b.original_number ?? "");
});
}
/** 保持 API 返回顺序下,按分组键首次出现顺序输出合并组 */
export function groupTicketItems(items: TicketItemListRow[]): TicketItemGroup[] {
const map = new Map<string, TicketItemGroup>();
const order: string[] = [];
for (const row of items) {
const key = getTicketItemGroupKey(row);
let group = map.get(key);
if (!group) {
const orderNo = (row.order_no ?? "").trim();
group = {
key,
order_no: orderNo || null,
draw_no: row.draw_no ?? null,
placed_at: row.placed_at ?? null,
currency_code: row.currency_code ?? null,
status: row.status,
items: [],
total_bet_amount: 0,
actual_deduct_amount: 0,
win_amount: 0,
jackpot_win_amount: 0,
};
map.set(key, group);
order.push(key);
}
group.items.push(row);
group.total_bet_amount += row.total_bet_amount;
group.actual_deduct_amount += row.actual_deduct_amount;
group.win_amount += row.win_amount;
group.jackpot_win_amount += row.jackpot_win_amount;
}
return order.map((key) => {
const group = map.get(key)!;
return { ...group, items: sortTicketGroupItems(group.items) };
});
}
export function persistOrderGroup(group: TicketItemGroup): void {
try {
sessionStorage.setItem(
ORDER_GROUP_STORAGE_PREFIX + group.key,
JSON.stringify(group),
);
} catch {
/* quota / private mode */
}
}
export function loadPersistedOrderGroup(groupKey: string): TicketItemGroup | null {
try {
const raw = sessionStorage.getItem(ORDER_GROUP_STORAGE_PREFIX + groupKey);
if (!raw) return null;
const parsed = JSON.parse(raw) as TicketItemGroup;
if (!parsed?.key || !Array.isArray(parsed.items)) return null;
return { ...parsed, items: sortTicketGroupItems(parsed.items) };
} catch {
return null;
}
}
export function orderGroupPath(groupKey: string, ticketNos?: string[]): string {
const base = `/orders/group/${encodeURIComponent(groupKey)}`;
if (!ticketNos?.length) return base;
return `${base}?tickets=${encodeURIComponent(ticketNos.join(","))}`;
}
export function orderGroupHref(group: TicketItemGroup): string {
if (group.items.length === 1) {
return `/orders/${encodeURIComponent(group.items[0].ticket_no)}`;
}
return orderGroupPath(
group.key,
group.items.map((i) => i.ticket_no),
);
}
/** 注项详情;来自订单组时带上 fromGroup便于返回订单详情 */
export function ticketDetailHref(
ticketNo: string,
fromGroup?: TicketItemGroup | null,
): string {
const base = `/orders/${encodeURIComponent(ticketNo)}`;
const key = (fromGroup?.key ?? "").trim();
if (!key) return base;
const params = new URLSearchParams({ fromGroup: key });
if (fromGroup && fromGroup.items.length > 1) {
params.set("tickets", fromGroup.items.map((i) => i.ticket_no).join(","));
}
return `${base}?${params.toString()}`;
}

View File

@@ -0,0 +1,27 @@
"use client";
import type { TFunction } from "i18next";
import { formatLotteryInstant } from "@/lib/player-datetime";
type OrderMetaLineProps = {
orderNo: string | null | undefined;
placedAt: string | null | undefined;
t: TFunction<"player">;
className?: string;
};
/** 订单号 + 下单时间(与 hall.result.orderNo 文案一致) */
export function OrderMetaLine({ orderNo, placedAt, t, className }: OrderMetaLineProps) {
const trimmed = (orderNo ?? "").trim();
const displayNo = trimmed || t("orders.noOrderNo");
return (
<p className={className ?? "mt-1 truncate text-xs text-slate-500"}>
<span>{t("orders.orderNoLabel")}</span>{" "}
<span className="font-mono font-semibold text-slate-600">{displayNo}</span>
<span aria-hidden> · </span>
<span>{formatLotteryInstant(placedAt ?? null)}</span>
</p>
);
}

View File

@@ -19,13 +19,16 @@ export function ticketStatusDisplay(
if (status === "settled_win" && total > 0) {
return { label: t?.("ticketStatus.settled_win") ?? status, dotClass: "bg-emerald-500" };
}
if (status === "settled_lose" || status === "settled_win") {
if (status === "settled_lose" || (status === "settled_win" && total <= 0)) {
return {
label: t?.("ticketStatus.settled_lose") ?? status,
dotClass: "bg-background",
ring: true,
};
}
if (status === "failed") {
return { label: t?.("ticketStatus.failed") ?? status, dotClass: "bg-red-500" };
}
return {
label: t?.("ticketStatus.unknown", { status, defaultValue: status }) ?? status,
dotClass: "bg-red-500",

View File

@@ -1,7 +1,8 @@
"use client";
import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { getTicketItemDetail } from "@/api/ticket-items";
@@ -17,6 +18,7 @@ import {
import { Skeleton } from "@/components/ui/skeleton";
import { TwentyThreeResultsGrid } from "@/features/results/twenty-three-results-grid";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { orderGroupPath } from "@/features/orders/group-ticket-items";
import { StatusDot, ticketStatusDisplay } from "@/features/orders/ticket-item-status";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { formatMinorAsCurrency } from "@/lib/money";
@@ -67,6 +69,7 @@ type TicketItemDetailWithExtras = TicketItemDetailPayload & {
/** 界面文档 §4.8 注单详情 */
export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
const searchParams = useSearchParams();
const { t } = useTranslation("player");
const { activeCurrency } = useActivePlayerCurrency();
useCurrencyCatalog();
@@ -74,6 +77,27 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const fromGroupKey = useMemo(
() => (searchParams.get("fromGroup") ?? "").trim(),
[searchParams],
);
const groupTickets = useMemo(
() => (searchParams.get("tickets") ?? "").trim(),
[searchParams],
);
const backNav = useMemo(() => {
if (!fromGroupKey) {
return { href: "/orders", label: t("orders.title") };
}
const ticketNos = groupTickets
? groupTickets.split(",").map((s) => s.trim()).filter(Boolean)
: undefined;
return {
href: orderGroupPath(fromGroupKey, ticketNos),
label: t("orders.groupDetail"),
};
}, [fromGroupKey, groupTickets, t]);
const load = useCallback(async () => {
setLoading(true);
setError(null);
@@ -98,8 +122,8 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
return (
<PlayerPanel
title={t("orders.betDetail")}
backHref="/orders"
backLabel={t("orders.title")}
backHref={backNav.href}
backLabel={backNav.label}
>
<div className="space-y-3">
<Skeleton className="h-12 rounded-xl" />
@@ -113,8 +137,8 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
return (
<PlayerPanel
title={t("orders.betDetail")}
backHref="/orders"
backLabel={t("orders.title")}
backHref={backNav.href}
backLabel={backNav.label}
>
<div className="rounded-xl border border-red-200 bg-red-50 px-3 py-3 text-sm text-red-700">
<p>{error ?? t("orders.noData")}</p>
@@ -174,8 +198,8 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
return (
<PlayerPanel
title={t("orders.betDetail")}
backHref="/orders"
backLabel={t("orders.title")}
backHref={backNav.href}
backLabel={backNav.label}
>
<div className="flex flex-col gap-3">
<Card className="ring-0 border border-[#e8eef7] bg-white shadow-[0_8px_28px_rgba(15,23,42,0.05)]">
@@ -368,12 +392,12 @@ export function TicketOrderDetailScreen({ ticketNo }: { ticketNo: string }) {
</Link>
) : null}
<Link
href="/orders"
href={backNav.href}
className={cn(
"inline-flex h-11 min-w-[140px] flex-1 items-center justify-center rounded-xl border border-[#dce7f7] bg-white px-4 text-sm font-semibold text-[#07459f] transition-colors hover:bg-[#f1f6ff]",
)}
>
{t("orders.backToOrders")}
{fromGroupKey ? t("orders.backToGroup") : t("orders.backToOrders")}
</Link>
</div>
</div>

View File

@@ -0,0 +1,167 @@
"use client";
import Link from "next/link";
import { useMemo } from "react";
import { ChevronRight } from "lucide-react";
import { useTranslation } from "react-i18next";
import { PlayerPanel } from "@/components/layout/player-panel";
import { OrderMetaLine } from "@/features/orders/order-meta-line";
import { StatusDot, ticketStatusDisplay } from "@/features/orders/ticket-item-status";
import {
loadPersistedOrderGroup,
ticketDetailHref,
type TicketItemGroup,
} from "@/features/orders/group-ticket-items";
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { formatMinorAsCurrency } from "@/lib/money";
import { playLabel } from "@/lib/play-labels";
import { cn } from "@/lib/utils";
type TicketOrderGroupScreenProps = {
groupKey: string;
};
export function TicketOrderGroupScreen({ groupKey }: TicketOrderGroupScreenProps) {
const { t } = useTranslation("player");
const { activeCurrency } = useActivePlayerCurrency();
useCurrencyCatalog();
const decodedKey = decodeURIComponent(groupKey);
const group = useMemo(
(): TicketItemGroup | null => loadPersistedOrderGroup(decodedKey),
[decodedKey],
);
if (!group) {
return (
<PlayerPanel
title={t("orders.groupDetail")}
backHref="/orders"
backLabel={t("orders.title")}
>
<div className="rounded-xl border border-dashed border-[#dce7f7] bg-[#f8fbff] px-3 py-8 text-center">
<p className="text-sm font-bold text-slate-700">{t("orders.groupNotFound")}</p>
<Link
href="/orders"
className="mt-4 inline-flex h-9 items-center rounded-lg bg-[#07459f] px-4 text-sm font-bold text-white"
>
{t("orders.backToOrders")}
</Link>
</div>
</PlayerPanel>
);
}
const cur = group.currency_code ?? activeCurrency;
const st = ticketStatusDisplay(
group.status,
group.win_amount,
group.jackpot_win_amount,
t,
);
const totalWin = group.win_amount + group.jackpot_win_amount;
return (
<PlayerPanel
title={t("orders.groupDetail")}
backHref="/orders"
backLabel={t("orders.title")}
>
<div className="space-y-3">
<div className="rounded-xl border border-[#e5edf8] bg-white p-3 shadow-[0_8px_24px_rgba(15,23,42,0.05)]">
<div className="flex items-start justify-between gap-3">
<p className="truncate font-mono text-lg font-black text-[#0b3f96]">
{group.draw_no ?? "—"}
</p>
<StatusDot label={st.label} dotClass={st.dotClass} ring={st.ring} />
</div>
<OrderMetaLine orderNo={group.order_no} placedAt={group.placed_at} t={t} />
<p className="mt-2 text-[11px] font-bold uppercase tracking-wide text-[#7890b8]">
{t("orders.itemCount", { count: group.items.length })}
</p>
<div className="mt-3 grid grid-cols-2 gap-2">
<div className="rounded-lg bg-[#f8fbff] px-3 py-2">
<p className="text-[10px] font-bold uppercase text-[#7890b8]">{t("orders.stake")}</p>
<p className="mt-1 text-sm font-black text-slate-900">
{formatMinorAsCurrency(group.total_bet_amount, cur)}
</p>
</div>
<div className="rounded-lg bg-[#f8fbff] px-3 py-2">
<p className="text-[10px] font-bold uppercase text-[#7890b8]">{t("orders.deduction")}</p>
<p className="mt-1 text-sm font-black text-[#0b3f96]">
{formatMinorAsCurrency(group.actual_deduct_amount, cur)}
</p>
</div>
</div>
{totalWin > 0 && group.status === "settled_win" ? (
<p className="mt-2 text-xs font-bold text-emerald-600">
{t("orders.win", { amount: formatMinorAsCurrency(totalWin, cur) })}
</p>
) : null}
</div>
<p className="px-0.5 text-sm font-black text-[#0b3f96]">{t("orders.betItems")}</p>
<div className="space-y-2">
{group.items.map((row, index) => {
const lineCur = row.currency_code ?? cur;
const lineSt = ticketStatusDisplay(
row.status,
row.win_amount,
row.jackpot_win_amount,
t,
);
const lineWin = row.win_amount + row.jackpot_win_amount;
return (
<Link
key={row.ticket_no}
href={ticketDetailHref(row.ticket_no, group)}
aria-label={t("orders.viewBetLine")}
className={cn(
"flex items-center gap-3 rounded-xl border border-[#e5edf8] bg-white px-3 py-3",
"shadow-[0_6px_18px_rgba(15,23,42,0.04)] transition-colors hover:border-[#b9ccf6]",
)}
>
<span className="flex size-7 shrink-0 items-center justify-center rounded-full bg-[#eaf2ff] text-xs font-black text-[#0b56b7]">
{index + 1}
</span>
<div className="min-w-0 flex-1">
<p className="text-sm font-black text-[#0b3f96]">
{playLabel(row.play_code, t)} · {row.original_number ?? row.play_code}
</p>
<p className="mt-0.5 truncate font-mono text-[11px] text-slate-500">
{t("orders.ticketNo", { ticketNo: row.ticket_no })}
</p>
<div className="mt-2 flex flex-wrap gap-x-3 gap-y-1 text-xs text-slate-600">
<span>
{t("orders.stake")}{" "}
<span className="font-bold tabular-nums text-slate-900">
{formatMinorAsCurrency(row.total_bet_amount, lineCur)}
</span>
</span>
<span>
{t("orders.deduction")}{" "}
<span className="font-bold tabular-nums text-[#0b3f96]">
{formatMinorAsCurrency(row.actual_deduct_amount, lineCur)}
</span>
</span>
</div>
{lineWin > 0 && row.status === "settled_win" ? (
<p className="mt-1 text-xs font-bold text-emerald-600">
{t("orders.win", { amount: formatMinorAsCurrency(lineWin, lineCur) })}
</p>
) : null}
</div>
<div className="flex shrink-0 flex-col items-end gap-1">
<StatusDot label={lineSt.label} dotClass={lineSt.dotClass} ring={lineSt.ring} />
<ChevronRight className="size-4 text-[#7890b8]" />
</div>
</Link>
);
})}
</div>
</div>
</PlayerPanel>
);
}

View File

@@ -14,11 +14,16 @@ import { Input } from "@/components/ui/input";
import { PlayerPanel } from "@/components/layout/player-panel";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Skeleton } from "@/components/ui/skeleton";
import {
groupTicketItems,
orderGroupHref,
persistOrderGroup,
} from "@/features/orders/group-ticket-items";
import { OrderMetaLine } from "@/features/orders/order-meta-line";
import { StatusDot, ticketStatusDisplay } from "@/features/orders/ticket-item-status";
import { useCurrencyCatalog } from "@/hooks/use-currency-catalog";
import { useIsMobile } from "@/hooks/use-mobile";
import { formatMinorAsCurrency } from "@/lib/money";
import { formatLotteryInstant } from "@/lib/player-datetime";
import { useActivePlayerCurrency } from "@/hooks/use-active-player-currency";
import { playLabel } from "@/lib/play-labels";
import { cn } from "@/lib/utils";
@@ -92,6 +97,7 @@ export function TicketOrdersListScreen() {
return `${fromDate ? formatCompactDate(fromDate) : "..." } ~ ${toDate ? formatCompactDate(toDate) : "..."}`;
}, [formatCompactDate, fromDate, t, toDate]);
const visiblePages = useMemo(() => buildPageWindow(page, lastPage), [lastPage, page]);
const orderGroups = useMemo(() => groupTicketItems(items), [items]);
const fetchPage = useCallback(
async (nextPage: number, append: boolean) => {
@@ -354,49 +360,62 @@ export function TicketOrdersListScreen() {
) : (
<>
<div className="space-y-3">
{items.map((row) => {
const cur = row.currency_code ?? activeCurrency;
const st = ticketStatusDisplay(row.status, row.win_amount, row.jackpot_win_amount, t);
const totalWin = row.win_amount + row.jackpot_win_amount;
{orderGroups.map((group) => {
const cur = group.currency_code ?? activeCurrency;
const st = ticketStatusDisplay(
group.status,
group.win_amount,
group.jackpot_win_amount,
t,
);
const totalWin = group.win_amount + group.jackpot_win_amount;
return (
<Link
key={row.ticket_no}
href={`/orders/${encodeURIComponent(row.ticket_no)}`}
key={group.key}
href={orderGroupHref(group)}
onClick={() => persistOrderGroup(group)}
className="block rounded-xl border border-[#e5edf8] bg-white p-3 shadow-[0_8px_24px_rgba(15,23,42,0.05)] transition-colors hover:border-[#b9ccf6]"
>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p className="truncate font-mono text-sm font-black text-[#0b3f96]">
{row.draw_no ?? "—"}
</p>
<p className="mt-1 truncate text-xs text-slate-500">
<p className="min-w-0 truncate font-mono text-sm font-black text-[#0b3f96]">
{group.draw_no ?? "—"}
</p>
<StatusDot label={st.label} dotClass={st.dotClass} ring={st.ring} />
</div>
<OrderMetaLine
orderNo={group.order_no}
placedAt={group.placed_at}
t={t}
/>
<div className="mt-2.5 space-y-1">
{group.items.map((row) => (
<p
key={row.ticket_no}
className="text-sm font-semibold text-[#32518d]"
>
{playLabel(row.play_code, t)} · {row.original_number ?? row.play_code}
</p>
</div>
<StatusDot label={st.label} dotClass={st.dotClass} ring={st.ring} />
))}
</div>
<div className="mt-3 grid grid-cols-2 gap-2">
<div className="rounded-lg bg-[#f8fbff] px-3 py-2">
<p className="text-[10px] font-bold uppercase text-[#7890b8]">{t("orders.stake")}</p>
<p className="mt-1 text-sm font-black text-slate-900">
{formatMinorAsCurrency(row.total_bet_amount, cur)}
{formatMinorAsCurrency(group.total_bet_amount, cur)}
</p>
</div>
<div className="rounded-lg bg-[#f8fbff] px-3 py-2">
<p className="text-[10px] font-bold uppercase text-[#7890b8]">{t("orders.deduction")}</p>
<p className="mt-1 text-sm font-black text-[#0b3f96]">
{formatMinorAsCurrency(row.actual_deduct_amount, cur)}
{formatMinorAsCurrency(group.actual_deduct_amount, cur)}
</p>
</div>
</div>
{totalWin > 0 && row.status === "settled_win" ? (
{totalWin > 0 && group.status === "settled_win" ? (
<p className="mt-2 text-xs font-bold text-emerald-600">
{t("orders.win", { amount: formatMinorAsCurrency(totalWin, cur) })}
</p>
) : null}
<p className="mt-2 text-[11px] text-slate-500">
{formatLotteryInstant(row.placed_at ?? null)}
</p>
</Link>
);
})}

View File

@@ -396,6 +396,14 @@
"detailTitle": "Ticket detail",
"ticketNo": "Ticket {{ticketNo}}",
"orderNo": "Order {{orderNo}}",
"orderNoLabel": "Order No.",
"orderNoAt": "Order No. {{orderNo}} · {{time}}",
"noOrderNo": "—",
"groupDetail": "Order detail",
"groupNotFound": "Could not load this order. Please open it again from My Bets.",
"betItems": "Bet lines",
"itemCount": "{{count}} bet line(s)",
"viewBetLine": "View bet line detail",
"drawNo": "Issue",
"placedAt": "Placed at",
"number": "Number",
@@ -421,6 +429,7 @@
"settledAt": "Settled at {{time}}",
"viewDraw": "View this draw",
"backToOrders": "Back to My Bets",
"backToGroup": "Back to order detail",
"notFound": "Ticket does not exist or cannot be viewed.",
"noData": "No data",
"loadFailed": "Failed to load"
@@ -555,6 +564,7 @@
"pending_payout": "Won, pending payout",
"settled_win": "Paid",
"settled_lose": "Not won",
"failed": "Failed",
"unknown": "{{status}}"
},
"prizeTier": {

View File

@@ -396,6 +396,14 @@
"detailTitle": "टिकट विवरण",
"ticketNo": "टिकट {{ticketNo}}",
"orderNo": "अर्डर {{orderNo}}",
"orderNoLabel": "अर्डर नं.",
"orderNoAt": "अर्डर नं. {{orderNo}} · {{time}}",
"noOrderNo": "—",
"groupDetail": "अर्डर विवरण",
"groupNotFound": "यो अर्डर लोड हुन सकेन। कृपया मेरा बेटबाट फेरि खोल्नुहोस्।",
"betItems": "बेट लाइन विवरण",
"itemCount": "जम्मा {{count}} बेट लाइन",
"viewBetLine": "बेट लाइन विवरण हेर्नुहोस्",
"drawNo": "इश्यू",
"placedAt": "राखेको समय",
"number": "नम्बर",
@@ -413,9 +421,15 @@
"jackpotAmount": "Jackpot {{amount}}",
"payoutTotal": "कुल भुक्तानी {{amount}}",
"matchLose": "मिलान नतिजा: जितेन",
"matchResult": "मिलान नतिजा",
"drawPendingMatch": "यस इश्यूका ड्र नम्बर प्रकाशित भएका छैनन्। जित-नजित अझै निर्धारण गर्न मिल्दैन।",
"matchPendingDraw": "ड्र पर्खँदैछ। जित-नजित अझै निर्धारण गर्न मिल्दैन।",
"matchPendingSettlement": "ड्र नतिजा प्रकाशित भयो। सेटल पछि जित-नजित देखाइनेछ।",
"timeline": "समयरेखा",
"settledAt": "सेटल समय {{time}}",
"viewDraw": "यो ड्र हेर्नुहोस्",
"backToOrders": "मेरा बेटमा फर्कनुहोस्",
"backToGroup": "अर्डर विवरणमा फर्कनुहोस्",
"notFound": "टिकट छैन वा हेर्न अनुमति छैन।",
"noData": "डेटा छैन",
"loadFailed": "लोड असफल"
@@ -550,6 +564,7 @@
"pending_payout": "जितेको, भुक्तानी बाँकी",
"settled_win": "भुक्तानी भयो",
"settled_lose": "जितेन",
"failed": "असफल",
"unknown": "{{status}}"
},
"prizeTier": {

View File

@@ -396,6 +396,14 @@
"detailTitle": "注单详情",
"ticketNo": "注单号 {{ticketNo}}",
"orderNo": "订单 {{orderNo}}",
"orderNoLabel": "订单号",
"orderNoAt": "订单号 {{orderNo}} · {{time}}",
"noOrderNo": "—",
"groupDetail": "订单详情",
"groupNotFound": "无法加载该订单,请从注单列表重新进入。",
"betItems": "注项明细",
"itemCount": "共 {{count}} 条注项",
"viewBetLine": "查看注项详情",
"drawNo": "期号",
"placedAt": "下单时间",
"number": "号码",
@@ -421,6 +429,7 @@
"settledAt": "结算时间 {{time}}",
"viewDraw": "查看本期开奖",
"backToOrders": "返回我的注单",
"backToGroup": "返回订单详情",
"notFound": "注单不存在或无权查看",
"noData": "无数据",
"loadFailed": "加载失败"
@@ -555,6 +564,7 @@
"pending_payout": "已中奖待派彩",
"settled_win": "已派彩",
"settled_lose": "未中奖",
"failed": "失败",
"unknown": "{{status}}"
},
"prizeTier": {