diff --git a/web/src/App.tsx b/web/src/App.tsx index bed69bb8..913ecb73 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -680,6 +680,8 @@ export default function App() { const [docRevOverlayMode, setDocRevOverlayMode] = useState('manual'); const [docRevOverlay, setDocRevOverlay] = useState(null); const [docRevConflict, setDocRevConflict] = useState(null); + /** 人工保存 / 受控正文 hydrate 后递增,驱动工具条重新拉取 revision-state 与 overlay */ + const [docRevRefreshKey, setDocRevRefreshKey] = useState(0); const [deckDrawerOpen, setDeckDrawerOpen] = useState(false); // 认证与会话状态 @@ -1123,6 +1125,7 @@ export default function App() { setDocCache((prev) => ({ ...prev, [activeDocId]: ast })); setControlledContentReady((prev) => ({ ...prev, [activeDocId]: true })); setDocLoadErrors((prev) => ({ ...prev, [activeDocId]: false })); + setDocRevRefreshKey((k) => k + 1); }) .catch((err) => { console.warn('自动拉取受控文档正文失败:', err); @@ -1160,6 +1163,7 @@ export default function App() { setDocCache((prev) => ({ ...prev, [activeDocId]: ast })); setControlledContentReady((prev) => ({ ...prev, [activeDocId]: true })); setDocLoadErrors((prev) => ({ ...prev, [activeDocId]: false })); + setDocRevRefreshKey((k) => k + 1); }) .catch((err) => { console.warn('重试拉取受控文档正文失败:', err); @@ -3192,6 +3196,7 @@ export default function App() { setEditingBlock(null); setDocRevConflict(null); setDocRevOverlay(res.manual_overlay || null); + setDocRevRefreshKey((k) => k + 1); } catch (e) { const err = e as Error & { code?: string; conflict?: { current_revision_no?: number } }; if (err.code === 'DOCUMENT_HEAD_CONFLICT') { @@ -3864,6 +3869,7 @@ export default function App() { overlayMode={docRevOverlayMode} onOverlayModeChange={setDocRevOverlayMode} onOverlay={setDocRevOverlay} + revisionRefreshKey={docRevRefreshKey} /> {docRevConflict && (
diff --git a/web/src/components/doc/ChangeReportPanel.tsx b/web/src/components/doc/ChangeReportPanel.tsx index 7423ad81..bd47a2e9 100644 --- a/web/src/components/doc/ChangeReportPanel.tsx +++ b/web/src/components/doc/ChangeReportPanel.tsx @@ -7,19 +7,84 @@ export function ChangeReportPanel(props: { docId: string; reportId: number; onCl useEffect(() => { getChangeReport(props.docId, props.reportId).then(setReport).catch(() => setReport(null)); }, [props.docId, props.reportId]); + + const projectSummary = + report?.project_impacts && typeof report.project_impacts.summary === 'string' + ? String(report.project_impacts.summary) + : null; + return ( -
+

变更报告

- +
- {!report ?

加载中…

: ( + {!report ? ( +

加载中…

+ ) : ( <> -

风险:{report.risk_level} · {report.analysis_status}

-
    {report.summary.map((s) =>
  • {s}
  • )}
- {report.document_impacts?.length > 0 && ( -

影响文档 {report.document_impacts.length} 份

+

+ 风险:{report.risk_level} + {' · '} + {report.analysis_status} + {report.analysis_status === 'fallback' || report.analysis_status === 'unassessed' ? ( + (确定性分析,非完整 PI 叙事) + ) : null} +

+

摘要

+
    + {(report.summary || []).map((s) => ( +
  • {s}
  • + ))} +
+ {projectSummary ? ( +

+ 项目影响:{projectSummary} +

+ ) : null} + {report.document_impacts?.length > 0 ? ( +
+

文档影响

+
    + {report.document_impacts.map((d) => ( +
  • + {d.doc_id}:{d.reason || d.impact_type} +
  • + ))} +
+
+ ) : ( +

文档影响:无(或尚未登记下游 lineage)

)} + {report.open_questions?.length > 0 ? ( +
+

开放问题

+
    + {report.open_questions.map((q) => ( +
  • {q}
  • + ))} +
+
+ ) : null} )}
diff --git a/web/src/components/doc/DocumentRevisionToolbar.tsx b/web/src/components/doc/DocumentRevisionToolbar.tsx index 28de0072..a2caf851 100644 --- a/web/src/components/doc/DocumentRevisionToolbar.tsx +++ b/web/src/components/doc/DocumentRevisionToolbar.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import type { DiffResult, DocumentVersion, OverlayMode, RevisionState } from '../../features/documentRevision/types'; import { DOC_REV_ERROR } from '../../features/documentRevision/types'; +import { pickLatestChangeReportId } from '../../features/documentRevision/toolbarRefresh'; import { getDiff, getManualOverlay, getRevisionState, listVersions } from '../../api/documentRevision'; import { VersionHistoryDrawer } from './VersionHistoryDrawer'; import { VersionCompareView } from './VersionCompareView'; @@ -12,10 +13,13 @@ export function DocumentRevisionToolbar(props: { overlayMode: OverlayMode; onOverlayModeChange: (m: OverlayMode) => void; onOverlay: (d: DiffResult | null) => void; + /** Bump after human save / squad hydrate so r 与 overlay 重新拉取(勿仅依赖 docId)。 */ + revisionRefreshKey?: number; }) { - const { docId, overlayMode, onOverlayModeChange, onOverlay } = props; + const { docId, overlayMode, onOverlayModeChange, onOverlay, revisionRefreshKey = 0 } = props; const [state, setState] = useState(null); const [versions, setVersions] = useState([]); + const [latestReportId, setLatestReportId] = useState(null); const [historyOpen, setHistoryOpen] = useState(false); const [compare, setCompare] = useState<{ a: number; b: number; diff: DiffResult | null } | null>(null); const [reportId, setReportId] = useState(null); @@ -31,26 +35,37 @@ export function DocumentRevisionToolbar(props: { setState(st); setError(null); setErrorCode(null); + try { + const res = await listVersions(docId); + if (cancelled) return; + const vs = res.versions || []; + setVersions(vs); + setLatestReportId(pickLatestChangeReportId(vs)); + } catch { + if (!cancelled) { + setVersions([]); + setLatestReportId(null); + } + } if (overlayMode === 'pure') { onOverlay(null); } else if (overlayMode === 'manual') { onOverlay(await getManualOverlay(docId)); - } else { - // all: prefer last sealed→current when possible, else manual overlay - if (st.current_version_no >= 2) { - try { - onOverlay(await getDiff(docId, st.current_version_no - 1, st.current_version_no)); - } catch { - onOverlay(await getManualOverlay(docId)); - } - } else { + } else if (st.current_version_no >= 2) { + try { + onOverlay(await getDiff(docId, st.current_version_no - 1, st.current_version_no)); + } catch { onOverlay(await getManualOverlay(docId)); } + } else { + onOverlay(await getManualOverlay(docId)); } } catch (e) { if (!cancelled) { const err = e as Error & { code?: string }; setState(null); + setVersions([]); + setLatestReportId(null); setErrorCode(err.code || null); setError(err.message); onOverlay(null); @@ -60,7 +75,7 @@ export function DocumentRevisionToolbar(props: { return () => { cancelled = true; }; - }, [docId, overlayMode, onOverlay]); + }, [docId, overlayMode, onOverlay, revisionRefreshKey]); const emptyHint = errorCode === DOC_REV_ERROR.NEEDS_CONTENT @@ -73,10 +88,10 @@ export function DocumentRevisionToolbar(props: {
{state ? ( <> - V{state.current_version_no} + V{state.current_version_no} {state.version_status === 'open' ? '当前' : '已封存'} - r{state.head_revision_no} - {state.manual_change_count} 处人工修改 + r{state.head_revision_no} + {state.manual_change_count} 处人工修改 ) : ( {emptyHint} @@ -99,12 +114,23 @@ export function DocumentRevisionToolbar(props: { disabled={!state} onClick={async () => { const res = await listVersions(docId); - setVersions(res.versions || []); + const vs = res.versions || []; + setVersions(vs); + setLatestReportId(pickLatestChangeReportId(vs)); setHistoryOpen(true); }} > 版本历史 +