@@ -44,6 +44,14 @@ class ItemData:
4444 name : str
4545 reco_id : int = 0
4646 status : Status = Status .PENDING
47+ # 是否通过 anchor 解析得到
48+ is_anchor : bool = False
49+ # 锚点名称(如 "锚点"),仅当 is_anchor=True 时有效
50+ anchor_name : str = ""
51+ # 锚点指向的目标节点名称
52+ anchor_target : str = ""
53+ # 锚点设置者节点名称(即定义 anchor 的节点)
54+ anchor_setter : str = ""
4755 # 嵌套子项的容器引用(用于 RecognitionNode 添加子识别项)
4856 nested_container : Optional [Any ] = field (default = None , repr = False )
4957 # 嵌套子项列表
@@ -59,6 +67,11 @@ class ListData:
5967 row_len : int
6068 current : str
6169 next_list : List [str ]
70+ anchor_flags : List [bool ] = field (default_factory = list )
71+ # 每个 next_list 项对应的锚点名称(空字符串表示非锚点)
72+ anchor_names : List [str ] = field (default_factory = list )
73+ # 每个 next_list 项对应的锚点目标节点名称(空字符串表示非锚点)
74+ anchor_targets : List [str ] = field (default_factory = list )
6275
6376
6477def main ():
@@ -193,8 +206,25 @@ def on_page_change(self, page: int):
193206 for row_len in row_len_list [start_index :end_index ]:
194207 self .create_list (self .other_page_row , self .list_data_map [row_len ])
195208
196- def add_item_data (self , index , name , row_len : int ):
197- data = ItemData (row_len , index , name )
209+ def add_item_data (
210+ self ,
211+ index ,
212+ name ,
213+ row_len : int ,
214+ is_anchor : bool = False ,
215+ anchor_name : str = "" ,
216+ anchor_target : str = "" ,
217+ anchor_setter : str = "" ,
218+ ):
219+ data = ItemData (
220+ row_len ,
221+ index ,
222+ name ,
223+ is_anchor = is_anchor ,
224+ anchor_name = anchor_name ,
225+ anchor_target = anchor_target ,
226+ anchor_setter = anchor_setter ,
227+ )
198228 self .data [row_len ][index ] = data
199229
200230 def create_list (self , row : ui .row , data : ListData ):
@@ -227,7 +257,17 @@ def create_items(self, index: int, name: str, row_len: int):
227257 StatusIndicator (data , "status" )
228258
229259 with ui .item_section ():
230- ui .item_label (name )
260+ if data .is_anchor :
261+ with ui .row (wrap = False ).classes ("items-center gap-1" ):
262+ display_name = data .anchor_target or name
263+ anchor_label = (
264+ f"{ display_name } (anchor:{ data .anchor_name or '未知' } , "
265+ f"from:{ data .anchor_setter or '未知' } )"
266+ )
267+ ui .item_label (anchor_label )
268+ ui .badge ("⚓" , color = "blue-grey-4" ).props ("rounded" )
269+ else :
270+ ui .item_label (name )
231271
232272 with ui .item_section ().props ("side" ):
233273 ui .item_label ().bind_text_from (data , "reco_id" ).bind_visibility_from (
@@ -296,9 +336,12 @@ async def _handle_message(self, msg: Dict[str, Any]):
296336 if msg_type == "NextList.Starting" :
297337 name = msg .get ("name" , "" )
298338 next_names = msg .get ("next_list" , [])
339+ anchor_flags = msg .get ("anchor_flags" , [])
299340 if debug_mode :
300- print (f"[DEBUG] NextList.Starting: name={ name } , next_list={ next_names } " )
301- self ._on_next_list_starting (name , next_names )
341+ print (
342+ f"[DEBUG] NextList.Starting: name={ name } , next_list={ next_names } , anchor_flags={ anchor_flags } "
343+ )
344+ self ._on_next_list_starting (name , next_names , anchor_flags )
302345 await maafw .screenshotter .refresh (False )
303346
304347 # RecognitionNode.Starting - 标记进入嵌套识别模式
@@ -407,7 +450,11 @@ def _on_recognized(self, reco_id: int, name: str, hit: bool):
407450 # 如果通过 reco_id 没找到,检查当前 row(非嵌套项)
408451 if not found :
409452 for item in self .data [self .row_len ].values ():
410- if item .status == Status .PENDING and item .name == name :
453+ if item .status != Status .PENDING :
454+ continue
455+ name_matched = item .name == name
456+ anchor_matched = item .is_anchor and item .anchor_target == name
457+ if name_matched or anchor_matched :
411458 item .reco_id = reco_id
412459 item .status = Status .SUCCEEDED if hit else Status .FAILED
413460 matched_item = item
@@ -421,7 +468,11 @@ def _on_recognized(self, reco_id: int, name: str, hit: bool):
421468 if not found :
422469 for row_data in self .data .values ():
423470 for item in row_data .values ():
424- if item .status == Status .PENDING and item .name == name :
471+ if item .status != Status .PENDING :
472+ continue
473+ name_matched = item .name == name
474+ anchor_matched = item .is_anchor and item .anchor_target == name
475+ if name_matched or anchor_matched :
425476 item .reco_id = reco_id
426477 item .status = Status .SUCCEEDED if hit else Status .FAILED
427478 matched_item = item
@@ -458,11 +509,74 @@ def _on_reco_node_ended(self):
458509 f"[DEBUG] RecognitionNode depth decreased to { self ._reco_node_depth } , stack size: { len (self ._recognition_stack )} "
459510 )
460511
461- def _on_next_list_starting (self , current : str , next_list : List [str ]):
512+ def _on_next_list_starting (
513+ self ,
514+ current : str ,
515+ next_list : List [str ],
516+ anchor_flags : Optional [List [bool ]] = None ,
517+ ):
462518 """处理 NextList 开始事件"""
463519 self .row_len += 1
464520
465- list_data = ListData (self .row_len , current , next_list )
521+ normalized_anchor_flags = anchor_flags or []
522+
523+ # 从 pipeline 的 next 列表中提取 [Anchor] 标记,得到锚点名和目标节点
524+ anchor_names : List [str ] = []
525+ anchor_targets : List [str ] = []
526+ if any (normalized_anchor_flags ):
527+ node_data = maafw .get_node_data (current )
528+ if isinstance (node_data , dict ):
529+ node_next = node_data .get ("next" , [])
530+ anchor_map = node_data .get ("anchor" , {})
531+ else :
532+ node_next = []
533+ anchor_map = {}
534+
535+ for i , _name in enumerate (next_list ):
536+ is_anchor = (
537+ i < len (normalized_anchor_flags ) and normalized_anchor_flags [i ]
538+ )
539+ if not is_anchor :
540+ anchor_names .append ("" )
541+ anchor_targets .append ("" )
542+ continue
543+
544+ anchor_name = ""
545+ anchor_target = ""
546+
547+ if i < len (node_next ) and isinstance (node_next [i ], str ):
548+ raw_next = node_next [i ]
549+ if raw_next .startswith ("[Anchor]" ):
550+ anchor_name = raw_next .replace ("[Anchor]" , "" , 1 )
551+ if isinstance (anchor_map , dict ):
552+ anchor_target = anchor_map .get (anchor_name , "" )
553+
554+ if (
555+ not anchor_name
556+ and isinstance (anchor_map , dict )
557+ and isinstance (_name , str )
558+ ):
559+ if _name in anchor_map :
560+ anchor_name = _name
561+ anchor_target = anchor_map .get (_name , "" )
562+
563+ if not anchor_target and isinstance (_name , str ):
564+ anchor_target = _name
565+
566+ anchor_names .append (anchor_name )
567+ anchor_targets .append (anchor_target )
568+ else :
569+ anchor_names = ["" ] * len (next_list )
570+ anchor_targets = ["" ] * len (next_list )
571+
572+ list_data = ListData (
573+ self .row_len ,
574+ current ,
575+ next_list ,
576+ normalized_anchor_flags ,
577+ anchor_names ,
578+ anchor_targets ,
579+ )
466580 self .add_list_data (list_data )
467581
468582 # 299/300 -> page:1 | 300/300 -> page:2
@@ -507,7 +621,22 @@ def add_list_data(self, data: ListData):
507621 self .list_data_map [data .row_len ] = data
508622 for index in range (len (data .next_list )):
509623 name = data .next_list [index ]
510- self .add_item_data (index , name , data .row_len )
624+ is_anchor = index < len (data .anchor_flags ) and data .anchor_flags [index ]
625+ anchor_name = (
626+ data .anchor_names [index ] if index < len (data .anchor_names ) else ""
627+ )
628+ anchor_target = (
629+ data .anchor_targets [index ] if index < len (data .anchor_targets ) else ""
630+ )
631+ self .add_item_data (
632+ index ,
633+ name ,
634+ data .row_len ,
635+ is_anchor = is_anchor ,
636+ anchor_name = anchor_name ,
637+ anchor_target = anchor_target ,
638+ anchor_setter = data .current if is_anchor else "" ,
639+ )
511640
512641 def on_resource_loading (
513642 self ,
0 commit comments