修截止时间时/分保存不上:改为读 AntD 可见框 (#77)
用户 2026-11-05:「截止日期的时间保存有bug,时和分没保存上。」
根因:save-attrs 原来直接读 .dt-hour / .dt-minute 的 value。
那两个 <select> 是 AntD 挂上后**被 CSS 收起**的镜像(.dt-input.is-antd
> .dt-native { display:none }),它们的值依赖 mountAntdSingleDT 的
writeBack() 写回 —— 而 writeBack 走 writeDT(box, str),str 来自 antd
的 onChange 第二参数。
用 Playwright + 站点真实 app.js/antd 实测发现问题:
· 只给 select 放一个 option 时,writeDT 写入不存在的值 → value 变空串;
· 时序上(首屏挂载 / 面板 AJAX 换入 / 受控重渲染)隐藏字段可能滞后于
用户看到的那个值。
⇒ 读隐藏字段本质上是"读镜像",镜像一旦滞后或缺失,时分就丢。
修法:新增 readDue(),按优先级读:
① AntD 的可见输入框 .ant-picker-input > input(用户实际看到/改的那一个,
格式 YYYY-MM-DD HH:mm)—— 这是唯一权威来源;
② 没有 AntD(降级成原生下拉)时才读 .dt-date/.dt-hour/.dt-minute;
③ 都没有 → 空(= 清掉截止时间)。
AntD 的框是只读文本框(用户通过弹层选,不能直接打字),
所以解析它的 text 不会碰到"敲了半截日期"的中间态。
实测(Playwright 复刻完整 mountAntdSingleDT + readDue):
AntD 可见框 = 2026-10-16 20:30
隐藏 hour/min = 20 / 30
readDue 结果 = 2026-10-16 20:30:00 ✅
服务端侧已确认无需改动:Task::dtOrNull 会把
YYYY-MM-DD HH:mm 补秒成 YYYY-MM-DD HH:mm:ss,
YYYY-MM-DD 补成 00:00:00,格式非法才回 null。
This commit is contained in:
+61
-13
@@ -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 = {};
|
||||
|
||||
Reference in New Issue
Block a user