首页任务面板:可直接改截止时间 (#74)

用户 2026-11-05:「点开之后,就可以修改任务的所有信息,包括:
1. 上传附件;2. 添加评论;3. 添加子任务;4. 修改截止时间
(已有值要带进来);5. 改状态、挪看板。这些操作全部都在这一个页面完成。」

盘点发现:附件 / 评论 / 子任务 / 状态 / 看板 **本来就有**(_panel.php 里
早就实现了),只有「截止时间」是只读的一行 dd。所以只补这一项。

四处改动:

① Task::setDue()(新增)
   不复用 update():那是全字段覆盖(title/note/status/assignee/repeat/
   parent/board/category 一起写)。表单只交 due_at 的话,其余字段会被
   默认值抹成空 ——「只想改个日期,结果标题和备注没了」,不可恢复。
   ⇒ 单独一条只写 due_at 的 UPDATE。

② tasks/due 路由(新增,紧跟 tasks/status)
   与 status/move 同一套约定:ajax_panel=1 回整块新面板,否则 flash+302。
   空串 = 清掉截止时间。

③ _panel.php:只读「截止」行 → AntD 单点控件
   原来:
     <?php if ($tpDue !== ): ?>…<dd>截止 …</dd>…<?php endif; ?>
   **没有截止时间时整行都不显示** —— 用户根本不知道该去哪填。
   现在不管有没有值都画控件,$dtValue = $tpDue 自动带出已有值。
   有值且在逾期时,右侧补一行「还剩 N 天」小字(.tk-due-word)。

④ task_panel.js
   · paint() 后调 fbBootAntd():面板是 AJAX 换进来的,bootAntd 首屏
     扫过一次 [data-dt-antd-single],那时面板还不存在 ⇒ 必须补挂。
   · bindDue():监听 .dt-input 的 change 立即保存。
     不用按钮式 data-tk-act(面板里其它动作都是"点一下/选完就生效",
     再让用户点一次保存会与其它处不一致)。
     监听隐藏字段的 change 而不是 AntD 回调 ⇒ 覆盖 AntD 与原生下拉两种形态。
     data-fb-due-bound 幂等标记防止重复绑监听(否则改一次发好几条请求)。

⑤ CSS:.tk-sec-inline > .dt-input 加 flex/min-width/max-width ——
   .dt-input 是 flex-wrap,放进也是 flex-wrap 的 .tk-sec-inline 里
   会被当弹性项按内容撑,窄屏上可能把标签挤走或控件溢出。

实测:
  setDue 设值  => [2026-12-25 18:30:00] ✅
  标题仍为原值 ✅ 没被清空(证明没用 update())
  setDue 清空  => NULL ✅
  面板渲染:data-tk-url-due / data-dt-antd-single / due_at_date 全在,
            带出的日期值 = [2026-10-22] ✅
            状态/附件/子任务/评论 均仍在
This commit is contained in:
2026-11-05 17:44:11 +08:00
parent a43950069d
commit 836f6f1d2f
5 changed files with 186 additions and 6 deletions
+61
View File
@@ -78,9 +78,70 @@
var ti2 = b.querySelector('.tk-inline-form input[name="title"]');
if (ti2) { ti2.value = draftTitle; }
}
/* 🔴 #74(用户 2026-11-05):「点开之后,就可以修改任务的所有信息,
包括…修改截止时间。」
⇒ 面板里现在有 AntD 的单点日期控件(`[data-dt-antd-single]`)。
这段 HTML 是 AJAX 换进来的,**不会自己跑初始化** ——
`bootAntd()` 只在页面加载时扫过一次 `[data-dt-antd-single]`,
那时面板还不存在 ⇒ 控件会是一片原生下拉(或空白)。
⇒ 每次重画面板都要补挂一次。
⚠️ `mountAntd*` 有 `data-antd-done` 幂等标记,重复调用不会挂两遍。
⚠️ 必须放在 `innerHTML` 之后 —— 要挂的节点得先真的在 DOM 里。 */
if (typeof window.fbBootAntd === 'function') {
try { window.fbBootAntd(); } catch (err) { /* 挂不上就留在原生下拉,不阻断面板 */ }
}
/* 重画之后节点是新的 ⇒ 监听要重新绑一遍(旧的已经随旧节点一起没了) */
bindDue();
return true;
}
/**
* 监听「截止时间」控件的变化,改完**立即保存**(#74)。
*
* 为什么不用 `data-tk-act="due"` 那种按钮:
* 面板里其它动作都是"点一下就生效",截止时间是**选完时间**就该生效,
* 再让用户点一次「保存」会让这一处与其它处不一致。
*
* 为什么监听**隐藏字段的 change** 而不是 AntD 的回调:
* · AntD 挂载时会把选中的值写回 `.dt-date` / `.dt-hour` / `.dt-minute`
* 这些原生字段,并 copy 一个 `change` 事件(见 app.js 的 writeDT);
* · 原生下拉那一档(AntD 没挂上时的兜底)也是同一批发 `change`;
* · ⇒ 监听一次就能覆盖**两种形态**,不用分别处理。
*
* ⚠️ `data-fb-due-bound` 幂等标记:`bindDue()` 在每次 paint 后都会跑,
* 没有它会给同一个节点挂上多个监听 ⇒ 改一次时间发好几条请求。
*/
function bindDue() {
var b = bodyEl();
if (!b) { return; }
var box = b.querySelector('.dt-input[data-dt]');
if (!box) { return; }
var cfg = {};
try { cfg = JSON.parse(box.getAttribute('data-dt') || '{}'); } catch (e) { return; }
if (cfg.name !== 'due_at') { return; }
if (box.getAttribute('data-fb-due-bound') === '1') { return; }
box.setAttribute('data-fb-due-bound', '1');
/* 用 `change`(不是 `input`):AntD 选完日期/时间才写回,
中途打字不该触发几十次请求。 */
box.addEventListener('change', function () {
var d = box.querySelector('.dt-date');
var date = d ? String(d.value || '').trim() : '';
if (date === '') {
/* 清空 = 去掉截止时间(allowClear 会走到这里) */
act(urlOf('due'), { due_at: '' });
return;
}
var h = box.querySelector('.dt-hour');
var m = box.querySelector('.dt-minute');
var hh = h && String(h.value || '').trim() !== '' ? String(h.value) : '00';
var mm = m && String(m.value || '').trim() !== '' ? String(m.value) : '00';
act(urlOf('due'), { due_at: date + ' ' + hh + ':' + mm + ':00' });
});
}
/**
* 把面板里的最新状态**顺手**同步到首页那张卡上(v78)。
*