From 11c13a4e9d908fa82e5060182bd1504e235f175e Mon Sep 17 00:00:00 2001 From: laoyang Date: Fri, 2 Oct 2026 13:26:11 +0800 Subject: [PATCH] =?UTF-8?q?v18:=20=E5=AE=B6=E8=B0=B1=E6=94=B9=E6=88=90?= =?UTF-8?q?=E3=80=8C=E4=BB=BF=E4=BA=B2=E5=8F=8B+=E3=80=8DApp=20=E7=9A=84?= =?UTF-8?q?=E7=94=BB=E6=B3=95=EF=BC=88=E6=8C=89=E7=94=A8=E6=88=B7=E7=BB=99?= =?UTF-8?q?=E7=9A=84=E5=8F=82=E8=80=83=E5=AE=9E=E7=8E=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户反馈(原话):「不行,非常乱。我给你个链接 https://gitee.com/coku2333/FamilyTreeView 你去读这个代码,尤其是它那个仿亲友+app 的效果,就是我想要的,按照这个做。」 参考实现是 Android 的 NewFamilyTreeView(ViewGroup + onDraw 画线)。我读了它的 README 与源码,把它的**布局规则**逐条搬过来(不是搬代码,是搬算法): ① **从下往上定位**:先孙辈(+2)→ 子女(+1)→ 我(0)→ 父母(-1)→ 祖辈(-2)。 因为"子层位置定了,父层就定了" —— 父居中于子之上。 ② 每一代维护 **genLeft / genRight 两个游标**(该代已用到的左右边界); 往右铺从 genRight 起、往左铺从 genLeft 起(对应它的 setPart)。 ③ **配偶并排在主位右侧**(一对夫妻 = 相邻两张卡)。 ④ 兄姐往左、弟妹往右;舅姨(母系)往右、叔伯姑(父系)往左 —— 都是"以我/父母为中心"向两侧展开,而不是按格子平铺。 ⑤ 我居中于我的子女之上;父母居中于我之上;祖辈居中于父母之上。 ⑥ **连线画在卡片背后**(参考是 ViewGroup.onDraw 先画线再绘子 View)—— 所以线可以从卡中心连到卡中心,被卡面挡住即可,不用绕路。 ⑦ 卡片改成参考的窄长比例(它 50×80,我们 88×106):头像占上半、姓名与称呼在下。 画线(沿用参考的三段式,子女一律从夫妻横线中点出): 夫妻横线(两卡中心之间)→ 从**横线中点**往下垂到横梁 → 横梁分到每个孩子。 上一版(v17「整齐树」)的问题:线虽然不穿卡了,但整张图看着像表格 —— 每一代从左到右平铺、兄弟姐被推到最外侧、伯父排在爸爸右边。这一版才对上参考的观感。 验证(按用户要求只做简单测试): · `_fb_kin_v18.py`(新)—— 20 人夹具渲染:**0 重叠**、20 张卡、28 段连线、无 JS 报错; 各行分布确认:祖辈居中于父母之上(575/653)、伯父在爸爸左边(403 < 575)、 兄姐在我左(442/519)、弟妹在我右、堂兄紧贴伯父下方。 · e2e 553 条全过(家谱段断言随 v18 更新为 takeSlot/placePart/putCentered/midX) · 修掉的既有 bug:`parentsOf` 的键是**户索引**,而多处按**人 id** 去查父母 —— 混用会永远查不到,导致"户的主位永远取第一张卡"(伯父那户取成伯母)、 父系/母系判定全错、叔伯姑全跑到右边。新增 personParents 映射统一。 · 修掉的布局 bug:兄姐/弟妹原本从"整代游标末尾"起 → 被推到同代最外侧; 堂表亲调 placePart 会与我的兄姐重叠(实测抓到 1 处)→ 改成紧贴其父母下方。 另:其他功能一行未动(用户明确要求)。 --- assets/css/app.css | 34 +- templates/kin/tree.php | 722 +++++++++++++++++++++++++---------------- 2 files changed, 466 insertions(+), 290 deletions(-) diff --git a/assets/css/app.css b/assets/css/app.css index f0960c3..04839ae 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -468,7 +468,11 @@ textarea.input { min-height: 76px; resize: vertical; } position: absolute; left: 0; top: 0; will-change: transform; } -.kin-chart svg.kin-lines { pointer-events: none; } +.kin-chart svg.kin-lines { pointer-events: none; z-index: 1; } +/* ⚠️ v18:参考实现(亲友+)把连线画在**卡片背后**(ViewGroup.onDraw 在子 View 之前), + 所以线可以从卡中心连到卡中心、被卡面挡住即可 —— 不需要绕路。 + 这里用 z-index 保证「连线在卡片下面」。 */ +.kin-stage { z-index: 2; } /* 连线:夫妻短横线 + 子女下垂线,全部一条笔触 */ .kin-chart path.kin-link { @@ -478,30 +482,42 @@ textarea.input { min-height: 76px; resize: vertical; } vector-effect: non-scaling-stroke; /* 缩放时线不跟着变粗变细 */ } +/* v18:卡片改成「上图下名 + 称呼」的窄长形,对齐参考实现(亲友+)的比例: + 参考里 50dp 宽 × 80dp 高,头像占满上半、姓名与称呼各一行贴在下半。 + 我们把 88×106 按同样比例放大 —— 窄一点才排得下"好几代 + 兄弟姐妹"。 */ .kin-card { position: absolute; - display: flex; flex-direction: column; align-items: center; justify-content: center; - gap: 2px; - box-sizing: border-box; padding: 7px 6px 6px; + display: flex; flex-direction: column; align-items: center; + gap: 0; + box-sizing: border-box; padding: 4px 4px 3px; text-align: center; background: var(--surface); border: 1px solid var(--line); - border-radius: 14px; - box-shadow: var(--shadow); + border-radius: 10px; + box-shadow: 0 1px 3px rgba(43,38,32,.10); color: var(--ink); transform: translate(-50%, -50%); /* 坐标给的是卡片中心 */ transition: border-color .15s ease, box-shadow .15s ease; + overflow: hidden; } .kin-card:hover { box-shadow: var(--shadow-l); border-color: var(--brand-2); text-decoration: none; } .kin-card .kin-ph { - width: 38px; height: 38px; border-radius: 50%; overflow: hidden; + width: 100%; height: 58px; border-radius: 7px; overflow: hidden; background: #F1E7D6; flex: 0 0 auto; } .kin-card .kin-ph img { width: 100%; height: 100%; object-fit: cover; display: block; } -.kin-card .kin-nm { font-size: 13px; font-weight: 600; line-height: 1.25; } -.kin-card .kin-lb { font-size: 11px; color: var(--brand); line-height: 1.25; } +.kin-card .kin-nm { + font-size: 12.5px; font-weight: 600; line-height: 1.25; max-width: 100%; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + margin-top: 3px; +} +/* 称呼在姓名下面一行,用品牌色的浅字(参考实现是「(称呼)」) */ +.kin-card .kin-lb { + font-size: 10.5px; color: var(--brand); line-height: 1.2; max-width: 100%; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} .kin-card .kin-mark { font-size: 10px; color: var(--faint); } /* 「我」:品牌色描边 + 光圈 */ diff --git a/templates/kin/tree.php b/templates/kin/tree.php index 47e3866..2c99b63 100644 --- a/templates/kin/tree.php +++ b/templates/kin/tree.php @@ -66,6 +66,10 @@ foreach ($units as $i => $u) { 'me' => $id === (int)$meId, 'ph' => (int)($p['is_placeholder'] ?? 0) === 1, 'gender' => $g === 'm' ? 'M' : ($g === 'f' ? 'F' : 'U'), + // ⚠️ 长幼排序键:前端要靠它分「哥哥/弟弟」「姐姐/妹妹」。 + // 不用生日硬排(很多人没填生日),用 Kinship 里已有的 rankValueFor —— + // 它先看 birth_rank,再退回生日,最后用 id 兜底,保证顺序稳定。 + 'rank' => \App\Kinship::rankValueFor($people, $id), ]; } if (!$cards) { @@ -157,6 +161,19 @@ $payloadJson = json_encode(