fix: bind mileage list to request context
This commit is contained in:
@@ -177,6 +177,7 @@ SUM(fee) / NULLIF(SUM(kwh), 0)
|
|||||||
- 里程考核归因车辆与 7 天趋势已提供独立加载、故障、空数据和重试状态;子请求失败时停止展示“0 台未达标”和“最新日变化 +0”等伪业务结果。
|
- 里程考核归因车辆与 7 天趋势已提供独立加载、故障、空数据和重试状态;子请求失败时停止展示“0 台未达标”和“最新日变化 +0”等伪业务结果。
|
||||||
- 里程考核车辆日期侧栏仅展示带目标和日期标识的成功快照;切换日期失败时保留并标注上一成功日期,首次失败不再回退为当前日数据或伪造“0 辆”。
|
- 里程考核车辆日期侧栏仅展示带目标和日期标识的成功快照;切换日期失败时保留并标注上一成功日期,首次失败不再回退为当前日数据或伪造“0 辆”。
|
||||||
- 里程实时监控的首页、分页、全屏和导出已共用同一组筛选参数;全屏首次故障不再显示零值,刷新故障会保留并标注上一成功快照,导出成功或失败均提供可见结果和重试入口。
|
- 里程实时监控的首页、分页、全屏和导出已共用同一组筛选参数;全屏首次故障不再显示零值,刷新故障会保留并标注上一成功快照,导出成功或失败均提供可见结果和重试入口。
|
||||||
|
- 里程实时监控主列表快照已绑定完整筛选与排序口径;新口径请求失败时隐藏旧 KPI、趋势和车辆,原口径刷新失败时保留并标注旧快照,快速筛选与分页的过期响应不会覆盖当前结果。
|
||||||
- 已完成电能数据截至时间和实际趋势月展示。
|
- 已完成电能数据截至时间和实际趋势月展示。
|
||||||
- 电能日期下钻自动核对日汇总与订单全量合计的日期、车辆范围、电量和费用,并显式展示通过或差异状态。
|
- 电能日期下钻自动核对日汇总与订单全量合计的日期、车辆范围、电量和费用,并显式展示通过或差异状态。
|
||||||
- 已完成氢能结构化故障状态与重试体验。
|
- 已完成氢能结构化故障状态与重试体验。
|
||||||
|
|||||||
@@ -499,14 +499,14 @@ export default function MonitoringView() {
|
|||||||
const [loadingMore, setLoadingMore] = useState(false);
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
const [pageLoading, setPageLoading] = useState(true);
|
const [pageLoading, setPageLoading] = useState(true);
|
||||||
const [pageError, setPageError] = useState<{ message: string; scope: 'first' | 'more' } | null>(null);
|
const [pageError, setPageError] = useState<{ message: string; scope: 'first' | 'more' } | null>(null);
|
||||||
|
const [pageSnapshotKey, setPageSnapshotKey] = useState<string | null>(null);
|
||||||
const [manualRefreshing, setManualRefreshing] = useState(false);
|
const [manualRefreshing, setManualRefreshing] = useState(false);
|
||||||
const [dataUpdatedAt, setDataUpdatedAt] = useState<string | null>(null);
|
const [dataUpdatedAt, setDataUpdatedAt] = useState<string | null>(null);
|
||||||
const [showBackToTop, setShowBackToTop] = useState(false);
|
const [showBackToTop, setShowBackToTop] = useState(false);
|
||||||
const [relativeNow, setRelativeNow] = useState(() => Date.now());
|
const [relativeNow, setRelativeNow] = useState(() => Date.now());
|
||||||
|
const firstPageRequestRef = useRef(0);
|
||||||
|
const loadMoreRequestRef = useRef(0);
|
||||||
const PAGE_SIZE = 50;
|
const PAGE_SIZE = 50;
|
||||||
const detailVehicle = drillContext.level === 'vehicle' && drillContext.plate
|
|
||||||
? vehicles.find(vehicle => vehicle.plate === drillContext.plate) || null
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const commitDrillContext = useCallback((next: MileageDrillContext, mode: 'push' | 'replace') => {
|
const commitDrillContext = useCallback((next: MileageDrillContext, mode: 'push' | 'replace') => {
|
||||||
const url = buildMileageDrillUrl(window.location, next);
|
const url = buildMileageDrillUrl(window.location, next);
|
||||||
@@ -553,11 +553,6 @@ export default function MonitoringView() {
|
|||||||
|
|
||||||
const departments = filterOptions.departments;
|
const departments = filterOptions.departments;
|
||||||
const plateNumbers = filterOptions.plates;
|
const plateNumbers = filterOptions.plates;
|
||||||
const rangeLabel = normalizeRangeLabel(effectiveRange.start, effectiveRange.end);
|
|
||||||
const isRangeMode = !!effectiveRange.start && !!effectiveRange.end && effectiveRange.start !== effectiveRange.end;
|
|
||||||
const averageDailyKm = rangeDailyTotals.length > 0
|
|
||||||
? rangeDailyTotals.reduce((sum, item) => sum + item.totalKm, 0) / rangeDailyTotals.length
|
|
||||||
: 0;
|
|
||||||
const applyRangePreset = useCallback((preset: RangePreset) => {
|
const applyRangePreset = useCallback((preset: RangePreset) => {
|
||||||
const range = getRangePreset(preset);
|
const range = getRangePreset(preset);
|
||||||
setRangeStart(range.start);
|
setRangeStart(range.start);
|
||||||
@@ -605,11 +600,31 @@ export default function MonitoringView() {
|
|||||||
searchTerm,
|
searchTerm,
|
||||||
sourcePriority,
|
sourcePriority,
|
||||||
]);
|
]);
|
||||||
const monitoringFilterKey = useMemo(() => JSON.stringify(monitoringFilters), [monitoringFilters]);
|
const monitoringRequestKey = useMemo(
|
||||||
const fullscreenHasSnapshot = fullscreenSnapshotKey === monitoringFilterKey;
|
() => JSON.stringify({ ...monitoringFilters, sortBy, sortOrder }),
|
||||||
|
[monitoringFilters, sortBy, sortOrder],
|
||||||
|
);
|
||||||
|
const pageHasSnapshot = pageSnapshotKey === monitoringRequestKey;
|
||||||
|
const fullscreenHasSnapshot = fullscreenSnapshotKey === monitoringRequestKey;
|
||||||
|
const pageUpdatedAt = pageHasSnapshot ? dataUpdatedAt : null;
|
||||||
|
const detailVehicle = pageHasSnapshot && drillContext.level === 'vehicle' && drillContext.plate
|
||||||
|
? vehicles.find(vehicle => vehicle.plate === drillContext.plate) || null
|
||||||
|
: null;
|
||||||
|
const displayedRange = pageHasSnapshot
|
||||||
|
? effectiveRange
|
||||||
|
: { start: rangeStart, end: rangeEnd };
|
||||||
|
const rangeLabel = normalizeRangeLabel(displayedRange.start, displayedRange.end);
|
||||||
|
const isRangeMode = !!displayedRange.start && !!displayedRange.end && displayedRange.start !== displayedRange.end;
|
||||||
|
const averageDailyKm = pageHasSnapshot && rangeDailyTotals.length > 0
|
||||||
|
? rangeDailyTotals.reduce((sum, item) => sum + item.totalKm, 0) / rangeDailyTotals.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
// 加载首页数据
|
// 加载首页数据
|
||||||
const loadFirstPage = useCallback((showPageLoading = true, force = false) => {
|
const loadFirstPage = useCallback((showPageLoading = true, force = false) => {
|
||||||
|
const requestId = ++firstPageRequestRef.current;
|
||||||
|
const requestKey = monitoringRequestKey;
|
||||||
|
++loadMoreRequestRef.current;
|
||||||
|
setLoadingMore(false);
|
||||||
if (showPageLoading) setPageLoading(true);
|
if (showPageLoading) setPageLoading(true);
|
||||||
setPageError(null);
|
setPageError(null);
|
||||||
return fetchMonitoring({
|
return fetchMonitoring({
|
||||||
@@ -620,6 +635,7 @@ export default function MonitoringView() {
|
|||||||
page: 1,
|
page: 1,
|
||||||
force,
|
force,
|
||||||
}).then(d => {
|
}).then(d => {
|
||||||
|
if (requestId !== firstPageRequestRef.current) return;
|
||||||
setVehicles(d.vehicles);
|
setVehicles(d.vehicles);
|
||||||
setStats(d.stats);
|
setStats(d.stats);
|
||||||
setFilterOptions(d.filters);
|
setFilterOptions(d.filters);
|
||||||
@@ -629,15 +645,17 @@ export default function MonitoringView() {
|
|||||||
setPage(1);
|
setPage(1);
|
||||||
setHasMore(d.page < d.totalPages);
|
setHasMore(d.page < d.totalPages);
|
||||||
setDataUpdatedAt(d.updatedAt);
|
setDataUpdatedAt(d.updatedAt);
|
||||||
|
setPageSnapshotKey(requestKey);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
if (requestId !== firstPageRequestRef.current) return;
|
||||||
setPageError({
|
setPageError({
|
||||||
message: error instanceof Error ? error.message : '里程数据加载失败,请稍后重试',
|
message: error instanceof Error ? error.message : '里程数据加载失败,请稍后重试',
|
||||||
scope: 'first',
|
scope: 'first',
|
||||||
});
|
});
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
if (showPageLoading) setPageLoading(false);
|
if (requestId === firstPageRequestRef.current) setPageLoading(false);
|
||||||
});
|
});
|
||||||
}, [monitoringFilters, rangeEnd, rangeStart, sortBy, sortOrder]);
|
}, [monitoringFilters, monitoringRequestKey, rangeEnd, rangeStart, sortBy, sortOrder]);
|
||||||
|
|
||||||
const handleManualRefresh = useCallback(async () => {
|
const handleManualRefresh = useCallback(async () => {
|
||||||
if (manualRefreshing) return;
|
if (manualRefreshing) return;
|
||||||
@@ -653,6 +671,7 @@ export default function MonitoringView() {
|
|||||||
const loadMore = useCallback(() => {
|
const loadMore = useCallback(() => {
|
||||||
if (loadingMore || !hasMore) return;
|
if (loadingMore || !hasMore) return;
|
||||||
const nextPage = page + 1;
|
const nextPage = page + 1;
|
||||||
|
const requestId = ++loadMoreRequestRef.current;
|
||||||
setLoadingMore(true);
|
setLoadingMore(true);
|
||||||
setPageError(null);
|
setPageError(null);
|
||||||
fetchMonitoring({
|
fetchMonitoring({
|
||||||
@@ -662,16 +681,20 @@ export default function MonitoringView() {
|
|||||||
limit: PAGE_SIZE,
|
limit: PAGE_SIZE,
|
||||||
page: nextPage,
|
page: nextPage,
|
||||||
}).then(d => {
|
}).then(d => {
|
||||||
|
if (requestId !== loadMoreRequestRef.current) return;
|
||||||
setVehicles(prev => [...prev, ...d.vehicles]);
|
setVehicles(prev => [...prev, ...d.vehicles]);
|
||||||
setPage(nextPage);
|
setPage(nextPage);
|
||||||
setHasMore(nextPage < d.totalPages);
|
setHasMore(nextPage < d.totalPages);
|
||||||
setDataUpdatedAt(d.updatedAt);
|
setDataUpdatedAt(d.updatedAt);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
if (requestId !== loadMoreRequestRef.current) return;
|
||||||
setPageError({
|
setPageError({
|
||||||
message: error instanceof Error ? error.message : '更多车辆加载失败,请稍后重试',
|
message: error instanceof Error ? error.message : '更多车辆加载失败,请稍后重试',
|
||||||
scope: 'more',
|
scope: 'more',
|
||||||
});
|
});
|
||||||
}).finally(() => setLoadingMore(false));
|
}).finally(() => {
|
||||||
|
if (requestId === loadMoreRequestRef.current) setLoadingMore(false);
|
||||||
|
});
|
||||||
}, [hasMore, loadingMore, monitoringFilters, page, sortBy, sortOrder]);
|
}, [hasMore, loadingMore, monitoringFilters, page, sortBy, sortOrder]);
|
||||||
|
|
||||||
// 筛选/排序变化时重新加载
|
// 筛选/排序变化时重新加载
|
||||||
@@ -737,7 +760,7 @@ export default function MonitoringView() {
|
|||||||
document.body.scrollTop = 0;
|
document.body.scrollTop = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredVehicles = vehicles;
|
const filteredVehicles = pageHasSnapshot ? vehicles : [];
|
||||||
|
|
||||||
const toggleFullscreen = () => {
|
const toggleFullscreen = () => {
|
||||||
setIsFullscreen(!isFullscreen);
|
setIsFullscreen(!isFullscreen);
|
||||||
@@ -760,9 +783,8 @@ export default function MonitoringView() {
|
|||||||
setFullscreenVehicles(d.vehicles);
|
setFullscreenVehicles(d.vehicles);
|
||||||
setFullscreenStats(d.stats);
|
setFullscreenStats(d.stats);
|
||||||
setFilterOptions(d.filters);
|
setFilterOptions(d.filters);
|
||||||
setDataUpdatedAt(d.updatedAt);
|
|
||||||
setFullscreenUpdatedAt(d.updatedAt);
|
setFullscreenUpdatedAt(d.updatedAt);
|
||||||
setFullscreenSnapshotKey(monitoringFilterKey);
|
setFullscreenSnapshotKey(monitoringRequestKey);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
setFullscreenError(error instanceof Error ? error.message : '全屏监控加载失败,请稍后重试');
|
setFullscreenError(error instanceof Error ? error.message : '全屏监控加载失败,请稍后重试');
|
||||||
@@ -770,7 +792,7 @@ export default function MonitoringView() {
|
|||||||
if (!cancelled) setFullscreenLoading(false);
|
if (!cancelled) setFullscreenLoading(false);
|
||||||
});
|
});
|
||||||
return () => { cancelled = true; };
|
return () => { cancelled = true; };
|
||||||
}, [fullscreenRefresh, isFullscreen, monitoringFilterKey, monitoringFilters, sortBy, sortOrder]);
|
}, [fullscreenRefresh, isFullscreen, monitoringFilters, monitoringRequestKey, sortBy, sortOrder]);
|
||||||
|
|
||||||
// 全屏时禁止背景滚动
|
// 全屏时禁止背景滚动
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1143,9 +1165,9 @@ export default function MonitoringView() {
|
|||||||
<span className="h-1.5 w-1.5 rounded-full bg-blue-500" />
|
<span className="h-1.5 w-1.5 rounded-full bg-blue-500" />
|
||||||
<span
|
<span
|
||||||
className="truncate text-[9px] font-bold tracking-tight text-slate-400"
|
className="truncate text-[9px] font-bold tracking-tight text-slate-400"
|
||||||
title={`OneOS 分钟级快照 · 数据截至 ${formatMileageSnapshotTime(dataUpdatedAt)}`}
|
title={`OneOS 分钟级快照 · 数据截至 ${formatMileageSnapshotTime(pageUpdatedAt)}`}
|
||||||
>
|
>
|
||||||
OneOS 分钟级快照 · 截至 {formatMileageSnapshotTime(dataUpdatedAt)}
|
OneOS 分钟级快照 · 截至 {formatMileageSnapshotTime(pageUpdatedAt)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1153,7 +1175,7 @@ export default function MonitoringView() {
|
|||||||
|
|
||||||
<div className="flex flex-shrink-0 items-center gap-1.5">
|
<div className="flex flex-shrink-0 items-center gap-1.5">
|
||||||
<span className="hidden text-[9px] font-bold tabular-nums text-slate-400 sm:inline">
|
<span className="hidden text-[9px] font-bold tabular-nums text-slate-400 sm:inline">
|
||||||
数据截至 {formatMileageSnapshotTime(dataUpdatedAt)}
|
数据截至 {formatMileageSnapshotTime(pageUpdatedAt)}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1508,30 +1530,73 @@ export default function MonitoringView() {
|
|||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
|
{pageError?.scope === 'first' && (
|
||||||
|
<div
|
||||||
|
role="alert"
|
||||||
|
className={`flex items-center justify-between gap-3 rounded-xl border px-3 py-2 ${
|
||||||
|
pageHasSnapshot
|
||||||
|
? 'border-amber-200 bg-amber-50 text-amber-700'
|
||||||
|
: 'border-rose-200 bg-rose-50 text-rose-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<p className="truncate text-[10px] font-black" title={pageError.message}>
|
||||||
|
{pageHasSnapshot
|
||||||
|
? `刷新失败,仍展示截至 ${formatMileageSnapshotTime(pageUpdatedAt)} 的数据`
|
||||||
|
: '当前筛选数据暂不可用'}
|
||||||
|
</p>
|
||||||
|
{!pageHasSnapshot && <p className="mt-0.5 truncate text-[9px] opacity-70">{pageError.message}</p>}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => pageHasSnapshot ? handleManualRefresh() : loadFirstPage()}
|
||||||
|
disabled={pageLoading || manualRefreshing}
|
||||||
|
className="inline-flex h-7 shrink-0 items-center gap-1 rounded-lg border border-current/20 bg-white/70 px-2 text-[9px] font-black disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<RefreshCw size={11} className={pageLoading || manualRefreshing ? 'animate-spin' : ''} />
|
||||||
|
重试
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Sticky header: KPI + 清单标题 */}
|
{/* Sticky header: KPI + 清单标题 */}
|
||||||
<div className="sticky top-[44px] z-20 bg-[var(--app-bg)] pt-1 pb-1 space-y-2">
|
<div className="sticky top-[44px] z-20 bg-[var(--app-bg)] pt-1 pb-1 space-y-2">
|
||||||
<div className={`grid grid-cols-4 gap-2 transition-opacity ${pageLoading ? 'opacity-60' : ''}`}>
|
<div className={`grid grid-cols-4 gap-2 transition-opacity ${pageLoading && pageHasSnapshot ? 'opacity-60' : ''}`}>
|
||||||
<div className="relative col-span-2 flex min-h-[68px] flex-col justify-center overflow-hidden rounded-xl bg-slate-900 p-2.5 text-white">
|
<div className="relative col-span-2 flex min-h-[68px] flex-col justify-center overflow-hidden rounded-xl bg-slate-900 p-2.5 text-white">
|
||||||
<div className="text-[7px] font-bold text-slate-500 uppercase tracking-wider">{sortBy === 'total' ? '累计' : (isRangeMode ? '区间' : '当日')}总里程</div>
|
<div className="text-[7px] font-bold text-slate-500 uppercase tracking-wider">{sortBy === 'total' ? '累计' : (isRangeMode ? '区间' : '当日')}总里程</div>
|
||||||
<div className="text-lg font-black tracking-tighter leading-tight flex items-baseline gap-1">
|
<div className="text-lg font-black tracking-tighter leading-tight flex items-baseline gap-1">
|
||||||
{pageLoading ? <div className="h-5 w-20 bg-slate-700 rounded animate-pulse"></div> : <>{Math.round(sortBy === 'total' ? stats.totalAll : stats.totalToday).toLocaleString()} <span className="text-[8px] text-slate-400">km</span></>}
|
{pageLoading && !pageHasSnapshot
|
||||||
|
? <div className="h-5 w-20 bg-slate-700 rounded animate-pulse"></div>
|
||||||
|
: <>{pageHasSnapshot ? Math.round(sortBy === 'total' ? stats.totalAll : stats.totalToday).toLocaleString() : '—'} <span className="text-[8px] text-slate-400">km</span></>}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-0.5 truncate text-[8px] font-bold text-slate-500">{rangeLabel}</div>
|
<div className="mt-0.5 truncate text-[8px] font-bold text-slate-500">{rangeLabel}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex min-h-[68px] flex-col justify-center rounded-xl border border-gray-100 bg-white p-2.5 shadow-sm">
|
<div className="flex min-h-[68px] flex-col justify-center rounded-xl border border-gray-100 bg-white p-2.5 shadow-sm">
|
||||||
<div className="text-[7px] font-bold text-slate-400 uppercase">平均单车</div>
|
<div className="text-[7px] font-bold text-slate-400 uppercase">平均单车</div>
|
||||||
<div className="text-sm font-black text-slate-800 leading-tight">{pageLoading ? <div className="h-4 w-8 bg-slate-100 rounded animate-pulse"></div> : (stats.vehicleCount > 0 ? (sortBy === 'total' ? stats.totalAll : stats.totalToday) / stats.vehicleCount : 0).toFixed(0)}</div>
|
<div className="text-sm font-black text-slate-800 leading-tight">
|
||||||
|
{pageLoading && !pageHasSnapshot
|
||||||
|
? <div className="h-4 w-8 bg-slate-100 rounded animate-pulse"></div>
|
||||||
|
: pageHasSnapshot ? (stats.vehicleCount > 0 ? (sortBy === 'total' ? stats.totalAll : stats.totalToday) / stats.vehicleCount : 0).toFixed(0) : '—'}
|
||||||
|
</div>
|
||||||
<div className="text-[7px] text-slate-400">km/台</div>
|
<div className="text-[7px] text-slate-400">km/台</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex min-h-[68px] flex-col justify-center rounded-xl border border-gray-100 bg-white p-2.5 shadow-sm">
|
<div className="flex min-h-[68px] flex-col justify-center rounded-xl border border-gray-100 bg-white p-2.5 shadow-sm">
|
||||||
<div className="text-[7px] font-bold text-slate-400 uppercase">监控台数</div>
|
<div className="text-[7px] font-bold text-slate-400 uppercase">监控台数</div>
|
||||||
<div className="text-sm font-black text-slate-800 leading-tight">{pageLoading ? <div className="h-4 w-8 bg-slate-100 rounded animate-pulse"></div> : stats.vehicleCount}</div>
|
<div className="text-sm font-black text-slate-800 leading-tight">
|
||||||
|
{pageLoading && !pageHasSnapshot
|
||||||
|
? <div className="h-4 w-8 bg-slate-100 rounded animate-pulse"></div>
|
||||||
|
: pageHasSnapshot ? stats.vehicleCount : '—'}
|
||||||
|
</div>
|
||||||
<div className="text-[7px] text-slate-400">台</div>
|
<div className="text-[7px] text-slate-400">台</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-xl border border-slate-100 bg-white shadow-sm overflow-hidden">
|
<div className="rounded-xl border border-slate-100 bg-white shadow-sm overflow-hidden">
|
||||||
{pageLoading ? (
|
{pageLoading && !pageHasSnapshot ? (
|
||||||
<div className="h-[74px] bg-slate-50 animate-pulse" />
|
<div className="h-[74px] bg-slate-50 animate-pulse" />
|
||||||
|
) : !pageHasSnapshot ? (
|
||||||
|
<div className="flex h-[74px] items-center justify-center px-3 text-[10px] font-bold text-slate-400">
|
||||||
|
当前筛选尚无可用快照
|
||||||
|
</div>
|
||||||
) : isRangeMode ? (
|
) : isRangeMode ? (
|
||||||
<div className="grid grid-cols-[92px_minmax(0,1fr)_62px] items-center gap-2 px-2 py-2">
|
<div className="grid grid-cols-[92px_minmax(0,1fr)_62px] items-center gap-2 px-2 py-2">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
@@ -1589,7 +1654,7 @@ export default function MonitoringView() {
|
|||||||
<div className="flex items-center justify-between px-1">
|
<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-black text-slate-400 uppercase tracking-widest">车辆详情清单</span>
|
||||||
<span className="text-[9px] font-bold text-slate-400">
|
<span className="text-[9px] font-bold text-slate-400">
|
||||||
已加载 {filteredVehicles.length} / 共 {total} 条
|
{pageHasSnapshot ? `已加载 ${filteredVehicles.length} / 共 ${total} 条` : '数据不可用'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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">
|
<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">
|
||||||
@@ -1603,7 +1668,7 @@ export default function MonitoringView() {
|
|||||||
{/* Vehicle List */}
|
{/* Vehicle List */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
|
|
||||||
{pageLoading && (
|
{pageLoading && !pageHasSnapshot && (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
{Array.from({ length: 6 }).map((_, i) => (
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
<div key={i} className="bg-white px-3 py-3 rounded-xl border border-slate-50 shadow-sm flex items-center justify-between animate-pulse">
|
<div key={i} className="bg-white px-3 py-3 rounded-xl border border-slate-50 shadow-sm flex items-center justify-between animate-pulse">
|
||||||
@@ -1705,18 +1770,18 @@ export default function MonitoringView() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{filteredVehicles.length === 0 && !loadingMore && !pageError && (
|
{pageHasSnapshot && filteredVehicles.length === 0 && !loadingMore && !pageError && (
|
||||||
<div className="py-10 text-center bg-white rounded-2xl border border-dashed border-slate-100">
|
<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>
|
<p className="text-xs font-bold text-slate-300">未找到匹配数据</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{pageError && (
|
{pageError?.scope === 'more' && pageHasSnapshot && (
|
||||||
<div className="rounded-xl border border-rose-100 bg-rose-50 px-3 py-3 text-center">
|
<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>
|
<p className="text-[10px] font-bold text-rose-600">{pageError.message}</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => pageError.scope === 'more' ? loadMore() : loadFirstPage()}
|
onClick={loadMore}
|
||||||
disabled={pageLoading || loadingMore}
|
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"
|
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"
|
||||||
>
|
>
|
||||||
@@ -1725,7 +1790,7 @@ export default function MonitoringView() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasMore && !pageError && filteredVehicles.length > 0 && (
|
{pageHasSnapshot && hasMore && !pageError && filteredVehicles.length > 0 && (
|
||||||
<div className="flex flex-col items-center gap-1.5 py-4">
|
<div className="flex flex-col items-center gap-1.5 py-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1741,7 +1806,7 @@ export default function MonitoringView() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!hasMore && filteredVehicles.length > 0 && (
|
{pageHasSnapshot && !hasMore && filteredVehicles.length > 0 && (
|
||||||
<div className="py-4 text-center">
|
<div className="py-4 text-center">
|
||||||
<span className="text-[10px] font-bold text-slate-300">已加载全部 {total} 条</span>
|
<span className="text-[10px] font-bold text-slate-300">已加载全部 {total} 条</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user