Files
FamilyBook/templates/tasks/_edit_form.php
T
laoyang 190dc56301 v55:笔记详情不重复标题 / 任务状态选完即改(去按钮)/ 编辑任务弹框 / 子任务截止时间换最新控件
用户 2026-10-17 四条。

① 笔记详情:正文里不再重复显示标题
   用户原话:「笔记详情里面的标题已经变成这个日笔记的题目了,那**下面那个里面
   就不用再显示这个题目**了。」
   背景:v52 已把**页头**改成笔记自己的标题(并参与 <title>),
   而**正文区里又写了一遍**同一个标题 ⇒ 同一页同一个名字出现两次。
   ⇒ 删正文里那个 `<h1 class="note-read-title">`;**页头那份保留**。
   连带删掉已无使用者的 `.note-read-title` CSS 规则
   (留一条没有使用者的规则,只会让下一个人以为"正文里还该有个标题")。

② 任务详情:状态**选一下就改**(去掉「改」按钮)
   用户原话:「任务详情里边,状态后面**不用加按钮**了。不是说选了之后再点一下才改,
   而是**只要一选就让它改**:选一下,状态就发生改变就行了。」
   ⇒ `<select>` 的 `change` 直接 fetch 提交到**既有的** `tasks/status`
      (它本来就认 `X-Requested-With` 并回 JSON —— 不新开路由、不加新契约)。
   成功后**就地**同步三处(不刷页、不丢滚动位置):
     · 下拉本身;
     · 页头副标题的状态词;
     · 页头那颗「标记完成 ↔ 重新打开」按钮的文案与 hidden 值
       (改了状态而按钮还写"标记完成"就自相矛盾)。
   反馈:一条**浮在顶部**的轻提示 `.tk-flash`(`position:fixed` 不占文档流 ⇒
   改状态不会让页面跳动)。循环任务那句"下一次也排上了"(`d.msg`)照旧显示 ——
   不能因为改交互把 v24 定下的行为丢掉。
   ⚠️ **无 JS 兜底**:`<noscript>` 里那颗「改」按钮
      (规范:脚本开启时 noscript **整块不渲染**)。不要用 `<button hidden>`+JS
      去"显示"它(会闪、脚本报错时永远不出现)。
   🔴🔴 **踩坑(本轮最值钱)**:第一版写成"先 `sel.disabled = true`、再
      `new FormData(form)`" ⇒ **HTML 规范:禁用的控件不参与表单数据构造** ⇒
      字段丢了 ⇒ 服务端收到空 status ⇒ 落回 `todo`。
      症状是"请求成功、回包 ok、页面还给了提示,唯独值不对" —— 极具误导性。
      ⇒ **先构建、后禁用**。

③ 编辑任务 = 弹框
   用户原话:「**编辑任务,弹框处理**。」
   原来详情页是个 `<details>` 就地展开表单(展开会把下面的子任务/附件/评论整段往下推);
   现在点「编辑这项任务」**浮起弹框**(`.cal-modal`,与「新建任务 / 新建日程 /
   编辑观影记录」同一套类名)。
   ⚠️ **表单一个字没改** —— 仍 require 同一份 `_edit_form.php`
      (条目浮层 `tasks/edit?embed=1` 也用它,两处永远同一张)。
      这次只加了个 `$tkEditModal` 开关:true 时套 `.cal-modal` 壳 +
      「保存」挪到底栏(与新建任务一致:主要动作在右下角)。
   ⚠️ 打开那一刻才 `fbBootAntd()` / `fbUpgradeRange()` 升级里面的日期控件
      (hidden 时也能挂上,但 antd 弹层定位需要元素有实际尺寸)。
   ⚠️ 事件委托挂 `document`(与观影详情页同一理由:校验失败若重画这块,委托仍有效)。

④ 子任务截止时间:换最新控件
   用户原话:「**子任务的截止时间,使用最新的时间控件**。」
   **真根因**:`tasks/show.php` 子任务那块 require `_datetime.php` 时
   **漏传 `$dtPlugin`** ⇒ 空值时走**旧的三下拉(年/月/日)**
   ⇒ 与「新建任务」「任务编辑」里的 antd 控件**不一样**。
   ⇒ 补 `$dtPlugin = 'antd-single'`(截止时间是**单个时刻**,不是范围)。
   ⚠️ 全仓这一族的调用点**逐处核对过**:只有子任务这一处漏了;其余
      (日程起止 / 观影起止 / 任务新建 / 任务编辑 / 行程起止 / 修正起止)
      都已有 `'antd'` 或 `'antd-single'`。
   ⚠️ `people/form.php`(生日)**有意不传** —— 生日是"日期",年份要能下拉到 1900,
      与"截止时间"不是一回事,别顺手改。

验证(新增 2 套件 + 更新 deploy 断言):
 · _fb_v55_ui.py(**30 条**真机断言)
 · _b55_falsify.py(**8 注入 / 8 全抓**,基线 30/0)
 · e2e **1518/0**;familybook_deploy.sh 加「第55批形态」段(**13 条**)
 · 部署 **0 FAIL**;线上 4 文件 md5 逐字节一致;
   线上剥注释核实:笔记模板/`app.css` 里已无 `note-read-title`(反向)、
   任务模板有 `data-st-select` / `fd=FormData` / `noscript` 兜底 / `antd-single`、
   编辑表单有 `tkEditCard`/`tkEditSubmit`、`app.css` 有 `.tk-flash`;
   诊断页已删复验 404

⚠️ 本轮踩坑(已写进 pitfalls.md):
 · 🔴🔴 **先 `disabled=true` 再 `new FormData(form)` ⇒ 字段会丢**(表单数据不含禁用控件);
 · 连带印证:**用裸 `strpos`/`grep` 做反向断言会命中注释**(我的线上诊断脚本
   就因为这个连报两次"还有",剥注释后才对)—— 反向断言一律走"剥注释"。

⚠️ 另:工作树里 `app/ApiHandler.php` / `app/PersonQuery.php` / `app/Home.php` /
   `app/Api.php` / `app/ApiToken.php` / `app/Schema.php` / `index.php` /
   `templates/settings/me.php` 是**另一个会话**正在做的 App 端 / 设备管理改动,
   一字未碰、也未提交。
2026-10-17 23:10:49 +08:00

173 lines
9.0 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
/**
* 🔴 v43(用户 2026-10-11 第 6 条)—— 任务的「编辑」表单,**独立一份**。
*
* 为什么要独立出来:
* 原来它内联在 `tasks/show.php` 的 `<details>` 里。而条目详情浮层点「编辑」时,
* 需要**单独取这份表单**(`tasks/edit?embed=1`)—— 不可能去详情页里抠。
* 抽成一份后,详情页与浮层用的是**同一张表单**,不会出现"两处字段不一致"。
*
* ⚠️ 字段名 / 服务端契约与原来**一字未改**(tasks/update 收的就是这些)。
* ⚠️ 「状态」不在这里改:状态由专门的 `tasks/status` 路由管(它还要处理循环任务),
* 文案里也说明了这一点(与 v24 第 14 批的口径一致)。
*
* 🔴🔴 **本片段一律用带前缀的局部变量(`$tk*`),绝不碰入参的名字。**
* 第一版写的是 `$t = is_array($t ?? null) ? $t : [];` —— 看似无害,其实是在
* **覆盖宿主模板的 `$t`**(那是"这条任务"本身)。只读一次没问题,
* 但下一个人只要把某个 `$tk*` 改回 `$t`(很自然)就会让详情页后半段的
* `$t['children']` / 评论区全是 null —— **而且是静默的**。
* 项目的"片段体检"(`_fb_frag_guard`)当场抓到了这处裸赋值,是对的。
* ⇒ 入参先**收进 `$tkT`**,后面全用它。
*
* 用法(include 前设好):
* $t 任务(Task::find 的形状)
* $people 名册
* $parents 可选父任务候选
* $categories / $boards 分类与看板
* $tkEditEmbed 是否"浮层形态"(true = 不输出自己的「保存」,由浮层底栏给)
* $tkEditModal 🔴 v55:是否**套 `.cal-modal` 弹框壳**(用户 2026-10-17 第 3 条
* 「编辑任务,弹框处理」)。详情页传 true;条目浮层 / AJAX 片段不传
* (它自己已经在浮层里,再套一层弹框就是"框中框")。
* $tkEditOpen 弹框初始是否打开(默认 **关闭** —— 详情页要点「编辑」才开)。
* $tkEditTitle 弹框标题(默认「编辑任务」)
*/
$tkT = is_array($t ?? null) ? $t : [];
$tkPeople = (array)($people ?? []);
$tkParents = (array)($parents ?? []);
$tkCats = (array)($categories ?? []);
$tkBoards = (array)($boards ?? []);
$tkModal = !empty($tkEditModal);
$tkOpen = !empty($tkEditOpen);
$tkMdlTitle = (string)($tkEditTitle ?? '编辑任务');
$tkEmbed = !empty($tkEditEmbed);
?>
<?php
/* 🔴 v55(用户 2026-10-17 第 3 条):「编辑任务,弹框处理。」
弹框壳(`.cal-modal`,与「新建任务 / 新建日程 / 编辑观影记录」同一套类名)。
⚠️ **表单本体一个字未改** —— 只是外面多包一层;字段名 / action / 契约全不动。
⚠️ `$tkEditEmbed` 为 true 时(详情页与条目浮层都是)保存键由**底栏**给,
所以下面 `$tkEmbed` 那段的「保存」不输出,改在底栏放。 */
?>
<?php if ($tkModal): ?>
<div class="cal-modal" id="tkEditCard" role="dialog" aria-modal="true"
aria-label="<?= e($tkMdlTitle) ?>"<?= $tkOpen ? '' : ' hidden' ?>>
<div class="cal-modal-panel">
<div class="cal-modal-head">
<b><?= e($tkMdlTitle) ?></b>
<button type="button" class="cal-modal-x" id="tkEditX" aria-label="关闭">✕</button>
</div>
<div class="cal-modal-body">
<?php endif; ?>
<form method="post" action="<?= e(url('tasks/update')) ?>" class="node-form" id="tkEditForm">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= (int)($tkT['id'] ?? 0) ?>">
<?php /* 🔴 v55:编辑完回详情页(服务端 `tasks/update` 本来就 `redirect('tasks/show')`)——
这里**不传** back,与原来一致。 */ ?>
<?php /* 🔴 v44(用户 2026-10-12 第 5 条):编辑表单也统一成观影式排版 +
最新时间控件 + 文案精简(与「新建任务」弹框一致)——
同一件事在两个入口长得不一样,用户会以为"进了别的页面"。 */ ?>
<div class="mv-grid">
<div class="mv-row">
<span class="mv-lb">任务名称<i>*</i></span>
<input class="input" id="tk-title" name="title" required value="<?= e((string)($tkT['title'] ?? '')) ?>">
</div>
<div class="mv-row">
<span class="mv-lb">截止时间</span>
<?php /* 🔴 v44:截止时间用 AntD 单点控件(与新建同一套);括号去掉。 */ ?>
<?php $dtName = 'due_at'; $dtValue = $tkT['due_at'] ?? null;
$dtRequired = false; $dtPlugin = 'antd-single';
require __DIR__ . '/../_datetime.php'; ?>
</div>
<div class="mv-row">
<span class="mv-lb">负责人</span>
<?php /* 第 8 条同一诉求:负责人也是「选人」,人多时用弹窗 + 首字母更快。
保留原下拉的「不指定」(pp-item-none + value=0)。 */ ?>
<?php
$ppName = 'assignee_id';
$ppPeople = $tkPeople;
$ppSelected = [(int)($tkT['assignee_id'] ?? 0)];
$ppMode = 'single';
$ppOpenBtn = '更换负责人';
$ppTitle = '谁负责这件事';
$ppEmptyText = '不指定';
$ppAllowEmpty = true;
require __DIR__ . '/../_person_picker.php';
?>
</div>
<div class="mv-row">
<span class="mv-lb">循环</span>
<select class="select" name="repeat_rule">
<?php foreach (\App\Task::REPEAT as $rk => $rv): ?>
<option value="<?= e($rk) ?>"<?= (string)($tkT['repeat_rule'] ?? '') === (string)$rk ? ' selected' : '' ?>>
<?= e($rv) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mv-row">
<span class="mv-lb">父任务</span>
<select class="select" name="parent_id">
<option value="0">(这是一项独立的任务)</option>
<?php foreach ($tkParents as $p): ?>
<option value="<?= (int)$p['id'] ?>"<?= (int)($tkT['parent_id'] ?? 0) === (int)$p['id'] ? ' selected' : '' ?>>
<?= e((string)$p['title']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php /* v24 第 9、10 条:分类与看板(看板把任务放到哪一块,分类是标签) */ ?>
<div class="mv-row">
<span class="mv-lb">分类</span>
<select class="select" name="category_id">
<option value="0">未分类</option>
<?php foreach ($tkCats as $c): ?>
<option value="<?= (int)$c['id'] ?>"<?= (int)($tkT['category_id'] ?? 0) === (int)$c['id'] ? ' selected' : '' ?>>
<?= e((string)$c['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mv-row">
<span class="mv-lb">看板</span>
<select class="select" name="board_id">
<?php foreach ($tkBoards as $b): ?>
<option value="<?= (int)$b['id'] ?>"<?= (int)($tkT['board_id'] ?? 0) === (int)$b['id'] ? ' selected' : '' ?>>
<?= e((string)$b['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="mv-row">
<span class="mv-lb">备注</span>
<textarea class="input" id="tk-note" name="note" rows="3"><?= e((string)($tkT['note'] ?? '')) ?></textarea>
</div>
</div>
<?php /* ⚠️ 「状态」不在这里改(由 tasks/status 路由管,它还要处理循环任务)。 */ ?>
<?php if ($tkModal): ?>
<?php /* 🔴 v55:**弹框形态下不在这里放「保存」** —— 它归底栏(与新建任务一致:
主要动作在右下角)。`.btn-row` 那份是给"页面上的普通卡片形态"用的。 */ ?>
<?php elseif (!$tkEmbed): ?>
<div class="btn-row"><button class="btn btn-sm" type="submit">保存</button></div>
<?php endif; ?>
</form>
<?php if ($tkModal): ?>
</div><?php /* /.cal-modal-body */ ?>
<div class="cal-modal-foot">
<button class="btn btn-sm" type="submit" form="tkEditForm" id="tkEditSubmit">保存</button>
<button class="btn btn-ghost btn-sm" type="button" id="tkEditCancel">取消</button>
</div>
</div><?php /* /.cal-modal-panel */ ?>
</div><?php /* /.cal-modal */ ?>
<?php endif; ?>
<?php /* 🔴🔴 —— **千万不要 unset 入参变量**!
这几种是宿主模板的"大局变量"(`$t` = 这条任务、`$people` = 名册…),
include 之后宿主还要继续用它们(`$t['children']`、评论、子任务…)。
我第一版照抄了 `_datetime.php` 那种"用完即清"的写法,把 `$t` 一并 unset ——
结果 `tasks/show.php` 后半段全部拿不到 `$t`,**子任务列表与评论区静默消失**
(e2e 当场抓到 2 条:子任务没了、@高亮没了)。
⇒ 这里只清**本片段自己造的**临时变量(`$tk*` 与 `$dt*` / `$pp*` 那几组)。 */
unset($tkT, $tkPeople, $tkParents, $tkCats, $tkBoards, $tkEmbed,
$tkModal, $tkOpen, $tkMdlTitle, // 🔴 v55:弹框壳那三个
$dtName, $dtValue, $dtRequired,
$ppName, $ppPeople, $ppSelected, $ppMode, $ppOpenBtn, $ppTitle,
$ppEmptyText, $ppAllowEmpty); ?>