v15: 家谱重写 —— 以我为中心检索全体亲属 + 自己排版画线
用户 2026-10-02 的反馈(原话):
「显示不齐全,应该显示本人的上一代,这一代,和下一代的所有人…以本人为中心去搜索,
只要有关系的,都检索出来进行展示」
「夫妻之间有一条横线…这对夫妻下面所有的子女,都应该从夫妻之间的这个横线出来,
而不是从这对夫妻的其中一个出来,这样可能会出现一个 bug,就是如果从一个出来,
例如从父亲出来,母亲登录进去竟然看不到自己的孩子」
「数据仍然不全,差很多」
根因(实测,不是猜):第三方库 family-chart **只画了 20 人里的 9 人**。
_tooling/_fb_kin_cover_probe.py 的硬数据:
画出的:祖父/祖母/父亲/母亲/我/妻子/哥哥/儿子/女儿(9)
没画的:伯父、伯母、岳父、岳母、嫂子、堂兄、堂嫂、舅子、侄子、堂侄、外甥(11)
库的算法只画「树根的祖先链 + 后代 + 树根的兄弟姐妹 + 各人配偶」,
**横向亲戚(伯父那一支)与姻亲旁支(妻子的兄弟)永远进不来** ——
换树根也救不了(换到最高祖先,"妻子的兄弟"照样不在树里)。
「母亲看不到孩子」还有第二层原因:孩子只登记了父亲时,母亲那一侧根本没有边。
改法(不再依赖库的树算法):
· app/Kinship::familyMap() —— 以「我」为中心的两轮贯穿检索,带 seen 防环:
父母(-1 代)/ 子女(+1 代)/ 配偶(同代)/ 每一层的配偶
/ 配偶的父母与子女(姻亲旁支由此进来)/ 兄弟姐妹的子女(侄甥)
→ 上一代、这一代、下一代的**所有有关系的人**都在名单里。
· app/Kinship::householdChildren() —— 子女按**父母所在的户**归堆,
而不是按"某个人的 children"。于是孩子只登记了一位父母时,
照样挂到这对夫妻的户上(语义:这孩子是我们俩的)。
· templates/kin/tree.php —— 自己排版:每代一行、按「户」水平排开;
夫妻两张卡之间画短横线;子女一律从**这条横线的中点**垂下来
(x = (左卡中心 + 右卡中心) / 2,与"谁登录"无关)。
· 拖拽/缩放保留(用户明确要),改用原生 d3.zoom;不再引 family-chart。
验证(只跑这一个模块,按用户要求):
· _tooling/_fb_kin_v15.py —— 22 项全过:
✔ 20/20 全员画出(== 从「我」出发可达的人数)
✔ 7 对夫妻都有横线;7 个有子女的户**下垂线全部落在横线中点**
✔ 用户点名的场景一:删掉「母亲→儿子」这条边(模拟只登记父亲)后,
下垂线**仍然**从夫妻横线中点出来,孩子仍在图上
✔ 用户点名的场景二:给母亲开账号、**用她的手机号登录**,
家谱里能看到自己的孩子,且与父亲视角画的是同一批人
✔ 截图存档(全图 / 母亲视角 / 窄屏)
· _tooling/_fb_kin_v15_falsify.py —— 反向验证 2/2 抓住:
注入「从其中一张卡出来」→「从横线中点出来」变红
注入「只走直系血亲」→「每个人都被画出来」变红
· e2e 550 条全过(家谱段改到 v15 的判据上)
顺带修的既有问题:
· app/Kinship.php 老库升级回填按实际存在的列动态拼(缺列不再 1054 炸断升级)
· _fb_e2e.py 家谱断言改到 kin_v15_nodes(旧解析器在新页面上只会解析出空)
This commit is contained in:
+39
-45
@@ -432,82 +432,76 @@ textarea.input { min-height: 76px; resize: vertical; }
|
||||
}
|
||||
|
||||
/* ---------------- 家谱(以我为中心) ----------------
|
||||
v13:改由第三方库 **family-chart**(D3 生态,MIT)画,不再自研网格布局。
|
||||
v15:**自己排版、自己画线**,不再用 family-chart。
|
||||
|
||||
为什么不再自研:v12 的「整棵树一个 grid」几何上对齐了,但连线的横杆是**网格项**,
|
||||
只能横跨「从父母列到子女列」的整段列区间 —— 中间只要夹着跟这条血缘无关的人
|
||||
(最典型是配偶带进来的岳父母),横杆就会从他们卡片中间穿过去。这是布局范式的死结,
|
||||
不是调样式能救的;换成库的树算法之后这层结构本身就不存在了。
|
||||
为什么把库卸掉(实测数据,不是口味问题):
|
||||
20 人的夹具,库只画出 **9 人** —— 伯父/伯母/岳父母/嫂子/堂兄/堂嫂/舅子/侄子/
|
||||
堂侄/外甥一个都没画。库的算法只画「树根的祖先链 + 后代 + 树根的兄弟姐妹 + 各人配偶」,
|
||||
**横向亲戚与姻亲旁支永远进不来**(换树根也救不了)。用户两次反馈
|
||||
「显示不齐全」「数据仍然不全,差很多」说的就是这个。
|
||||
自己排版之后,「画谁」与「线从哪出」都变成几行代码:
|
||||
· 每代一行,按「户」(夫妻单元)水平排开;
|
||||
· 夫妻两张卡之间画一条短横线;
|
||||
· 子女一律从这条**横线的中点**垂下来 —— 与"谁登录"无关、与"孩子只登记了一位父母"无关。
|
||||
|
||||
⚠️ 库自己的 family-chart.css **没有引入**:实测库里所有规则都写在 `.f3` 作用域下,
|
||||
而运行时并不会生成任何 `.f3` 元素(卡片定位全靠 inline style)——引进来是死文件,
|
||||
白白多 16KB。所以下面这些是**库真正需要**的那几条,全部收在 `.kin-chart` 作用域里。
|
||||
拖拽/缩放继续保留(用户明确说「反正可以缩放和拖动」),用原生 d3.zoom 实现。 */
|
||||
|
||||
⚠️ 卡片/连线都不是我们生成的:卡片是库建的真实 <div>(.card_cont > .card > 我们的
|
||||
.kin-card),连线是 svg 里的 <path class="link">。改版式只能从这两个钩子下手。 */
|
||||
|
||||
/* v14:树根换成「最高祖先」之后整棵树会很大,光靠拖很累 ——
|
||||
这两个按钮把「看全部」与「回到我」分开。放在画布**外面**,
|
||||
免得被 svg 的拖拽吞掉点击(画布整块都被库接管了手势)。 */
|
||||
.kin-tools { display: flex; gap: 8px; justify-content: flex-end; margin: 0 0 10px; }
|
||||
|
||||
.kin-chart {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
/* 太矮三代挤成一团、太高白占屏 */
|
||||
height: clamp(420px, 62vh, 720px);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
overflow: hidden;
|
||||
/* 库自己接管拖拽/缩放,别让浏览器插手 */
|
||||
cursor: grab;
|
||||
/* 拖拽/缩放自己接管,别让浏览器插手滚动 */
|
||||
touch-action: none;
|
||||
}
|
||||
/* 库在容器里自己建 #f3Canvas(position:relative; width/height:100%),
|
||||
再往里插 svg.main_svg 与 #htmlSvg。这里只要保证那两层撑满。 */
|
||||
.kin-chart #f3Canvas,
|
||||
.kin-chart #htmlSvg { width: 100%; height: 100%; }
|
||||
.kin-chart:active { cursor: grabbing; }
|
||||
|
||||
/* ⚠️ 库的 `.f3 svg.main_svg{width:100%;height:100%}` 选择器**永远不会命中**
|
||||
(SVG 的直接父是 #f3Canvas,中间没有 .f3 元素)→ svg 会退回默认的 300×150,
|
||||
于是库按 300×150 算 fit,整棵树被缩到 0.4 倍、卡片只有 37px 宽。
|
||||
这里直接点名修掉。 */
|
||||
.kin-chart svg.main_svg { width: 100%; height: 100%; display: block; }
|
||||
/* 卡片层与连线层都靠 transform 一起平移缩放(两层必须同源,否则线会跟卡错位) */
|
||||
.kin-stage, .kin-chart svg.kin-lines {
|
||||
position: absolute; left: 0; top: 0;
|
||||
will-change: transform;
|
||||
}
|
||||
.kin-chart svg.kin-lines { pointer-events: none; }
|
||||
|
||||
/* 连线:库默认 stroke="#fff"(深色主题),在浅色纸底上等于隐形 —— 必须覆盖成褐灰。
|
||||
用 !important 是因为颜色是**写在 path 的行内属性**上的(stroke="#fff"),
|
||||
普通 CSS 声明(无论特异性)都压不过行内属性;这个钩子是唯一入口。 */
|
||||
.kin-chart .link,
|
||||
.kin-chart path.link { stroke: #C9BDA9 !important; stroke-width: 1.5 !important; }
|
||||
|
||||
/* 卡片:库给 .card_cont 定位、给 .card 一律加 translate(-50%,-50%)(居中锚点),
|
||||
我们只负责卡面观感。 */
|
||||
.kin-chart .card { background: transparent; border: 0; }
|
||||
/* 连线:夫妻短横线 + 子女下垂线,全部一条笔触 */
|
||||
.kin-chart path.kin-link {
|
||||
fill: none;
|
||||
stroke: #C9BDA9;
|
||||
stroke-width: 1.6;
|
||||
vector-effect: non-scaling-stroke; /* 缩放时线不跟着变粗变细 */
|
||||
}
|
||||
|
||||
.kin-card {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 3px;
|
||||
width: 116px; box-sizing: border-box; padding: 9px 8px 8px;
|
||||
position: absolute;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: 2px;
|
||||
box-sizing: border-box; padding: 7px 6px 6px;
|
||||
text-align: center;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--shadow);
|
||||
color: var(--ink);
|
||||
transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
|
||||
transform: translate(-50%, -50%); /* 坐标给的是卡片中心 */
|
||||
transition: border-color .15s ease, box-shadow .15s ease;
|
||||
}
|
||||
.kin-chart .card:hover .kin-card {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-l);
|
||||
border-color: var(--brand-2);
|
||||
.kin-card:hover {
|
||||
box-shadow: var(--shadow-l); border-color: var(--brand-2); text-decoration: none;
|
||||
}
|
||||
.kin-card .kin-ph {
|
||||
width: 46px; height: 46px; border-radius: 50%; overflow: hidden;
|
||||
width: 38px; height: 38px; border-radius: 50%; 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.3; }
|
||||
.kin-card .kin-lb { font-size: 11px; color: var(--brand); line-height: 1.35; }
|
||||
.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-mark { font-size: 10px; color: var(--faint); }
|
||||
|
||||
/* 「我」:品牌色描边 + 光圈 */
|
||||
@@ -516,11 +510,11 @@ textarea.input { min-height: 76px; resize: vertical; }
|
||||
box-shadow: 0 0 0 3px rgba(168, 74, 40, .12), var(--shadow);
|
||||
}
|
||||
.kin-card.is-me .kin-lb { color: var(--brand-ink); font-weight: 600; }
|
||||
|
||||
/* 「待确认」(只填了称呼、没对应到真人):虚线 */
|
||||
.kin-card.is-ph { border-style: dashed; background: #FCFAF6; }
|
||||
|
||||
.tree-legend { font-size: 12px; color: var(--muted); margin: 14px 2px 0; }
|
||||
|
||||
/* 详情页里的迷你家谱 */
|
||||
.mini-tree { display: flex; flex-direction: column; gap: 8px; }
|
||||
.mini-gen { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
|
||||
Reference in New Issue
Block a user