Files
FamilyBook/templates/kin/tree.php
T
laoyang e9b3b8666b v11: 家谱按户排布(夫妻并排/长幼排序/父母括号/姻亲);观影设置下拉收拢影院·搜索·统计·年度报告;观影人头像
家谱(用户第 2 点,重写模板与 Kinship::tree):
- 同代聚成「夫妻单元」(户):血亲当主位、配偶并排在其右,兄弟_姐妹按长幼(长子左)
- 血亲可达性判「姻亲」:只走 parent/child 边从「我」可达者为本家血亲,妻子的父母
  走不到 → 标「姻亲」并排到本代末尾(旧判据「上无父母且下无子女」对岳父母恒为假,是真 bug)
- 父母括号连线:横杆盖住父母列到其子女各列,竖线落父母列中心、每子女列一道下线
- 称呼按长幼定死:姐夫/妹夫、嫂子/弟媳、大/小舅子、大/小姨子、大伯子/小叔子、
  大姑子/小姑子、侄子/侄女、外甥/外甥女、堂/表
- 列宽地板 minmax(min-content,1fr):原 minmax(0,1fr) 在窄屏+户数多时把列压到卡片
  (96px)以下 → 同代卡片互相重叠;.tnode 加 flex:0 0 auto 防被 flex 压扁

观影(用户第 3~5 点):
- 顶栏删「影院」入口(影院是观影模块的子功能);观影列表右侧加「⚙ 设置」下拉,
  内含 影院管理 / 高级搜索 / 统计 / 年度报告(后三者从 MovieLog 迁入,
  app/MovieStats.php + 三个只读路由,MySQL 方言;图表全用纯 CSS,不引 CDN)
- 评价区删除「我」的长条徽标(被 flex column stretch 拉成整行宽的丑色带)
- 观影人弹窗列表与已选 chips 带头像;syncChips 点名取 .att-name/.att-av
  (旧写法取「第一个 span」,v11 起那是隐藏占位,chip 上会没名字)

验证:e2e 432/0、桌面几何 268/0、移动几何 464/0、交互几何 97/0、
静态空跑 199/0、反向验证 v11 十条 10抓/0漏。本轮无 DB 变更(Schema 仍 v10)。
移动几何抓出两个真溢出并已修:高级搜索页 select 长选项撑破 .field、
原生 date 输入撑破 .date-range(360/375px)。
2026-10-01 12:11:43 +08:00

163 lines
8.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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; ?>