任务面板:加保存按钮,负责人和分类改为可编辑 (#76)

用户 2026-11-05:「时间控件是对的,但是现在没有保存按钮,只有下面一个
评论的发表按钮。另外,负责人和分类都没法修改,按道理讲都应该可以修改才对。」

三处改动:

① 取消"立即保存",改成显式「保存」按钮
   上一版(#74)做的是"选完截止时间就发请求"。用户不认可:他要的是
   "改完点一下保存"那种确定感。
   ⇒ 移除 bindDue() 的 change 监听,改成 save-attrs 动作一次性提交三项。

② 负责人 / 分类 从只读改成可编辑
   改前是只读 dl.kv 行,而且**值为空时整行不渲染**
   (if (!empty($tpT[assignee_name])) / if ($tpCatName !== ))
   ⇒ 用户不仅改不了,连"这一项存在"都看不出来。
   现在三项合成「更多信息」小节:
     截止   [ AntD 日期控件 ]
     负责人 [ 下拉 ]
     分类   [ 下拉 ]
            [ 保存 ]
   负责人用原生 select 而不是 _person_picker.php 的弹窗选人:
   面板已在浮层里,再弹一层模态 = 两层遮罩,手机上容易点错;
   且 picker 需要 fbInitPickers() 初始化,loadPanel() 目前没调它。
   家里 3~10 人 select 够用;人多了再换,那时记得把 fbInitPickers
   也补进 loadPanel()。

③ Task::setAttrs()(新增)
   只更新 due_at / assignee_id / category_id 三列。
   不复用 update():那是全字段覆盖,面板只交三项的话
   title/note/status/repeat/parent/board 会被默认值抹空
   —— 与 setDue() 记的是同一个坑。
   用 array_key_exists 判"传没传",这样可以只改其中一项
   (面板在"没有成员"等极端情况下可能不渲染负责人下拉,
   那时不该把它当成 0 去清掉原值)。

路由 tasks/panel_save:只把表单**真的提交了**的字段放进 $sd,
与 status/move 同一套约定(ajax_panel=1 回整块新面板)。

清理:bindDue / window.fbTaskPanelBindDue 全部移除
(entry_popup.js 里的调用、task_panel.js 里的定义与导出)
—— 留着会引用未定义函数,直接抛错。

实测:
  三项一起改     => due_at/assignee/category 全对 ✅
  标题/状态/备注/看板 均未被破坏 ✅
  只改 due_at    => 另两项保留、未被清空 ✅
  面板渲染       => save-url / 保存按钮 / 两个下拉 / AntD 控件 /
                    原有状态/附件/评论 全部在位 ✅
This commit is contained in:
2026-11-05 18:07:17 +08:00
parent 7a108396ab
commit 9ab7d40a3b
6 changed files with 260 additions and 102 deletions
+54
View File
@@ -7326,6 +7326,60 @@ $want = array_keys($want);
}
redirect('tasks');
/* 🔴 #76(用户 2026-11-05):「时间控件是对的,但是现在没有保存按钮,
只有下面一个评论的发表按钮。另外,负责人和分类都没法修改,
按道理讲都应该可以修改才对。」
⇒ 面板里「截止 / 负责人 / 分类」三项做成可编辑控件 + 一颗「保存」。
这是用户明确要的交互:先改完,再一次性提交,而不是每动一下发一次请求
(上一版 `tasks/due` 是"选完立即保存",用户不认可)。
⚠️ 用 `Task::setAttrs()`,只写这三列 —— 不是 `Task::update()`。
后者全字段覆盖,面板没交的 title/note/status/repeat 会被抹空。
⚠️ 三个字段都**可选**:表单里 `category_id` 没提交就不动它
(`setAttrs` 用 `array_key_exists` 判,与 `?? null` 的"没传"区分开)。 */
case 'tasks/panel_save':
$familyId = (int)Auth::familyId();
$me = (int)Auth::personId();
Auth::requireWrite();
csrf_check();
$id = (int)($_POST['id'] ?? 0);
$t = Task::find($familyId, $id, $me);
if (!$t || !Task::canWrite($t, $me)) {
if (($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') === 'XMLHttpRequest') {
header('Content-Type: application/json; charset=utf-8', true, 403);
echo json_encode(['ok' => false, 'msg' => '这项任务不能改。'], JSON_UNESCAPED_UNICODE);
exit;
}
flash('err', '这项任务不能改。');
redirect('tasks');
}
/* 只把**表单真的提交了**的字段放进 $sd:
面板里负责人下拉在"没有成员"等极端情况下可能不渲染,
那时就不该把 assignee_id 当成 0 去清掉原值。 */
$sd = [];
if (array_key_exists('due_at', $_POST)) {
$sd['due_at'] = trim((string)$_POST['due_at']);
}
if (array_key_exists('assignee_id', $_POST)) {
$sd['assignee_id'] = (int)$_POST['assignee_id'];
}
if (array_key_exists('category_id', $_POST)) {
$sd['category_id'] = (int)$_POST['category_id'];
}
Task::setAttrs($familyId, $id, $sd);
$msg = '已保存。';
if (task_panel_wanted()) {
task_panel_json($id, $msg);
}
if (($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '') === 'XMLHttpRequest') {
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['ok' => true, 'msg' => $msg], JSON_UNESCAPED_UNICODE);
exit;
}
flash('ok', $msg);
redirect('tasks/show', ['id' => $id]);
case 'tasks/comment_add':
$familyId = (int)Auth::familyId();
$me = (int)Auth::personId();