任务面板五项细化:弹窗选人、协作人、保存关窗、去编辑、评论附件 (#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:
+116
-21
@@ -68,6 +68,12 @@ foreach ($tpCats as $tpCat) {
|
||||
}
|
||||
}
|
||||
$tpDue = (string)($tpT['due_at'] ?? '');
|
||||
/* #78:协作人(共享给)。Task::find 已经带回 `shares`,直接用。 */
|
||||
$tpShares = (array)($tpT['shares'] ?? []);
|
||||
$tpShareIds = [];
|
||||
foreach ($tpShares as $tpS) {
|
||||
$tpShareIds[] = (int)($tpS['person_id'] ?? 0);
|
||||
}
|
||||
$tpDueWord = \App\Task::dueWord($tpDue !== '' ? $tpDue : null);
|
||||
$tpLate = $tpDue !== '' && $tpStatus !== 'done' && \App\Task::isOverdue($tpDue);
|
||||
?>
|
||||
@@ -149,6 +155,22 @@ $tpLate = $tpDue !== '' && $tpStatus !== 'done' && \App\Task::isOverdue($tpD
|
||||
<div class="tk-sec">
|
||||
<div class="tk-sec-h">更多信息</div>
|
||||
<div class="tk-attrs">
|
||||
|
||||
<?php /* 🔴 #78(用户 2026-11-05):「现在在首页上点一个任务,直接就是编辑页面啊。
|
||||
所以不需要再加一个编辑按钮了。」
|
||||
|
||||
⇒ 既然要删掉右下角的「编辑」按钮,标题和备注就必须能在这里改 ——
|
||||
原来它俩只在 `_edit_form.php` 里,面板做不了。 */ ?>
|
||||
<div class="tk-attr-row">
|
||||
<span class="tk-attr-lb">标题</span>
|
||||
<?php if ($tpCan): ?>
|
||||
<input class="input" id="tkp-title" name="title" maxlength="200"
|
||||
value="<?= e((string)($tpT['title'] ?? '')) ?>">
|
||||
<?php else: ?>
|
||||
<span><?= e((string)($tpT['title'] ?? '')) ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="tk-attr-row">
|
||||
<span class="tk-attr-lb">截止</span>
|
||||
<?php if ($tpCan): ?>
|
||||
@@ -170,19 +192,28 @@ $tpLate = $tpDue !== '' && $tpStatus !== 'done' && \App\Task::isOverdue($tpD
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php /* 负责人:**弹窗选人**(#78)。
|
||||
🔴 用户 2026-11-05:「选人的时候不能按列表,因为人太多了。
|
||||
你参考一下观影模式里面选人的方式。」
|
||||
⇒ 换成全站统一的 `_person_picker.php`(弹窗 + 拼音首字母联想),
|
||||
与新建任务弹框、观影表单同一套。
|
||||
⚠️ 单选 + `$ppAllowEmpty` ⇒ 弹窗里带「不指定」(值 0)。
|
||||
⚠️ 它在 AJAX 面板里需要 `fbInitPickers()` 初始化 ——
|
||||
`entry_popup.js` 的 `loadPanel()` 已补(见那边注释)。 */ ?>
|
||||
<div class="tk-attr-row">
|
||||
<span class="tk-attr-lb">负责人</span>
|
||||
<?php if ($tpCan): ?>
|
||||
<select class="select" name="assignee_id" id="tkp-assignee">
|
||||
<option value="0">不指定</option>
|
||||
<?php foreach ($tpPeople as $tpP): ?>
|
||||
<?php $tpPid = (int)($tpP['id'] ?? 0); ?>
|
||||
<option value="<?= $tpPid ?>"
|
||||
<?= $tpPid === (int)($tpT['assignee_id'] ?? 0) ? ' selected' : '' ?>>
|
||||
<?= e(person_name($tpP)) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php
|
||||
$ppName = 'assignee_id';
|
||||
$ppPeople = $tpPeople;
|
||||
$ppSelected = [(int)($tpT['assignee_id'] ?? 0)];
|
||||
$ppMode = 'single';
|
||||
$ppOpenBtn = '更换';
|
||||
$ppTitle = '谁负责这件事';
|
||||
$ppEmptyText = '不指定';
|
||||
$ppAllowEmpty = true;
|
||||
require __DIR__ . '/../_person_picker.php';
|
||||
?>
|
||||
<?php else: ?>
|
||||
<span><?= !empty($tpT['assignee_name'])
|
||||
? e(person_name(['name' => $tpT['assignee_name'],
|
||||
@@ -191,6 +222,41 @@ $tpLate = $tpDue !== '' && $tpStatus !== 'done' && \App\Task::isOverdue($tpD
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php /* 协作人 / 共享给(#78)。
|
||||
🔴 用户:「我记得任务其实可以共享给别人,也就是协作人。
|
||||
但现在怎么感觉编辑这里面没有这个选项呢?」
|
||||
|
||||
⇒ 数据层早就支持(`task_shares` 表 + Task::share/unshare +
|
||||
`_add_modal.php` 的新建弹框都有),只有**编辑表单和面板漏了入口**。
|
||||
这里补上:多选弹窗 + 当前协作人 chips。
|
||||
⚠️ 候选里去掉自己 —— 「共享给自己」是空操作(与新建弹框同一口径)。 */ ?>
|
||||
<div class="tk-attr-row tk-attr-row-top">
|
||||
<span class="tk-attr-lb">协作人</span>
|
||||
<?php if ($tpCan): ?>
|
||||
<?php
|
||||
$ppName = 'shares[]';
|
||||
$ppPeople = array_values(array_filter($tpPeople,
|
||||
fn($pp) => (int)($pp['id'] ?? 0) !== $tpMe));
|
||||
$ppSelected = array_values(array_filter($tpShareIds,
|
||||
fn($sid) => $sid !== $tpMe));
|
||||
$ppMode = 'multi';
|
||||
$ppOpenBtn = '+ 添加人';
|
||||
$ppTitle = '共享给谁';
|
||||
require __DIR__ . '/../_person_picker.php';
|
||||
?>
|
||||
<?php else: ?>
|
||||
<?php if (!$tpShares): ?>
|
||||
<span>无</span>
|
||||
<?php else: ?>
|
||||
<span class="tk-share-chips">
|
||||
<?php foreach ($tpShares as $tpS): ?>
|
||||
<span class="tk-share-chip"><?= e(person_name($tpS)) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="tk-attr-row">
|
||||
<span class="tk-attr-lb">分类</span>
|
||||
<?php if ($tpCan): ?>
|
||||
@@ -209,24 +275,31 @@ $tpLate = $tpDue !== '' && $tpStatus !== 'done' && \App\Task::isOverdue($tpD
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="tk-attr-row tk-attr-row-top">
|
||||
<span class="tk-attr-lb">备注</span>
|
||||
<?php if ($tpCan): ?>
|
||||
<textarea class="input" id="tkp-note" name="note" rows="2"
|
||||
placeholder="补充说明…"><?= e((string)($tpT['note'] ?? '')) ?></textarea>
|
||||
<?php else: ?>
|
||||
<span><?= trim((string)($tpT['note'] ?? '')) !== ''
|
||||
? nl2br(e((string)$tpT['note'])) : '无' ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($tpCan): ?>
|
||||
<div class="tk-attr-actions">
|
||||
<button type="button" class="btn btn-sm" data-tk-act="save-attrs">保存</button>
|
||||
<span class="tk-attr-tip" data-tk-save-tip hidden>已保存</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php /* ---- 其余只读事实(循环 / 备注) ---- */ ?>
|
||||
<dl class="kv tk-kv">
|
||||
<?php if (!empty($tpT['repeat_rule'])): ?>
|
||||
<?php /* ---- 其余只读事实(循环)——备注已挪进「更多信息」(#78) ---- */ ?>
|
||||
<?php if (!empty($tpT['repeat_rule'])): ?>
|
||||
<dl class="kv tk-kv">
|
||||
<div class="kv-row"><dt>循环</dt><dd><?= e(\App\Task::repeatWord((string)$tpT['repeat_rule'])) ?></dd></div>
|
||||
<?php endif; ?>
|
||||
<?php if (trim((string)($tpT['note'] ?? '')) !== ''): ?>
|
||||
<div class="kv-row"><dt>备注</dt><dd><?= nl2br(e((string)$tpT['note'])) ?></dd></div>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
</dl>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php /* ---- 子任务(子任务自己不再挂子任务,与详情页同一口径) ---- */ ?>
|
||||
<?php if (!$tpIsChild): ?>
|
||||
@@ -341,12 +414,34 @@ $tpLate = $tpDue !== '' && $tpStatus !== 'done' && \App\Task::isOverdue($tpD
|
||||
<p class="empty-hint">还没有留言。写 @名字 可以点名。</p>
|
||||
<?php endif; ?>
|
||||
<?php if ($tpCan): ?>
|
||||
<form method="post" action="<?= e(url('tasks/comment_add')) ?>" class="cmt-form">
|
||||
<form method="post" action="<?= e(url('tasks/comment_add')) ?>" class="cmt-form"
|
||||
enctype="multipart/form-data">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= $tpId ?>">
|
||||
<input type="hidden" name="ajax_panel" value="1">
|
||||
<textarea class="input" name="content" rows="2" required
|
||||
<textarea class="input" name="content" rows="2"
|
||||
placeholder="说点什么… 写 @名字 可以点名"></textarea>
|
||||
<?php /* 🔴 #78(用户 2026-11-05):「评论那个地方也需要可以添加附件。」
|
||||
|
||||
服务端**早就支持**了 —— `tasks/comment_add` 里一直有
|
||||
`attach_save_files($familyId, 'task_comment', $cid, 'attach', $me);`
|
||||
(index.php 那一行),只是这个表单里**没有放 file input**。
|
||||
⇒ 补上。字段名 `attach[]` 与详情页、任务附件同一套。
|
||||
|
||||
⚠️ `<form>` 必须带 `enctype="multipart/form-data"`,
|
||||
否则浏览器只提交文件名、文件本体丢失(那种"上传成功但打开是空的"最迷惑)。
|
||||
⚠️ 纯附件不写字也允许(把 `required` 从 textarea 上去掉了)——
|
||||
"只发一张图"是常见用法;服务端在 content 为空时会用一句
|
||||
占位文案,见 comment_add 的处理。
|
||||
⚠️ 用原生 `<input type=file>` 而不是拖拽框:评论是一次性提交,
|
||||
拖拽框那套(`data-tk-drop-box`)是"点了立刻上传",两者语义不同。 */ ?>
|
||||
<div class="cmt-attach-row">
|
||||
<label class="cmt-attach-pick">
|
||||
<input type="file" name="attach[]" multiple class="sr-only">
|
||||
<span aria-hidden="true">📎</span> 添加附件
|
||||
</label>
|
||||
<span class="cmt-attach-name" data-cmt-file-name></span>
|
||||
</div>
|
||||
<div class="btn-row"><button class="btn btn-sm" type="submit">发表</button></div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
Reference in New Issue
Block a user