diff --git a/src/components/layout/player-panel.tsx b/src/components/layout/player-panel.tsx
index 5b2ab65..ce6e1f3 100644
--- a/src/components/layout/player-panel.tsx
+++ b/src/components/layout/player-panel.tsx
@@ -22,7 +22,6 @@ type PlayerPanelProps = {
export function PlayerPanel({
title,
subtitle,
- eyebrow,
children,
backHref = "/hall",
backLabel,
@@ -49,11 +48,6 @@ export function PlayerPanel({
{resolvedBackLabel}
- {eyebrow ? (
-
- {eyebrow}
-
- ) : null}
{title}
diff --git a/src/features/hall/hall-betting-grid.tsx b/src/features/hall/hall-betting-grid.tsx
index a71b785..b5e2597 100644
--- a/src/features/hall/hall-betting-grid.tsx
+++ b/src/features/hall/hall-betting-grid.tsx
@@ -1007,7 +1007,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
- {t("hall.table.draftTotal", { defaultValue: "草稿合计" })}
+ {t("hall.table.draftTotal", { defaultValue: "投注金额" })}
{formatMinorAsCurrency(debouncedSummary.actual, currencyCode)}
diff --git a/src/features/orders/ticket-orders-list-screen.tsx b/src/features/orders/ticket-orders-list-screen.tsx
index 4e1ef36..45e37bf 100644
--- a/src/features/orders/ticket-orders-list-screen.tsx
+++ b/src/features/orders/ticket-orders-list-screen.tsx
@@ -2,7 +2,7 @@
import Link from "next/link";
import { useSearchParams } from "next/navigation";
-import { useCallback, useEffect, useMemo, useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { getTicketItems } from "@/api/ticket-items";
@@ -15,6 +15,8 @@ import { formatLotteryInstant } from "@/lib/player-datetime";
import { playLabel } from "@/lib/play-labels";
import type { TicketItemListRow } from "@/types/api/ticket-items";
+const ORDERS_PAGE_SIZE = 10;
+
export function TicketOrdersListScreen() {
const searchParams = useSearchParams();
const { t } = useTranslation("player");
@@ -30,6 +32,7 @@ export function TicketOrdersListScreen() {
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [error, setError] = useState(null);
+ const loadMoreRef = useRef(null);
const fetchPage = useCallback(
async (nextPage: number, append: boolean) => {
@@ -39,7 +42,7 @@ export function TicketOrdersListScreen() {
try {
const res = await getTicketItems({
page: nextPage,
- per_page: 20,
+ per_page: ORDERS_PAGE_SIZE,
draw_no: drawNoFilter || undefined,
});
setItems((prev) => (append ? [...prev, ...res.items] : res.items));
@@ -63,10 +66,27 @@ export function TicketOrdersListScreen() {
});
}, [fetchPage]);
- const loadMore = () => {
+ const loadMore = useCallback(() => {
if (page >= lastPage || loadingMore) return;
void fetchPage(page + 1, true);
- };
+ }, [fetchPage, lastPage, loadingMore, page]);
+
+ useEffect(() => {
+ const target = loadMoreRef.current;
+ if (!target || loading || loadingMore || page >= lastPage) return;
+
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry?.isIntersecting) {
+ loadMore();
+ }
+ },
+ { rootMargin: "160px" },
+ );
+
+ observer.observe(target);
+ return () => observer.disconnect();
+ }, [lastPage, loadMore, loading, loadingMore, page]);
return (
@@ -186,17 +206,24 @@ export function TicketOrdersListScreen() {
);
})}
+
{page < lastPage ? (
- ) : null}
+ ) : (
+
+ {t("orders.noMore", { defaultValue: "没有更多注单" })}
+
+ )}
>
)}
diff --git a/src/features/wallet/wallet-logs-block.tsx b/src/features/wallet/wallet-logs-block.tsx
index 468ab83..8f73f08 100644
--- a/src/features/wallet/wallet-logs-block.tsx
+++ b/src/features/wallet/wallet-logs-block.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useMemo } from "react";
+import { useMemo, type Ref } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
@@ -37,6 +37,10 @@ function txnStatusLabel(
type WalletLogsBlockProps = {
logs: WalletLogsData | null;
logsLoading: boolean;
+ loadingMore?: boolean;
+ hasMore?: boolean;
+ onLoadMore?: () => void;
+ loadMoreRef?: Ref;
filter: string;
onFilterChange: (value: string) => void;
currency: string;
@@ -48,6 +52,10 @@ type WalletLogsBlockProps = {
export function WalletLogsBlock({
logs,
logsLoading,
+ loadingMore = false,
+ hasMore = false,
+ onLoadMore,
+ loadMoreRef,
filter,
onFilterChange,
currency,
@@ -132,6 +140,28 @@ export function WalletLogsBlock({
))
)}
+ {logs.items.length > 0 ? (
+ <>
+
+ {hasMore ? (
+
+ ) : (
+
+ {t("wallet.noMoreLogs", { defaultValue: "没有更多流水" })}
+
+ )}
+ >
+ ) : null}
>
) : null}
diff --git a/src/features/wallet/wallet-logs-screen.tsx b/src/features/wallet/wallet-logs-screen.tsx
index 59061cd..b04ee31 100644
--- a/src/features/wallet/wallet-logs-screen.tsx
+++ b/src/features/wallet/wallet-logs-screen.tsx
@@ -9,7 +9,9 @@ import { PlayerPanel } from "@/components/layout/player-panel";
import { WalletLogsBlock } from "@/features/wallet/wallet-logs-block";
import { formatWalletClientError } from "@/lib/wallet-api-error";
import { usePlayerSessionStore } from "@/stores/player-session-store";
-import type { WalletLogsData } from "@/types/api/wallet-logs";
+import { getWalletLogsLastPage, type WalletLogsData } from "@/types/api/wallet-logs";
+
+const WALLET_LOGS_PAGE_SIZE = 10;
export function WalletLogsScreen() {
const profile = usePlayerSessionStore((s) => s.profile);
@@ -18,7 +20,9 @@ export function WalletLogsScreen() {
const [filter, setFilter] = useState("");
const [loading, setLoading] = useState(true);
const [logsLoading, setLogsLoading] = useState(false);
+ const [loadingMore, setLoadingMore] = useState(false);
const [error, setError] = useState(null);
+ const loadMoreRef = useRef(null);
const currency = useMemo(
() => (profile?.default_currency ?? "NPR").toUpperCase(),
@@ -27,35 +31,69 @@ export function WalletLogsScreen() {
const fetchPassRef = useRef(true);
- const load = useCallback(async () => {
+ const load = useCallback(async (targetPage = 1, append = false) => {
setError(null);
- if (fetchPassRef.current) {
+ if (append) {
+ setLoadingMore(true);
+ } else if (fetchPassRef.current) {
setLoading(true);
fetchPassRef.current = false;
} else {
setLogsLoading(true);
}
try {
- const L = await getWalletLogs({
- page: 1,
- size: 50,
+ const nextLogs = await getWalletLogs({
+ page: targetPage,
+ size: WALLET_LOGS_PAGE_SIZE,
type: filter || undefined,
});
- setLogs(L);
+ setLogs((current) =>
+ append && current
+ ? { ...nextLogs, items: [...current.items, ...nextLogs.items] }
+ : nextLogs,
+ );
} catch (e) {
setError(formatWalletClientError(e, t));
+ if (!append) {
+ setLogs(null);
+ }
} finally {
setLoading(false);
setLogsLoading(false);
+ setLoadingMore(false);
}
}, [filter, t]);
useEffect(() => {
queueMicrotask(() => {
- void load();
+ void load(1, false);
});
}, [load]);
+ const hasMore = logs ? logs.page < getWalletLogsLastPage(logs) : false;
+
+ const loadMore = useCallback(() => {
+ if (!logs || !hasMore || loadingMore) return;
+ void load(logs.page + 1, true);
+ }, [hasMore, load, loadingMore, logs]);
+
+ useEffect(() => {
+ const target = loadMoreRef.current;
+ if (!target || loading || logsLoading || loadingMore || !hasMore) return;
+
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry?.isIntersecting) {
+ loadMore();
+ }
+ },
+ { rootMargin: "160px" },
+ );
+
+ observer.observe(target);
+ return () => observer.disconnect();
+ }, [hasMore, loadMore, loading, loadingMore, logsLoading]);
+
return (
s.profile);
@@ -28,7 +29,9 @@ export function WalletScreen() {
const [filter, setFilter] = useState("");
const [loading, setLoading] = useState(true);
const [logsLoading, setLogsLoading] = useState(false);
+ const [loadingMore, setLoadingMore] = useState(false);
const [error, setError] = useState(null);
+ const loadMoreRef = useRef(null);
const currency = useMemo(() => {
return (
@@ -40,6 +43,20 @@ export function WalletScreen() {
const fetchPassRef = useRef(true);
+ const loadLogs = useCallback(async (targetPage = 1, append = false) => {
+ const nextLogs = await getWalletLogs({
+ page: targetPage,
+ size: WALLET_LOGS_PAGE_SIZE,
+ type: filter || undefined,
+ });
+ setLogs((current) =>
+ append && current
+ ? { ...nextLogs, items: [...current.items, ...nextLogs.items] }
+ : nextLogs,
+ );
+ return nextLogs;
+ }, [filter]);
+
useEffect(() => {
let cancelled = false;
@@ -55,13 +72,9 @@ export function WalletScreen() {
const b = await getWalletBalance();
if (cancelled) return;
setBalance(b);
- const L = await getWalletLogs({
- page: 1,
- size: 50,
- type: filter || undefined,
- });
+ const nextLogs = await loadLogs(1, false);
if (cancelled) return;
- setLogs(L);
+ setLogs(nextLogs);
} catch (e) {
if (!cancelled) {
setError(formatWalletClientError(e, t));
@@ -77,7 +90,7 @@ export function WalletScreen() {
return () => {
cancelled = true;
};
- }, [filter, t]);
+ }, [loadLogs, t]);
const refreshAll = useCallback(async () => {
setError(null);
@@ -85,19 +98,47 @@ export function WalletScreen() {
try {
const b = await getWalletBalance();
setBalance(b);
- const L = await getWalletLogs({
- page: 1,
- size: 50,
- type: filter || undefined,
- });
- setLogs(L);
+ await loadLogs(1, false);
} catch (e) {
setError(formatWalletClientError(e, t));
} finally {
setLogsLoading(false);
setLoading(false);
}
- }, [filter, t]);
+ }, [loadLogs, t]);
+
+ const hasMore = logs ? logs.page < getWalletLogsLastPage(logs) : false;
+
+ const loadMore = useCallback(() => {
+ if (!logs || !hasMore || loadingMore) return;
+
+ setError(null);
+ setLoadingMore(true);
+ void loadLogs(logs.page + 1, true)
+ .catch((e) => {
+ setError(formatWalletClientError(e, t));
+ })
+ .finally(() => {
+ setLoadingMore(false);
+ });
+ }, [hasMore, loadLogs, loadingMore, logs, t]);
+
+ useEffect(() => {
+ const target = loadMoreRef.current;
+ if (!target || loading || logsLoading || loadingMore || !hasMore) return;
+
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry?.isIntersecting) {
+ loadMore();
+ }
+ },
+ { rootMargin: "160px" },
+ );
+
+ observer.observe(target);
+ return () => observer.disconnect();
+ }, [hasMore, loadMore, loading, loadingMore, logsLoading]);
return (
@@ -159,30 +200,13 @@ export function WalletScreen() {
/>
-
-
- {t("wallet.inPage")}
-
-
- {t("wallet.outPage")}
-
-
- {t("wallet.logs")}
-
-
-
| null): number {
+ if (!logs || logs.per_page <= 0) return 1;
+ return Math.max(1, Math.ceil(logs.total / logs.per_page));
+}
+
export type GetWalletLogsParams = {
page?: number;
/** 每页条数(PRD 示例 `size`) */