diff --git a/src/components/RdmAuditWorkspace.tsx b/src/components/RdmAuditWorkspace.tsx index b2e152a..b446b7d 100644 --- a/src/components/RdmAuditWorkspace.tsx +++ b/src/components/RdmAuditWorkspace.tsx @@ -6,7 +6,7 @@ import { api } from '../api' import { Chip, Collapsible, EmptyState, SectionLabel } from '@ui' import { classifyRdmError } from '../rdmErrors' import { - describeAuditEvent, humanizeKind, humanizeRelation, humanizeState, + describeAuditEvent, humanizeKind, humanizeRelation, humanizeState, isTreeItemExpandable, type AuditEventViewInput, } from '../rdmAuditPresentation' import { store, useStore } from '../store' @@ -19,7 +19,7 @@ interface Capabilities { actor: string; projects: InspectProject[]; crossProject interface CoverageItem { face: string; reason: string } interface ListShell { items: T[]; nextCursor?: string; hasMore: boolean; truncated: boolean; coverage?: CoverageItem[] } interface SummaryData { projectId: string; counts: Record; watermark: number; lastOpAt: string; coverage?: CoverageItem[] } -interface TreeItem { kind: string; id: string; title: string; state?: string } +interface TreeItem { kind: string; id: string; title: string; state?: string; hasChildren?: boolean } interface RelationEntry { layer: string; edgeId?: string; kind?: string; type?: string; state: string; depth?: number; from: any; to: any; conf?: number } interface EventDTO extends AuditEventViewInput {} interface OpGroup { operationId: string; actor: string; at: string; events: EventDTO[] } @@ -331,19 +331,20 @@ function TreeList({ pid, kind }: { pid: string; kind: 'notes' | 'docs' }) { .catch(e => { if (gen === genRef.current) setErr(e.message) }) } - const toggleDoc = (item: TreeItem) => { + const toggleNode = (item: TreeItem) => { if (children[item.id]) { setChildren(prev => { const next = { ...prev }; delete next[item.id]; return next }) return } const gen = genRef.current - api.rdmCommand>('rdm.inspect.tree', { projectId: pid, kind: 'docs', parent: item.id, cursor: '' }) + api.rdmCommand>('rdm.inspect.tree', { projectId: pid, kind, parent: item.id, cursor: '' }) .then(s => { if (gen === genRef.current) setChildren(prev => ({ ...prev, [item.id]: s.items })) }) .catch(e => { if (gen === genRef.current) setErr(e.message) }) } const selectNode = (item: TreeItem) => { - if (item.kind === 'doc') { toggleDoc(item); return } + if (isTreeItemExpandable(item)) toggleNode(item) + if (item.kind === 'doc') return setSelected(item); setDetail(null) const gen = genRef.current api.rdmCommand('rdm.inspect.object', { projectId: pid, kind: item.kind, id: item.id }) @@ -358,30 +359,7 @@ function TreeList({ pid, kind }: { pid: string; kind: 'notes' | 'docs' }) { return (
- {shell.items.map(item => ( -
- - {item.kind === 'doc' && children[item.id] && ( -
- {children[item.id].length === 0 &&
无章节
} - {children[item.id].map(sub => ( - - ))} -
- )} -
- ))} + {shell.items.map(item => )} {shell.hasMore && } {shell.truncated &&
已截断(深度或数量上限)
}
@@ -395,6 +373,32 @@ function TreeList({ pid, kind }: { pid: string; kind: 'notes' | 'docs' }) { ) } +function TreeNodeRow({ item, selectedId, children, onSelect }: { + item: TreeItem + selectedId?: string + children: Record + onSelect: (item: TreeItem) => void +}) { + const expandable = isTreeItemExpandable(item) + const nested = children[item.id] + return ( +
+ + {nested && ( +
+ {nested.length === 0 &&
无下级节点
} + {nested.map(child => )} +
+ )} +
+ ) +} + // ---- 关联追溯 ---- function TraceTab({ pid }: { pid: string }) { diff --git a/src/rdmAuditPresentation.test.ts b/src/rdmAuditPresentation.test.ts index fbcc6ce..8616370 100644 --- a/src/rdmAuditPresentation.test.ts +++ b/src/rdmAuditPresentation.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { describeAuditEvent, humanizeKind, humanizeRelation, humanizeState } from './rdmAuditPresentation' +import { describeAuditEvent, humanizeKind, humanizeRelation, humanizeState, isTreeItemExpandable } from './rdmAuditPresentation' describe('RDM audit presentation', () => { it('turns a structured document event into a readable action and impact', () => { @@ -54,4 +54,10 @@ describe('RDM audit presentation', () => { state: '已生效', }) }) + + it('allows recursive sections to expand when the server reports children', () => { + expect(isTreeItemExpandable({ kind: 'doc', id: 'doc-1', title: '文档' })).toBe(true) + expect(isTreeItemExpandable({ kind: 'section', id: 'sec-1', title: '章节', hasChildren: true })).toBe(true) + expect(isTreeItemExpandable({ kind: 'paragraph', id: 'p-1', title: '段落', hasChildren: false })).toBe(false) + }) }) diff --git a/src/rdmAuditPresentation.ts b/src/rdmAuditPresentation.ts index 3c15f3c..2618676 100644 --- a/src/rdmAuditPresentation.ts +++ b/src/rdmAuditPresentation.ts @@ -132,3 +132,7 @@ export function humanizeRelation(input: { layer?: string; type?: string; kind?: state: humanizeState(input.state || ''), } } + +export function isTreeItemExpandable(item: { kind: string; hasChildren?: boolean; id?: string; title?: string }): boolean { + return item.kind === 'doc' || item.hasChildren === true +}