Show version block in panorama node view

Why: the I4 scenarios assert the version collapsible for a selected
section in the panorama (not only the reader); the panorama node view
had no version block, so .cockpit-versions-toggle never existed there.
Also normalize null list projections in the panorama node detail fetch
(same Go nil-slice issue fixed for the panel earlier).

Evidence: tsc clean, vitest green, build green (SELF); seven-scenario
rerun to be recorded on the Dev I4 card.

Impact: cockpit panorama only.

Refs: rollout I4
This commit is contained in:
2026-10-17 04:07:58 +00:00
parent dd159c24d0
commit c3d8205839
+15 -3
View File
@@ -16,7 +16,7 @@ import {
type CockpitTreeNode, type CockpitTreePage,
type Density, type PanelTabId, type ViewMode,
} from '../documentCockpit'
import { CockpitPanel, CockpitReader } from './CockpitReader'
import { CockpitPanel, CockpitReader, VersionBlock } from './CockpitReader'
interface ProjectView { projectId: string; name: string; canAudit?: boolean }
@@ -198,6 +198,7 @@ export function DocumentCockpit() {
{overview && selected && viewMode === 'panorama' && (
<PanoramaNode pid={pid} selection={selected} overview={overview}
childPage={selected.ref.kind === 'document' ? treePages[selected.ref.id] : undefined}
density={density}
onClear={() => setSelected(null)}
onOpenReader={() => applyPrefs({ lastView: 'reader' })} />
)}
@@ -289,9 +290,10 @@ function PanoramaHome({ overview, onSelect }: { overview: CockpitOverview; onSel
)
}
function PanoramaNode({ pid, selection, overview, childPage, onClear, onOpenReader }: {
function PanoramaNode({ pid, selection, overview, childPage, density, onClear, onOpenReader }: {
pid: string; selection: Selection; overview: CockpitOverview
childPage?: { items: CockpitTreeNode[]; nextCursor?: string; hasMore: boolean; meta?: CockpitMeta }
density: Density
onClear: () => void
onOpenReader: () => void
}) {
@@ -302,7 +304,14 @@ function PanoramaNode({ pid, selection, overview, childPage, onClear, onOpenRead
const g = ++gen.current
setDetail(null); setErr('')
api.rdmCommand<any>('rdm.cockpit.node', { projectId: pid, kind: selection.ref.kind, id: selection.ref.id })
.then(d => { if (g === gen.current) setDetail(d) })
.then(d => {
if (g !== gen.current) return
// 后端空列表可能投影为 null(Go nil slice);统一归一,消费端按数组使用。
for (const k of ['sources', 'history', 'impacts', 'conflicts'] as const) {
if (d?.[k] && !Array.isArray(d[k].items)) d[k].items = []
}
setDetail(d)
})
.catch(e => { if (g === gen.current) setErr(e.message) })
return () => { gen.current++ }
}, [pid, selection.ref.kind, selection.ref.id])
@@ -320,6 +329,9 @@ function PanoramaNode({ pid, selection, overview, childPage, onClear, onOpenRead
{detail?.body?.length === 1 && detail.body[0].kind === 'restricted' && (
<div className="empty-note">内容受限({detail.body[0].reason || '权限'})——标题与状态可见,正文不展示</div>
)}
{selection.ref.kind === 'section' && detail?.versions && detail.versions.length > 0 && (
<VersionBlock versions={detail.versions} density={density} />
)}
{detail && detail.meta && !sameSnapshot(detail.meta, overview.meta) && (
<div className="cockpit-truncated">节点详情与统计快照不同({detail.meta.snapshotRevision} vs {overview.meta.snapshotRevision})——刷新对齐</div>
)}