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']); usort($sorted, static function (int $a, int $b) use ($rankKey): int { return strcmp($rankKey($a), $rankKey($b)) ?: ($a <=> $b); }); $cards = []; foreach ($sorted as $id) { if (!isset($people[$id]) || !is_array($people[$id])) { continue; } $p = $people[$id]; $g = strtolower((string)($p['gender'] ?? '')); $cards[] = [ 'id' => $id, 'name' => \App\Nickname::display((int)$meId, $p) ?: (string)($p['name'] ?? ''), 'label' => \App\Kinship::label($graph, (int)$meId, $id, $people), 'avatar' => avatar_url($p['avatar'] ?? null), '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 兜底,保证顺序稳定。 'rank' => \App\Kinship::rankValueFor($people, $id), ]; } if (!$cards) { continue; } // ⚠️⚠️ 户内部:**本家血亲在前、配偶在后**(配偶排右手边,参考实现就是这样)。 // // "血亲"不能只按"他有父母在本图上"判定 —— 一对夫妻**两边父母都可能在图上** // (我的父母 + 妻子的父母),那样两边都算血亲,取到第一个就成了"妻子当主位、 // 我排在她右边"(实测线上就是这个症状:红围脖@639 在我的左边)。 // // 正解:按「**与"我"的血缘距离**」判定 —— 这一户里离「我」更近的那一位才是主位。 // 血亲集合 $blood 已在循环外算好(只走 parent/child 边的 BFS), // 两边都在血亲集合里时,退到「与我的步数」比大小。 $head = null; $headScore = null; foreach ($cards as $c) { $cid = (int)$c['id']; // 分数越小越"本家":0 = 血亲本身,1 = 配偶,2 = 其它 $sc = isset($blood[$cid]) ? 0 : 1; if ($head === null || $sc < $headScore) { $head = $c; $headScore = $sc; } } if ($head !== null) { $rest = []; foreach ($cards as $c) { if ((int)$c['id'] !== (int)$head['id']) { $rest[] = $c; } } $cards = array_merge([$head], $rest); } $kids = []; foreach ($unitKids[$i] ?? [] as $cid) { if (isset($people[$cid])) { $kids[] = (int)$cid; } } usort($kids, static fn(int $a, int $b): int => strcmp($rankKey($a), $rankKey($b)) ?: ($a <=> $b)); $payloadUnits[] = ['gen' => (int)$u['gen'], 'cards' => $cards, 'kids' => $kids]; } // 同代之间按「主位长幼」排序,保证顺序稳定 usort($payloadUnits, static function (array $a, array $b) use ($rankKey): int { return ($a['gen'] <=> $b['gen']) ?: strcmp($rankKey((int)$a['cards'][0]['id']), $rankKey((int)$b['cards'][0]['id'])) ?: ((int)$a['cards'][0]['id'] <=> (int)$b['cards'][0]['id']); }); $totalPeople = 0; foreach ($payloadUnits as $u) { $totalPeople += count($u['cards']); } $canChart = $totalPeople > 1; $meName = (string)($people[$meId]['name'] ?? '自己'); $payloadJson = json_encode( ['units' => $payloadUnits, 'me' => (int)$meId], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ); ?>

我的家谱

以我为中心 · 上一代 / 这一代 / 下一代所有人 · 子女从夫妻横线中点连出
还没连上任何亲属关系 到「」的资料页,把父母、配偶、子女填进去, 这张家谱就会自动长出来。兄弟姐妹不用单独填 —— 只要你们有同一对父母,就会自动认出来。

每代一行,一代里按「户」排开;一对夫妻之间有一条短横线, 他们的子女一律从这条横线的正中间垂下来(不管是谁登录,孩子都挂在这对夫妻上), 再由横梁分到每个孩子。 「待确认」= 只填了称呼、还没对应到真实的人(虚线卡片)。 可以**拖拽平移**、**滚轮缩放**;点「回到我」把镜头挪回你自己,点卡片进对应成员页。