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

@@ -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<string | null>(null);
const loadMoreRef = useRef<HTMLDivElement | null>(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 (
<PlayerPanel title={t("orders.title")} subtitle={t("orders.subtitle")} eyebrow={t("brand.name")}>
@@ -186,17 +206,24 @@ export function TicketOrdersListScreen() {
);
})}
</div>
<div ref={loadMoreRef} className="min-h-1" />
{page < lastPage ? (
<Button
type="button"
size="sm"
className="h-10 w-full rounded-lg bg-[#07459f] text-white hover:bg-[#063b88]"
variant="outline"
className="h-10 w-full rounded-xl border-[#dce7f7] bg-white text-sm font-bold text-[#32518d] hover:bg-[#f8fbff]"
disabled={loadingMore}
onClick={() => loadMore()}
onClick={loadMore}
>
{loadingMore ? t("actions.loading") : t("actions.loadMore")}
{loadingMore
? t("actions.loading", { defaultValue: "加载中..." })
: t("actions.loadMore", { defaultValue: "加载更多" })}
</Button>
) : null}
) : (
<p className="py-2 text-center text-xs text-slate-400">
{t("orders.noMore", { defaultValue: "没有更多注单" })}
</p>
)}
</>
)}
</div>