1515 * 模式/多选态(清选集退出);右键菜单=单行语义(打开详情/重命名/
1616 * 复制标题/转笔记/删除——SessionRowContextMenu 委托父层处理)。
1717 */
18- import { useCallback , useEffect , useMemo , useRef , useState } from "react" ;
18+ import { useEffect , useRef , useState } from "react" ;
1919import { invoke } from "@tauri-apps/api/core" ;
20- import type { CourseGroup , OcrBlockHit , SegmentHit , Session , SessionListItem } from "../types" ;
20+ import type { CourseGroup , OcrBlockHit , SegmentHit , SessionListItem } from "../types" ;
2121import { fmtMs } from "../utils/fmt" ;
2222import { isSessionConvertible } from "../utils/sessionEligibility" ;
23+ import {
24+ useSessionListView ,
25+ type ConvertedFilter ,
26+ type SortBy ,
27+ type StatusFilter ,
28+ } from "../hooks/useSessionListView" ;
2329import { useSessionSelection } from "../hooks/useSessionSelection" ;
2430import SessionListRow from "./SessionListRow" ;
2531import type { SessionRenameRequest } from "./SessionListRow" ;
@@ -30,9 +36,6 @@ const selectStyle: React.CSSProperties = {
3036 fontSize : 12 , padding : "4px 6px" , border : "1px solid #e5e7eb" , borderRadius : 6 , background : "#fff" ,
3137} ;
3238
33- type StatusFilter = "all" | "recording" | "finished" | "failed" ;
34- type ConvertedFilter = "all" | "todo" | "done" ;
35- type SortBy = "time-desc" | "time-asc" | "duration" ;
3639// TD-2026-08-19-E 清偿:三模式搜索——标题(本地)/ 内容(段搜索)/ 画面(图内文字检索)
3740type SearchMode = "title" | "content" | "ocr" ;
3841
@@ -124,69 +127,10 @@ export default function SessionListPanel({
124127 }
125128 } ;
126129
127- /** 筛选谓词(列表与课程分组共用) */
128- const matchFilters = useCallback (
129- ( item : SessionListItem ) => {
130- if ( filterStatus !== "all" && item . session . status !== filterStatus ) return false ;
131- if ( filterConverted === "todo" && ( item . hasNote || ! item . hasContent ) ) return false ;
132- if ( filterConverted === "done" && ! item . hasNote ) return false ;
133- const kw = keyword . trim ( ) . toLowerCase ( ) ;
134- if ( kw ) {
135- const hay = `${ item . session . title } ${ item . session . source_window ?? "" } ` . toLowerCase ( ) ;
136- if ( ! hay . includes ( kw ) ) return false ;
137- }
138- return true ;
139- } ,
140- [ filterStatus , filterConverted , keyword ] ,
141- ) ;
142-
143- /** 本地排序(时间倒序为后端默认序,保持稳定不重排) */
144- const sorted = useMemo ( ( ) => {
145- const list = [ ...items ] ;
146- const endOf = ( s : Session ) => s . ended_at ?? Math . floor ( Date . now ( ) / 1000 ) ;
147- if ( sortBy === "time-asc" ) {
148- list . sort ( ( a , b ) => a . session . started_at - b . session . started_at ) ;
149- } else if ( sortBy === "duration" ) {
150- list . sort (
151- ( a , b ) =>
152- ( endOf ( b . session ) - b . session . started_at ) - ( endOf ( a . session ) - a . session . started_at ) ,
153- ) ;
154- }
155- return list ;
156- } , [ items , sortBy ] ) ;
157-
158- const filtered = useMemo ( ( ) => sorted . filter ( matchFilters ) , [ sorted , matchFilters ] ) ;
159-
160- /** 课程分组视图(组内同样应用筛选 + 排序) */
161- const groupedView = useMemo ( ( ) => {
162- if ( ! groups ) return null ;
163- const endOf = ( s : Session ) => s . ended_at ?? Math . floor ( Date . now ( ) / 1000 ) ;
164- return groups
165- . map ( ( g ) => ( {
166- ...g ,
167- sessions : g . sessions . filter ( matchFilters ) . sort ( ( a , b ) => {
168- if ( sortBy === "time-asc" ) return a . session . started_at - b . session . started_at ;
169- if ( sortBy === "duration" )
170- return ( endOf ( b . session ) - b . session . started_at ) - ( endOf ( a . session ) - a . session . started_at ) ;
171- return 0 ; // time-desc:后端已按新→旧
172- } ) ,
173- } ) )
174- . filter ( ( g ) => g . sessions . length > 0 ) ;
175- } , [ groups , matchFilters , sortBy ] ) ;
176-
177- // 当前可见行序(批 4 区间语义基准):分组视图=展开组顺次;平铺=筛选后序。
178- // 折叠组行不可见——不参与区间与选集裁剪(与笔记树语义一致)
179- const visibleOrder = useMemo ( ( ) => {
180- if ( grouped && groupedView ) {
181- const out : number [ ] = [ ] ;
182- for ( const g of groupedView ) {
183- if ( collapsed [ g . course ] ) continue ;
184- for ( const i of g . sessions ) out . push ( i . session . id ) ;
185- }
186- return out ;
187- }
188- return filtered . map ( ( i ) => i . session . id ) ;
189- } , [ grouped , groupedView , filtered , collapsed ] ) ;
130+ // ── 视图模型(批 0-C2 拆至 hooks/useSessionListView)──
131+ const { filtered, groupedView, visibleOrder } = useSessionListView ( {
132+ items, groups, grouped, collapsed, filterStatus, filterConverted, keyword, sortBy,
133+ } ) ;
190134
191135 // ── 多选/选择模式状态机(批 0-C2 拆至 hooks/useSessionSelection)──
192136 const {
0 commit comments