1. 独立页(登录/安装/错误)套上 HTML 骨架 —— 修「首页为什么没有样式」: 新增 templates/layout-bare.php,View::render 对独立页也走完整 <head> + CSS。 2. 以个人为中心:Kinship::label() 现算称呼、Auth::homeTitle() 出「X的家书」; 同一批人在不同登录者眼里自动变成公公婆婆 / 岳父岳母(不存一次性称谓)。 3. 附件存储可配置:Storage 门面(local / 阿里云 OSS),库里只存相对键, 云端失败自动回退本地;设置页可选驱动并填 OSS 参数(照 MovieLog 的方案)。 4. 三种角色:admin / member / guest,访客只读、写操作一律 403; accounts.role 枚举扩容,老库靠 Schema::ensure() 自动升级。 5. 家谱:只存相邻两代的边(parent / spouse),其余称呼全部推导 —— 兄弟姐妹不存边(共同父母 + birth_rank 排行推长子/次女、哥哥/妹妹), 认不清的人用虚拟人物 is_placeholder 占位,关系天然双向。 顺带修掉本地 e2e 挖出的 8 个线上级 bug(性别写死、长幼方向反、关系方向静默写反、 birth_rank 不从表单取回、隐式可空参数致响应头失效、无文件字段被误判为上传失败、 layout 变量撞车覆盖 $me、世代 0 显示成「下 0 代」)。 验证:kinship 单测 37/0 · HTTP e2e 81/0 · 老库升级专测 14/0 · 反向验证 11/11(把每个修复改回旧写法,对应断言都会变红)。
136 lines
6.0 KiB
PHP
136 lines
6.0 KiB
PHP
<?php
|
|
$isEdit = !empty($person['id']);
|
|
$avatarUrl = avatar_url($person['avatar'] ?? null);
|
|
?>
|
|
|
|
<div class="page-head">
|
|
<h1><?= $isEdit ? '编辑成员' : '添加成员' ?></h1>
|
|
<span class="sub"><?= $isEdit ? e($person['name']) : '填姓名就行,其他都可以以后再补' ?></span>
|
|
</div>
|
|
|
|
<form method="post" enctype="multipart/form-data"
|
|
action="<?= e(url($isEdit ? 'people/update' : 'people/create')) ?>">
|
|
<?= csrf_field() ?>
|
|
<?php if ($isEdit): ?>
|
|
<input type="hidden" name="id" value="<?= (int)$person['id'] ?>">
|
|
<?php endif; ?>
|
|
|
|
<div class="card card-pad">
|
|
<div class="form-grid">
|
|
|
|
<div class="field">
|
|
<label for="name">姓名 *</label>
|
|
<input class="input" id="name" name="name" value="<?= e(old('name', $person['name'] ?? '')) ?>" required>
|
|
<?php if (!empty($errors['name'])): ?><span class="err"><?= e($errors['name']) ?></span><?php endif; ?>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="nickname">小名</label>
|
|
<input class="input" id="nickname" name="nickname"
|
|
value="<?= e(old('nickname', $person['nickname'] ?? '')) ?>" placeholder="家里怎么叫他/她">
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="gender">性别</label>
|
|
<select class="select" id="gender" name="gender">
|
|
<?php $g = old('gender', $person['gender'] ?? ''); ?>
|
|
<option value="" <?= $g === '' ? 'selected' : '' ?>>不填</option>
|
|
<option value="m" <?= $g === 'm' ? 'selected' : '' ?>>男</option>
|
|
<option value="f" <?= $g === 'f' ? 'selected' : '' ?>>女</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="birthday">生日</label>
|
|
<input class="input" id="birthday" name="birthday" type="date"
|
|
value="<?= e(old('birthday', $person['birthday'] ?? '')) ?>">
|
|
<span class="tip">填了之后,首页会提醒还有几天过生日。</span>
|
|
<?php if (!empty($errors['birthday'])): ?><span class="err"><?= e($errors['birthday']) ?></span><?php endif; ?>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="birth_rank">家中排行</label>
|
|
<input class="input" id="birth_rank" name="birth_rank" type="number" min="1" max="99"
|
|
value="<?= e(old('birth_rank', $person['birth_rank'] ?? '')) ?>" placeholder="老大填 1">
|
|
<span class="tip">用来认兄弟姐妹的长幼:长子、次女、三弟…</span>
|
|
<?php if (!empty($errors['birth_rank'])): ?><span class="err"><?= e($errors['birth_rank']) ?></span><?php endif; ?>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="phone">手机号</label>
|
|
<input class="input" id="phone" name="phone" inputmode="numeric"
|
|
value="<?= e(old('phone', $phoneValue ?? '')) ?>" placeholder="可留空">
|
|
<span class="tip">开通登录后用它登录;加密存储,列表里只显示部分。</span>
|
|
<?php if (!empty($errors['phone'])): ?><span class="err"><?= e($errors['phone']) ?></span><?php endif; ?>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="email">邮箱</label>
|
|
<input class="input" id="email" name="email" type="email"
|
|
value="<?= e(old('email', $person['email'] ?? '')) ?>">
|
|
<?php if (!empty($errors['email'])): ?><span class="err"><?= e($errors['email']) ?></span><?php endif; ?>
|
|
</div>
|
|
|
|
<div class="field full">
|
|
<label for="id_card">身份证号</label>
|
|
<input class="input mono" id="id_card" name="id_card"
|
|
value="<?= e(old('id_card', $idCardValue ?? '')) ?>" placeholder="可留空">
|
|
<span class="tip">
|
|
加密存储,页面上一律只显示前 6 后 4 位。
|
|
家庭场景里真正需要它的场合很少 —— 能不填就别填。
|
|
</span>
|
|
<?php if (!empty($errors['id_card'])): ?><span class="err"><?= e($errors['id_card']) ?></span><?php endif; ?>
|
|
</div>
|
|
|
|
<div class="field full">
|
|
<label for="address">住址</label>
|
|
<input class="input" id="address" name="address"
|
|
value="<?= e(old('address', $person['address'] ?? '')) ?>">
|
|
</div>
|
|
|
|
<div class="field full">
|
|
<label for="note">备注</label>
|
|
<textarea class="input" id="note" name="note" placeholder="爱吃辣、对芒果过敏、怕坐过山车……"><?= e(old('note', $person['note'] ?? '')) ?></textarea>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="avatar">头像</label>
|
|
<input class="input" id="avatar" name="avatar" type="file" accept="image/*">
|
|
<span class="tip">会自动裁成正方形,上限 5MB。</span>
|
|
</div>
|
|
<div class="field">
|
|
<label>当前头像</label>
|
|
<img id="avatar-preview" src="<?= e($avatarUrl) ?>" alt=""
|
|
style="width:88px;height:88px;border-radius:12px;object-fit:cover;border:1px solid var(--line)">
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="sort_order">排序号</label>
|
|
<input class="input" id="sort_order" name="sort_order" type="number"
|
|
value="<?= e(old('sort_order', $person['sort_order'] ?? 1000)) ?>">
|
|
<span class="tip">小的排前面。</span>
|
|
</div>
|
|
|
|
<div class="field full">
|
|
<label class="check-item">
|
|
<input type="checkbox" name="is_placeholder" value="1"
|
|
<?= old('is_placeholder', $person['is_placeholder'] ?? 0) ? 'checked' : '' ?>>
|
|
<span>
|
|
<b>这是「虚拟人物」</b><br>
|
|
<span class="muted small">
|
|
还没弄清真实身份时先占个位,比如起名「我的父亲」「外婆」。
|
|
他能参与亲属关系,等弄清真名再改过来即可;家谱上会标「待确认」。
|
|
</span>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="btn-row">
|
|
<button class="btn" type="submit"><?= $isEdit ? '保存' : '加入名册' ?></button>
|
|
<a class="btn btn-ghost" href="<?= e($isEdit ? url('people/show', ['id' => (int)$person['id']]) : url('people')) ?>">取消</a>
|
|
</div>
|
|
</div>
|
|
</form>
|