feat(assets): 完善故障风险多维分析与管理总览
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
shishengliang
2026-10-16 12:38:52 +08:00
parent 4dec824285
commit f107e96d3c
17 changed files with 592 additions and 82 deletions
@@ -1,3 +1,4 @@
import type { FaultDashboardFilters } from '../../../shared/assets/faults';
import { useCallback, useEffect, useRef, useState } from 'react';
import { ClipboardList, RefreshCw } from 'lucide-react';
import { CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
@@ -11,8 +12,10 @@ import { FaultAnalysisModal, type FaultAnalysisSource } from './FaultAnalysisMod
const metrics: FaultWeeklyMetric[] = ['reported', 'closed'];
const definition = '新增故障按提报时间统计,解决故障按解决时间统计;同一故障可在不同日期分别计入两个指标。';
export function FaultWeeklyReport({ refresh }: { refresh: number }) {
const [range, setRange] = useState(() => getWeeklyFlowRange());
export function FaultWeeklyReport({ refresh, scope = {}, scopeLabel = '', period }: { refresh: number; scope?: FaultDashboardFilters; scopeLabel?: string; period?: { start: string; end: string } }) {
const [range, setRange] = useState(() => period || getWeeklyFlowRange());
const scopeKey = new URLSearchParams(scope).toString();
useEffect(() => { if (period) setRange(period); }, [period?.start, period?.end]);
const [data, setData] = useState<FaultWeeklyResponse | null>(null);
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
@@ -24,22 +27,22 @@ export function FaultWeeklyReport({ refresh }: { refresh: number }) {
useEffect(() => {
const controller = new AbortController();
setLoading(true); setData(null); setError('');
fetchJson<FaultWeeklyResponse>(`/api/asset-faults/weekly-report?${new URLSearchParams({ start: range.start, end: range.end })}`, { signal: controller.signal })
fetchJson<FaultWeeklyResponse>(`/api/asset-faults/weekly-report?${new URLSearchParams({ start: range.start, end: range.end, ...scope })}`, { signal: controller.signal })
.then(result => { if (!controller.signal.aborted) setData(result); })
.catch(e => { if (!controller.signal.aborted) setError(e instanceof Error ? e.message : '加载失败'); })
.finally(() => { if (!controller.signal.aborted) setLoading(false); });
return () => controller.abort();
}, [range.start, range.end, revision, refresh]);
}, [range.start, range.end, revision, refresh, scopeKey]);
const drill = (metric: FaultWeeklyMetric, day?: string) => {
if (!data || loading) return;
const params = new URLSearchParams({ start: data.start, end: data.end, metric });
const params = new URLSearchParams({ start: data.start, end: data.end, metric, ...scope });
if (day) params.set('day', day);
setSource({ url: `/api/asset-faults/weekly-report-analysis?${params}`, title: `故障运维周报 · ${FAULT_WEEKLY_METRICS[metric]}`,
periodLabel: `${day || `${data.start} 至 ${data.end}`} · 按事件发生时间`, definition });
};
return <section className="fault-weekly fault-trend fault-summary" aria-label="故障运维周报" aria-busy={loading}>
<div className="fault-trend-heading"><h2><ClipboardList size={16} />故障运维周报</h2><div className="fault-report-actions"><span className="fault-weekly-unit">单位:单</span><AnalysisExportButton disabled={loading || !data || !!error} onExport={() => data ? downloadFaultReport({ kind: 'weekly', data }, chartRef.current?.querySelector<SVGSVGElement>('svg.recharts-surface[role=application]') || undefined) : undefined} /></div></div>
<p className="fault-trend-definition">按日展示新增故障和解决故障。默认业务周为周六至周五,可自选日期。</p>
<div className="fault-trend-heading"><h2><ClipboardList size={16} />故障运维周报</h2><div className="fault-report-actions"><span className="fault-weekly-unit">单位:单</span><AnalysisExportButton disabled={loading || !data || !!error} onExport={() => data ? downloadFaultReport({ kind: 'weekly', data, filterLabel: scopeLabel }, chartRef.current?.querySelector<SVGSVGElement>('svg.recharts-surface[role=application]') || undefined) : undefined} /></div></div>
<p className="fault-trend-definition">按日展示新增故障和解决故障。{period ? '统计周期沿用查询范围,可在下方单独调整。' : '默认业务周为周六至周五,可自选日期。'}</p>
<FlowDateRangePicker dateLabel="提报 / 解决时间" value={range} onChange={setRange} />
{error ? <div className="fault-message" role="alert">故障运维周报加载失败,请稍后重试。<button onClick={() => setRevision(v => v + 1)}>重试</button></div> : loading || !data ? <div className="fault-trend-loading" role="status"><RefreshCw size={16} className="animate-spin" />正在加载故障运维周报…</div> : <>
<div className="fault-trend-totals">{metrics.map(metric => <button key={metric} className={`fault-weekly-${metric}`} disabled={!data.totals[metric]} onClick={() => drill(metric)} aria-label={`周报${FAULT_WEEKLY_METRICS[metric]} ${data.totals[metric]} 单,查看分析`}><strong>{data.totals[metric].toLocaleString()}</strong><span>{FAULT_WEEKLY_METRICS[metric]} ↗</span></button>)}</div>