节点表单:时间框整块可点、起点终点一键导航、可直接传多张附件

三件事都是「界面上明明该有、但用起来不通」的地方:

1) 时间输入框只能点右侧那个小日历图标才弹选择器
   原生行为如此(而且各浏览器还不一致),用户会以为输入框是只读的。
   在全局脚本里给 datetime-local / date 挂 click → showPicker(),
   等于把整块输入区都当成那个图标;选择器已在显示时再调是空操作,
   不支持的浏览器静默跳过。顺带给可点光标。
   写在 layout.php 里,所以签到时间等其它时间框一并生效。

2) 起点/终点的精确位置此前只存在页面上,没有落库
   高德选点回调里拿到的 lng/lat 现在随节点一起存库,节点详情页和
   表单页都生成高德导航链接,点一下就能直接导航过去(手机端会尝试
   唤起高德 App)。链接只给 to 不给 from,让移动端自动用当前位置当起点。
   ⚠️ 高德 URI API 的坐标顺序是「经度,纬度」,写反了不报错、只会安静地
   导到地球另一边,所以单独写了单元测试守住顺序。
   手工改地名时会清掉旧坐标,退化成关键字搜索,避免「名字改了位置没改」。

3) 节点处不能上传图片附件
   节点表单改成 multipart,加拖放区 + 多选 + 本地预览 + 逐个移除,
   编辑态可勾选删除已有附件(逐条校验归属,防止改 id 删到别人家的文件)。
   上传逻辑从 AttachmentController 抽到 src/Uploads.php,两处共用一份:
   MIME 白名单、大小上限、孤儿回滚各抄一份迟早走偏。
   请求体超过 post_max_size 时 PHP 会静默丢掉 $_POST 与 $_FILES,紧随的
   CSRF 校验必然失败、报「请求已过期」——这是最难排查的一类问题。
   现在按 CONTENT_LENGTH 提前识别并给出人话提示,且该守卫必须排在
   csrf_check() 之前(只读长度、不改状态,所以顺序上是安全的)。

已验证:本地 php -l 全量、模板渲染 255 项、amap_nav_url 单测 18 项、
真实浏览器交互 47 项(时间框三处点击都弹、多选/移除后 input.files 真被
改写、超限被前端拦下、导航链接随选点刷新、勾选删除变红、三视口无溢出)、
打包 57 文件、远端 e2e 267 项,全部通过;部署后双 PHP 渲染逐字节一致。
This commit is contained in:
2026-09-23 07:53:20 +08:00
parent 43171597e2
commit 44ffee6c92
9 changed files with 869 additions and 163 deletions
+23
View File
@@ -128,6 +128,29 @@ document.addEventListener('keydown', function (ev) {
menu.open = false;
}
});
// ===== 时间框:点输入框任意一处都弹出选择器 =====
// 原生行为只在点右侧那个小日历图标时才弹(而且各浏览器还不一致),用户会以为框只能读。
// showPicker() 是标准做法:在 click 里调它,相当于把整块输入区都当成了那个图标。
//
// 为什么能跟浏览器自带的弹窗共存:若浏览器这次点击已经弹过了,再调一次是空操作
// (规范里「show the picker」对已经在显示的选择器直接返回),不会闪两下。
// 不支持的浏览器(老 Safari 等)静默跳过 —— 退化成原生行为,不会比现在更差。
(function () {
var pickers = document.querySelectorAll('input[type="datetime-local"], input[type="date"]');
function hook(el) {
el.addEventListener('click', function () {
if (typeof el.showPicker !== 'function') return;
try {
el.showPicker();
} catch (err) {
// NotAllowedError:不是用户手势触发的(或被跨域 iframe 挡住)。
// 忽略即可 —— 原生那套点击行为还在,用户仍有办法选时间。
}
});
}
for (var i = 0; i < pickers.length; i++) hook(pickers[i]);
})();
</script>
<?php endif; ?>
</body>