feat: 分页组件统一封装 + 移动端无限滚动

- 新增 PaginationBar 组件,支持 isDark / loading / 页码导航 / 每页大小切换
- 重写 pay/orders/page.tsx 使用 PaginationBar,summary 来自 API groupBy 全量统计
- admin/page.tsx 替换内联分页 UI 为 PaginationBar
- MobileOrderList 支持无限滚动:IntersectionObserver 哨兵 + hasMore/loadingMore props
- pay/page.tsx 新增 loadMoreOrders(append 模式),初始化/刷新时重置分页状态
This commit is contained in:
erio
2026-03-01 20:12:32 +08:00
parent 292c14b882
commit b4eebb0b1b
5 changed files with 251 additions and 194 deletions

View File

@@ -46,6 +46,9 @@ function PayContent() {
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
const [resolvedUserId, setResolvedUserId] = useState<number | null>(null);
const [myOrders, setMyOrders] = useState<MyOrder[]>([]);
const [ordersPage, setOrdersPage] = useState(1);
const [ordersHasMore, setOrdersHasMore] = useState(false);
const [ordersLoadingMore, setOrdersLoadingMore] = useState(false);
const [activeMobileTab, setActiveMobileTab] = useState<'pay' | 'orders'>('pay');
const [config, setConfig] = useState<AppConfig>({
@@ -113,8 +116,12 @@ function PayContent() {
if (Array.isArray(meData.orders)) {
setMyOrders(meData.orders);
setOrdersPage(1);
setOrdersHasMore((meData.total_pages ?? 1) > 1);
} else {
setMyOrders([]);
setOrdersPage(1);
setOrdersHasMore(false);
}
return;
}
@@ -123,11 +130,35 @@ function PayContent() {
// 无 token 或 token 失效:只显示用户 ID不展示隐私信息
setUserInfo({ id: userId, username: `用户 #${userId}`, balance: 0 });
setMyOrders([]);
setOrdersPage(1);
setOrdersHasMore(false);
} catch {
// ignore and keep page usable
}
};
const loadMoreOrders = async () => {
if (!token || ordersLoadingMore || !ordersHasMore) return;
const nextPage = ordersPage + 1;
setOrdersLoadingMore(true);
try {
const res = await fetch(`/api/orders/my?token=${encodeURIComponent(token)}&page=${nextPage}&page_size=20`);
if (!res.ok) return;
const data = await res.json();
if (Array.isArray(data.orders) && data.orders.length > 0) {
setMyOrders((prev) => [...prev, ...data.orders]);
setOrdersPage(nextPage);
setOrdersHasMore(nextPage < (data.total_pages ?? 1));
} else {
setOrdersHasMore(false);
}
} catch {
// ignore
} finally {
setOrdersLoadingMore(false);
}
};
useEffect(() => {
loadUserAndOrders();
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -329,7 +360,10 @@ function PayContent() {
isDark={isDark}
hasToken={hasToken}
orders={myOrders}
hasMore={ordersHasMore}
loadingMore={ordersLoadingMore}
onRefresh={loadUserAndOrders}
onLoadMore={loadMoreOrders}
/>
)
) : (