2026-09-30 00:49:53 +08:00
|
|
|
|
<?php
|
2026-09-30 08:13:07 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 成员详情 —— 同时是「以我为中心」的呈现页与亲属关系的录入入口。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 页面上每个人名旁边都带着「我该怎么称呼他」,同一个页面
|
|
|
|
|
|
* 爸爸看到「我的儿子」,爱人看到「我的丈夫」,全靠 Kinship 现算。
|
|
|
|
|
|
*/
|
2026-09-30 09:44:55 +08:00
|
|
|
|
$age = age_from(\App\Birthday::solarOf($person));
|
2026-09-30 08:13:07 +08:00
|
|
|
|
$admin = \App\Auth::isAdmin();
|
|
|
|
|
|
$canW = \App\Auth::canWrite();
|
|
|
|
|
|
$pid = (int)$person['id'];
|
|
|
|
|
|
$p = $person;
|
|
|
|
|
|
$isSelf = (int)$p['id'] === (int)\App\Auth::personId();
|
|
|
|
|
|
$role = (string)($p['account_role'] ?? '');
|
|
|
|
|
|
|
2026-09-30 09:44:55 +08:00
|
|
|
|
// ⚠️ 显示名一律走「我怎么称呼他」,不要直接 echo 姓名:
|
|
|
|
|
|
// 同一个「小明」,妈妈看到的是"明明"、姐姐看到的是"小弟"。
|
|
|
|
|
|
// 这里一次性把「我设过的称呼」取出来做本地查表,避免每个节点一次 SQL。
|
|
|
|
|
|
$nickMap = $isSelf ? [] : \App\Nickname::mapFor((int)$meId);
|
|
|
|
|
|
$nameOf = function (int $id) use ($people, $pid, $nickMap): string {
|
|
|
|
|
|
$real = (string)($people[$id]['name'] ?? '(已移除)');
|
|
|
|
|
|
if ($id === $pid) {
|
|
|
|
|
|
return $real; // 我自己看到自己,永远用本名
|
|
|
|
|
|
}
|
|
|
|
|
|
$nk = (string)($nickMap[$id] ?? '');
|
|
|
|
|
|
return $nk !== '' ? $nk : $real;
|
2026-09-30 08:13:07 +08:00
|
|
|
|
};
|
2026-09-30 09:44:55 +08:00
|
|
|
|
// 当前这个人在我眼里叫什么(页面上所有"给他看的人名"都用它)
|
|
|
|
|
|
$pShown = $isSelf ? (string)$p['name'] : (string)($shown !== '' ? $shown : $p['name']);
|
|
|
|
|
|
$isMyNick = !$isSelf && $pShown !== (string)$p['name'];
|
|
|
|
|
|
|
2026-09-30 08:13:07 +08:00
|
|
|
|
// 当前这个人的父母/配偶/子女(用于在录入表单里显示已填的内容)
|
|
|
|
|
|
$theirParents = \App\Kinship::parents($graph, $pid);
|
|
|
|
|
|
$theirSpouses = \App\Kinship::spouseIds($graph, $pid);
|
|
|
|
|
|
$theirKids = \App\Kinship::childIds($graph, $pid);
|
|
|
|
|
|
$theirSibs = \App\Kinship::siblingIds($graph, $pid);
|
2026-10-03 08:18:05 +08:00
|
|
|
|
$shortKind = \App\Birthday::KINDS;
|
2026-09-30 00:49:53 +08:00
|
|
|
|
?>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="page-head">
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<h1><?= e($pShown) ?></h1>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<span class="sub">
|
2026-10-03 08:18:05 +08:00
|
|
|
|
<?php if (!$isSelf && (string)$labelToMe !== ''): ?>
|
|
|
|
|
|
<?php /* v24:没关系就不显示标签(Kinship::label() 推不出关系时返回空串) */ ?>
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<span class="rel-label"><?= e($labelToMe) ?></span>
|
|
|
|
|
|
<?php if ($isMyNick): ?>
|
|
|
|
|
|
· 本名 <?= e((string)$p['name']) ?>
|
|
|
|
|
|
<?php endif; ?>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
<?php if (!empty($p['is_placeholder'])): ?><span class="badge">虚拟人物</span><?php endif; ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
<div class="actions">
|
2026-09-30 13:24:26 +08:00
|
|
|
|
<?php if ($isSelf || $canW): ?>
|
|
|
|
|
|
<a class="btn btn-ghost btn-sm" href="<?= e(url('people/edit', ['id' => $pid])) ?>">
|
|
|
|
|
|
<?= $isSelf ? '编辑我的资料' : '编辑资料' ?>
|
|
|
|
|
|
</a>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<?php endif; ?>
|
2026-10-03 19:27:14 +08:00
|
|
|
|
<?php /* 用户 2026-10-03 第 2 条:右上角那个「移除」换成**返回**(回名册列表)。
|
|
|
|
|
|
理由:详情页最常做的事是"看完回去",而"移除"是低频且危险的破坏性操作,
|
|
|
|
|
|
不该占着右上角最顺手的位置。
|
|
|
|
|
|
「删除」已移到页面最下方、放在「开通登录账号」下面(见文件末尾)。 */ ?>
|
|
|
|
|
|
<a class="btn btn-ghost btn-sm" href="<?= e(url('people')) ?>">返回</a>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<?php if ($isSelf && !empty($p['slogan'])): ?>
|
|
|
|
|
|
<p class="motto-line">“<?= e((string)$p['slogan']) ?>”</p>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<div class="detail">
|
|
|
|
|
|
<aside class="detail-photo">
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<img src="<?= e(avatar_url($p['avatar'])) ?>" alt="">
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<div class="name"><?= e($pShown) ?></div>
|
|
|
|
|
|
<?php if ($isMyNick): ?><div class="sub">本名 <?= e((string)$p['name']) ?></div><?php endif; ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<div class="sub">
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<?php if ($role === 'admin' && $p['account_status'] === 'active'): ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
家庭管理员
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<?php elseif ($role === 'guest'): ?>
|
|
|
|
|
|
访客(只读)
|
|
|
|
|
|
<?php elseif (!empty($p['account_id'])): ?>
|
|
|
|
|
|
已开通登录<?= $p['account_status'] === 'disabled' ? '(已停用)' : '' ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<?php else: ?>
|
|
|
|
|
|
还没有开通登录
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="card card-pad">
|
|
|
|
|
|
<dl class="kv">
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<div class="kv-row"><dt>姓名</dt><dd><?= e((string)$p['name']) ?></dd></div>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>性别</dt>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<dd><?= e(\App\Person::genderWord((string)($p['gender'] ?? '')) ?: '未填写') ?></dd>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</div>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<?php if (!empty($p['birth_rank'])): ?>
|
|
|
|
|
|
<div class="kv-row"><dt>家中排行</dt><dd>第 <?= (int)$p['birth_rank'] ?> 个孩子</dd></div>
|
|
|
|
|
|
<?php endif; ?>
|
2026-09-30 09:44:55 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- ============ 三个生日 ============ -->
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>生日</dt>
|
|
|
|
|
|
<dd>
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<?php if (!$birthdays): ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<span class="muted">未填写</span>
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<?php else: ?>
|
|
|
|
|
|
<ul class="bd-list">
|
|
|
|
|
|
<?php foreach ($birthdays as $b): ?>
|
|
|
|
|
|
<li>
|
|
|
|
|
|
<span class="bd-kind bd-kind-<?= e((string)$b['kind']) ?>">
|
|
|
|
|
|
<?= e($shortKind[$b['kind']] ?? (string)$b['kind']) ?>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="bd-val"><?= e((string)$b['text']) ?></span>
|
|
|
|
|
|
<?php if ((string)$b['note'] !== ''): ?>
|
|
|
|
|
|
<span class="muted small">(<?= e((string)$b['note']) ?>)</span>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
<?php if ($age !== null): ?>
|
|
|
|
|
|
<div class="muted small" style="margin-top:4px">今年 <?= (int)$age ?> 岁</div>
|
|
|
|
|
|
<?php endif; ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-09-30 09:44:55 +08:00
|
|
|
|
|
|
|
|
|
|
<?php if ($birthdayNext): ?>
|
|
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>接下来</dt>
|
|
|
|
|
|
<dd>
|
|
|
|
|
|
<?php foreach ($birthdayNext as $n): ?>
|
|
|
|
|
|
<?php
|
|
|
|
|
|
$words = [];
|
|
|
|
|
|
foreach ((array)$n['kinds'] as $k) {
|
|
|
|
|
|
$words[] = $shortKind[$k] ?? (string)$k;
|
|
|
|
|
|
}
|
|
|
|
|
|
?>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<?= e((string)$n['date']) ?> ·
|
|
|
|
|
|
<?php if ((int)$n['days'] === 0): ?>
|
|
|
|
|
|
<b style="color:var(--brand)">今天</b>
|
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
|
还有 <b><?= (int)$n['days'] ?></b> 天
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
<span class="muted small">
|
|
|
|
|
|
(<?= e(implode('、', $words)) ?><?= (string)$n['lunar_label'] !== '' ? ',农历' . e((string)$n['lunar_label']) : '' ?>)
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>手机号</dt>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<dd class="mono"><?= e(\App\Crypto::maskPhone((string)($p['phone_encrypted'] ?? ''))) ?></dd>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</div>
|
2026-09-30 15:02:27 +08:00
|
|
|
|
<?php foreach (($contacts['phone'] ?? []) as $r): ?>
|
|
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>电话<?= (string)$r['label'] !== '' ? '(' . e((string)$r['label']) . ')' : '' ?></dt>
|
|
|
|
|
|
<dd class="mono"><?= e(\App\Crypto::maskPhone((string)$r['value'])) ?></dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endforeach; ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>身份证号</dt>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<dd class="mono"><?= e(\App\Crypto::maskIdCard((string)($p['id_card_encrypted'] ?? ''))) ?></dd>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</div>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<?php if (!empty($p['email'])): ?>
|
|
|
|
|
|
<div class="kv-row"><dt>邮箱</dt><dd><?= e($p['email']) ?></dd></div>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<?php endif; ?>
|
2026-09-30 15:02:27 +08:00
|
|
|
|
<?php foreach (($contacts['email'] ?? []) as $r): ?>
|
|
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>邮箱<?= (string)$r['label'] !== '' ? '(' . e((string)$r['label']) . ')' : '' ?></dt>
|
|
|
|
|
|
<dd><?= e((string)$r['value']) ?></dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endforeach; ?>
|
2026-10-03 19:27:14 +08:00
|
|
|
|
<?php /* 用户 2026-10-03 第 4 条:工作单位/职务/职称**可以有多段** —— 所以这里列出
|
|
|
|
|
|
完整的"工作经历"(当前那段标「现在」),而不是只显示一个"当前单位"。
|
|
|
|
|
|
⚠️ 老数据(只有 persons.org/title、没有经历行)也要照常显示:
|
|
|
|
|
|
那样的情况下退回显示原来那一行(见下面的 $jobs 为空分支)。 */ ?>
|
|
|
|
|
|
<?php if ($jobs): ?>
|
|
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>工作</dt>
|
|
|
|
|
|
<dd>
|
|
|
|
|
|
<ul class="job-history">
|
|
|
|
|
|
<?php foreach ($jobs as $j): ?>
|
|
|
|
|
|
<?php $isNow = ($j['end_ym'] === null || $j['end_ym'] === ''); ?>
|
|
|
|
|
|
<li class="job-history-item">
|
|
|
|
|
|
<?php if ($isNow): ?><span class="node-badge">现在</span><?php endif; ?>
|
|
|
|
|
|
<span class="job-org"><?= e((string)($j['org'] ?? '')) ?></span>
|
|
|
|
|
|
<?php $jt = \App\PersonJob::joinTitle($j['title'] ?? '', $j['rank_name'] ?? ''); ?>
|
|
|
|
|
|
<?php if ($jt !== null): ?><span class="job-title"><?= e($jt) ?></span><?php endif; ?>
|
|
|
|
|
|
<?php if (($j['start_ym'] ?? '') !== '' || ($j['end_ym'] ?? '') !== ''): ?>
|
|
|
|
|
|
<span class="small muted job-span">
|
|
|
|
|
|
<?= e((string)($j['start_ym'] ?? '')) ?> ~ <?= $isNow ? '现在' : e((string)($j['end_ym'] ?? '')) ?>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
<?php if (($j['note'] ?? '') !== ''): ?>
|
|
|
|
|
|
<span class="small muted">(<?= e((string)$j['note']) ?>)</span>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php elseif (!empty($p['org']) || !empty($p['title'])): ?>
|
2026-09-30 15:02:27 +08:00
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>工作</dt>
|
|
|
|
|
|
<dd><?= e(trim((string)$p['org'] . (($p['org'] ?? '') !== '' && ($p['title'] ?? '') !== '' ? ' · ' : '') . (string)$p['title'])) ?></dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<?php if (!empty($p['address'])): ?>
|
|
|
|
|
|
<div class="kv-row"><dt>住址</dt><dd><?= e($p['address']) ?></dd></div>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<?php endif; ?>
|
2026-09-30 15:02:27 +08:00
|
|
|
|
<?php foreach (($contacts['address'] ?? []) as $r): ?>
|
|
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>地址<?= (string)$r['label'] !== '' ? '(' . e((string)$r['label']) . ')' : '' ?></dt>
|
|
|
|
|
|
<dd><?= e((string)$r['value']) ?></dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
<?php foreach (($contacts['url'] ?? []) as $r): ?>
|
|
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>网址<?= (string)$r['label'] !== '' ? '(' . e((string)$r['label']) . ')' : '' ?></dt>
|
|
|
|
|
|
<dd class="mono"><?= e((string)$r['value']) ?></dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endforeach; ?>
|
2026-10-03 19:27:14 +08:00
|
|
|
|
<?php /* 用户 2026-10-03 第 5 条:备注分两种 ——
|
|
|
|
|
|
① 他本人写的「自我介绍」(谁都能看,就是他愿意公开说的那句);
|
|
|
|
|
|
② 我对他的私人备注(**只有我自己看得到**,见下面单独一块)。 */ ?>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<?php if (!empty($p['note'])): ?>
|
2026-10-03 19:27:14 +08:00
|
|
|
|
<div class="kv-row"><dt>自我介绍</dt><dd><?= nl2br(e($p['note'])) ?></dd></div>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
<div class="kv-row">
|
|
|
|
|
|
<dt>最近登录</dt>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<dd><?= !empty($p['last_login_at']) ? e($p['last_login_at']) : '从未登录' ?></dd>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</dl>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-10-03 19:27:14 +08:00
|
|
|
|
<?php /* ============ 我对他的私人备注(用户 2026-10-03 第 5 条)============
|
|
|
|
|
|
只有**写的人自己**看得到(存在 person_notes,按 viewer 区分)。
|
|
|
|
|
|
⚠️ 不是 person.note —— 那是"他愿意公开说的自我介绍"。
|
|
|
|
|
|
这一块属于"我和他之间的事"(怎么认识的、要注意什么),
|
|
|
|
|
|
所以它只出现在我的视角里,而且别人(包括他本人)看不到。 */ ?>
|
|
|
|
|
|
<?php if (!$isSelf): ?>
|
|
|
|
|
|
<div class="card card-pad my-note-card" style="margin-top:16px">
|
|
|
|
|
|
<h2 class="section-title">
|
|
|
|
|
|
我对 <?= e($pShown) ?> 的备注
|
|
|
|
|
|
<span class="hint">只有我自己看得到</span>
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<form method="post" action="<?= e(url('people/note')) ?>">
|
|
|
|
|
|
<?= csrf_field() ?>
|
|
|
|
|
|
<input type="hidden" name="id" value="<?= $pid ?>">
|
|
|
|
|
|
<label class="sr-only" for="pn-note">我对他的备注</label>
|
|
|
|
|
|
<textarea class="input" id="pn-note" name="note" rows="2"
|
|
|
|
|
|
placeholder="怎么认识的、要注意什么、上次见面聊了什么……"><?= e((string)$myNote) ?></textarea>
|
|
|
|
|
|
<div class="btn-row" style="margin-top:8px">
|
|
|
|
|
|
<button class="btn btn-sm" type="submit">保存备注</button>
|
|
|
|
|
|
<span class="small muted">别人看不到这一栏(包括他本人)</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<!-- ============ 我叫他/她什么(视角属性,只有我自己能改) ============ -->
|
|
|
|
|
|
<?php if (!$isSelf): ?>
|
|
|
|
|
|
<div class="card card-pad" style="margin-top:16px">
|
|
|
|
|
|
<h2 class="section-title">
|
|
|
|
|
|
我怎么称呼 <?= e((string)$p['name']) ?>
|
|
|
|
|
|
<span class="hint">只影响我自己看到的</span>
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<form method="post" action="<?= e(url('nickname/save')) ?>" class="nick-inline">
|
|
|
|
|
|
<?= csrf_field() ?>
|
|
|
|
|
|
<input type="hidden" name="target_id" value="<?= $pid ?>">
|
|
|
|
|
|
<input type="hidden" name="back" value="people/show">
|
|
|
|
|
|
<input class="input" name="nickname" maxlength="50"
|
|
|
|
|
|
value="<?= e($myNickname) ?>" placeholder="留空 = 恢复成用姓名显示">
|
|
|
|
|
|
<button class="btn btn-sm" type="submit">记下</button>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
<p class="small muted" style="margin:10px 0 0">
|
|
|
|
|
|
同一个「小明」,妈妈叫"明明"、姐姐叫"小弟" —— 称呼是<b>每个人各自定的</b>,
|
|
|
|
|
|
不是 TA 自带的属性。所以这里改的只影响你自己的页面,别人看到的还是他们自己的叫法。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<!-- ============ 亲属关系 ============ -->
|
|
|
|
|
|
<div class="card card-pad" style="margin-top:16px">
|
|
|
|
|
|
<h2 class="section-title">
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<?= e($pShown) ?> 的亲属
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<span class="hint">关系是双向的,填一次两边都生效</span>
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
|
// 已有关系一览(从「当前这个人」的视角)
|
|
|
|
|
|
$blocks = [
|
|
|
|
|
|
['父母', $theirParents['father'] ? [[$theirParents['father'], '父亲']] : [],
|
|
|
|
|
|
$theirParents['mother'] ? [[$theirParents['mother'], '母亲']] : []],
|
|
|
|
|
|
];
|
|
|
|
|
|
?>
|
|
|
|
|
|
<div class="rel-summary">
|
|
|
|
|
|
<div class="rel-sum-item">
|
|
|
|
|
|
<span class="rel-sum-k">父亲</span>
|
|
|
|
|
|
<span class="rel-sum-v">
|
|
|
|
|
|
<?php if ($theirParents['father']): ?>
|
|
|
|
|
|
<a href="<?= e(url('people/show', ['id' => $theirParents['father']])) ?>"><?= e($nameOf($theirParents['father'])) ?></a>
|
|
|
|
|
|
<?php else: ?><span class="muted">未填写</span><?php endif; ?>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="rel-sum-item">
|
|
|
|
|
|
<span class="rel-sum-k">母亲</span>
|
|
|
|
|
|
<span class="rel-sum-v">
|
|
|
|
|
|
<?php if ($theirParents['mother']): ?>
|
|
|
|
|
|
<a href="<?= e(url('people/show', ['id' => $theirParents['mother']])) ?>"><?= e($nameOf($theirParents['mother'])) ?></a>
|
|
|
|
|
|
<?php else: ?><span class="muted">未填写</span><?php endif; ?>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="rel-sum-item">
|
|
|
|
|
|
<span class="rel-sum-k">配偶</span>
|
|
|
|
|
|
<span class="rel-sum-v">
|
|
|
|
|
|
<?php if ($theirSpouses): ?>
|
|
|
|
|
|
<?php foreach ($theirSpouses as $s): ?>
|
|
|
|
|
|
<a href="<?= e(url('people/show', ['id' => $s])) ?>"><?= e($nameOf($s)) ?></a>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
<?php else: ?><span class="muted">未填写</span><?php endif; ?>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="rel-sum-item">
|
|
|
|
|
|
<span class="rel-sum-k">子女</span>
|
|
|
|
|
|
<span class="rel-sum-v">
|
|
|
|
|
|
<?php if ($theirKids): ?>
|
|
|
|
|
|
<?php foreach ($theirKids as $c): ?>
|
|
|
|
|
|
<a href="<?= e(url('people/show', ['id' => $c])) ?>"><?= e($nameOf($c)) ?></a>
|
|
|
|
|
|
<?php $rk = $people[$c]['birth_rank'] ?? null; ?>
|
|
|
|
|
|
<?php if ($rk): ?><span class="muted small">(第<?= (int)$rk ?>)</span><?php endif; ?>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
<?php else: ?><span class="muted">未填写</span><?php endif; ?>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="rel-sum-item">
|
|
|
|
|
|
<span class="rel-sum-k">兄弟姐妹</span>
|
|
|
|
|
|
<span class="rel-sum-v">
|
|
|
|
|
|
<?php if ($theirSibs): ?>
|
|
|
|
|
|
<?php foreach ($theirSibs as $s): ?>
|
|
|
|
|
|
<a href="<?= e(url('people/show', ['id' => $s])) ?>"><?= e($nameOf($s)) ?></a>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
<span class="muted small">(由共同父母自动认出)</span>
|
|
|
|
|
|
<?php else: ?><span class="muted">暂无</span><?php endif; ?>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<?php if ($canW): ?>
|
|
|
|
|
|
<div class="rel-add">
|
|
|
|
|
|
<h3 class="rel-add-title">添加/修改关系</h3>
|
|
|
|
|
|
<form method="post" action="<?= e(url('kin/relate')) ?>" class="rel-form">
|
|
|
|
|
|
<?= csrf_field() ?>
|
|
|
|
|
|
<input type="hidden" name="from_id" value="<?= $pid ?>">
|
|
|
|
|
|
<div class="field">
|
|
|
|
|
|
<label>关系</label>
|
|
|
|
|
|
<select class="select" name="kind" id="relKind">
|
|
|
|
|
|
<option value="parent">是我的父母</option>
|
|
|
|
|
|
<option value="child">是我的子女</option>
|
|
|
|
|
|
<option value="spouse">是我的配偶</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="field" id="relRoleWrap">
|
|
|
|
|
|
<label id="relRoleLabel">是哪一位</label>
|
|
|
|
|
|
<select class="select" name="role">
|
|
|
|
|
|
<option value="father">父亲</option>
|
|
|
|
|
|
<option value="mother">母亲</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="field">
|
|
|
|
|
|
<label>对方</label>
|
|
|
|
|
|
<select class="select" name="to_id" required>
|
|
|
|
|
|
<option value="">— 选择一个人 —</option>
|
|
|
|
|
|
<?php foreach ($options as $o): ?>
|
|
|
|
|
|
<option value="<?= (int)$o['id'] ?>">
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<?= e((string)$o['name']) ?><?= !empty($o['my_nickname']) ? '(' . e((string)$o['my_nickname']) . ')' : '' ?>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
</option>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="field" style="justify-content:flex-end">
|
|
|
|
|
|
<button class="btn btn-sm" type="submit">记下这条关系</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
|
|
<p class="small muted" style="margin:12px 0 0">
|
|
|
|
|
|
找不到人?先到「添加成员」把人建出来 —— 不确定真实姓名时,
|
|
|
|
|
|
起个「我的父亲」这样的名字也可以,之后再改。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<?php if ($theirRels): ?>
|
|
|
|
|
|
<details class="rel-list">
|
|
|
|
|
|
<summary>已有关系明细(<?= count($theirRels) ?> 条)</summary>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<?php foreach ($theirRels as $r): ?>
|
|
|
|
|
|
<li>
|
|
|
|
|
|
<?php if ($r['kind'] === 'parent'): ?>
|
|
|
|
|
|
<?= e($nameOf((int)$r['from_id'])) ?>
|
|
|
|
|
|
(<?= $r['from_role'] === 'mother' ? '母亲' : '父亲' ?>)
|
|
|
|
|
|
→
|
|
|
|
|
|
<?= e($nameOf((int)$r['to_id'])) ?>
|
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
|
<?= e($nameOf((int)$r['from_id'])) ?> 与 <?= e($nameOf((int)$r['to_id'])) ?> 是配偶
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
<form method="post" action="<?= e(url('kin/unrelate')) ?>" style="display:inline">
|
|
|
|
|
|
<?= csrf_field() ?>
|
|
|
|
|
|
<input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
|
|
|
|
|
|
<input type="hidden" name="person_id" value="<?= $pid ?>">
|
|
|
|
|
|
<button class="btn btn-danger btn-sm" type="submit">解除</button>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</details>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
|
<p class="small muted" style="margin:12px 0 0">你的身份是访客,只能查看亲属关系,不能修改。</p>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ============ 以这个人看出去的家谱 ============ -->
|
|
|
|
|
|
<?php if (!empty($theirTree['nodes']) && count($theirTree['nodes']) > 1): ?>
|
|
|
|
|
|
<div class="card card-pad" style="margin-top:16px">
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<h2 class="section-title">从 <?= e($pShown) ?> 的角度看 <span class="hint">上下各两代</span></h2>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<div class="mini-tree">
|
|
|
|
|
|
<?php foreach ($theirTree['generations'] as $level => $ids): ?>
|
|
|
|
|
|
<div class="mini-gen">
|
|
|
|
|
|
<?php foreach ($ids as $id): $n = $theirTree['nodes'][$id]; ?>
|
|
|
|
|
|
<a class="mini-node <?= (int)$id === $pid ? 'is-me' : '' ?>"
|
|
|
|
|
|
href="<?= e(url('people/show', ['id' => (int)$id])) ?>">
|
|
|
|
|
|
<img src="<?= e(avatar_url($n['avatar'])) ?>" alt="">
|
2026-09-30 09:44:55 +08:00
|
|
|
|
<span class="mn-name"><?= e($nameOf((int)$id)) ?></span>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<span class="mn-label"><?= e($n['label']) ?></span>
|
|
|
|
|
|
</a>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-09-30 13:24:26 +08:00
|
|
|
|
<!-- ============ 名册归属 / 分享名片 ============ -->
|
|
|
|
|
|
<?php if (!$isSelf): ?>
|
|
|
|
|
|
<div class="card card-pad" style="margin-top:16px">
|
|
|
|
|
|
<h2 class="section-title">名册 <span class="hint">名册是每人一份的</span></h2>
|
|
|
|
|
|
<p class="small muted" style="margin:0 0 14px">
|
|
|
|
|
|
<?php if (!empty($inMyRoster)): ?>
|
|
|
|
|
|
「<?= e($pShown) ?>」在你的名册里。归到哪一组、排第几,只影响你自己看到的名册 ——
|
|
|
|
|
|
家里其他人各有一份,互不打搅。
|
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
|
「<?= e($pShown) ?>」还不在你的名册里。你仍然能在家谱与首页看到他,
|
|
|
|
|
|
但名册只列<b>你自己加的人</b>和<b>别人分享给你的名片</b>。
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div class="btn-row" style="margin-top:0;flex-wrap:wrap;align-items:center">
|
|
|
|
|
|
<?php if (empty($inMyRoster) && $canW): ?>
|
|
|
|
|
|
<form method="post" action="<?= e(url('people/pick')) ?>" style="display:inline">
|
|
|
|
|
|
<?= csrf_field() ?>
|
|
|
|
|
|
<input type="hidden" name="ids[]" value="<?= $pid ?>">
|
|
|
|
|
|
<button class="btn btn-sm" type="submit">加进我的名册</button>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
<?php elseif (!empty($inMyRoster) && $canW): ?>
|
|
|
|
|
|
<form method="post" action="<?= e(url('people/forget')) ?>" style="display:inline">
|
|
|
|
|
|
<?= csrf_field() ?>
|
|
|
|
|
|
<input type="hidden" name="person_id" value="<?= $pid ?>">
|
|
|
|
|
|
<button class="btn btn-ghost btn-sm" type="submit">从我的名册里移走</button>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
|
|
|
|
|
<?php if ($canW && !empty($options)): ?>
|
|
|
|
|
|
<?php /* 需求原话:「我可以把一个人的名片,分享给一个用户,
|
|
|
|
|
|
则这个名片,自动出现在这个用户的名册中」。 */ ?>
|
|
|
|
|
|
<details class="share-wrap">
|
|
|
|
|
|
<summary class="btn btn-ghost btn-sm">分享他的名片给…</summary>
|
|
|
|
|
|
<form method="post" action="<?= e(url('people/share')) ?>" class="inline-form">
|
|
|
|
|
|
<?= csrf_field() ?>
|
|
|
|
|
|
<input type="hidden" name="person_id" value="<?= $pid ?>">
|
|
|
|
|
|
<input type="hidden" name="back" value="<?= $pid ?>">
|
|
|
|
|
|
<label class="sr-only" for="shareTo">分享给谁</label>
|
|
|
|
|
|
<select class="select" id="shareTo" name="to_id">
|
|
|
|
|
|
<?php foreach ($options as $o): $oid = (int)$o['id']; ?>
|
|
|
|
|
|
<option value="<?= $oid ?>"><?= e((string)$o['name']) ?></option>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<button class="btn btn-sm" type="submit">分享</button>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</details>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<?php if ($admin): ?>
|
|
|
|
|
|
<div class="card card-pad" style="margin-top:16px">
|
|
|
|
|
|
<h2 class="section-title">登录账号</h2>
|
|
|
|
|
|
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<?php if (empty($p['account_id'])): ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<p class="small muted" style="margin:0 0 14px">
|
2026-09-30 08:13:07 +08:00
|
|
|
|
开通后,<?= e($p['name']) ?> 就能用手机号和密码登录家书了。
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</p>
|
|
|
|
|
|
<form method="post" action="<?= e(url('accounts/create')) ?>" class="form-grid">
|
|
|
|
|
|
<?= csrf_field() ?>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<input type="hidden" name="person_id" value="<?= $pid ?>">
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<div class="field">
|
|
|
|
|
|
<label>登录密码</label>
|
|
|
|
|
|
<input class="input" name="password" type="password" placeholder="至少 8 位,含字母和数字" required>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="field">
|
|
|
|
|
|
<label>角色</label>
|
|
|
|
|
|
<select class="select" name="role">
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<option value="member">家庭成员(可创建与编辑)</option>
|
|
|
|
|
|
<option value="admin">管理员(可改系统设置)</option>
|
|
|
|
|
|
<option value="guest">访客(只能查看)</option>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="field full">
|
|
|
|
|
|
<button class="btn btn-sm" type="submit">开通账号</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
|
<p class="small muted" style="margin:0 0 14px">
|
2026-09-30 08:13:07 +08:00
|
|
|
|
账号已开通,登录名是 <?= e($p['name']) ?> 的手机号。
|
|
|
|
|
|
当前角色:<b><?= e(\App\Account::roleWord($role)) ?></b>
|
|
|
|
|
|
<?= $role === 'guest' ? '—— 只能查看,不能新增或修改任何内容。' : '' ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</p>
|
|
|
|
|
|
<div class="btn-row" style="margin-top:0;flex-wrap:wrap">
|
2026-09-30 13:24:26 +08:00
|
|
|
|
<form method="post" action="<?= e(url('accounts/role')) ?>" style="display:inline">
|
|
|
|
|
|
<?= csrf_field() ?>
|
|
|
|
|
|
<input type="hidden" name="person_id" value="<?= $pid ?>">
|
|
|
|
|
|
<select class="select" name="role" style="width:auto;display:inline-block">
|
|
|
|
|
|
<option value="member" <?= $role === 'member' ? 'selected' : '' ?>>家庭成员</option>
|
|
|
|
|
|
<option value="admin" <?= $role === 'admin' ? 'selected' : '' ?>>管理员</option>
|
|
|
|
|
|
<option value="guest" <?= $role === 'guest' ? 'selected' : '' ?>>访客</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<button class="btn btn-ghost btn-sm" type="submit">改角色</button>
|
|
|
|
|
|
<?php if ($isSelf): ?>
|
|
|
|
|
|
<?php /* 自己也能改自己的权限(哪怕给自己降级)—— 系统的家可能就一个人用,
|
|
|
|
|
|
难道他还不能调自己的权限?唯一拦的是"降完一个管理员都不剩"。 */ ?>
|
|
|
|
|
|
<span class="small muted">可以改自己的。只有「把最后一位管理员降级」会被拦下。</span>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</form>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<?php if (!$isSelf): ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<form method="post" action="<?= e(url('accounts/status')) ?>" style="display:inline">
|
|
|
|
|
|
<?= csrf_field() ?>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<input type="hidden" name="person_id" value="<?= $pid ?>">
|
|
|
|
|
|
<input type="hidden" name="status" value="<?= $p['account_status'] === 'active' ? 'disabled' : 'active' ?>">
|
2026-09-30 00:49:53 +08:00
|
|
|
|
<button class="btn btn-ghost btn-sm" type="submit">
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<?= $p['account_status'] === 'active' ? '停用账号' : '启用账号' ?>
|
2026-09-30 00:49:53 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-09-30 08:13:07 +08:00
|
|
|
|
|
2026-10-03 19:27:14 +08:00
|
|
|
|
<?php /* ==================== 危险操作:删除(用户 2026-10-03 第 2 条)====================
|
|
|
|
|
|
「删除」从右上角搬到了页面**最下方**、「开通登录账号」下面。
|
|
|
|
|
|
放在最后 + 用 <details> 两步确认:破坏性操作不该一眼就看到、更不该一键完成。
|
|
|
|
|
|
⚠️ 措辞改成了「从名册中删除」并写明会连带删掉什么 ——
|
|
|
|
|
|
原来那句"从名册里移除"听着像"只是从我的名册里移走",
|
|
|
|
|
|
实际会删账号与亲属关系,说法与后果不符。 */ ?>
|
|
|
|
|
|
<?php if ($admin): ?>
|
|
|
|
|
|
<div class="danger-zone">
|
|
|
|
|
|
<details class="del-wrap">
|
|
|
|
|
|
<summary class="link-quiet">删除这个人…</summary>
|
|
|
|
|
|
<div class="del-confirm">
|
|
|
|
|
|
<p class="small muted" style="margin:0 0 8px;flex:1 1 100%">
|
|
|
|
|
|
会把他从名册中删除,**并且**删掉他的登录账号与全部亲属关系连线(不可恢复)。
|
|
|
|
|
|
如果你只是想让他从<b>你的</b>名册里消失,用名册列表上的「⋯ → 从我的名册移走」。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<form method="post" action="<?= e(url('people/destroy')) ?>"
|
|
|
|
|
|
onsubmit="return confirm('确定要删除 <?= e($p['name']) ?> 吗?账号与亲属关系会一起删掉,无法恢复。')">
|
|
|
|
|
|
<?= csrf_field() ?>
|
|
|
|
|
|
<input type="hidden" name="id" value="<?= $pid ?>">
|
|
|
|
|
|
<button class="btn btn-danger btn-sm" type="submit">确认删除</button>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</details>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-09-30 08:13:07 +08:00
|
|
|
|
<script>
|
|
|
|
|
|
// 关系表单的「是哪一位」下拉有**两种含义**:
|
|
|
|
|
|
// kind=parent → 对方是父亲/母亲(填的是对方)
|
|
|
|
|
|
// kind=child → 我是孩子的父亲/母亲(填的是自己)
|
|
|
|
|
|
// 所以标签必须跟着换,否则用户会以为在填对方的身份,把关系记反。
|
|
|
|
|
|
(function () {
|
|
|
|
|
|
var kind = document.getElementById('relKind');
|
|
|
|
|
|
var wrap = document.getElementById('relRoleWrap');
|
|
|
|
|
|
var lab = document.getElementById('relRoleLabel');
|
|
|
|
|
|
if (!kind || !wrap || !lab) return;
|
|
|
|
|
|
function sync() {
|
|
|
|
|
|
if (kind.value === 'spouse') {
|
|
|
|
|
|
wrap.style.display = 'none';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
wrap.style.display = '';
|
|
|
|
|
|
lab.textContent = kind.value === 'child' ? '我是孩子的' : '对方是我的';
|
|
|
|
|
|
}
|
|
|
|
|
|
kind.addEventListener('change', sync);
|
|
|
|
|
|
sync();
|
|
|
|
|
|
})();
|
|
|
|
|
|
</script>
|