Files
TripLog/templates/nodes/_members.php
T
laoyang 86e0481a19 节点表单:24 小时制时间控件、附件可裁剪、保存失败不丢已填内容
1) 时间选择不再出现「上午/下午」
   datetime-local 的显示格式由浏览器 UI 语言决定(中文环境渲染成
   「下午 03:30」),页面与元素的 lang 都压不住(截图实测)。
   拆成「原生 date 控件 + 时下拉(00-23) + 分下拉(00-59)」组合控件
   (templates/_datetime.php),24 小时制由控件本身决定,浏览器改不掉。
   服务端 read_dt_input() 把三段拼回 Y-m-d H:i:s 并校验,兼容旧单字段。
   节点起止时间与成员签到时间一并换掉。

2) 附件可裁剪,不想裁点「确定」就按原图上传
   裁剪弹窗抽成 templates/_crop_modal.php 与照片页共用;表单里选完图
   自动逐张进入裁剪,整图选框=没裁(保留原文件、不重新编码、省 base64
   流量),裁过的走 cropped 通道(隐藏域 JSON,原生表单提交也能带),
   没裁的留在 input.files,已裁卡片带角标,可随时重置重来。

3) 节点保存失败不再清空整页
   服务端:失败分支 flash_old($_POST),重开的表单按 old()/old_array()/
   old_dt() 全字段回填(含勾选删除状态),渲染完即清(layout.php)。
   前端:sessionStorage 草稿兜底服务端拿不到 $_POST 的情况
   (post_max_size 超限、会话过期),恢复条件收窄到「页面有错误提示条」,
   避免陈旧草稿污染新表单。

已验证:本地 php -l 全量 0 错、模板渲染 275 项、表单交互(真实浏览器)
73 项——含真实裁剪一刀、草稿恢复、三段式时间选择;远端 e2e 289 项
0 失败(存储设置测试期间归一为 local,不再写用户真实 OSS 桶,收尾还原);
部署后双 PHP 渲染逐字节一致、59 文件哈希校验一致。
2026-09-23 09:11:42 +08:00

148 lines
7.1 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
/**
* 节点参与人面板
* 依赖外部变量:$node、$members、$familyUsers、$roleTags(id=>name)、$emotions(emoji=>中文)
*/
$members = $members ?? [];
$familyUsers = $familyUsers ?? [];
$roleTags = $roleTags ?? [];
$emotions = $emotions ?? [];
$joinedIds = [];
foreach ($members as $m) {
$joinedIds[(int)$m['user_id']] = true;
}
$notJoined = [];
foreach ($familyUsers as $u) {
if (!isset($joinedIds[(int)$u['id']])) $notJoined[] = $u;
}
?>
<section class="panel" id="members">
<h2 class="section-title">👥 参与人 (<?= count($members) ?>)</h2>
<?php if (empty($members)): ?>
<p class="muted">还没有添加参与人</p>
<?php else: ?>
<div class="member-list">
<?php foreach ($members as $m):
$mName = (string)($m['name'] ?? '');
if ($mName === '') $mName = '已注销用户';
?>
<div class="member-row">
<?php if (!empty($m['avatar'])): ?>
<img src="<?= e($m['avatar']) ?>" alt="" class="avatar-sm">
<?php else: ?>
<span class="avatar-sm avatar-placeholder"><?= e(mb_substr($mName, 0, 1)) ?></span>
<?php endif; ?>
<span class="member-name"><?= e($mName) ?></span>
<?php foreach ($m['role_tag_names'] as $tagName): ?>
<span class="member-role"><?= e($tagName) ?></span>
<?php endforeach; ?>
<?php if (!empty($m['emotion'])): ?>
<span class="member-emotion" title="<?= e($emotions[$m['emotion']] ?? '') ?>">
<?= e($m['emotion']) ?><?= e($emotions[$m['emotion']] ?? '') ?>
</span>
<?php endif; ?>
<?php if (!empty($m['check_in_time'])): ?>
<span class="member-checkin">✓ 签到 <?= date('m-d H:i', strtotime((string)$m['check_in_time'])) ?></span>
<?php endif; ?>
<form method="post" action="<?= url('members/destroy') ?>" class="member-del-form"
onsubmit="return confirm('把 <?= e($mName) ?> 从本节点移出?');">
<input type="hidden" name="_csrf" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="node_id" value="<?= (int)$node['id'] ?>">
<input type="hidden" name="user_id" value="<?= (int)$m['user_id'] ?>">
<button type="submit" class="comment-del">移出</button>
</form>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if (!empty($notJoined)): ?>
<form method="post" action="<?= url('members/addAll') ?>" style="display:inline"
onsubmit="return confirm('把家庭里的 <?= count($familyUsers) ?> 位成员全部加入这个节点?');">
<input type="hidden" name="_csrf" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="node_id" value="<?= (int)$node['id'] ?>">
<button type="submit" class="btn-sm">+ 一键加入全家(<?= count($notJoined) ?> 人待加入)</button>
</form>
<?php endif; ?>
<details class="inline-panel">
<summary>+ 添加 / 修改参与人</summary>
<?php $mOldFilled = !empty($_SESSION['_old']); ?>
<form method="post" action="<?= url('members/store') ?>" class="form form-plain"
data-old="<?= $mOldFilled ? '1' : '0' ?>">
<input type="hidden" name="_csrf" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="node_id" value="<?= (int)$node['id'] ?>">
<div class="form-row">
<label>成员 *</label>
<select name="user_id" required>
<option value="">— 请选择 —</option>
<?php $mSelUser = (string)old('user_id', ''); ?>
<?php foreach ($familyUsers as $u): ?>
<option value="<?= (int)$u['id'] ?>"<?= $mSelUser === (string)(int)$u['id'] ? ' selected' : '' ?>>
<?= e($u['name']) ?><?= isset($joinedIds[(int)$u['id']]) ? '(已加入,保存将覆盖)' : '' ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php if (!empty($roleTags)): ?>
<div class="form-row">
<label>角色标签(可多选)</label>
<div class="check-grid">
<?php $mSelTags = old_array('role_tag_ids'); ?>
<?php foreach ($roleTags as $tagId => $tagName): ?>
<label class="checkbox-label">
<input type="checkbox" name="role_tag_ids[]" value="<?= (int)$tagId ?>"
<?= in_array((string)(int)$tagId, $mSelTags, true) ? ' checked' : '' ?>>
<span><?= e($tagName) ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<?php else: ?>
<p class="muted" style="font-size:12px;">
还没有角色标签,去
<a href="<?= url('role_tags/index') ?>">角色标签管理</a>
先建几个(例如:司机 / 摄影师 / 记账员)
</p>
<?php endif; ?>
<div class="form-row">
<label>心情(可选)</label>
<div class="emotion-grid">
<?php $mSelEmotion = (string)old('emotion', ''); ?>
<label class="emotion-option">
<input type="radio" name="emotion" value=""<?= $mSelEmotion === '' ? ' checked' : '' ?>>
<span class="emotion-face">🚫</span>
<span class="emotion-text">不记录</span>
</label>
<?php foreach ($emotions as $emoji => $label): ?>
<label class="emotion-option" title="<?= e($label) ?>">
<input type="radio" name="emotion" value="<?= e($emoji) ?>"<?= $mSelEmotion === (string)$emoji ? ' checked' : '' ?>>
<span class="emotion-face"><?= e($emoji) ?></span>
<span class="emotion-text"><?= e($label) ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<div class="form-row">
<label>签到时间(可选,留空表示未签到)</label>
<?php $dtName = 'check_in_time'; $dtValue = old_dt('check_in_time', null); $dtRequired = false; $dtId = 'check-in-date'; ?>
<?php require __DIR__ . '/../_datetime.php'; ?>
</div>
<div class="form-actions">
<button type="submit" class="btn-primary">保存</button>
</div>
</form>
</details>
</section>