Files
FamilyBook/templates/kin/tree.php
T

163 lines
8.0 KiB
PHP
Raw Normal View History

<?php
/**
* 我的家谱:以我为中心,向上向下各三代。**v11 重写** —— 从「按代平铺」改成「按户排布」。
*
* 用户提出的四点,对应这里的四个做法:
* ① 「每一层级都需要按照长子左、次子右的顺序排序,夫妻要放在一起」
* → 同一代不再平铺一堆卡片,而是先聚成**夫妻单元**(一户),
* 夫妻并排(男左女右、血亲在左),再把单元按长幼从左到右排。
* 聚合与排序在 App\Kinship::tree() 里算好(['units']),这里只负责画。
* ② 「上下代之间要有连线,明确谁是谁的父母与孩子」
* → 用**父母括号**(family bracket):父母那一户下方画一个横跨它所有子女的
* 方括号,括号中心垂下一根线接到父母,每个子女各自的竖线接进同一根横杆。
* 谁是谁的父母/孩子因此一眼可辨,而不是"两代之间一条装饰竖线"。
* ③ 「妹夫、姐夫……谁是姐姐谁是妹妹是很明确的事情」等称呼
* → 全部在 Kinship::label() 里按长幼定死(本模板只渲染结果,不自己做判断)。
* ④ 「小姨子、小姑子、大姨子这种称呼,有错的,需要完全正确」
* → 同上;另对「妻子的父母」这类漂在图上的旁支标一句「姻亲」,免得看着像断线。
*/
$gens = $tree['generations'];
$units = $tree['units'] ?? [];
$unitName = $tree['unit_names'] ?? [];
$nodes = $tree['nodes'];
$me = $nodes[$meId] ?? null;
// 代数 → 人类说法
$genWord = function (int $level): string {
if ($level === 0) {
return '我这一辈'; // ⚠️ 必须先判 0:它既不 <0 也不在 $down 里,
// 漏判就会显示成「下 0 代」这种没人看得懂的东西
}
if ($level < 0) {
$up = [-3 => '曾祖辈', -2 => '祖辈', -1 => '父母辈'];
return $up[$level] ?? '上 ' . abs($level) . ' 代';
}
$down = [1 => '子女辈', 2 => '孙辈', 3 => '曾孙辈'];
return $down[$level] ?? '下 ' . $level . ' 代';
};
$levels = array_keys($gens);
// 整棵图的宽度取「最宽那一代」—— 所有代共用一个宽度,连线才能跟人对齐
$maxUnits = 1;
foreach ($levels as $lv) {
$maxUnits = max($maxUnits, count($units[$lv] ?? []));
}
// ⚠️ 列宽最小值必须是 min-content(**不能**是 0):
// v11 实测踩到 —— 最小值给 0 时列会被压到 96px 以下,而卡片是固定 96px 宽,
// 于是窄屏 + 户数多时同代卡片**互相重叠**(照片与名字糊成一团)。
// minmax(min-content, 1fr) 给列一个「不小于内容」的地板:内容窄时照旧均分填满,
// 内容宽过版心时整棵树交给 .tree-inner 横向滚动(这正是家谱的设计意图)。
$colStyle = 'grid-template-columns: repeat(' . (int)$maxUnits . ', minmax(min-content, 1fr));';
?>
<div class="page-head">
<h1>我的家谱</h1>
<span class="sub">同代按长幼从左到右 · 夫妻并排 · 括号表示谁是谁的父母</span>
</div>
<?php if (!$nodes || count($nodes) <= 1): ?>
<div class="empty">
<b>还没连上任何亲属关系</b>
到「<?= e($me['name'] ?? '自己') ?>」的资料页,把父母、配偶、子女填进去,
这张家谱就会自动长出来。兄弟姐妹不用单独填 —— 只要你们有同一对父母,就会自动认出来。
</div>
<?php else: ?>
<div class="tree">
<div class="tree-inner">
<?php foreach ($levels as $li => $level): ?>
<?php $levelUnits = $units[$level] ?? []; ?>
<div class="tree-gen">
<div class="tree-gen-label">
<span class="gname"><?= e($genWord((int)$level)) ?></span>
<span class="gtag"><?= count($levelUnits) ?> 户</span>
</div>
<div class="tree-row" style="<?= e($colStyle) ?>">
<?php foreach ($levelUnits as $u): ?>
<div class="tree-unit">
<?php foreach ($u['ids'] as $k => $id): ?>
<?php if ($k === 1): ?>
<span class="tree-bond" aria-label="夫妻"><i></i></span>
<?php endif; ?>
<?php $n = $nodes[$id]; ?>
<a class="tnode <?= (int)$id === $meId ? 'is-me' : '' ?> <?= $n['ph'] ? 'is-placeholder' : '' ?> <?= $u['inlaw'] && $k === 0 ? 'is-inlaw' : '' ?>"
href="<?= e(url('people/show', ['id' => $n['id']])) ?>"
title="<?= e($n['name'] . ' · ' . $n['label']) ?>">
<span class="tph"><img src="<?= e(avatar_url($n['avatar'])) ?>" alt=""></span>
<span class="tname"><?= e($n['name']) ?></span>
<span class="tlabel"><?= e($n['label']) ?></span>
<?php if ($n['ph']): ?><span class="tph-mark">待确认</span><?php endif; ?>
</a>
<?php endforeach; ?>
<?php if ($u['inlaw']): ?>
<span class="tree-inlaw-tag" title="因婚姻进来的人,不是本家的血亲">姻亲</span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<?php
// ---- 父母括号:这一代(父母)↔ 下一代(子女)的连线 ----
//
// 画法(每个「父母户 + 它的子女们」一组):
// · 一根横杆盖住「父母那一列 → 它的子女各列」;
// · 横杆在**父母列的中心**向上伸一根竖线,接到父母那一户;
// · 横杆在**每个子女列的中心**向下伸一根短线,接到各自的子女户。
// 于是「谁是谁的父母与孩子」是几何上可读的,而不是两代之间一条装饰线。
$childLevel = $levels[$li + 1] ?? null;
if ($childLevel !== null && !empty($units[$childLevel])):
$kids = $units[$childLevel];
$ranges = []; // 父母单元key => [第一个子女列, 最后一个子女列]
foreach ($kids as $i => $ku) {
$pk = $ku['parent_key'];
if ($pk === null) {
continue;
}
if (!isset($ranges[$pk])) {
$ranges[$pk] = [$i, $i];
} else {
$ranges[$pk][1] = $i;
}
}
// 父母单元key => 它在上一代里的列号(拿来决定竖线落点)
$pidx = [];
foreach (($units[$level] ?? []) as $i => $pu) {
$pidx[$pu['key']] = $i;
}
?>
<div class="tree-conn" style="<?= e($colStyle) ?>">
<?php foreach ($ranges as $pk => $rg): ?>
<?php
$pn = $unitName[$pk] ?? ['head' => '', 'spouse' => null];
$who = $pn['head'] . ($pn['spouse'] !== null ? ' 夫妇' : '');
$pCol = $pidx[$pk] ?? $rg[0];
$lo = min($pCol, $rg[0]);
$hi = max($pCol, $rg[1]);
$span = $hi - $lo + 1;
// 竖线落在父母列的中心(在横杆这一段里的百分比位置)
$pct = ($pCol - $lo + 0.5) / $span * 100;
?>
<div class="tc-bar" style="grid-column: <?= $lo + 1 ?> / <?= $hi + 2 ?>; grid-row: 1"
title="<?= e($who . ' 的子女') ?>">
<span class="tc-up" style="left: <?= round($pct, 3) ?>%"></span>
</div>
<?php endforeach; ?>
<?php foreach ($kids as $i => $ku): ?>
<div class="tc-down" style="grid-column: <?= $i + 1 ?>; grid-row: 1"></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<p class="tree-legend">
「姻亲」= 因为婚姻进到这张图里的人(如我妻子的父母),不是本家的血亲。
括号横跨的那几户,就是括号上方那一户的子女。
</p>
<?php endif; ?>