任务面板五项细化:弹窗选人、协作人、保存关窗、去编辑、评论附件 (#78)

用户 2026-11-05 五条:
  「选人的时候不能按列表,因为人太多了。参考观影模式里面选人的方式。」
  「任务其实可以共享给别人,也就是协作人,但编辑这里面没有这个选项。」
  「点击保存之后,按道理说应该直接把这个窗口关闭才对呀。」
  「现在在首页上点一个任务,直接就是编辑页面啊,所以不需要编辑按钮了。」
  「评论那个地方也需要可以添加附件。」

① 选人改弹窗
   负责人/协作人都换成 `_person_picker.php`(弹窗 + 拼音首字母联想),
   与观影表单、新建任务弹框同一套。
   负责人 single + allowEmpty(可选「不指定」);协作人 multi。
   ⚠️ 关键修复:`entry_popup.js` 的 `loadPanel()` 一直漏了
      `fbInitPickers()` —— 组件不初始化,按钮点了没反应。
      `loadEdit()` 里有这一句,`loadPanel()` 没有,面板里塞选人就是这个下场。
      同时 `task_panel.js` 的 `paint()` 后也补一次(重画后是新节点)。

② 补协作人
   数据层其实**早就完整支持**:task_shares 表 + Task::setShare/dropShare
   + 新建弹框的「共享给」+ 详情页的共享展示。只有面板和编辑表单没给入口。
   现在面板里能看当前协作人、能加、能改。
   保存语义:以提交的列表为准同步(多了就 setShare、少了就 dropShare)。
   ⚠️ 带 `shares_submitted=1` 哨兵,区分"表单里没这个控件"(不动)
      和"用户全删了"(清空)—— 否则 PHP 收不到空数组的键,无法分辨。

③ 保存后关窗
   `act()` 加 `closeOnOk`,`send()` 支持它:成功后先 `syncRow()`
   (更新背后那张首页卡片,否则关掉浮层看到旧标题更迷惑)再 `fbEntryClose()`。
   ⚠️ 只给「保存」用 —— 改状态/挪看板/加子任务/发评论/传附件都还要继续操作。
   ⚠️ 失败时不关,让用户看到错误就地改。

④ 去掉「编辑」按钮 + 面板补标题/备注
   用户说得对:点开任务就是完整编辑面板了,再给编辑按钮是重复。
   但原来面板改不了标题和备注(只在 _edit_form.php 里)
   ⇒ 先在「更多信息」里补上这两个字段,再藏编辑按钮。
   `Task::setAttrs()` 扩展支持 title/note,走 title()/textOrNull() 清洗,
   与 update() 同一口径。
   ⚠️ 只对**任务**(带 data.panel)隐藏 —— 观影/旅程/日程/纪念日仍需要那颗按钮。

⑤ 评论可加附件
   服务端**早就支持**:tasks/comment_add 里一直有
   `attach_save_files(..., task_comment, ...)`,只是表单里没有 file input。
   补上 `<input type="file" name="attach[]" multiple>` +
   `enctype="multipart/form-data"`(少了它只提交文件名、文件本体丢失)。
   顺带允许「只发附件不写字」(原来 content 空会返回"留言是空的"):
   有附件且没文字时用占位文案「(附件)」。

实测:
  setAttrs 标题/备注 => 都正确写入 ✅
  setShare / dropShare => 1 人 / 0 人 ✅
  面板渲染 => 标题框/备注框/两个选人组件(single+multi)/评论附件/
              multipart/save-attrs/antd 控件 全部在位 ✅
  选人模式 => single(allowEmpty=1), multi ✅

已知问题(另单处理):家庭有 800 人,两个选人组件各 485KB,
面板渲染 997KB。服务端只要 11ms,但传输量对移动端不友好。
这是 _person_picker.php 的既有设计(观影那边也一样),
需要改成"弹窗打开时才渲染列表"或按需加载。
This commit is contained in:
2026-11-05 18:28:15 +08:00
parent aac7dd5974
commit 11aa7c6ca5
6 changed files with 320 additions and 30 deletions
+71 -2
View File
@@ -7355,7 +7355,7 @@ $want = array_keys($want);
redirect('tasks');
}
/* 只把**表单真的提交了**的字段放进 $sd:
面板里负责人下拉在"没有成员"等极端情况下可能不渲染,
面板里负责人控件在"没有成员"等极端情况下可能不渲染,
那时就不该把 assignee_id 当成 0 去清掉原值。 */
$sd = [];
if (array_key_exists('due_at', $_POST)) {
@@ -7367,7 +7367,54 @@ $want = array_keys($want);
if (array_key_exists('category_id', $_POST)) {
$sd['category_id'] = (int)$_POST['category_id'];
}
/* 🔴 #78(用户 2026-11-05):「现在在首页上点一个任务,直接就是编辑页面啊。
所以不需要再加一个编辑按钮了。」
⇒ 既然要删掉「编辑」按钮,标题和备注必须能在这里改。
⚠️ 标题不允许被改成空 —— 与 tasks/update 同一口径(那里空标题会被打回)。
空了就**不更新标题**(而不是把标题清掉)。 */
if (array_key_exists('title', $_POST)) {
$nt = trim((string)$_POST['title']);
if ($nt !== '') {
$sd['title'] = $nt;
}
}
if (array_key_exists('note', $_POST)) {
$sd['note'] = trim((string)$_POST['note']);
}
Task::setAttrs($familyId, $id, $sd);
/* 协作人(共享给)—— #78。
🔴 用户:「我记得任务其实可以共享给别人,也就是协作人。
但现在怎么感觉编辑这里面没有这个选项呢?」
⇒ 数据层早就有 `task_shares` + Task::share/unshare,只是没入口。
⚠️ 语义是"**以提交的列表为准**":表单没带 `shares[]` 时(比如只改截止时间
的旧面板/其它调用方)**完全不动**协作人,避免把已有共享清空。
表单带了(哪怕是空数组 `shares=[]` 表示"清空全部")就按它同步。 */
if (array_key_exists('shares', $_POST) || array_key_exists('shares_submitted', $_POST)) {
$wantIds = [];
foreach ((array)($_POST['shares'] ?? []) as $sid) {
$sid = (int)$sid;
/* 不能共享给自己(空操作);也不能共享给不存在的人 */
if ($sid > 0 && $sid !== $me) {
$wantIds[$sid] = true;
}
}
$haveIds = [];
foreach (Task::shares($id) as $sh) {
$haveIds[(int)$sh['person_id']] = true;
}
foreach (array_keys($wantIds) as $sid) {
if (!isset($haveIds[$sid])) {
Task::setShare($id, $sid, 'read');
}
}
foreach (array_keys($haveIds) as $sid) {
if (!isset($wantIds[$sid])) {
Task::dropShare($id, $sid);
}
}
}
$msg = '已保存。';
if (task_panel_wanted()) {
task_panel_json($id, $msg);
@@ -7391,7 +7438,29 @@ $want = array_keys($want);
flash('err', '找不到这项任务。');
redirect('tasks');
}
$cid = Task::addComment($id, $me, (string)($_POST['content'] ?? ''));
/* 🔴 #78(用户 2026-11-05):「评论那个地方也需要可以添加附件。」
⇒ 允许「只发附件、不写字」—— 这是很常见的一次性用法
(拍张照片甩过来,不需要配文字)。
⚠️ 但不能两边都空:那是误点。这时返回 0 走原来的"留言是空的"分支。
⚠️ 判"有没有附件"要看 `$_FILES`,不能看 content:
`attach_save_files` 内部会过滤空文件槽(未选择的 file input
也会出现在 $_FILES 里,name 是空串)。
这里用一个轻量判断:只要有任一非空 tmp_name 就算有附件。 */
$rawContent = trim((string)($_POST['content'] ?? ''));
$hasFiles = false;
foreach ((array)($_FILES['attach'] ?? []) as $k => $v) {
if ($k === 'name' && is_array($v)) {
foreach ($v as $nm) {
if (trim((string)$nm) !== '') { $hasFiles = true; break 2; }
}
}
}
if ($rawContent === '' && $hasFiles) {
/* 占位文案:让列表里能看懂"这条是什么"(纯空白没人知道是图还是文件) */
$rawContent = '(附件)';
}
$cid = Task::addComment($id, $me, $rawContent);
if ($cid > 0) {
attach_save_files($familyId, 'task_comment', $cid, 'attach', $me);
if (task_panel_wanted()) {