节点表单:时间框整块可点、起点终点一键导航、可直接传多张附件
三件事都是「界面上明明该有、但用起来不通」的地方:
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:
@@ -158,6 +158,43 @@ function media_avatar(?string $url): string
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高德导航链接:点一下直接导航到这个位置(手机端会尝试唤起高德 App)。
|
||||
*
|
||||
* 格式依据高德 URI API:https://uri.amap.com/navigation?to=lon,lat,name&mode=car
|
||||
* ⚠️ 坐标顺序是「经度,纬度」(lon 在前),写反了会导航到地球另一边。
|
||||
* ⚠️ 只给 to 不给 from:from 留空时移动端会自动用「当前位置」当起点,
|
||||
* 这正是「导航到这个位置」的语义。
|
||||
*
|
||||
* 没坐标时退化成关键词搜索 —— 用户在表单里只敲了名字、没从下拉里选中的情况很常见,
|
||||
* 这种节点也给一个能用的链接,总比什么都不显示强。
|
||||
*
|
||||
* @param array $loc 形如 ['name'=>..,'address'=>..,'lat'=>..,'lng'=>..]
|
||||
* @return string 无可用信息时返回空串(模板据此决定要不要渲染链接)
|
||||
*/
|
||||
function amap_nav_url(array $loc, string $mode = 'car'): string
|
||||
{
|
||||
$name = trim((string)($loc['name'] ?? ''));
|
||||
$lng = trim((string)($loc['lng'] ?? ''));
|
||||
$lat = trim((string)($loc['lat'] ?? ''));
|
||||
|
||||
if (is_numeric($lng) && is_numeric($lat)) {
|
||||
// 只对名称做 URL 编码(逗号保持字面量,否则高德解析不出三段式);
|
||||
// 名称里若含 & 或 = 也能被正确转义。
|
||||
$to = $lng . ',' . $lat . ($name !== '' ? ',' . rawurlencode($name) : '');
|
||||
return 'https://uri.amap.com/navigation?to=' . $to
|
||||
. '&mode=' . rawurlencode($mode)
|
||||
. '&policy=1&src=trip.yangyq.net&coordinate=gaode&callnative=1';
|
||||
}
|
||||
|
||||
$keyword = $name !== '' ? $name : trim((string)($loc['address'] ?? ''));
|
||||
if ($keyword === '') {
|
||||
return '';
|
||||
}
|
||||
return 'https://uri.amap.com/search?keyword=' . rawurlencode($keyword)
|
||||
. '&src=trip.yangyq.net';
|
||||
}
|
||||
|
||||
/** 人类可读的文件大小 */
|
||||
function human_size(?int $bytes): string
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user