44 * @ai -context: 纯展示/受控组件——数据与状态全部由 NotesPage 编排(参照
55 * SessionsPage → SessionListPanel/SessionDetailPanel 既有模式)。
66 * 行 id=`note-row-{id}` 供 focusNoteId 跨页直达滚动定位。
7+ * @ai -context: v0.12.7 勾选批量删除(与会话管理台同逻辑:行内勾选 +
8+ * 底部批量操作栏;副作用经 onBatchDelete 上抛父层确认/invoke)。
79 */
10+ import { useState } from "react" ;
811import type { Note } from "../types" ;
912
1013export type SortMode = "updated-desc" | "pin-first" | "created-desc" ;
1114
15+ const btn : React . CSSProperties = { padding : "5px 10px" , cursor : "pointer" , fontSize : 12 } ;
16+
1217/** 解析 tags JSON 为字符串数组(损坏 JSON 回退空数组——防御性) */
1318export function parseTags ( note : Note ) : string [ ] {
1419 try {
@@ -39,12 +44,29 @@ interface Props {
3944 onCreate : ( ) => void ;
4045 onRefresh : ( ) => void ;
4146 onOpenSession : ( sessionId : number ) => void ;
47+ /** 批量删除(父层负责确认/invoke/刷新;resolve=true 表示已执行删除——
48+ * 勾选集合在删除执行后才清空,取消确认时保留勾选——与会话管理台一致) */
49+ onBatchDelete : ( ids : number [ ] ) => Promise < boolean > ;
4250}
4351
4452export default function NoteListView ( {
4553 notes, keyword, tagFilter, sortMode, allTags, selectedId, status,
46- onKeywordChange, onTagFilterChange, onSortModeChange, onSelect, onCreate, onRefresh, onOpenSession,
54+ onKeywordChange, onTagFilterChange, onSortModeChange, onSelect, onCreate, onRefresh, onOpenSession, onBatchDelete ,
4755} : Props ) {
56+ // v0.12.7:勾选集合(面板本地状态——结构与会话管理台 selected 一致)
57+ const [ selected , setSelected ] = useState < Set < number > > ( new Set ( ) ) ;
58+
59+ const toggleSelect = ( id : number ) => {
60+ setSelected ( ( cur ) => {
61+ const next = new Set ( cur ) ;
62+ if ( next . has ( id ) ) next . delete ( id ) ;
63+ else next . add ( id ) ;
64+ return next ;
65+ } ) ;
66+ } ;
67+
68+ const clearSelection = ( ) => setSelected ( new Set ( ) ) ;
69+
4870 return (
4971 < div style = { { width : 320 , flexShrink : 0 , borderRight : "1px solid #e5e7eb" , display : "flex" , flexDirection : "column" } } >
5072 < div style = { { padding : "10px 14px" , borderBottom : "1px solid #e5e7eb" , fontWeight : 600 , display : "flex" , alignItems : "center" , gap : 8 } } >
@@ -125,6 +147,14 @@ export default function NoteListView({
125147 } }
126148 >
127149 < div style = { { display : "flex" , alignItems : "center" , gap : 6 } } >
150+ < input
151+ type = "checkbox"
152+ checked = { selected . has ( n . id ) }
153+ onClick = { ( e ) => e . stopPropagation ( ) }
154+ onChange = { ( ) => toggleSelect ( n . id ) }
155+ style = { { cursor : "pointer" , flexShrink : 0 } }
156+ title = "勾选后可批量删除"
157+ />
128158 { n . pin ? < span style = { { fontSize : 11 , color : "#b45309" } } > 📌</ span > : null }
129159 < div style = { { fontSize : 13 , fontWeight : 500 , whiteSpace : "nowrap" , overflow : "hidden" , textOverflow : "ellipsis" , flex : 1 } } >
130160 { n . title }
@@ -150,6 +180,40 @@ export default function NoteListView({
150180 ) ;
151181 } ) }
152182 </ div >
183+ { /* v0.12.7:勾选批量删除栏(与会话管理台同模式——全选三态 + 计数 + 批量删除) */ }
184+ { selected . size > 0 && (
185+ < div style = { { borderTop : "1px solid #e5e7eb" , padding : 8 , display : "flex" , gap : 6 , alignItems : "center" , background : "#fff" } } >
186+ < input
187+ type = "checkbox"
188+ ref = { ( el ) => {
189+ if ( el ) el . indeterminate = selected . size > 0 && selected . size < notes . length ;
190+ } }
191+ checked = { selected . size === notes . length && notes . length > 0 }
192+ onChange = { ( ) => {
193+ if ( selected . size === notes . length && notes . length > 0 ) {
194+ clearSelection ( ) ;
195+ } else {
196+ setSelected ( new Set ( notes . map ( ( n ) => n . id ) ) ) ;
197+ }
198+ } }
199+ style = { { cursor : "pointer" , flexShrink : 0 } }
200+ title = "全选当前列表的笔记"
201+ />
202+ < span style = { { fontSize : 12 , color : "#374151" } } > 已选 { selected . size } 个</ span >
203+ < button
204+ style = { { ...btn , fontSize : 11 , borderRadius : 6 , border : "1px solid #fca5a5" , color : "#dc2626" , background : "#fff" } }
205+ onClick = { async ( ) => {
206+ const ids = [ ...selected ] ;
207+ if ( await onBatchDelete ( ids ) ) clearSelection ( ) ;
208+ } }
209+ >
210+ 批量删除
211+ </ button >
212+ < button style = { { ...btn , marginLeft : "auto" , fontSize : 11 } } onClick = { clearSelection } >
213+ 取消
214+ </ button >
215+ </ div >
216+ ) }
153217 { status && < p style = { { padding : 8 , fontSize : 12 , color : "#dc2626" } } > { status } </ p > }
154218 </ div >
155219 ) ;
0 commit comments