# 2026-09-16 ## rond-with-you:编辑页表单必填收紧 + 换 flatpickr 时间选择器 ### 需求 到达时间、离开时间、活动设为必选;备注可选但不折叠;原生 datetime-local 不好用,换别的。 ### 改动 - **新建地点**:`
` 折叠移除,到访三要素平铺显示且必填(到达/离开/活动), 备注可选;语义从「可选同时创建到访」变为「与地点同时创建到访」。 - **新增到访**:离开时间、活动必填;「类型」改叫「活动」,默认 option 从「(无)」改「请选择活动」。 - **编辑到访(行内)**:离开时间也必填(历史 departure 0 条 NULL,无副作用); 活动不强制(历史 73 条 NULL 活动,强制会拦住编辑)。 - **时间选择器**:引入 flatpickr v4.6.13(vendor 到 `internal/web/static/vendor/flatpickr/`, 含 l10n/zh.js,共 ~69KB,离线可用);`Page.Flatpickr` 开关控制 layout 按页加载; 24 小时制、中文、`allowInput` 可手输,`dateFormat: 'Y-m-dTH:i'` 保证提交值仍是 datetime-local 格式,服务端 `parseLocalDT` 无需改动。 - **服务端**:`parseVisitTimes` 离开时间必填;`editVisitCreate` / `editPlaceCreate` 活动必填 (resolveActivity 返回 nil 即拒);editPlaceCreate 删除「arrival 空则不建到访」分支。 - **CSS**:`.flatpickr-calendar` 圆角/阴影与站点一致,选中/今天/hover 用 `--accent`。 ### 验证 HTTP 实测:缺活动 → 「请选择活动类型」;缺离开 → 「请填写离开时间」;三要素齐 → 创建成功; 测试数据已删(visits 回到 823)。管理员密码已还原。 ## 追加修复:搜索失效(flatpickr l10n 崩溃)+ 地点下拉信息不足 ### 搜索失效根因 配置文件没动过(`conf/app.ini` 的 `geocode_key` 原样在,直调高德 API 也正常)。 真正根因是我给编辑页加的 flatpickr 初始化写错了 API:flatpickr 的语言包静态属性是 **`flatpickr.l10ns`(复数)**,我写成 `flatpickr.l10n.zh` → 读取 undefined 的 `.zh` 抛 TypeError → 同一个 `whenReady` 回调里**后面的地图初始化和地点搜索绑定全部没执行**。 表现就是「搜索按钮点了没反应」。 修复:`flatpickr.l10ns.zh` + 整段 try/catch 兜底(防止一处错毁掉全部初始化)。 教训:给页面加同步脚本时,先于其它初始化的代码必须自行兜错。 ### 第二个坑(排查中新引入又修复) 改「新增到访」地点下拉时,`COALESCE(NULLIF(sublocality,''), NULLIF(district,''))` 在两列 同为 NULL 时结果仍是 NULL,pgx 扫进 string 报错 → **整个 /admin/edit 页面 500**。 COALESCE 兜底要兜到底:末尾再加一个 `''`。 ### 地点下拉信息不足(用户反馈「看不出是哪次到访、有重复、太笼统」) 原 Label 只有「城市 · 名称」。改为从库里的 province/city/区县(sublocality 缺失时退 district)/ 街道拼完整地址 + 到访次数 + src_pk: `#196 家 — 广西壮族自治区 河池市 金城江区 西环路(246次)` src_pk 直接对应「新增行程」表单里要填的值。相邻重复段去重(直辖市不会出现「北京市北京市」)。 ### 无头验证结论 CDP 实测:0 JS 报错、flatpickr 中文包注册成功、Leaflet 地图初始化完成(执行流走完整)、 页内 XHR 调 `/api/geocode?q=南宁东站` 返回 8 条。 ## 再修正:离开时间不该必填(用户纠正) 用户指出逻辑错误:补录「家」这类高频地点的到访,常常只知道到达时间(刚到家/只记到达), 强制填离开时间不成立。已回退:离开时间恢复可选,留空=只记了到达(原语义), 前端 required/星号移除,`parseVisitTimes` 恢复原实现。 新增单元测试 `internal/web/parse_visit_times_test.go` 覆盖四种情况 (只到达/双时间/缺到达/离开早于到达),无需重置密码即可验证服务端逻辑。 保持必填的:到达时间、活动(用户未质疑)。 教训:把「必填」要求落到每个字段前先想补录场景——必填清单该由「这条数据不填就无意义」决定, 而不是照单全收;用户说的"必选"清单可能覆盖不了真实的补录姿势。 ## 再修正:到达时间默认当前时刻(用户质疑「246 次到家还让我手填到达时间」) **这版方向错了,用户又否定了**(见下节)。到达时间预填当前时刻已回退。 ## 根因澄清:不是必填问题,是「新增到访」语义传达失败 用户三层质疑(离开必填→到达重复→填一个时间代表什么)拼起来,真正的意思是: **用户以为选了「家(246次)」后,表单填的时间要「代表」这个地点的全部到访**—— "246 次到达时间都不一样,填一个时间代表它们当然不合理"。 而功能的真实语义是「往时间线追加一条**独立的**新到访(比如某次忘带手机的那段)」, 每次提交一条、互不影响。UI 从没把这层讲清楚。 修正: 1. 回退「预填当前时刻」——补录的永远是历史时刻(忘带手机的那天),预填当下只会诱导提交错误数据。 2. 表单文案写明:「追加一次单独的到访……每提交一次就是一条全新的独立记录,和该地点已有的到访互不影响, 更不是去『代表』它们;下拉里的次数只是参考。时间凭当时记忆填个大概即可」。 3. flatpickr `minuteIncrement` 1→5:补录靠回忆,没人记得 12:59:47,5 分钟步进贴合「大概那个点」。 教训(重要):**用户连续质疑表单逻辑时,先怀疑语义传达,而不是急着调必填/默认值**。 我顺着「必填是否合理」修了三轮,实际是用户没看懂这个表单在创建什么。 ## 融合表单:新增地点 + 新增到访合一(用户最终指令) 用户拍板:「把新增地点和新增到访融合起来,而不是分开,到访时间和日期选填,但是不要折叠选项。」 实现(internal/web/handlers_edit.go + templates/admin/edit.html + server.go): - 一个融合表单(action 仍为 /admin/edit/place),radio 切两种模式: `new`=新建地点(名称/坐标必填,地图选点+搜索);`existing`=给已有地点补到访(下拉选地点)。 - 到访时段**整体选填平铺**(到达/离开/活动/备注/收藏/标签): 到达留空 → 只建地点;填了到达 → 同时追加一条独立到访(活动也可不选,存 NULL)。 补已有地点时到达必填(否则没意义),后端给出明确提示。 - 删除独立「新增到访」card 与 `editVisitCreate` + 路由 POST /admin/edit/visit (update/delete 保留)。到访的增删改现在全部走融合表单。 - HTML5 required 不能写在会被隐藏的字段上(display:none 仍会拦截提交),校验全交后端。 - 模式切换时 Leaflet 在 display:none 容器里会拿到 0 尺寸,切回 new 模式时 `window.dispatchEvent(new Event('resize'))` 触发 invalidateSize。 端到端实测(用 site_cdp.py 签名 cookie,无需动密码):四条路径 (补到访缺到达→拒 / 补到访→成 / 新地点无到访→只建地点 / 新地点带到访→地点+到访) 全部正确;测试数据经 HTTP 删除,计数核对 places=324(323 原始+昆明站)、visits=823、家=246。 ## 编辑页三项易用性改进(用户第 4 轮反馈) 1. **补到访的地点选择器**:324 个地点用原生 `