1111import { useCallback , useEffect , useState } from "react" ;
1212import { invoke } from "@tauri-apps/api/core" ;
1313import type { Fragment , NoteGroup } from "../types" ;
14+ import type { KnowledgeLink , KnowledgeSystem } from "../types/knowledge" ;
1415import { parseRouteReason , routeLineState } from "../utils/routeReason" ;
1516import RouteInfoPopover from "./RouteInfoPopover" ;
17+ import SystemBadge from "./SystemBadge" ;
1618
1719interface Props {
1820 /** 当前过滤组(null=全部笔记) */
@@ -30,6 +32,8 @@ interface Props {
3032 inboxActive : boolean ;
3133 /** 外部刷新令牌(捕获/升笔记/升卡后触发本栏重载计数) */
3234 refreshToken : number ;
35+ /** 跳转体系页并选中体系(v0.13.7 触点①) */
36+ onOpenSystem : ( systemId : number ) => void ;
3337}
3438
3539/** 组类别徽标(文案+配色) */
@@ -52,7 +56,7 @@ async function blobToBase64(blob: Blob): Promise<string> {
5256
5357export default function GroupSidebar ( {
5458 groupFilter, onGroupFilterChange, onChanged, onOpenReview, selectedNoteId,
55- onOpenInbox, inboxActive, refreshToken,
59+ onOpenInbox, inboxActive, refreshToken, onOpenSystem ,
5660} : Props ) {
5761 const [ groups , setGroups ] = useState < NoteGroup [ ] > ( [ ] ) ;
5862 const [ feedCaptureOn , setFeedCaptureOn ] = useState ( true ) ;
@@ -62,6 +66,9 @@ export default function GroupSidebar({
6266 // 收件箱待处理数(active 碎片计数)
6367 const [ inboxCount , setInboxCount ] = useState ( 0 ) ;
6468 const [ status , setStatus ] = useState ( "" ) ;
69+ // 体系引用映射(组 id → 引用它的体系列表;v0.13.7 触点① 徽标)
70+ const [ systemLinks , setSystemLinks ] = useState < Record < number , { systemId : number ; count : number } [ ] > > ( { } ) ;
71+ const [ systems , setSystems ] = useState < KnowledgeSystem [ ] > ( [ ] ) ;
6572 // ⓘ 弹层态(受控单开——同一时间只开一个组)
6673 const [ popover , setPopover ] = useState < { group : NoteGroup ; anchor : { x : number ; y : number } } | null > ( null ) ;
6774
@@ -74,6 +81,24 @@ export default function GroupSidebar({
7481 setDueTotal ( due ) ;
7582 const frags = await invoke < Fragment [ ] > ( "list_fragments" , { status : "active" , limit : 500 } ) ;
7683 setInboxCount ( frags . length ) ;
84+ // v0.13.7 触点①:体系 + 引用拉取(并行——徽标数据与组列表无依赖)
85+ const [ sysList , links ] = await Promise . all ( [
86+ invoke < KnowledgeSystem [ ] > ( "list_knowledge_systems" ) ,
87+ invoke < KnowledgeLink [ ] > ( "list_knowledge_links" ) ,
88+ ] ) ;
89+ setSystems ( sysList ) ;
90+ const map : Record < number , { systemId : number ; count : number } [ ] > = { } ;
91+ for ( const l of links ) {
92+ if ( l . targetType !== "note_group" ) continue ;
93+ ( map [ l . targetId ] ??= [ ] ) . push ( { systemId : l . systemId , count : 0 } ) ;
94+ }
95+ // 合并同体系多引用为一条(count 累加——一行一个徽标,不重复堆叠)
96+ for ( const gid of Object . keys ( map ) ) {
97+ const merged : Record < number , number > = { } ;
98+ for ( const item of map [ Number ( gid ) ] ) merged [ item . systemId ] = ( merged [ item . systemId ] ?? 0 ) + 1 ;
99+ map [ Number ( gid ) ] = Object . entries ( merged ) . map ( ( [ sid , count ] ) => ( { systemId : Number ( sid ) , count } ) ) ;
100+ }
101+ setSystemLinks ( map ) ;
77102 setStatus ( "" ) ;
78103 } catch ( e ) {
79104 setStatus ( `组加载失败: ${ e } ` ) ;
@@ -222,6 +247,19 @@ export default function GroupSidebar({
222247 { g . terrain === "feed" && (
223248 < span style = { { fontSize : 10 , color : "#7c3aed" , background : "#faf5ff" , borderRadius : 8 , padding : "0 5px" } } > feed</ span >
224249 ) }
250+ { /* 体系徽标(触点①:该组被哪些体系引用;点击跳体系页) */ }
251+ { systemLinks [ g . id ] ?. map ( ( sl ) => {
252+ const sys = systems . find ( ( s ) => s . id === sl . systemId ) ;
253+ if ( ! sys || sys . status === "archived" ) return null ;
254+ return (
255+ < SystemBadge
256+ key = { sl . systemId }
257+ name = { sys . name }
258+ linkCount = { sl . count }
259+ onClick = { ( ) => onOpenSystem ( sl . systemId ) }
260+ />
261+ ) ;
262+ } ) }
225263 < span style = { { fontSize : 10 , color : line . needsConfirm ? "#b45309" : "#9ca3af" , overflow : "hidden" , textOverflow : "ellipsis" , whiteSpace : "nowrap" , minWidth : 0 } } >
226264 { line . label }
227265 </ span >
0 commit comments