Files
FamilyBook/templates/tasks/_panel.php
T
laoyang 33940fa730 评论附件拖放区放大:小胶囊 → 整块虚线大框 (#81)
用户 2026-11-05:「评论里面拖文件上传附件,那个地方太小了。」

原来:`padding:4px 10px` 的小胶囊(约 26px 高)+ 一行小字提示,
拖动落点只有那一条窄带 —— 触屏不好瞄,桌面也容易拖偏。

改成与「任务附件」同一套 `.upbox`(那个是站点级上传组件的标准外观):
  · 高度 89px(触屏建议下限 44px,绰绰有余)
  · 宽度占满整行(桌面 840px / 手机 330px)
  · 虚线大框 + 加号图标 + 说明文字
  · 副标题写清「点『发表』时随这条评论一起发出」——
    与上面那块「拖上去立刻上传」语义不同,不写清楚用户会以为已经传了
  · 比「任务附件」矮一点(96 → 72 min-height,实测 89px):
    评论框已经在下面,96px 会把讨论区推太长;89px 仍然很好点

顺带改的:
  · 已选文件从"逗号拼一行"改成"一行一个,带 📎",长名截断
  · 渲染用 createElement + textContent,**不拼 innerHTML**
    (文件名是用户可控的,拼 innerHTML 就是 XSS)
  · 列表为空时 hidden,不挂空灰条
  · 加键盘支持:`.upbox` 带 role=button + tabindex,Enter/Space 能打开选择框

实测(Playwright,两种视口):
  桌面 900px:840 × 89px,边框 1px dashed,触屏可点性 ✅
  手机 390px:330 × 89px,触屏可点性 ✅
  文件列表:2 项,高 56px,正常显示
2026-11-05 18:57:56 +08:00

477 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 首页「近期任务」的就地面板(v78 用户 2026-10-22)。
*
* 用户原话:「网页端,首页点击一个任务后,除了展示信息,应该有相应的功能,
* 可以修改状态:待办、进行中,可以挪动分组,可以拖动上传附件,可以添加评论,
* 添加子任务等。其目地,是为了让用户不离开首页,就能对任务进行简单的处理。」
*
* ────────────────────────────────────────────────────────────────
* 这个片段**只在浮层里用**(`#fbEntryBody`,由 `tasks/panel` 路由吐出)。
* 它的每一个动作都打到**既有路由**(tasks/status、tasks/move、tasks/create、
* tasks/comment_add、tasks/comment_del、tasks/attach_add、tasks/attach_del),
* 一个字节的新契约都没有 —— 只是在每个表单里多带一个 `ajax_panel=1`,
* 服务端据此把"flash + 跳转"换成"一段新面板 + 一句人话"。
*
* 三条纪律(每条都对应一次踩过的坑):
* ① **每个表单自带 CSRF**:这段 HTML 是 AJAX 塞进 DOM 的,不在
* `_entry_popup.php` 那颗 `.fb-entry-csrf` 的表单里,靠外层拿不到。
* ② **动作按钮用 `data-tk-act` 而不是"每行一个 form"**:一条任务可能有十几条
* 子任务与几十条评论,每行一个 form 就是名册页那个"2369 个 form"的坑
* (密码管理器会把页面拖到几秒一帧)。这里只在真正需要输入的三处用 form:
* 加子任务 / 发评论 / 传附件。
* ③ **不做任何"猜"的显示**:拿不到 `cover`、拿不到人、拿不到看板就不画那一行,
* 与 `tasks/show.php` 的口径一致(那里也是"有才显示")。
*
* 用法(`task_panel_json()` 里已经备好):
* $t 任务(Task::find 的形状,含 children / comments / attachs)
* $people 名册
* $names @人 高亮用的名字表
* $boards 看板清单
* $categories 分类清单
* $canWrite 我能不能改这一条
* $meId 我是谁
*
* ⚠️ 局部变量一律 `tp` 前缀:这段会被 renderPartial 塞进浮层,宿主页面
* (首页)里也有 `$tasks` / `$t` 这类名字,用裸名就是静默覆盖。
*/
$tpT = is_array($t ?? null) ? $t : [];
$tpId = (int)($tpT['id'] ?? 0);
if ($tpId <= 0) {
return;
}
$tpBoards = (array)($boards ?? []);
$tpCats = (array)($categories ?? []);
$tpPeople = (array)($people ?? []);
$tpNames = (array)($names ?? []);
$tpCan = !empty($canWrite);
$tpMe = (int)($meId ?? 0);
$tpStatus = \App\Task::normalizeStatus((string)($tpT['status'] ?? 'todo'));
$tpBoardId = (int)($tpT['board_id'] ?? 0);
$tpKids = (array)($tpT['children'] ?? []);
$tpAtts = (array)($tpT['attachs'] ?? []);
$tpCmts = (array)($tpT['comments'] ?? []);
$tpKidDone = 0;
foreach ($tpKids as $tpKid) {
if ((string)($tpKid['status'] ?? '') === 'done') {
$tpKidDone++;
}
}
$tpIsChild = (int)($tpT['parent_id'] ?? 0) > 0;
/* 分类名:Task::find 不带 join,这里按 id 在清单里找(清单一次查询就够,
比再跑一条 SQL 划算;找不到就当未分类)。 */
$tpCatName = '';
foreach ($tpCats as $tpCat) {
if ((int)$tpCat['id'] === (int)($tpT['category_id'] ?? 0)) {
$tpCatName = (string)$tpCat['name'];
break;
}
}
$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);
?>
<div class="tk-panel" data-tk-panel="<?= $tpId ?>"
data-tk-url-status="<?= e(url('tasks/status')) ?>"
data-tk-url-move="<?= e(url('tasks/move')) ?>"
data-tk-url-cmt-del="<?= e(url('tasks/comment_del')) ?>"
data-tk-url-attach="<?= e(url('tasks/attach_add')) ?>"
<?php /* #74:面板里直接改截止时间(不必点「编辑」进整页表单) */ ?>
data-tk-url-due="<?= e(url('tasks/due')) ?>"
<?php /* #76:截止/负责人/分类 三项一起保存(用户要显式「保存」按钮) */ ?>
data-tk-url-save="<?= e(url('tasks/panel_save')) ?>">
<?php /* CSRF 放在面板里(见头部第 ① 条):表单各自带一份,JS 里的按钮动作
从这里取一份 —— 两处同一个值,`csrf_token()` 天然幂等。 */ ?>
<input type="hidden" name="_csrf" value="<?= e(csrf_token()) ?>" data-tk-csrf>
<?php /* ---- 状态:三段平铺,点一下就改(不是下拉 —— 手机上少一次展开) ---- */ ?>
<div class="tk-sec tk-sec-inline">
<span class="tk-sec-lb">状态</span>
<div class="tk-seg">
<?php foreach (\App\Task::STATUS as $tpSk => $tpSv): ?>
<?php if ($tpCan): ?>
<button type="button"
class="kb-st is-<?= e($tpSk) ?><?= $tpSk === $tpStatus ? ' is-on' : '' ?>"
data-tk-act="status" data-status="<?= e($tpSk) ?>"><?= e($tpSv) ?></button>
<?php else: ?>
<span class="kb-st is-<?= e($tpSk) ?><?= $tpSk === $tpStatus ? ' is-on' : '' ?> is-static"><?= e($tpSv) ?></span>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<?php /* ---- 看板("挪动分组"就是挪到另一个看板) ---- */ ?>
<?php if ($tpBoards): ?>
<div class="tk-sec tk-sec-inline">
<span class="tk-sec-lb">看板</span>
<div class="tk-chips">
<?php foreach ($tpBoards as $tpB): ?>
<?php
$tpBid = (int)$tpB['id'];
$tpBOn = $tpBid === $tpBoardId;
$tpBCol = \App\Task::boardColor($tpB['color'] ?? null, (string)$tpB['name']);
?>
<?php if ($tpCan): ?>
<button type="button" class="tk-chip<?= $tpBOn ? ' is-on' : '' ?>"
style="--bc: <?= e($tpBCol) ?>;"
data-tk-act="board" data-board="<?= $tpBid ?>">
<i class="tk-chip-dot" aria-hidden="true"></i><?= e((string)$tpB['name']) ?>
</button>
<?php else: ?>
<span class="tk-chip is-static<?= $tpBOn ? ' is-on' : '' ?>" style="--bc: <?= e($tpBCol) ?>;">
<i class="tk-chip-dot" aria-hidden="true"></i><?= e((string)$tpB['name']) ?>
</span>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php /* ---- 更多信息:截止 / 负责人 / 分类 ---- 可编辑 + 保存(#76) ----
🔴 用户 2026-11-05:「时间控件是对的,但是现在没有保存按钮,只有下面一个
评论的发表按钮。另外,负责人和分类都没法修改,按道理讲都应该可以修改才对。」
改之前的状态:
· 截止:上一版做成"选完立即保存",用户不认可 —— 他要显式保存按钮;
· 负责人 / 分类:**只读的 `dl.kv` 行**,而且值为空时整行都不渲染
(`if (!empty($tpT['assignee_name']))` / `if ($tpCatName !== '')`)
⇒ 用户不仅改不了,连"这一项存在"都看不出来。
⇒ 三项合成一个带「保存」的小节:先改完,点一次提交。
`Task::setAttrs()` 只写这三列,不碰 title/note/status(见那里的说明)。
⚠️ 负责人用原生 `<select>` 而不是 `_person_picker.php` 的弹窗选人:
面板本身已在浮层里,再弹一层模态 = 两层遮罩,手机上很容易点错;
而且 picker 需要 `fbInitPickers()` 初始化,`loadPanel()` 目前没调它。
家庭成员通常 3~10 人,select 够用。**人多了再换弹窗**,那时记得
把 `fbInitPickers` 也补进 `loadPanel()`。 */ ?>
<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): ?>
<?php
/* ⚠️ 变量名必须与 `_datetime.php` 的约定一致;
它内部一律用 `dt` 前缀的临时变量,不会踩到这里的 $tp* 或宿主页的 $t。 */
$dtName = 'due_at';
$dtValue = $tpDue;
$dtPlugin = 'antd-single';
$dtRequired = false;
$dtId = 'tkp-due';
require __DIR__ . '/../_datetime.php';
?>
<?php if ($tpDueWord !== ''): ?>
<span class="tk-due-word<?= $tpLate ? ' kb-late' : '' ?>"><?= e($tpDueWord) ?></span>
<?php endif; ?>
<?php else: ?>
<span class="<?= $tpLate ? 'kb-late' : '' ?>"><?= $tpDue !== '' ? e($tpDue) : '未设' ?></span>
<?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): ?>
<?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'],
'my_nickname' => $tpT['assignee_nick'] ?? '']))
: '不指定' ?></span>
<?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): ?>
<select class="select" name="category_id" id="tkp-category">
<option value="0">未分类</option>
<?php foreach ($tpCats as $tpC): ?>
<?php $tpCid = (int)($tpC['id'] ?? 0); ?>
<option value="<?= $tpCid ?>"
<?= $tpCid === (int)($tpT['category_id'] ?? 0) ? ' selected' : '' ?>>
<?= e((string)($tpC['name'] ?? '')) ?>
</option>
<?php endforeach; ?>
</select>
<?php else: ?>
<span><?= $tpCatName !== '' ? e($tpCatName) : '未分类' ?></span>
<?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>
</div>
<?php endif; ?>
</div>
</div>
<?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>
</dl>
<?php endif; ?>
<?php /* ---- 子任务(子任务自己不再挂子任务,与详情页同一口径) ---- */ ?>
<?php if (!$tpIsChild): ?>
<div class="tk-sec">
<div class="tk-sec-h">
子任务
<span class="group-n"><?= (int)$tpKidDone ?>/<?= count($tpKids) ?></span>
</div>
<?php if ($tpKids): ?>
<ul class="sub-list">
<?php foreach ($tpKids as $tpKidRow): ?>
<li class="sub-row<?= (string)$tpKidRow['status'] === 'done' ? ' is-done' : '' ?>">
<?php
/* 复选框用全站那一颗(`_task_check.php`)—— 但这里**不接**首页那份
`#taskDoneForm`:面板是 AJAX 塞进来的,那份表单的监听绑在加载时
存在的元素上。面板里的复选框由 task_panel.js 委托处理,
表单提交目标仍是 tasks/status(只是多带 ajax_panel=1)。 */
$tkId = (int)$tpKidRow['id'];
$tkStatus = (string)$tpKidRow['status'];
$tkBack = 'home';
$tkStatic = !$tpCan;
/* 无障碍说明:片段自己会渲染成「<label>:<状态词>」——
所以这里只给标题,别把"子任务"三个字再拼一遍
(否则读屏会念成「子任务子任务甲:待办」)。 */
$tkLabel = (string)$tpKidRow['title'];
require __DIR__ . '/../_task_check.php';
?>
<span class="sub-title"><?= e((string)$tpKidRow['title']) ?></span>
<?php if ((string)($tpKidRow['due_at'] ?? '') !== ''): ?>
<span class="node-time<?= \App\Task::isOverdue($tpKidRow['due_at'] ?? null) && $tkStatus !== 'done' ? ' kb-late' : '' ?>">
<?= e(\App\Task::dueWord($tpKidRow['due_at'] ?? null)) ?>
</span>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p class="empty-hint">还没有子任务。</p>
<?php endif; ?>
<?php if ($tpCan): ?>
<form method="post" action="<?= e(url('tasks/create')) ?>" class="tk-inline-form">
<?= csrf_field() ?>
<input type="hidden" name="parent_id" value="<?= $tpId ?>">
<input type="hidden" name="board_id" value="<?= $tpBoardId ?>">
<input type="hidden" name="ajax_panel" value="1">
<input class="input" name="title" required maxlength="200" placeholder="加一项子任务,回车即可">
<button class="btn btn-sm" type="submit">加上</button>
</form>
<?php endif; ?>
</div>
<?php endif; ?>
<?php /* ---- 附件:缩略图墙复用全站那一份;拖框是"拖进来立刻传" ---- */ ?>
<div class="tk-sec">
<div class="tk-sec-h">附件 <span class="group-n"><?= count($tpAtts) ?></span></div>
<?php if ($tpAtts): ?>
<?php
/* ⚠️ 删除表单走 `_attachments.php`(全站唯一画法),多带一个 `ajax_panel`
让服务端回"新面板"而不是跳详情页 —— 由 task_panel.js 拦下提交。 */
$attRows = $tpAtts;
$attCanEdit = $tpCan;
$attDelUrl = 'tasks/attach_del';
$attDelField = 'id';
$attExtra = ['ajax_panel' => '1'];
require __DIR__ . '/../_attachments.php';
?>
<?php else: ?>
<p class="empty-hint">还没有附件。</p>
<?php endif; ?>
<?php if ($tpCan): ?>
<?php /* 🔴 这里**有意不用** `_stage_drop.php`:那个片段是"拖进来先落暂存区、
点保存时才记到这条上",因为它服务的都是"表单还没提交"的新建场景。
而这一条任务**已经存在**,用户拖进来就该立刻挂上去 ——
多一次"再点上传"是多余的一步(用户原话:「可以拖动上传附件」)。
框的样子与那句话仍然一模一样(`.upfield / .upbox`)。 */ ?>
<div class="upfield tk-drop" data-tk-drop>
<div class="upbox" data-tk-drop-box role="button" tabindex="0"
aria-label="点击或拖动进行上传">
<span class="upbox-ico" aria-hidden="true">+</span>
<span class="upbox-msg">点击或拖动进行上传</span>
</div>
<input class="sr-only" type="file" multiple data-tk-drop-input
aria-label="选择要上传的附件">
<div class="stage-list" data-tk-drop-list hidden></div>
<span class="stage-drop-tip" data-tk-drop-tip hidden></span>
</div>
<?php endif; ?>
</div>
<?php /* ---- 讨论 ---- */ ?>
<div class="tk-sec">
<div class="tk-sec-h">讨论 <span class="group-n"><?= count($tpCmts) ?></span></div>
<?php if ($tpCmts): ?>
<ul class="cmt-list">
<?php foreach ($tpCmts as $tpCmt): ?>
<li class="cmt-row">
<div class="cmt-who">
<?= person_chip($tpCmt) ?>
<span class="cmt-time"><?= e((string)$tpCmt['created_at']) ?></span>
<?php if ((int)$tpCmt['person_id'] === $tpMe && $tpCan): ?>
<?php /* `btn-inline` 是"长得像链接的按钮"(见 app.css 注释):
少了它,<button> 会带上浏览器的灰底与内边距。 */ ?>
<button type="button" class="link-quiet btn-inline cmt-x"
data-tk-act="cmt-del" data-cmt="<?= (int)$tpCmt['id'] ?>">删除</button>
<?php endif; ?>
</div>
<div class="cmt-body"><?= \App\Task::commentHtml((string)$tpCmt['content'], $tpNames) ?></div>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p class="empty-hint">还没有留言。写 @名字 可以点名。</p>
<?php endif; ?>
<?php if ($tpCan): ?>
<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"
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`)是"点了立刻上传",两者语义不同。 */ ?>
<?php /* 🔴 #79(用户 2026-11-05):「评论框下面的附件也需要可以拖动添加。」
🔴 #81(同日):「评论里面拖文件上传附件,那个地方太小了。」
⇒ 原来是一个 `padding:4px 10px` 的小胶囊 + 一行小字提示,
拖动落点只有 ~26px 高,触屏上根本不好瞄,桌面上也经常拖偏。
⇒ 改成与**「任务附件」那块完全一样**的 `.upbox`:
高 96px、虚线大框、加号图标 + 说明文字。
同一个交互在一个页面里不该长两个样子,也不该一个大一个小。
⚠️ 这块的语义是"挂到评论表单、点发表才一起提交",
而上面「任务附件」是"拖上去立刻上传" —— 外观一致但行为不同,
所以说明文字要写清"随这条评论一起发出",避免用户以为已经传上去了。
⚠️ `data-cmt-drop` 仍是拖拽落点标记(task_panel.js 认它),
不要改成 `data-tk-drop-box`(那是立刻上传那条路)。 */ ?>
<div class="cmt-attach-row" data-cmt-drop>
<label class="upbox cmt-upbox" role="button" tabindex="0"
aria-label="点击选择文件,或把文件拖进来">
<input type="file" name="attach[]" multiple class="sr-only"
id="tkp-cmt-files" data-cmt-file-input>
<span class="upbox-ico" aria-hidden="true">+</span>
<span class="upbox-msg">点击选择,或把文件拖到这里</span>
<span class="cmt-upbox-sub">点「发表」时随这条评论一起发出</span>
</label>
<div class="cmt-file-list" data-cmt-file-name hidden></div>
</div>
<div class="btn-row"><button class="btn btn-sm" type="submit">发表</button></div>
</form>
<?php endif; ?>
</div>
</div>
<?php
/* ⚠️ 只清本片段自己造的变量:`$t` / `$people` / `$boards` 这些是宿主的大局变量,
清掉之后宿主后半段会静默拿不到它们(`_edit_form.php` 头部记着这次事故)。 */
unset($tpT, $tpBoards, $tpCats, $tpPeople, $tpNames, $tpCan, $tpMe, $tpStatus,
$tpBoardId, $tpKids, $tpAtts, $tpCmts, $tpKidDone, $tpIsChild, $tpCatName,
$tpDue, $tpDueWord, $tpLate, $tpId,
$tpKid, $tpCat, $tpB, $tpBid, $tpBOn, $tpBCol, $tpKidRow, $tpCmt,
$attRows, $attCanEdit, $attDelUrl, $attDelField, $attExtra);
?>