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