节点表单: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 文件哈希校验一致。
This commit is contained in:
2026-09-23 09:11:42 +08:00
parent 44ffee6c92
commit 86e0481a19
10 changed files with 633 additions and 77 deletions
+44
View File
@@ -0,0 +1,44 @@
<?php
/**
* 「日期 + 24 小时制时:分」组合输入控件。
*
* ⚠️ 为什么不用 <input type="datetime-local">:它的显示格式由**浏览器 UI 语言 /
* 系统时间格式**决定,<html lang> 与元素 lang 都压不过它(实测 chromium 1243:
* --lang=zh-CN 渲染成「下午 03:30」,用户会混淆;--lang=en-GB 才是 24 小时制)。
* 拆成「原生日期选择器 + 时/分两个下拉」后,24 小时制由控件本身决定,浏览器改不掉。
* 日期部分仍是原生 date 控件(layout.php 里的 showPicker 全局脚本对它同样生效,
* 点输入框任意位置都能弹出日历)。
*
* 调用方式:include 前 set 好这几个变量(模板里只有它们会被读):
* $dtName 字段基名,如 'start_time' → 产出 *_date / *_hour / *_minute 三个字段
* $dtValue 可空;"Y-m-d H:i:s" / "Y-m-d H:i" / null。服务端取值用 read_dt_input($dtName)
* $dtRequired 日期是否必填(默认 false;必填时在日期控件上加 required)
* $dtId 可选;给日期控件的 id(供 <label for>)
*/
$dtName = (string)($dtName ?? 'time');
$dtValue = $dtValue ?? null;
$dtRequired = (bool)($dtRequired ?? false);
$dtId = (string)($dtId ?? '');
$dtTs = (!empty($dtValue) && ($t = strtotime((string)$dtValue))) ? $t : false;
$dtDate = $dtTs ? date('Y-m-d', $dtTs) : '';
$dtHour = $dtTs ? (int)date('G', $dtTs) : 0; // G = 0..23,天然 24 小时制
$dtMin = $dtTs ? (int)date('i', $dtTs) : 0;
?>
<div class="dt-input">
<input type="date" class="dt-date" name="<?= e($dtName) ?>_date"<?= $dtId !== '' ? ' id="' . e($dtId) . '"' : '' ?>
value="<?= e($dtDate) ?>"<?= $dtRequired ? ' required' : '' ?>
aria-label="日期">
<select class="dt-hour" name="<?= e($dtName) ?>_hour" aria-label="时(24 小时制)">
<?php for ($h = 0; $h < 24; $h++): $hv = sprintf('%02d', $h); ?>
<option value="<?= $hv ?>"<?= $h === $dtHour ? ' selected' : '' ?>><?= $hv ?></option>
<?php endfor; ?>
</select>
<span class="dt-colon" aria-hidden="true">:</span>
<select class="dt-minute" name="<?= e($dtName) ?>_minute" aria-label="分">
<?php for ($m = 0; $m < 60; $m++): $mv = sprintf('%02d', $m); ?>
<option value="<?= $mv ?>"<?= $m === $dtMin ? ' selected' : '' ?>><?= $mv ?></option>
<?php endfor; ?>
</select>
</div>
<?php /* 用完即清,避免残留的 $dtName 影响下一次 include */ unset($dtName, $dtValue, $dtRequired, $dtId, $dtTs, $dtDate, $dtHour, $dtMin); ?>