@@ -53,11 +53,13 @@ pub async fn capture_session_structures(
5353/// @ai-context: 入参归一化 0-1(前端框选与图同坐标系);像素换算按 image_ref
5454/// 解码帧的宽高;越界钳制;<32×32 拒绝(误触防护);原样裁剪
5555/// (不做白边——用户意图优先);手动不设预算上限。
56+ /// @ai-context: 审查修复:屏定位改用 first_seen_ms(屏号对旧数据聚类屏不唯一
57+ /// ——多聚类屏 screen_id 均为 NULL,按屏号匹配会错屏)。
5658#[ tauri:: command]
5759pub async fn capture_structure_manual (
5860 state : State < ' _ , AppState > ,
5961 session_id : i64 ,
60- screen_id : Option < i64 > ,
62+ first_seen_ms : u64 ,
6163 x : f32 ,
6264 y : f32 ,
6365 w : f32 ,
@@ -72,7 +74,7 @@ pub async fn capture_structure_manual(
7274 }
7375 let state: AppState = ( * state) . clone ( ) ;
7476 tauri:: async_runtime:: spawn_blocking ( move || {
75- manual_capture_inner ( & state, session_id, screen_id , x, y, w, h)
77+ manual_capture_inner ( & state, session_id, first_seen_ms , x, y, w, h)
7678 } )
7779 . await
7880 . map_err ( |e| format ! ( "任务调度失败: {}" , e) ) ?
@@ -82,19 +84,20 @@ pub async fn capture_structure_manual(
8284fn manual_capture_inner (
8385 state : & AppState ,
8486 session_id : i64 ,
85- screen_id : Option < i64 > ,
87+ first_seen_ms : u64 ,
8688 x : f32 ,
8789 y : f32 ,
8890 w : f32 ,
8991 h : f32 ,
9092) -> std:: result:: Result < StructureImageRecord , String > {
91- // ① 定位屏卡 image_ref(复用屏卡体系;无图 → 明确错误,前端禁用按钮)
93+ // ① 定位屏卡 image_ref(复用屏卡体系;按 first_seen 精确定位——聚类屏
94+ // 屏号不唯一;无图 → 明确错误,前端禁用按钮)
9295 let blocks = state. db . list_ocr_blocks ( session_id) . map_err ( |e| e. to_string ( ) ) ?;
9396 let images_dir = session_images_dir ( & state. data_dir , session_id) ;
9497 let screens = crate :: screens:: build_screens ( & blocks, Some ( & images_dir) ) ;
9598 let screen = screens
9699 . iter ( )
97- . find ( |s| s. screen_id == screen_id )
100+ . find ( |s| s. first_seen_ms == first_seen_ms )
98101 . ok_or_else ( || "未找到对应屏卡" . to_string ( ) ) ?;
99102 let image_ref = screen
100103 . image_ref
@@ -110,7 +113,7 @@ fn manual_capture_inner(
110113 let ( cx, cy) = ( px ( x, fw) , px ( y, fh) ) ;
111114 let ( cw, ch) = ( px ( w, fw) . min ( fw - cx) , px ( h, fh) . min ( fh - cy) ) ;
112115 if cw < 32 || ch < 32 {
113- return Err ( "框选区域过小(≥32×32 像素 )" . to_string ( ) ) ;
116+ return Err ( "框选区域无效(过小或超出画面边缘 )" . to_string ( ) ) ;
114117 }
115118 let crop = image:: imageops:: crop_imm ( & frame, cx, cy, cw, ch) . to_image ( ) ;
116119 // ④ 手动入库(不设预算/不去重)+ 记录
@@ -121,7 +124,7 @@ fn manual_capture_inner(
121124 . map ( |d| d. as_millis ( ) as u64 )
122125 . unwrap_or ( 0 ) ;
123126 let rel = store
124- . save_manual ( now, & rgb_to_bgra ( & crop) , crop. width ( ) , crop. height ( ) )
127+ . save_manual ( now, & crate :: structure_capture :: rgb_to_bgra ( & crop) , crop. width ( ) , crop. height ( ) )
125128 . map_err ( |e| e. to_string ( ) ) ?;
126129 let rec = StructureImageRecord {
127130 id : 0 ,
@@ -141,16 +144,6 @@ fn manual_capture_inner(
141144 Ok ( saved)
142145}
143146
144- /// RGB → BGRA(结构图存储契约;与 structure_capture 内部同实现——命令层
145- /// 独立小函数,避免跨模块依赖私有工具)。
146- fn rgb_to_bgra ( rgb : & image:: RgbImage ) -> Vec < u8 > {
147- let mut out = Vec :: with_capacity ( ( rgb. width ( ) * rgb. height ( ) * 4 ) as usize ) ;
148- for p in rgb. pixels ( ) {
149- out. extend_from_slice ( & [ p[ 2 ] , p[ 1 ] , p[ 0 ] , 255 ] ) ;
150- }
151- out
152- }
153-
154147/// 会话结构图列表(图库数据源;按入库时间升序)。
155148#[ tauri:: command]
156149pub fn list_session_structure_images (
@@ -163,7 +156,8 @@ pub fn list_session_structure_images(
163156 crate :: db_structures:: list_structure_images ( & state. db , session_id) . map_err ( |e| e. to_string ( ) )
164157}
165158
166- /// 删除结构图(记录驱动:删记录 + 删文件;文件仅被本表引用可安全删)。
159+ /// 删除结构图(记录驱动:先删文件后删记录——文件删除失败时记录保留可重试,
160+ /// 避免记录已删而文件残留的不一致;审查修复)。
167161#[ tauri:: command]
168162pub fn delete_structure_image (
169163 state : State < ' _ , AppState > ,
@@ -172,13 +166,15 @@ pub fn delete_structure_image(
172166 if id <= 0 {
173167 return Err ( "无效的记录 id" . to_string ( ) ) ;
174168 }
175- let rec = crate :: db_structures:: delete_structure_image ( & state. db , id)
169+ let rec = crate :: db_structures:: get_structure_image ( & state. db , id)
176170 . map_err ( |e| e. to_string ( ) ) ?;
177171 if let Some ( r) = rec {
178172 let images_dir = session_images_dir ( & state. data_dir , r. session_id ) ;
179173 let store = crate :: structure_store:: StructureImageStore :: new ( images_dir)
180174 . map_err ( |e| e. to_string ( ) ) ?;
181175 store. delete_image ( & r. crop_path ) . map_err ( |e| e. to_string ( ) ) ?;
176+ crate :: db_structures:: delete_structure_image ( & state. db , id)
177+ . map_err ( |e| e. to_string ( ) ) ?;
182178 }
183179 let _ = state. app . emit ( "session:structures-updated" , ( ) ) ;
184180 Ok ( true )
0 commit comments