feat: 优化注单与钱包流水分页加载体验
- 注单列表与钱包流水支持 10 条分页、滚动触底自动加载和手动加载更多 - 新增钱包流水无更多数据提示与分页末页计算工具 - 精简钱包首页快捷入口与页面标题眉标展示 - 将下注表格草稿合计文案调整为投注金额并同步多语言翻译
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user