diff --git a/assets/js/task_panel.js b/assets/js/task_panel.js index a57828b..187df02 100644 --- a/assets/js/task_panel.js +++ b/assets/js/task_panel.js @@ -243,25 +243,73 @@ 他要的是"改完点保存"这种确定感。 ⚠️ 只提交这三项,服务端 `Task::setAttrs()` 也只写这三列, 不碰 title/note/status/repeat(见那个方法的注释)。 */ + /** + * 读出「截止时间」框当前值 → `YYYY-MM-DD HH:mm:ss`(#77)。 + * + * 🔴 用户 2026-11-05:「截止日期的时间保存有bug,时和分没保存上。」 + * + * 为什么不能只读 `.dt-hour` / `.dt-minute`: + * AntD 一旦挂上(`.dt-input.is-antd`),`.dt-native` 被 CSS 收成 + * `display:none`。那几档原生 select 里的值是**控件写回来的镜像**, + * 而镜像的写入依赖 `mountAntdSingleDT.writeBack()`: + * onChange(val, str) → writeDT(box, str) + * `str` 来自 antd 的 `format: 'YYYY-MM-DD HH:mm'`。 + * 实测 antd 5 在**受控**模式下(我们传了 `value`), + * 某些路径给的是 `val`(dayjs 对象)而 `str` 可能是空串 —— + * 此时 writeDT 走进 else 分支,把 .dt-date/.dt-hour/.dt-minute **全清成空/00**。 + * ⇒ 日期还有值(另一条路径补齐了)、时分却恒为 00,正是用户报的现象。 + * + * ⇒ 读值时**以用户看得见的那个框为准**: + * ① 先看 AntD 的可见 input(`.ant-picker-input > input`)里的文本, + * 它的格式就是 `YYYY-MM-DD HH:mm`; + * ② 没有 AntD(降级成原生下拉)时才读隐藏字段; + * ③ 都没有就当空。 + * + * ⚠️ AntD 的输入框是**只读文本框**(用户通过弹层选,不直接打字), + * 所以解析它的 text 是安全的 —— 不会出现用户在框里敲了半截日期的中间态。 + */ + function readDue(box) { + if (!box) { return ''; } + /* ① AntD 可见框 */ + var vis = box.querySelector('.ant-picker-input > input'); + if (vis) { + var vt = String(vis.value || '').trim(); + if (vt !== '') { + /* 已选:'2026-12-31 20:00' 或 '2026-12-31 20:00:00' */ + var mv = vt.match(/^(\d{4})-(\d{1,2})-(\d{1,2})[ T](\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/); + if (mv) { + var p2 = function (n) { return (n < 10 ? '0' : '') + n; }; + return mv[1] + '-' + p2(+mv[2]) + '-' + p2(+mv[3]) + ' ' + + p2(+mv[4]) + ':' + p2(+mv[5]) + ':' + p2(+(mv[6] || 0)); + } + /* 只有日期没时间(用户只选了日子就点保存):补 00:00 */ + var md = vt.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/); + if (md) { + var p3 = function (n) { return (n < 10 ? '0' : '') + n; }; + return md[1] + '-' + p3(+md[2]) + '-' + p3(+md[3]) + ' 00:00:00'; + } + } + /* AntD 框存在但空着 = 用户清空了截止时间 */ + return ''; + } + /* ② 降级形态:读隐藏字段(与 app.js 的 readDT 同一口径) */ + var dt = box.querySelector('.dt-date'); + var dstr = dt ? String(dt.value || '').trim() : ''; + if (dstr === '') { return ''; } + var hh = box.querySelector('.dt-hour'); + var mm = box.querySelector('.dt-minute'); + var h2 = hh && String(hh.value || '').trim() !== '' ? String(hh.value) : '00'; + var m2 = mm && String(mm.value || '').trim() !== '' ? String(mm.value) : '00'; + return dstr + ' ' + h2 + ':' + m2 + ':00'; + } + if (what === 'save-attrs') { e.preventDefault(); if (btn.disabled) { return; } var bd = bodyEl(); if (!bd) { return; } var dbox = bd.querySelector('.dt-input[data-dt]'); - var dueVal = ''; - if (dbox) { - var dt = dbox.querySelector('.dt-date'); - var dstr = dt ? String(dt.value || '').trim() : ''; - if (dstr !== '') { - /* 截止时间是"日期 + 时刻":时分没选就补 00:00(与 readDT 同一口径) */ - var hh = dbox.querySelector('.dt-hour'); - var mm = dbox.querySelector('.dt-minute'); - var h2 = hh && String(hh.value || '').trim() !== '' ? String(hh.value) : '00'; - var m2 = mm && String(mm.value || '').trim() !== '' ? String(mm.value) : '00'; - dueVal = dstr + ' ' + h2 + ':' + m2 + ':00'; - } - } + var dueVal = readDue(dbox); var asg = bd.querySelector('select[name="assignee_id"]'); var cat = bd.querySelector('select[name="category_id"]'); var extra = {};