perf: make mileage pagination explicit
This commit is contained in:
@@ -492,6 +492,7 @@ export default function MonitoringView() {
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [loadingMore, setLoadingMore] = useState(false);
|
||||
const [pageLoading, setPageLoading] = useState(true);
|
||||
const [pageError, setPageError] = useState<{ message: string; scope: 'first' | 'more' } | null>(null);
|
||||
const [manualRefreshing, setManualRefreshing] = useState(false);
|
||||
const [lastRefreshedAt, setLastRefreshedAt] = useState<Date | null>(null);
|
||||
const [showBackToTop, setShowBackToTop] = useState(false);
|
||||
@@ -566,6 +567,7 @@ export default function MonitoringView() {
|
||||
// 加载首页数据
|
||||
const loadFirstPage = useCallback((showPageLoading = true) => {
|
||||
if (showPageLoading) setPageLoading(true);
|
||||
setPageError(null);
|
||||
return fetchMonitoring({
|
||||
sortBy,
|
||||
sortOrder,
|
||||
@@ -597,7 +599,12 @@ export default function MonitoringView() {
|
||||
setPage(1);
|
||||
setHasMore(d.page < d.totalPages);
|
||||
setLastRefreshedAt(new Date());
|
||||
}).catch(() => {}).finally(() => {
|
||||
}).catch(error => {
|
||||
setPageError({
|
||||
message: error instanceof Error ? error.message : '里程数据加载失败,请稍后重试',
|
||||
scope: 'first',
|
||||
});
|
||||
}).finally(() => {
|
||||
if (showPageLoading) setPageLoading(false);
|
||||
});
|
||||
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, appliedMileageRange, rangeStart, rangeEnd, filterBrands, sourcePriority]);
|
||||
@@ -617,6 +624,7 @@ export default function MonitoringView() {
|
||||
if (loadingMore || !hasMore) return;
|
||||
const nextPage = page + 1;
|
||||
setLoadingMore(true);
|
||||
setPageError(null);
|
||||
fetchMonitoring({
|
||||
sortBy,
|
||||
sortOrder,
|
||||
@@ -642,7 +650,12 @@ export default function MonitoringView() {
|
||||
setVehicles(prev => [...prev, ...d.vehicles]);
|
||||
setPage(nextPage);
|
||||
setHasMore(nextPage < d.totalPages);
|
||||
}).catch(() => {}).finally(() => setLoadingMore(false));
|
||||
}).catch(error => {
|
||||
setPageError({
|
||||
message: error instanceof Error ? error.message : '更多车辆加载失败,请稍后重试',
|
||||
scope: 'more',
|
||||
});
|
||||
}).finally(() => setLoadingMore(false));
|
||||
}, [sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterProject, filterEntity, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, appliedMileageRange, rangeStart, rangeEnd, page, loadingMore, hasMore, filterBrands, sourcePriority]);
|
||||
|
||||
// 筛选/排序变化时重新加载
|
||||
@@ -699,26 +712,6 @@ export default function MonitoringView() {
|
||||
return () => clearInterval(timer);
|
||||
}, [loadFirstPage]);
|
||||
|
||||
// 触底检测:用 IntersectionObserver 监听哨兵元素
|
||||
const loadMoreRef = useRef(loadMore);
|
||||
loadMoreRef.current = loadMore;
|
||||
const sentinelRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const sentinel = sentinelRef.current;
|
||||
if (!sentinel) return;
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
loadMoreRef.current();
|
||||
}
|
||||
},
|
||||
{ rootMargin: '200px' }
|
||||
);
|
||||
observer.observe(sentinel);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
// 回到顶部按钮:用 IntersectionObserver 检测顶部哨兵是否离开视口
|
||||
const topSentinelRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
@@ -1500,7 +1493,9 @@ export default function MonitoringView() {
|
||||
</div>
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<span className="text-[9px] font-black text-slate-400 uppercase tracking-widest">车辆详情清单</span>
|
||||
<span className="text-[9px] font-bold text-slate-300">{total} 条</span>
|
||||
<span className="text-[9px] font-bold text-slate-400">
|
||||
已加载 {filteredVehicles.length} / 共 {total} 条
|
||||
</span>
|
||||
</div>
|
||||
<div className="hidden grid-cols-[minmax(190px,1.1fr)_minmax(160px,1fr)_130px_130px] items-center gap-3 rounded-lg border border-slate-200 bg-white px-4 py-2 text-[10px] font-bold text-slate-400 md:grid">
|
||||
<span>车牌 / 数据状态</span>
|
||||
@@ -1615,16 +1610,40 @@ export default function MonitoringView() {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{filteredVehicles.length === 0 && !loadingMore && (
|
||||
{filteredVehicles.length === 0 && !loadingMore && !pageError && (
|
||||
<div className="py-10 text-center bg-white rounded-2xl border border-dashed border-slate-100">
|
||||
<p className="text-xs font-bold text-slate-300">未找到匹配数据</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 加载更多提示 */}
|
||||
{loadingMore && (
|
||||
<div className="py-4 text-center">
|
||||
<span className="text-[10px] font-bold text-slate-400">加载中...</span>
|
||||
{pageError && (
|
||||
<div className="rounded-xl border border-rose-100 bg-rose-50 px-3 py-3 text-center">
|
||||
<p className="text-[10px] font-bold text-rose-600">{pageError.message}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => pageError.scope === 'more' ? loadMore() : loadFirstPage()}
|
||||
disabled={pageLoading || loadingMore}
|
||||
className="mt-2 inline-flex h-8 items-center gap-1.5 rounded-lg border border-rose-200 bg-white px-3 text-[10px] font-black text-rose-700 transition-colors hover:bg-rose-100 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
<RefreshCw size={12} className={pageLoading || loadingMore ? 'animate-spin' : ''} />
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{hasMore && !pageError && filteredVehicles.length > 0 && (
|
||||
<div className="flex flex-col items-center gap-1.5 py-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={loadMore}
|
||||
disabled={loadingMore}
|
||||
className="inline-flex h-9 items-center gap-1.5 rounded-lg border border-slate-200 bg-white px-4 text-[11px] font-black text-slate-700 shadow-sm transition-colors hover:border-blue-200 hover:text-blue-600 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
{loadingMore ? <RefreshCw size={13} className="animate-spin" /> : <ChevronDown size={14} />}
|
||||
{loadingMore ? '加载中' : '加载更多'}
|
||||
</button>
|
||||
<span className="text-[9px] font-bold text-slate-300">
|
||||
已加载 {filteredVehicles.length} / 共 {total} 条
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!hasMore && filteredVehicles.length > 0 && (
|
||||
@@ -1632,8 +1651,6 @@ export default function MonitoringView() {
|
||||
<span className="text-[10px] font-bold text-slate-300">已加载全部 {total} 条</span>
|
||||
</div>
|
||||
)}
|
||||
{/* 哨兵元素:进入视口时触发加载更多 */}
|
||||
<div ref={sentinelRef} className="h-1" />
|
||||
</div>
|
||||
|
||||
<VehicleDetailModal
|
||||
|
||||
Reference in New Issue
Block a user