From a34449750554f52fc062ccfdcc7ca3e6e0dfe556 Mon Sep 17 00:00:00 2001 From: kkfluous Date: Fri, 7 Aug 2026 17:58:54 +0800 Subject: [PATCH] fix: distinguish mileage statistics failures --- docs/bi-refactor-roadmap.md | 1 + src/modules/mileage/StatisticsView.tsx | 87 ++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/docs/bi-refactor-roadmap.md b/docs/bi-refactor-roadmap.md index d6e8477..50e88cb 100644 --- a/docs/bi-refactor-roadmap.md +++ b/docs/bi-refactor-roadmap.md @@ -172,6 +172,7 @@ SUM(fee) / NULLIF(SUM(kwh), 0) - 里程年度归因使用考核台账 `current_mileage` 快照,按车辆目标封顶后加总完成量与缺口,并自动核对归因合计和目标汇总;带日期的 OneOS 里程只用于车辆实时明细。 - 里程考核目标接口发布车辆台账 `MAX(update_time)`,统计页使用真实日期和时间展示数据截至点,不再用页面访问日或考核结束日代替快照时间。 - 里程考核接口区分目标声明台数与当前有效车辆数;两者不一致时页面明确标注缺少或超配数量,并说明完成率、缺口和归因的实际计算范围。 +- 里程考核统计主数据已区分加载、真实空数据、首次请求故障和保留旧数据的刷新故障,并提供统一重试入口,接口失败不再显示为零值或空页面。 - 已完成电能数据截至时间和实际趋势月展示。 - 电能日期下钻自动核对日汇总与订单全量合计的日期、车辆范围、电量和费用,并显式展示通过或差异状态。 - 已完成氢能结构化故障状态与重试体验。 diff --git a/src/modules/mileage/StatisticsView.tsx b/src/modules/mileage/StatisticsView.tsx index 80640e3..c43f351 100644 --- a/src/modules/mileage/StatisticsView.tsx +++ b/src/modules/mileage/StatisticsView.tsx @@ -12,6 +12,7 @@ import { import type { TargetSummary, TargetVehicle, TargetYearlyAssessment, TrendPoint } from './types'; import { fetchTargets, fetchTargetVehicles, fetchTrend } from './api'; import Blur from '../../components/Blur'; +import { EmptyState, ErrorState, LoadingState } from '../../components/ui/surface'; import { weightedCompletionRate } from '../../shared/analytics/metrics'; import { formatMileageSnapshotDateTime } from './data-freshness'; import { assessVehicleCoverage } from './assessment-coverage'; @@ -64,11 +65,21 @@ function shortTargetName(name: string): string { return `${count}台${desc}`; } +function getRequestErrorMessage(error: unknown): string { + if (error instanceof Error && error.message && error.message !== 'Failed to fetch') { + return error.message; + } + return '无法获取考核目标数据,请检查服务连接后重试。'; +} + export default function StatisticsView() { const [drillContext, setDrillContext] = useState(() => ( parseMileageDrillContext(window.location.search) )); const [targets, setTargets] = useState([]); + const [targetsLoading, setTargetsLoading] = useState(true); + const [targetsError, setTargetsError] = useState(null); + const [targetsRequestVersion, setTargetsRequestVersion] = useState(0); const [trendData, setTrendData] = useState([]); const [targetVehiclesMap, setTargetVehiclesMap] = useState>({}); const [selectedTargetId, setSelectedTargetId] = useState(drillContext.targetId || null); @@ -124,6 +135,10 @@ export default function StatisticsView() { setDrillContext(next); }, []); + const retryTargets = useCallback(() => { + setTargetsRequestVersion(version => version + 1); + }, []); + const loadCurrentTargetVehicles = useCallback((targetId: number) => { if (targetVehicleRequestsRef.current.has(targetId)) return; targetVehicleRequestsRef.current.add(targetId); @@ -215,9 +230,13 @@ export default function StatisticsView() { window.dispatchEvent(new HashChangeEvent('hashchange')); }, [selectedAssessmentYear, selectedTargetId, viewAllDate, viewAllTargetId]); - // Load targets on mount + // Load targets on mount and through the shared retry path. useEffect(() => { + let cancelled = false; + setTargetsLoading(true); + setTargetsError(null); fetchTargets().then(data => { + if (cancelled) return; const initialContext = initialDrillContextRef.current; const focused = data.find(item => item.targetName.includes('羚牛136')) || data[0]; const ordered = focused @@ -242,9 +261,20 @@ export default function StatisticsView() { setDrillContext(normalizedContext); } } + } else { + setSelectedTargetId(null); + setExpandedTargetId(null); + setAssessmentYearMap({}); } - }).catch(() => {}); - }, []); + }).catch(error => { + if (!cancelled) setTargetsError(getRequestErrorMessage(error)); + }).finally(() => { + if (!cancelled) setTargetsLoading(false); + }); + return () => { + cancelled = true; + }; + }, [targetsRequestVersion]); // Load trend when selectedTargetId changes useEffect(() => { @@ -307,6 +337,30 @@ export default function StatisticsView() { )) ), [viewAllSearch, viewAllVehicles]); + if (targetsLoading && targets.length === 0) { + return ; + } + + if (targetsError && targets.length === 0) { + return ( + + ); + } + + if (!targetsLoading && targets.length === 0) { + return ( + + ); + } + return (
{/* Project Selector */} @@ -326,6 +380,16 @@ export default function StatisticsView() { ))}
+ {targetsError && ( + + )} + {selectedTarget && (
+ {targetsError && ( +
+ {targetsError} 当前仍展示上一次成功加载的数据。 + +
+ )} + {/* Table Area */}