$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'), ]; } if (!$cards) { continue; } // ⚠️ 户内部:**血亲在前、配偶在后**(配偶排右手边)。 // 只按长幼排会让"妻子排到丈夫左边",夫妻横线看着别扭。 $head = null; foreach ($cards as $c) { // 血亲 = 这个户里"有父母在本图上"的那一位;都没有就取第一位 if ($head === null) { $head = $c; } foreach ($graph['parents'][(int)$c['id']] ?? [] as $par) { if (isset($unitOf[(int)$par]) && (int)$unitOf[(int)$par] !== $i) { $head = $c; break 2; } } } 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 ); ?>
每代一行,一代里按「户」排开;一对夫妻之间有一条短横线, 他们的子女一律从这条横线的正中间垂下来(不管是谁登录,孩子都挂在这对夫妻上), 再由横梁分到每个孩子。 「待确认」= 只填了称呼、还没对应到真实的人(虚线卡片)。 可以**拖拽平移**、**滚轮缩放**;点「回到我」把镜头挪回你自己,点卡片进对应成员页。