@@ -168,16 +168,23 @@ export class AutoLayoutFastTree {
168168
169169 const parentRectangle = rootNode . collisionBox . getRectangle ( ) ;
170170
171- // 计算子树的外接矩形,考虑分组框标题栏和 padding
172- const childRects = childList . map ( ( child ) => {
173- const rect = child . collisionBox . getRectangle ( ) ;
174- const ps = child . parentSection ;
175- if ( ps && ps . text !== "" ) {
176- return new Rectangle ( new Vector ( rect . left , rect . top - 80 ) , new Vector ( rect . width , rect . height + 80 ) ) ;
177- }
178- return rect ;
179- } ) ;
180- const childsRectangle = Rectangle . getBoundingRectangle ( childRects ) ;
171+ // 计算子树的真实外接矩形(不膨胀,避免影响 center 计算)
172+ const childsRectangle = Rectangle . getBoundingRectangle ( childList . map ( ( child ) => child . collisionBox . getRectangle ( ) ) ) ;
173+
174+ // 检测子节点群是否整体被一个有标题的分组框包裹,
175+ // 若是,则在垂直/水平方向额外留出分组框标题栏和 padding 的空间,避免标题栏遮挡节点。
176+ // 注意:只在"第一个子节点"所在分组框与根节点不同时才需要额外偏移,
177+ // 且仅当所有子节点都在同一个分组框内时才整体偏移(否则由 alignTrees 的 extraGap 处理)。
178+ const sectionTopOverhead = 80 ; // 30 padding + 50 title bar
179+ const firstChildSection = childList [ 0 ] . parentSection ;
180+ const allInSameSection =
181+ firstChildSection !== null &&
182+ firstChildSection !== undefined &&
183+ firstChildSection . text !== "" &&
184+ childList . every ( ( c ) => c . parentSection === firstChildSection ) ;
185+ // 仅当子节点群整体在一个有标题的分组框内,且该分组框不是根节点自身所在的分组框时,才加偏移
186+ const needSectionOffset = allInSameSection && firstChildSection !== rootNode . parentSection ;
187+ const sectionOffset = needSectionOffset ? sectionTopOverhead : 0 ;
181188
182189 // 计算子树应该移动到的目标位置(使用边缘距离而不是中心位置)
183190 let targetLocation : Vector ;
@@ -186,19 +193,27 @@ export class AutoLayoutFastTree {
186193 switch ( position ) {
187194 case "rightCenter" :
188195 // 右侧:子树位于根节点的右侧,使用右边缘计算
189- targetLocation = new Vector ( parentRectangle . right + gap + childsRectangle . width / 2 , parentRectangle . center . y ) ;
196+ // 分组框标题栏在顶部,需要把子节点群整体下移,使标题栏有空间
197+ targetLocation = new Vector (
198+ parentRectangle . right + gap + childsRectangle . width / 2 ,
199+ parentRectangle . center . y + sectionOffset / 2 ,
200+ ) ;
190201 break ;
191202
192203 case "leftCenter" :
193204 // 左侧:子树位于根节点的左侧,使用左边缘计算
194- targetLocation = new Vector ( parentRectangle . left - gap - childsRectangle . width / 2 , parentRectangle . center . y ) ;
205+ targetLocation = new Vector (
206+ parentRectangle . left - gap - childsRectangle . width / 2 ,
207+ parentRectangle . center . y + sectionOffset / 2 ,
208+ ) ;
195209 break ;
196210
197211 case "bottomCenter" :
198212 // 下方:子树位于根节点的下方,使用底边缘计算
213+ // 分组框标题栏在顶部,需要在 gap 基础上额外加上 overhead
199214 targetLocation = new Vector (
200215 parentRectangle . center . x ,
201- parentRectangle . bottom + gap + childsRectangle . height / 2 ,
216+ parentRectangle . bottom + gap + sectionOffset + childsRectangle . height / 2 ,
202217 ) ;
203218 break ;
204219
0 commit comments