diff --git a/web/src/app/(main)/function/findings/detail/page.tsx b/web/src/app/(main)/function/findings/detail/page.tsx index 5bd2d4f..2afd8b1 100644 --- a/web/src/app/(main)/function/findings/detail/page.tsx +++ b/web/src/app/(main)/function/findings/detail/page.tsx @@ -63,6 +63,7 @@ function FindingDetailInner() { const [finding, setFinding] = React.useState(null); const [loaded, setLoaded] = React.useState(false); const [tab, setTab] = React.useState("overview"); + const [trafficReload, setTrafficReload] = React.useState(0); const load = React.useCallback(() => { if (!id) { @@ -79,6 +80,11 @@ function FindingDetailInner() { load(); }, [load]); + const handleTrafficChanged = React.useCallback(() => { + setTrafficReload((value) => value + 1); + load(); + }, [load]); + const changeSeverity = React.useCallback( async (next: Severity) => { if (!finding || finding.inherited || next === finding.severity) return; @@ -191,7 +197,8 @@ function FindingDetailInner() { reproduction={finding.reproduction} contextTask={contextTaskId || undefined} readOnly={finding.inherited} - onChanged={load} + onChanged={handleTrafficChanged} + trafficReload={trafficReload} /> ) : null}
@@ -215,7 +222,8 @@ function FindingDetailInner() { readOnly={finding.inherited} stepIndex={structuredReproduction ? 0 : undefined} title={structuredReproduction ? "未归入步骤的流量" : undefined} - onChanged={load} + onChanged={handleTrafficChanged} + trafficReload={trafficReload} /> {/* 证据下方:详细报告(Markdown 渲染) */} diff --git a/web/src/components/finding-reproduction-section.tsx b/web/src/components/finding-reproduction-section.tsx index 34c0770..fe24cd7 100644 --- a/web/src/components/finding-reproduction-section.tsx +++ b/web/src/components/finding-reproduction-section.tsx @@ -43,12 +43,14 @@ export function FindingReproductionSection({ contextTask, readOnly, onChanged, + trafficReload = 0, }: { findingId: string; reproduction: Reproduction; contextTask?: string; readOnly?: boolean; onChanged?: () => void; + trafficReload?: number; }) { const [traffic, setTraffic] = React.useState(null); const [pickerStep, setPickerStep] = React.useState(null); @@ -59,14 +61,20 @@ export function FindingReproductionSection({ const [editingStepIndex, setEditingStepIndex] = React.useState("0"); const [busy, setBusy] = React.useState(false); const [reload, setReload] = React.useState(0); - const load = React.useCallback(() => { + React.useEffect(() => { + let active = true; api .findingTraffic(findingId, contextTask) - .then(setTraffic) - .catch(() => setTraffic(null)); - }, [findingId, contextTask]); - // biome-ignore lint/correctness/useExhaustiveDependencies: retry token intentionally refreshes this request. - React.useEffect(() => load(), [load, reload]); + .then((value) => { + if (active) setTraffic(value); + }) + .catch(() => { + if (active) setTraffic(null); + }); + return () => { + active = false; + }; + }, [findingId, contextTask, reload, trafficReload]); const steps = reproduction.steps ?? []; const allBindings = traffic?.bindings ?? []; diff --git a/web/src/components/finding-traffic-panel.tsx b/web/src/components/finding-traffic-panel.tsx index f4b8cd5..fd107e0 100644 --- a/web/src/components/finding-traffic-panel.tsx +++ b/web/src/components/finding-traffic-panel.tsx @@ -46,6 +46,7 @@ export function FindingTrafficPanel({ onChanged, stepIndex: filterStepIndex, title, + trafficReload = 0, }: { findingId: string; contextTask?: string; @@ -53,6 +54,7 @@ export function FindingTrafficPanel({ onChanged: () => void; stepIndex?: number; title?: string; + trafficReload?: number; }) { const [data, setData] = React.useState(null); const [error, setError] = React.useState(""); @@ -84,7 +86,7 @@ export function FindingTrafficPanel({ return () => { active = false; }; - }, [findingId, contextTask, reload, filterStepIndex]); + }, [findingId, contextTask, reload, filterStepIndex, trafficReload]); async function mutate(action: () => Promise) { setBusy(true);