Files
FamilyBook/templates/home.php
T
laoyang ed38a40c08 名册每人一份 · 分组两层 · 拖动排序 · 表单合一 · 裁剪可缩放
按用户十点反馈逐条落地(v5,Schema::VERSION = 5):

- 名册改成每人一份:新表 person_roster(owner_id, person_id, group_id,
  sort_order, source)。默认只看「我添加的 + 别人分享给我的」;
  source='seeded' 是升级时从老 persons.group_id 播进来的(先播数据再删列)。
- 排序不再手填:名册页直接拖动卡片(整版提交,一次 applyLayout 落库),
  无 JS 时卡片上的「调整分组」表单照旧可用。
- 分组分两层:管理员在「系统设置」维护公共分组(owner_id=0),
  用户在「个人设置」加自己的分组;显示顺序各人各定、互不影响。
- 名册可共享:把名片分享给别的用户(people/share);添加成员时若系统里
  已有这个人,可直接复用(people/pick),不必重复录入。
- 「编辑成员」与「改我自己的资料」合并为同一张表(templates/people/form.php),
  两个入口 href 完全相同,h1 按改谁切换文案;个人设置页只留
  资料入口 + 称呼 + 我的分组三块,删除 templates/settings/_profile.php。
- 系统设置收进头像下拉,顶栏只剩 首页/家谱/名册。
- 裁剪器加缩放(滑块 1-4x、Ctrl+滚轮、还原):缩放只改视图大小,
  落库的归一化矩形保持不变。
- 权限:管理员可以改任何人的权限(含自己),只拦「最后一位管理员」被降级。
- 去掉首页「我的一句话」后的链接、家谱说明段、以及「还没归到任何分组」提示。

迁移安全:数据回填(PersonRoster::seedFromPersons)先于删列执行、且不进
tryStep 容错;失败步骤记入 settings.schema_upgrade_warning 并在系统设置页提示。
线上已验证:schema_upgrade_warning 为空,person_roster/person_groups.owner_id
就位,persons.group_id / sort_order 已删。

验证:php -l 48 文件;本地 e2e 277/0、老库升级 81/0、桌面几何 108/0、
移动端几何 180/0、交互 96/0;反向验证 38 条注入 + 2 条缩放注入全部命中;
部署静态断言 53/0,站点自检 7/7(--resolve 锁本机走 443,探针真的进 PHP)。
2026-09-30 13:24:26 +08:00

182 lines
7.7 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
/**
* 首页 —— 以「我」为中心。
*
* 同一份数据,爸爸登进来看到的是「我的父亲/我的儿子」,
* 爱人登进来看到的是同一批人,称呼却变成「公公/婆婆」。
* 称呼全部由 Kinship 现算、由 Nickname 决定"我叫他什么",库里不存任何一次性称谓。
*
* 标题下面那一行小字的取法(这处被明确纠正过):
* 先看**我自己**有没有写 slogan → 没写才回落到家庭统一的那句。
* 所以同一页不同人登进来看到的是不同的话,而不是全家共用一个口号。
* 家庭名称本身不再展示 —— 标题已经是「某某某的家书」,再挂一个家庭名只会打架。
*/
$kinCount = count($kin);
$hasRel = $relCount > 0;
// 显示名一律走「我怎么称呼他」:同一个孩子,爸爸看到"儿子"、妈妈看到"小宝"。
// ⚠️ 用**已经取好的那张表**(target_id => 我叫他什么)做本地查表,
// 不要再对每个人调一次 Nickname::display() —— 那是每人一条 SQL。
// 没有称呼的人回落到姓名,所以永远不会出现空白名字。
$shownOf = function (array $row) use ($myNicknames): string {
$nk = (string)($myNicknames[(int)($row['id'] ?? 0)] ?? '');
return $nk !== '' ? $nk : (string)($row['name'] ?? '');
};
// 有亲属关系 → 只显示家人;还没建 → 退回显示全名册,别让首页空着
$list = $hasRel ? $kin : [];
if (!$list) {
foreach (\App\Person::all((int)\App\Auth::familyId(), '', 'member', (int)$meId) as $p) {
$p['label'] = '';
$list[(int)$p['id']] = $p;
}
}
// 标题下的一行小字:我的一句话 → 家庭统一那句 → 兜底
$motto = trim((string)$mySlogan);
$mottoIsMine = $motto !== '';
if (!$mottoIsMine) {
$motto = trim((string)($family['motto'] ?? ''));
}
if ($motto === '') {
$motto = '一本用岁月写成的家谱';
}
// 生日事件上用简称,卡片才不会一行塞不下;长名字在详情页里出
$shortKind = ['id' => '身份证生日', 'solar' => '阳历生日', 'lunar' => '农历生日'];
?>
<section class="hero">
<img class="hero-logo" src="<?= e(asset('/assets/icons/icon-192.png')) ?>" alt="">
<div class="hero-main">
<h1><?= e(\App\Auth::homeTitle()) ?></h1>
<p class="motto">
<?= e($motto) ?>
<?php if ($mottoIsMine): ?>
<span class="motto-from">我的一句话</span>
<?php endif; ?>
</p>
</div>
<div class="hero-stats">
<div class="stat"><b><?= (int)$kinCount ?></b><span>与我有关</span></div>
<div class="stat"><b><?= (int)$allCount ?></b><span>名册总数</span></div>
<div class="stat"><b><?= (int)$relCount ?></b><span>亲属连线</span></div>
</div>
</section>
<?php if ($birthdays): ?>
<section class="section">
<h2 class="section-title">接下来的日子 <span class="hint">两个月内</span></h2>
<div class="bdays">
<?php foreach ($birthdays as $b): ?>
<?php
// 同一个人可能出两条(农历与阳历不在同一天);落在同一天时后端已经并成一条,
// kinds 里会有多项 —— 这里照着列出来,用户才知道"今天过的是哪个生日"。
$bkinds = (array)($b['kinds'] ?? []);
$bwords = [];
foreach ($bkinds as $k) {
$bwords[] = $shortKind[$k] ?? (string)$k;
}
$bshown = (string)($b['shown'] ?? $b['name'] ?? '');
?>
<a class="bday" href="<?= e(url('people/show', ['id' => (int)$b['id']])) ?>">
<img src="<?= e(avatar_url($b['avatar'])) ?>" alt="">
<div>
<b><?= e($bshown) ?></b>
<?php if (!empty($b['label']) && $b['label'] !== '我'): ?>
<span class="badge"><?= e($b['label']) ?></span>
<?php endif; ?>
<div class="d">
<?php if ((int)$b['days_left'] === 0): ?>
就是今天,生日快乐
<?php else: ?>
还有 <?= (int)$b['days_left'] ?> 天过生日
<?php endif; ?>
</div>
<div class="bday-meta">
<span class="bday-date"><?= e((string)$b['date']) ?></span>
<?php foreach ($bwords as $w): ?>
<span class="bd-kind"><?= e($w) ?></span>
<?php endforeach; ?>
<?php if (!empty($b['lunar_label'])): ?>
<span class="muted small">农历<?= e((string)$b['lunar_label']) ?></span>
<?php endif; ?>
</div>
</div>
</a>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<section class="section">
<div class="page-head" style="margin-bottom:12px">
<h1 style="font-size:15px"><?= $hasRel ? '我的家人' : '家庭成员' ?></h1>
<div class="actions">
<a class="btn btn-ghost btn-sm" href="<?= e(url('kin')) ?>">我的家谱</a>
<?php if ($me): ?>
<a class="btn btn-ghost btn-sm" href="<?= e(url('people/show', ['id' => (int)$me['id']])) ?>">补充亲属关系</a>
<?php endif; ?>
</div>
</div>
<?php if (!$hasRel): ?>
<div class="empty">
<b>先把家人连起来</b>
目前还没有任何亲属关系,所以下面显示的是整个名册。<br>
到自己的资料页填上「谁是我的父母、配偶、子女」,之后这一页就只显示跟你有关的人,
每个人的称呼也会按你的视角来算。
<?php if ($me): ?>
<div class="btn-row" style="justify-content:center">
<a class="btn" href="<?= e(url('people/show', ['id' => (int)$me['id']])) ?>">开始连关系</a>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!$list): ?>
<div class="empty">
<b>名册还是空的</b>
先把家里人加进来,之后他们就能出现在每一条记录里。
<div class="btn-row" style="justify-content:center">
<a class="btn" href="<?= e(url('people/new')) ?>">添加成员</a>
</div>
</div>
<?php else: ?>
<div class="people-grid">
<?php foreach ($list as $p): ?>
<?php
// ⚠️ 这里必须用「我给他起的称呼」,不能直接用姓名:
// 同一个「杨父」,儿媳妇看到的是"公公大人"、儿子看到的是"爸"。
$pshow = $shownOf($p);
$pIsMe = (int)$p['id'] === (int)$meId;
?>
<a class="person" href="<?= e(url('people/show', ['id' => (int)$p['id']])) ?>">
<div class="ph"><img src="<?= e(avatar_url($p['avatar'] ?? '')) ?>" alt=""></div>
<div class="pb">
<div class="pname">
<?= e($pshow) ?>
<?php if ($pIsMe): ?><span class="badge badge-me">我</span><?php endif; ?>
<?php if (!empty($p['is_placeholder'])): ?><span class="badge">待确认</span><?php endif; ?>
</div>
<div class="pmeta">
<?php if (!empty($p['label'])): ?>
<span class="rel-label"><?= e($p['label']) ?></span>
<?php endif; ?>
<?php
$role = $p['account_role'] ?? '';
if ($role === 'admin' && ($p['account_status'] ?? '') === 'active') {
echo '<span class="badge badge-admin">管理员</span>';
} elseif ($role === 'guest') {
echo '<span class="badge">访客</span>';
} elseif (!empty($p['account_id'])) {
echo '<span class="badge badge-open">已开通</span>';
}
$age = age_from(\App\Birthday::solarOf($p));
if ($age !== null) { echo ' · ' . (int)$age . ' 岁'; }
?>
</div>
</div>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>