From e22202271ad1bfe33bc81dd7f1aab73b79304456 Mon Sep 17 00:00:00 2001 From: laoyang Date: Fri, 2 Oct 2026 17:35:27 +0800 Subject: [PATCH] =?UTF-8?q?v20:=20=E5=AE=B6=E8=B0=B1=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E6=8E=A8=E5=80=92=E9=87=8D=E5=86=99=E4=B8=BA=E5=8C=BA=E9=97=B4?= =?UTF-8?q?=E6=B3=95=EF=BC=88tidy=20tree=EF=BC=89=E2=80=94=E2=80=94=20?= =?UTF-8?q?=E4=B8=80=E6=AC=A1=E6=88=90=E5=9E=8B=EF=BC=8C=E6=97=A0=E5=A4=9A?= =?UTF-8?q?=E8=BD=AE=E6=8E=A8=E6=8C=A4=E4=BF=AE=E8=A1=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户反馈:"错的更离谱了,这次连位置都不对了"(v19 实测"我"跑到离父母 800px 的最右侧)。 根因:v18/v19 是「游标铺放 → 防重叠 → 同代重排 → 再防重叠 → 再硬推」 叠了四五层修补,每一层都在破坏上一层的"父居中于子女",多轮互相拉扯后整棵树漂移。 区间法规则(一次成型,天然不叠卡): ① 每棵户子树自底向上算需求宽度 needW = max(自身宽, Σ子女宽 + 间距); ② 自顶向下分配互不重叠的 x 区间,子女依次铺在父区间内; ③ 父户居中于子女的实际跨度。 跨树边(岳父母户也把妻子列为子女)只走「主父户」进树递归 (主位卡是我方血亲的那个父户,服务端给每张卡标 blood), 其余父户只留给画线段 —— 岳父母的子女横梁照样连到妻子,布局不重复铺人。 验证(20 人夹具 + 真实渲染): · 版面五判据 A-E 全过(8/8):不重叠 / 线在卡后 / 下垂线从夫妻横线中点出 / 同代有空隙 / 横线在两卡缝隙;行分布:兄姐弟妹紧贴我、堂表在伯父下方、 岳父母贴妻子右上、外甥在舅子下方; · 连线完整性:该连上一代的卡 0 缺线、悬空竖线 0 段; · 反向验证 3/3:注入坏法(下垂线从左卡出 / needW 砍半 / 中点取左卡)全部变红。 --- templates/kin/tree.php | 623 ++++++++++------------------------------- 1 file changed, 142 insertions(+), 481 deletions(-) diff --git a/templates/kin/tree.php b/templates/kin/tree.php index 899976a..0912269 100644 --- a/templates/kin/tree.php +++ b/templates/kin/tree.php @@ -45,6 +45,24 @@ $rankKey = static function (int $id) use ($people): string { }; // ---- ④ 打成前端要的数据包(代数 → 户 → 人)---- + +// 「我」的血亲集合:只走 parent/child 边(不含配偶)能从「我」到达的人。 +// 用途:给每张卡标 blood —— 前端据此判定一户的「主父户」 +// (我的户同时被父母户与岳父母户认作子女,树递归只能走本家那条, +// 否则树变图、布局重复铺人;判定标准就是「父户的主位是不是我方血亲」)。 +$blood = [$meId => true]; +$bq = [$meId]; +while ($bq) { + $bid = (int)array_shift($bq); + foreach (array_merge($graph['parents'][$bid] ?? [], $graph['children'][$bid] ?? []) as $nb) { + $nb = (int)$nb; + if (!isset($blood[$nb])) { + $blood[$nb] = true; + $bq[] = $nb; + } + } +} + $payloadUnits = []; foreach ($units as $i => $u) { $sorted = array_map('intval', $u['ids']); @@ -66,6 +84,8 @@ foreach ($units as $i => $u) { 'me' => $id === (int)$meId, 'ph' => (int)($p['is_placeholder'] ?? 0) === 1, 'gender' => $g === 'm' ? 'M' : ($g === 'f' ? 'F' : 'U'), + // ⚠️ 本家血亲标记:前端用它判定一户的「主父户」(见上方 $blood 的说明) + 'blood' => isset($blood[$id]), // ⚠️ 长幼排序键:前端要靠它分「哥哥/弟弟」「姐姐/妹妹」。 // 不用生日硬排(很多人没填生日),用 Kinship 里已有的 rankValueFor —— // 它先看 birth_rank,再退回生日,最后用 id 兜底,保证顺序稳定。 @@ -82,21 +102,8 @@ foreach ($units as $i => $u) { // 我排在她右边"(实测线上就是这个症状:红围脖@639 在我的左边)。 // // 正解:按「**与"我"的血缘距离**」判定 —— 这一户里离「我」更近的那一位才是主位。 - // 我用一次不带配偶边的 BFS 算出血亲集合(Kinship::bloodRelatives 语义), + // 血亲集合 $blood 已在循环外算好(只走 parent/child 边的 BFS), // 两边都在血亲集合里时,退到「与我的步数」比大小。 - $blood = []; - $bq = [$meId]; - $blood[$meId] = true; - while ($bq) { - $bid = array_shift($bq); - foreach (array_merge($graph['parents'][$bid] ?? [], $graph['children'][$bid] ?? []) as $nb) { - $nb = (int)$nb; - if (!isset($blood[$nb])) { - $blood[$nb] = true; - $bq[] = $nb; - } - } - } $head = null; $headScore = null; foreach ($cards as $c) { @@ -180,17 +187,17 @@ $payloadJson = json_encode(