fix: surface mileage child request failures
This commit is contained in:
@@ -65,11 +65,11 @@ function shortTargetName(name: string): string {
|
||||
return `${count}台${desc}`;
|
||||
}
|
||||
|
||||
function getRequestErrorMessage(error: unknown): string {
|
||||
function getRequestErrorMessage(error: unknown, fallback: string): string {
|
||||
if (error instanceof Error && error.message && error.message !== 'Failed to fetch') {
|
||||
return error.message;
|
||||
}
|
||||
return '无法获取考核目标数据,请检查服务连接后重试。';
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export default function StatisticsView() {
|
||||
@@ -81,7 +81,12 @@ export default function StatisticsView() {
|
||||
const [targetsError, setTargetsError] = useState<string | null>(null);
|
||||
const [targetsRequestVersion, setTargetsRequestVersion] = useState(0);
|
||||
const [trendData, setTrendData] = useState<TrendPoint[]>([]);
|
||||
const [trendLoading, setTrendLoading] = useState(false);
|
||||
const [trendError, setTrendError] = useState<string | null>(null);
|
||||
const [trendRequestVersion, setTrendRequestVersion] = useState(0);
|
||||
const [targetVehiclesMap, setTargetVehiclesMap] = useState<Record<number, TargetVehicle[]>>({});
|
||||
const [targetVehicleLoadingMap, setTargetVehicleLoadingMap] = useState<Record<number, boolean>>({});
|
||||
const [targetVehicleErrorMap, setTargetVehicleErrorMap] = useState<Record<number, string>>({});
|
||||
const [selectedTargetId, setSelectedTargetId] = useState<number | null>(drillContext.targetId || null);
|
||||
const [attributionDimension, setAttributionDimension] = useState<AttributionDimension>(() => (
|
||||
drillContext.level === 'breakdown' && drillContext.dimension === 'customer' ? 'customer' : 'department'
|
||||
@@ -139,12 +144,28 @@ export default function StatisticsView() {
|
||||
setTargetsRequestVersion(version => version + 1);
|
||||
}, []);
|
||||
|
||||
const retryTrend = useCallback(() => {
|
||||
setTrendRequestVersion(version => version + 1);
|
||||
}, []);
|
||||
|
||||
const loadCurrentTargetVehicles = useCallback((targetId: number) => {
|
||||
if (targetVehicleRequestsRef.current.has(targetId)) return;
|
||||
targetVehicleRequestsRef.current.add(targetId);
|
||||
setTargetVehicleLoadingMap(prev => ({ ...prev, [targetId]: true }));
|
||||
setTargetVehicleErrorMap(prev => {
|
||||
const next = { ...prev };
|
||||
delete next[targetId];
|
||||
return next;
|
||||
});
|
||||
fetchTargetVehicles(targetId).then(vehicles => {
|
||||
setTargetVehiclesMap(prev => ({ ...prev, [targetId]: vehicles }));
|
||||
}).catch(() => {}).finally(() => {
|
||||
}).catch(error => {
|
||||
setTargetVehicleErrorMap(prev => ({
|
||||
...prev,
|
||||
[targetId]: getRequestErrorMessage(error, '无法获取考核车辆数据,请稍后重试。'),
|
||||
}));
|
||||
}).finally(() => {
|
||||
setTargetVehicleLoadingMap(prev => ({ ...prev, [targetId]: false }));
|
||||
targetVehicleRequestsRef.current.delete(targetId);
|
||||
});
|
||||
}, []);
|
||||
@@ -267,7 +288,9 @@ export default function StatisticsView() {
|
||||
setAssessmentYearMap({});
|
||||
}
|
||||
}).catch(error => {
|
||||
if (!cancelled) setTargetsError(getRequestErrorMessage(error));
|
||||
if (!cancelled) {
|
||||
setTargetsError(getRequestErrorMessage(error, '无法获取考核目标数据,请检查服务连接后重试。'));
|
||||
}
|
||||
}).finally(() => {
|
||||
if (!cancelled) setTargetsLoading(false);
|
||||
});
|
||||
@@ -278,13 +301,33 @@ export default function StatisticsView() {
|
||||
|
||||
// Load trend when selectedTargetId changes
|
||||
useEffect(() => {
|
||||
if (selectedTargetId === null) return;
|
||||
if (selectedTargetId === null) {
|
||||
setTrendData([]);
|
||||
setTrendError(null);
|
||||
setTrendLoading(false);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setExpandedTargetId(selectedTargetId);
|
||||
if (!targetVehiclesMap[selectedTargetId]) {
|
||||
loadCurrentTargetVehicles(selectedTargetId);
|
||||
}
|
||||
fetchTrend(selectedTargetId).then(setTrendData).catch(() => setTrendData([]));
|
||||
}, [selectedTargetId]);
|
||||
setTrendLoading(true);
|
||||
setTrendError(null);
|
||||
fetchTrend(selectedTargetId).then(data => {
|
||||
if (!cancelled) setTrendData(data);
|
||||
}).catch(error => {
|
||||
if (!cancelled) {
|
||||
setTrendData([]);
|
||||
setTrendError(getRequestErrorMessage(error, '无法获取里程趋势,请稍后重试。'));
|
||||
}
|
||||
}).finally(() => {
|
||||
if (!cancelled) setTrendLoading(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [selectedTargetId, trendRequestVersion]);
|
||||
|
||||
// Re-fetch target vehicles when viewAllDate changes
|
||||
useEffect(() => {
|
||||
@@ -421,8 +464,8 @@ export default function StatisticsView() {
|
||||
</div>
|
||||
<div className="rounded-2xl border border-slate-100 bg-white p-3 shadow-sm">
|
||||
<div className="text-[10px] font-black uppercase tracking-wide text-slate-400">最新日变化</div>
|
||||
<div className={`mt-1 text-lg font-black ${trendDelta >= 0 ? 'text-emerald-600' : 'text-rose-600'}`}>
|
||||
{trendDelta >= 0 ? '+' : ''}{fmtKm(trendDelta)}
|
||||
<div className={`mt-1 text-lg font-black ${trendLoading || trendError ? 'text-slate-500' : trendDelta >= 0 ? 'text-emerald-600' : 'text-rose-600'}`}>
|
||||
{trendLoading ? '加载中' : trendError ? '暂不可用' : `${trendDelta >= 0 ? '+' : ''}${fmtKm(trendDelta)}`}
|
||||
</div>
|
||||
<div className="mt-1 text-[10px] font-bold text-slate-400">达标率 {fmtPercent(selectedQualifiedRate)}</div>
|
||||
</div>
|
||||
@@ -448,7 +491,9 @@ export default function StatisticsView() {
|
||||
vehicles={selectedTargetVehicles}
|
||||
dimension={attributionDimension}
|
||||
selectedValue={selectedBreakdownValue}
|
||||
loading={!targetVehiclesMap[selectedTarget.id]}
|
||||
loading={targetVehicleLoadingMap[selectedTarget.id]
|
||||
|| (!targetVehiclesMap[selectedTarget.id] && !targetVehicleErrorMap[selectedTarget.id])}
|
||||
error={targetVehicleErrorMap[selectedTarget.id]}
|
||||
targetMileagePerVehicle={selectedTarget.annualMileagePerVehicle * (selectedAssessmentYear || 1)}
|
||||
expectedShortfallMileage={selectedAssessment?.remaining}
|
||||
assessmentLabel={selectedAssessment?.label}
|
||||
@@ -457,6 +502,7 @@ export default function StatisticsView() {
|
||||
if (drillContext.level === 'breakdown') closeVehiclePanel();
|
||||
}}
|
||||
onSelect={openAttributionGroup}
|
||||
onRetry={() => loadCurrentTargetVehicles(selectedTarget.id)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -521,6 +567,13 @@ export default function StatisticsView() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-[280px] w-full min-w-0">
|
||||
{trendLoading ? (
|
||||
<LoadingState label="正在加载里程趋势" variant="inline" />
|
||||
) : trendError ? (
|
||||
<ErrorState message={trendError} onRetry={retryTrend} variant="inline" />
|
||||
) : trendData.length === 0 ? (
|
||||
<EmptyState title="暂无趋势数据" description="当前考核目标在最近 7 天没有可用里程" variant="inline" />
|
||||
) : (
|
||||
<ResponsiveContainer width="100%" height={280} minWidth={0}>
|
||||
{chartType === 'bar' ? (
|
||||
<BarChart data={trendData} margin={{ top: 20, right: 10, left: 0, bottom: 0 }}>
|
||||
@@ -563,6 +616,7 @@ export default function StatisticsView() {
|
||||
</AreaChart>
|
||||
)}
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user