feat: 优化注单与钱包流水分页加载体验

- 注单列表与钱包流水支持 10 条分页、滚动触底自动加载和手动加载更多
- 新增钱包流水无更多数据提示与分页末页计算工具
- 精简钱包首页快捷入口与页面标题眉标展示
- 将下注表格草稿合计文案调整为投注金额并同步多语言翻译
This commit is contained in:
2026-05-15 16:52:25 +08:00
parent 7472a61db0
commit 01baf9c18b
10 changed files with 187 additions and 65 deletions

View File

@@ -1,7 +1,6 @@
"use client";
import { Wallet } from "lucide-react";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -18,7 +17,9 @@ import { formatMinorAsCurrency } from "@/lib/money";
import { formatWalletClientError } from "@/lib/wallet-api-error";
import { usePlayerSessionStore } from "@/stores/player-session-store";
import type { WalletBalanceData } from "@/types/api/wallet-balance";
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 WalletScreen() {
const profile = usePlayerSessionStore((s) => 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<string | null>(null);
const loadMoreRef = useRef<HTMLDivElement | null>(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 (
<PlayerPanel title={t("wallet.title")} subtitle={t("wallet.subtitle")} eyebrow={t("brand.name")}>
@@ -159,30 +200,13 @@ export function WalletScreen() {
/>
</div>
<div className="grid grid-cols-3 gap-2 text-center text-xs font-bold">
<Link
className="rounded-lg border border-[#e5edf8] bg-[#f8fbff] py-2 text-[#0b56b7]"
href="/wallet/transfer-in"
>
{t("wallet.inPage")}
</Link>
<Link
className="rounded-lg border border-[#e5edf8] bg-[#f8fbff] py-2 text-[#0b56b7]"
href="/wallet/transfer-out"
>
{t("wallet.outPage")}
</Link>
<Link
className="rounded-lg border border-[#e5edf8] bg-[#f8fbff] py-2 text-[#0b56b7]"
href="/wallet/logs"
>
{t("wallet.logs")}
</Link>
</div>
<WalletLogsBlock
logs={logs}
logsLoading={loading || logsLoading}
loadingMore={loadingMore}
hasMore={hasMore}
onLoadMore={loadMore}
loadMoreRef={loadMoreRef}
filter={filter}
onFilterChange={setFilter}
currency={currency}