用户原话(6 条,一次给全):
①「观影详情页面。观影图片下面有一个块…要加照片请点上方「编辑」…这个块不要,
直接把这个块删除。」
②「编辑页面的时间…选日期的这个控件非常非常不好用。就是如果我把年和月那个地方
拉下来,我选完年和月之后,它并不会回到让我选日的那个界面,它还是不动的…
要么换一个控件,要么在那个地方加一个确定按钮…现在开始和结束时间分在了两行…
能放得下的话就应该放一排…不要让它默认显示在两行上…"散场时间可不填。"这句话去掉。」
③「首页上今天的日程…分了几列,列之间的空隙非常非常的大…日程里面的那两个色块
就是紧挨着的。所以…应该让它们紧挨着。」
④「现在怎么部署的时间这么长?…你修改了哪些页面,你就只测试哪些页面就可以了。」
⑤「日程模块的新建日程…"加上"和"取消"两个按钮在右上角,离得特别远,根本看不到…
每个输入框前面的标题都太小了…都应该加粗、变大。」
⑥「1.新建任务的截止时间,也和观影一致。2.新建任务时竟然没有添加附件…
3.新建任务也弹出一个框,新建完之后刷新看板页面。4.观影新增也弹框输入所有信息。」
② 日期控件:**真根因是"这个控件给不了"**
`<input type="date">` 的日历弹层由**浏览器自己画**,我们碰不到 DOM、也控制不了流程;
Chrome 在"点顶部 `2026年 10月` 切到年月视图"之后,要靠用户**再点一次日期或按 Esc**
才回得到日期视图,各家还不一致 ⇒ 用户遇到的"选完年月就卡住、选不了日"。
修:`_datetime.php` 重写成 **年 / 月 / 日 三个原生 <select> + 时 / 分**,
选完即生效、无需"确定";秒默认去掉(用户"只记录到时和分")。
⚠️ 提交契约**一字未改**:`*_date` 仍是真字段(改 `type=hidden`)+ `*_hour/_minute`。
⚠️ `required` 落在隐藏控件上会让 Chrome 报 "not focusable"(点保存什么都不发生)
⇒ 改用 `data-dt-required` 声明、JS 自查(12 个调用点全部受影响)。
起止两端:`.dt-range.dt-cols` + `flex-wrap` → **宽屏一行、窄屏自动换行**
(不用 JS 量宽度:字体/动画/缩放时量不准,flex-wrap 是浏览器布局的一部分)。
🔴 顺带挖出真 bug:旅程「修正」表单的 `.dt-range` 只有 **22px 宽**
—— 它长在 `.node-ops`(一排小按钮的容器)里,展开的内容只有"修正"两个字那么宽。
修:`.rev-panel{position:absolute}` 纯 CSS 浮出来(不动 DOM,避免牵动节点列表排版)。
③ 首页冲突色块"空隙特别大":与日历页逐字对比出来的
日历页 `left: calc(2px + L%)` / `width: calc(W% - 4px)` → 只留 4px、紧挨;
首页却写死 `calc(46px + L%)` / `calc(W% - 50px)` —— 每块左右各扣 23/25px,
两列时那 46px **没法均分**、全挤进了中间的缝(实测 ≈50px)。
修:`left: calc(46px + L% + 2px)` / `width: calc(W% - 4px)` → 缝恒为 4px。
⑤ 日程弹框"按钮看不到":**两个**根因叠加
① `calendar/index.php` 里**多写了一个 `</div>`** —— `.cal-modal-panel` 被提前闭合,
底栏 `<div class="cal-modal-foot">` 成了 `.cal-modal` 的直接子元素、被挤到面板外;
② `.cal-modal-panel` 是 flex 列,但**它下面的 `<form>` 不是** →
`.cal-modal-body{flex:1 1 auto}` 的上下文只到 form 为止,body 压不下去也不滚。
另有 `.cal-modal-panel` 的 max-height 没算上 `.cal-modal` 自己的 padding(差 4px)。
三处都修;标签只对**弹框里**的 `.field > label` 加粗放大(14px/600/深色)。
⑥ 任务新建:内联卡片 → 弹框;**新增附件**(`attach[]`,服务端零改动能收);
`back=tasks` → 新建完回**看板**刷新。观影新增:列表页内嵌同一份 form.php 弹框。
① 详情页删掉那句提示块。④ 流水线改**增量**:`run_suite_if <标签>` + 从 git 改动
推模块;**一碰共用件就退回全量**(app.js/app.css/layout/index.php/_datetime.php…),
认不出来也全量(保守:少跑一段发坏东西的代价远大于多跑十分钟)。
🔴🔴 本轮最值得记的教训:**DOM 嵌套错误比语法错误难查得多**
在 movies/form.php 与 calendar/index.php 各写坏一次嵌套(多/少 `</div>`、多 `</form>`),
两处**都通过 php -l、页面也打得开**,症状却是"按钮跑到面板外"与
"编辑保存静默失效"(保存键落到 form 外面)。⇒ 模板里 `<?php if ?>` 与 `</div>`
必须**按分支配对**,改完用真机渲染 dump 一次 div 深度;光靠 php -l 发现不了。
验证(全部真机 + 反向验证):
· _fb_b34_dtrange.py **61/0**(四个页面:年/月/日/时/分 都可见可点 +
选完三个下拉**立刻**写回 *_date + 改月重建"日"列 + 起止默认同一行 + 无 JS 报错);
· _fb_b34_dtrange_falsify **7/0**(三处注入各自必须变红);
· _fb_b37_ui.py **27/0**(详情页无提示块 / 首页冲突缝 ≤8px / 弹框按钮在面板内且在
视口内 / 标签加粗 / 任务弹框+附件+回看板 / 观影列表页弹框);
· _fb_b37_falsify **5/0**;
· _fb_b35_movie_media.py **16/0**、_fb_geom_interact **109/0**、e2e **1503/0**、
b30_ui2 16/0(含首页冲突分列)、_b30_import 26/0、b18/b21/b23/…全绿、
DAV 三套全绿、部署静态断言空跑通过;
· 线上 app.js / app.css / movies/form.php / calendar/index.php 的 SHA256 与本地一致。
· 顺带修掉 _b30_import 的一个**时序竞争**:它用外部上游(联想词服务)的二次探测
当对照,浏览器取词在前、探测在后,上游抖一下会误报成"回归"(已改成两次确认)。
406 lines
19 KiB
PHP
406 lines
19 KiB
PHP
<?php
|
||
/**
|
||
* 任务详情(v12):正文 + 子任务 + 评论(@人)+ 附件 + 共享。
|
||
*
|
||
* @人:评论里写「@老杨」会高亮(Task::commentHtml),**不会**真的发通知 ——
|
||
* 家庭场景下"点名"主要是让人一眼看到在说谁,做通知要先解决推送渠道,先不做。
|
||
*
|
||
* @param array $t 任务(带 children / comments / attachs / shares)
|
||
* @param array $people 名册(负责人 / 共享 / @人 提示)
|
||
* @param array $parents 可选的父任务候选(把当前任务挂到别的父任务下)
|
||
*/
|
||
$canWrite = \App\Auth::canWrite() && \App\Task::canWrite($t, (int)\App\Auth::personId());
|
||
$meId = (int)\App\Auth::personId();
|
||
$names = [];
|
||
foreach ($people as $p) {
|
||
$names[] = person_name($p);
|
||
$nm = trim((string)($p['name'] ?? ''));
|
||
if ($nm !== '') {
|
||
$names[] = $nm;
|
||
}
|
||
}
|
||
$names = array_values(array_unique(array_filter($names)));
|
||
$isChild = !empty($t['parent_id']);
|
||
?>
|
||
<div class="page-head">
|
||
<h1>
|
||
<?php /* 🔴 v24 第 26 批 用户第 5 条:**详情页页头不再放复选框**。
|
||
用户原话:「任务详情页面,就不需要复选框了,因为有标记完成这个按钮。」
|
||
⇒ 右侧 actions 里那颗「标记完成 / 重新打开」已经把这个动作表达得很清楚,
|
||
标题前再挂一颗是重复的(同一页两个入口做同一件事,还容易看不出区别)。
|
||
⚠️ **子任务列表里的复选框保留**(见文件下面)—— 那条需求说的是"详情页",
|
||
而子任务**没有**自己的「标记完成」按钮,去掉它子任务就没法在列表里勾了。 */ ?>
|
||
<?= e((string)$t['title']) ?>
|
||
</h1>
|
||
<span class="sub"><?= e(\App\Task::statusWord((string)$t['status'])) ?></span>
|
||
<div class="actions">
|
||
<?php if ($canWrite): ?>
|
||
<form method="post" action="<?= e(url('tasks/move')) ?>" class="res-inline">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
||
<?php if ($t['status'] !== 'done'): ?>
|
||
<input type="hidden" name="status" value="done">
|
||
<button class="btn" type="submit">标记完成</button>
|
||
<?php else: ?>
|
||
<input type="hidden" name="status" value="doing">
|
||
<button class="btn btn-ghost" type="submit">重新打开</button>
|
||
<?php endif; ?>
|
||
</form>
|
||
<?php endif; ?>
|
||
<a class="btn btn-ghost" href="<?= e(url('tasks')) ?>">返回任务</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card card-pad">
|
||
<dl class="kv">
|
||
<?php /* v24 第 14 批 用户第 2 条:状态要**能选**(不只是"标记完成/重新打开"两档)。
|
||
这里给完整的三档下拉,与看板卡片上那个徽标走同一个路由。 */ ?>
|
||
<div class="kv-row">
|
||
<dt>状态</dt>
|
||
<dd>
|
||
<?php if ($canWrite): ?>
|
||
<form method="post" action="<?= e(url('tasks/status')) ?>" class="res-inline st-form">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
||
<input type="hidden" name="back" value="show">
|
||
<select class="select" name="status" aria-label="任务状态">
|
||
<?php foreach (\App\Task::STATUS as $sk => $sv): ?>
|
||
<option value="<?= e($sk) ?>" <?= (string)$t['status'] === $sk ? 'selected' : '' ?>>
|
||
<?= e($sv) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<button class="btn btn-sm" type="submit">改</button>
|
||
</form>
|
||
<?php else: ?>
|
||
<span class="kb-st is-<?= e((string)$t['status']) ?> is-static">
|
||
<?= e(\App\Task::statusWord((string)$t['status'])) ?>
|
||
</span>
|
||
<?php endif; ?>
|
||
</dd>
|
||
</div>
|
||
<?php if (($t['due_at'] ?? '') !== ''): ?>
|
||
<div class="kv-row"><dt>截止</dt>
|
||
<dd class="<?= \App\Task::isOverdue($t['due_at'] ?? null) && $t['status'] !== 'done' ? 'kb-late' : '' ?>">
|
||
<?= e((string)$t['due_at']) ?><?= \App\Task::dueWord($t['due_at'] ?? null) !== '' ? '(' . e(\App\Task::dueWord($t['due_at'] ?? null)) . ')' : '' ?>
|
||
</dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
<?php if (!empty($t['repeat_rule'])): ?>
|
||
<div class="kv-row"><dt>循环</dt><dd><?= e(\App\Task::repeatWord($t['repeat_rule'])) ?></dd></div>
|
||
<?php endif; ?>
|
||
<?php if (!empty($t['assignee_name'])): ?>
|
||
<div class="kv-row"><dt>负责人</dt>
|
||
<dd><?= e(person_name(['name' => $t['assignee_name'], 'my_nickname' => $t['assignee_nick'] ?? ''])) ?></dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
<?php if (!empty($t['creator_name'])): ?>
|
||
<div class="kv-row"><dt>谁建的</dt>
|
||
<dd><?= e(person_name(['name' => $t['creator_name'], 'my_nickname' => $t['creator_nick'] ?? ''])) ?></dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
<?php if ($isChild && !empty($t['parent_title'])): ?>
|
||
<div class="kv-row"><dt>属于</dt>
|
||
<dd><a href="<?= e(url('tasks/show', ['id' => (int)$t['parent_id']])) ?>"><?= e((string)$t['parent_title']) ?></a></dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
<?php if (!empty($t['note'])): ?>
|
||
<div class="kv-row"><dt>备注</dt><dd><?= nl2br(e((string)$t['note'])) ?></dd></div>
|
||
<?php endif; ?>
|
||
</dl>
|
||
|
||
<?php if ($canWrite): ?>
|
||
<details class="node-add">
|
||
<summary class="link-quiet">编辑这项任务</summary>
|
||
<form method="post" action="<?= e(url('tasks/update')) ?>" class="node-form">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
||
<div class="nf-grid">
|
||
<div class="field full">
|
||
<label>标题</label>
|
||
<input class="input" name="title" required value="<?= e((string)$t['title']) ?>">
|
||
</div>
|
||
<div class="field">
|
||
<label>截止时间(留空=不上日历)</label>
|
||
<?php $dtName = 'due_at'; $dtValue = $t['due_at'] ?? null;
|
||
$dtRequired = false;
|
||
require __DIR__ . '/../_datetime.php'; ?>
|
||
</div>
|
||
<div class="field">
|
||
<label>负责人</label>
|
||
<?php /* 第 8 条同一诉求:负责人也是「选人」,人多时用弹窗 + 首字母更快。
|
||
保留原下拉的「不指定」(pp-item-none + value=0)。 */ ?>
|
||
<?php
|
||
$ppName = 'assignee_id';
|
||
$ppPeople = $people;
|
||
$ppSelected = [(int)($t['assignee_id'] ?? 0)];
|
||
$ppMode = 'single';
|
||
$ppOpenBtn = '更换负责人';
|
||
$ppTitle = '谁负责这件事';
|
||
$ppEmptyText = '不指定';
|
||
$ppAllowEmpty = true;
|
||
require __DIR__ . '/../_person_picker.php';
|
||
?>
|
||
</div>
|
||
<div class="field">
|
||
<label>循环</label>
|
||
<select class="select" name="repeat_rule">
|
||
<?php foreach (\App\Task::REPEAT as $rk => $rv): ?>
|
||
<option value="<?= e($rk) ?>"<?= (string)($t['repeat_rule'] ?? '') === (string)$rk ? ' selected' : '' ?>>
|
||
<?= e($rv) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="field">
|
||
<label>挂到哪个父任务下</label>
|
||
<select class="select" name="parent_id">
|
||
<option value="0">(这是一项独立的任务)</option>
|
||
<?php foreach ($parents as $p): ?>
|
||
<option value="<?= (int)$p['id'] ?>"<?= (int)($t['parent_id'] ?? 0) === (int)$p['id'] ? ' selected' : '' ?>>
|
||
<?= e((string)$p['title']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<?php /* v24 第 9、10 条:分类与看板(看板把任务放到哪一块,分类是标签) */ ?>
|
||
<div class="field">
|
||
<label>分类</label>
|
||
<select class="select" name="category_id">
|
||
<option value="0">未分类</option>
|
||
<?php foreach (($categories ?? []) as $c): ?>
|
||
<option value="<?= (int)$c['id'] ?>"<?= (int)($t['category_id'] ?? 0) === (int)$c['id'] ? ' selected' : '' ?>>
|
||
<?= e((string)$c['name']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="field">
|
||
<label>看板</label>
|
||
<select class="select" name="board_id">
|
||
<?php foreach (($boards ?? []) as $b): ?>
|
||
<option value="<?= (int)$b['id'] ?>"<?= (int)($t['board_id'] ?? 0) === (int)$b['id'] ? ' selected' : '' ?>>
|
||
<?= e((string)$b['name']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="field full">
|
||
<label>备注</label>
|
||
<textarea class="input" name="note" rows="2"><?= e((string)($t['note'] ?? '')) ?></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="btn-row"><button class="btn btn-sm" type="submit">保存</button></div>
|
||
</form>
|
||
</details>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php /* ---- 子任务 ---- */ ?>
|
||
<?php if (!$isChild): ?>
|
||
<div class="card card-pad">
|
||
<h2 class="cinema-h2">子任务 <span class="group-n"><?= count($t['children']) ?> 项</span></h2>
|
||
<?php if (!$t['children']): ?>
|
||
<p class="empty-hint">还没有子任务。把一件大事拆成几件小事,进度才看得见。</p>
|
||
<?php else: ?>
|
||
<ul class="sub-list">
|
||
<?php foreach ($t['children'] as $c): ?>
|
||
<li class="sub-row<?= $c['status'] === 'done' ? ' is-done' : '' ?>">
|
||
<?php /* v24 第 25 批 用户第 6 条:子任务的复选框**改用全站那一颗**
|
||
—— 原来是"每行一个 form + ☐/☑ 字符":
|
||
· 每行一个 form = 子任务一多就是几十个 form(名册页那个坑);
|
||
· ☐/☑ 是普通字符,跨字体基线会歪,且与看板上的复选框长得不一样。
|
||
现在卡片上只放 data-tk-done,表单全页一份(见页尾)。 */ ?>
|
||
<?php
|
||
$tkId = (int)$c['id']; $tkStatus = (string)$c['status'];
|
||
$tkBack = 'show'; $tkStatic = !$canWrite;
|
||
$tkLabel = '子任务' . (string)$c['title'];
|
||
require __DIR__ . '/../_task_check.php';
|
||
?>
|
||
<a class="sub-title" href="<?= e(url('tasks/show', ['id' => (int)$c['id']])) ?>">
|
||
<?= e((string)$c['title']) ?>
|
||
</a>
|
||
<?php if (($c['due_at'] ?? '') !== ''): ?>
|
||
<span class="node-time<?= \App\Task::isOverdue($c['due_at'] ?? null) && $c['status'] !== 'done' ? ' kb-late' : '' ?>">
|
||
<?= e(\App\Task::dueWord($c['due_at'] ?? null)) ?>
|
||
</span>
|
||
<?php endif; ?>
|
||
</li>
|
||
<?php endforeach; ?>
|
||
</ul>
|
||
<?php endif; ?>
|
||
<?php if ($canWrite): ?>
|
||
<details class="node-add">
|
||
<summary class="link-quiet">+ 加一项子任务</summary>
|
||
<form method="post" action="<?= e(url('tasks/create')) ?>" class="inline-form">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="parent_id" value="<?= (int)$t['id'] ?>">
|
||
<input class="input" name="title" required placeholder="如:擦玻璃">
|
||
<?php $dtName = 'due_at'; $dtValue = null; $dtRequired = false;
|
||
require __DIR__ . '/../_datetime.php'; ?>
|
||
<button class="btn btn-sm" type="submit">加上</button>
|
||
</form>
|
||
</details>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php /* ---- 附件 ---- */ ?>
|
||
<div class="card card-pad">
|
||
<h2 class="cinema-h2">附件</h2>
|
||
<?php if (!$t['attachs']): ?>
|
||
<p class="empty-hint">还没有附件。</p>
|
||
<?php else: ?>
|
||
<?php
|
||
$attRows = $t['attachs'];
|
||
$attCanEdit = $canWrite;
|
||
$attDelUrl = 'tasks/attach_del';
|
||
$attDelField = 'id';
|
||
$attExtra = [];
|
||
require __DIR__ . '/../_attachments.php';
|
||
?>
|
||
<?php endif; ?>
|
||
<?php if ($canWrite): ?>
|
||
<form class="inline-form" method="post" enctype="multipart/form-data" action="<?= e(url('tasks/attach_add')) ?>">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
||
<?php /* v24 第 21 批 第 7 条:不再放原生文件框 + 按钮,改成一个「点击或拖动」的框。
|
||
⚠️ 这个表单**唯一**的用途就是传附件,所以提交键仍然要有 ——
|
||
"点击/拖动"只是选文件的方式,最后还得点一下才能提交。 */
|
||
$sdName = 'attach[]'; $sdSlot = 'photo';
|
||
require __DIR__ . '/../_stage_drop.php'; ?>
|
||
<div class="btn-row" style="margin-top:12px">
|
||
<button class="btn btn-sm" type="submit">上传附件</button>
|
||
</div>
|
||
</form>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php /* ---- 共享 ---- */ ?>
|
||
<div class="card card-pad">
|
||
<h2 class="cinema-h2">共享</h2>
|
||
<?php if (!$t['shares']): ?>
|
||
<p class="empty-hint">还没共享给别人。共享之后,任何一个人完成都算完成。</p>
|
||
<?php else: ?>
|
||
<div class="res-shares">
|
||
<?php foreach ($t['shares'] as $s): ?>
|
||
<?= person_chip($s) ?>
|
||
<span class="node-badge"><?= ($s['perm'] ?? '') === 'write' ? '可写' : '只读' ?></span>
|
||
<?php if ($canWrite): ?>
|
||
<form method="post" action="<?= e(url('tasks/unshare')) ?>" class="res-inline">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
||
<input type="hidden" name="person_id" value="<?= (int)$s['person_id'] ?>">
|
||
<button class="btn btn-ghost btn-sm" type="submit">取消</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
<?php if ($canWrite): ?>
|
||
<form class="inline-form" method="post" action="<?= e(url('tasks/share')) ?>">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
||
<?php
|
||
$ppName = 'person_id';
|
||
$ppPeople = array_values(array_filter($people, fn($p) => (int)$p['id'] !== $meId));
|
||
$ppSelected = [];
|
||
$ppMode = 'single';
|
||
$ppOpenBtn = '选择要共享给谁';
|
||
$ppTitle = '共享给谁';
|
||
$ppEmptyText = '未选择';
|
||
require __DIR__ . '/../_person_picker.php';
|
||
?>
|
||
<select class="select" name="perm">
|
||
<option value="write">可写</option>
|
||
<option value="read">只读</option>
|
||
</select>
|
||
<button class="btn btn-sm" type="submit">共享</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php /* ---- 评论 ---- */ ?>
|
||
<div class="card card-pad">
|
||
<h2 class="cinema-h2">讨论 <span class="group-n"><?= count($t['comments']) ?> 条</span></h2>
|
||
<?php if (!$t['comments']): ?>
|
||
<p class="empty-hint">还没有留言。写 @名字 可以点名。</p>
|
||
<?php else: ?>
|
||
<ul class="cmt-list">
|
||
<?php foreach ($t['comments'] as $c): ?>
|
||
<li class="cmt-row">
|
||
<div class="cmt-who">
|
||
<?= person_chip($c) ?>
|
||
<span class="cmt-time"><?= e((string)$c['created_at']) ?></span>
|
||
</div>
|
||
<div class="cmt-body"><?= \App\Task::commentHtml((string)$c['content'], $names) ?></div>
|
||
<?php if ((int)$c['person_id'] === $meId): ?>
|
||
<form method="post" action="<?= e(url('tasks/comment_del')) ?>" class="res-inline">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="id" value="<?= (int)$c['id'] ?>">
|
||
<input type="hidden" name="task_id" value="<?= (int)$t['id'] ?>">
|
||
<button class="btn btn-ghost btn-sm" type="submit" onclick="return confirm('删掉这条留言?')">删除</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
</li>
|
||
<?php endforeach; ?>
|
||
</ul>
|
||
<?php endif; ?>
|
||
<?php if ($canWrite): ?>
|
||
<form method="post" action="<?= e(url('tasks/comment_add')) ?>" class="cmt-form">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
||
<textarea class="input" name="content" rows="2" required
|
||
placeholder="说点什么… 写 @名字 可以点名"><?= e(old('content', '')) ?></textarea>
|
||
<div class="btn-row"><button class="btn btn-sm" type="submit">发表</button></div>
|
||
</form>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php if ($canWrite): ?>
|
||
<div class="task-danger">
|
||
<form method="post" action="<?= e(url('tasks/destroy')) ?>"
|
||
onsubmit="return confirm('删除「<?= e((string)$t['title']) ?>」?子任务、附件与留言会一起删掉。')">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
||
<button class="btn btn-danger btn-sm" type="submit">删除这项任务</button>
|
||
</form>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php /* 完成复选框的**全页一份**表单(用户第 6 条)—— 本页可能出现多颗
|
||
(任务自己 + 每条子任务),所以更要"卡片上只放数据、表单只此一份"。 */ ?>
|
||
<?php if ($canWrite): ?>
|
||
<form method="post" action="<?= e(url('tasks/status')) ?>" id="taskDoneForm" class="hidden-form">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="id" id="doneId">
|
||
<input type="hidden" name="status" id="doneStatus">
|
||
<input type="hidden" name="back" id="doneBack" value="show">
|
||
</form>
|
||
<?php /* ⚠️ 本页的复选框走**原生提交**(不 AJAX):
|
||
详情页是一次性动作,改完最常见的是"看一眼就返回";
|
||
就地改反而要在页面上同步三处(标题旁的状态、kv 里的状态、子任务进度)。
|
||
原生提交 + back=show → 服务端 302 回本页,状态永远与服务端一致。
|
||
代价只是刷一次页 —— 详情页本来就"打开看某一条",没有滚动位置要保住
|
||
(那是看板/首页才有的诉求)。
|
||
⚠️ 但**不能只渲染 form 不绑事件**:那样点了按钮毫无反应
|
||
("按钮坏了"比"刷一下"糟得多)。这里只做一件最小的事:
|
||
把"点的是哪一条 / 改成什么"灌进表单再提交。 */ ?>
|
||
<script>
|
||
(function () {
|
||
var form = document.getElementById('taskDoneForm');
|
||
if (!form) { return; }
|
||
Array.prototype.forEach.call(document.querySelectorAll('.tk-check[data-tk-done]'), function (b) {
|
||
b.addEventListener('click', function (ev) {
|
||
ev.preventDefault();
|
||
var now = b.getAttribute('data-tk-status') || 'todo';
|
||
document.getElementById('doneId').value = b.getAttribute('data-tk-done');
|
||
document.getElementById('doneStatus').value = (now === 'done') ? 'todo' : 'done';
|
||
document.getElementById('doneBack').value = b.getAttribute('data-tk-back') || 'show';
|
||
form.submit();
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
<?php endif; ?>
|