v78 服务端:首页任务改成「就地处理」面板 + 新建任务搬到首页

用户原话:
「网页端,首页点击一个任务后,除了展示信息,应该有相应的功能,可以修改状态:
  待办、进行中,可以挪动分组,可以拖动上传附件,可以添加评论,添加子任务等。
  其目地,是为了让用户不离开首页,就能对任务进行简单的处理。」
「另外,在全部任务旁边,放一个新建任务的功能,其实就是把任务中的新建和编辑功能,
  搬过来,用弹出框来处理。」

一、首页任务浮层由「只读信息」改成「可操作面板」
- 新增 templates/tasks/_panel.php:状态三段 / 看板 chips(带看板色)/
  子任务(复用 _task_check.php)/ 附件拖放框(复用 _attachments.php)/ 讨论
- 新增路由 tasks/panel:只吐一块 HTML(找不到 404)
- 既有七个动作路由各加一条 ajax_panel 回包分支(状态 / 挪分组 / 新建子任务 /
  评论增删 / 附件增删),没有新开写接口
- helpers.php 新增 task_panel_wanted() / task_panel_json();
  fb_entry_json() 的 payload 增加 panel 字段(只有任务会给)
- 每次动作回**整块新面板**:一次动作会牵动子任务计数、附件张数、看板选中态,
  逐处同步 DOM 必然漏一处
- 拖进来的附件**立刻上传**(任务已存在,不该再让用户点一次保存),
  不复用 _stage_drop.php 那套「暂存 + 认领」

二、新建任务搬到首页
- 从 templates/tasks/index.php 抽出公共片段 templates/tasks/_add_modal.php
  (字段与原来一字未改),首页与任务页共用同一份
- 新增 assets/js/task_add.js 管开关(两页共用,幂等绑定);
  打开那一刻才 fbUpgradeRange() + fbBootAntd()(hidden 时宽高为 0,antd 会定位错乱)
- App\HomeCards 登记表里加 add => 新建任务,首页卡头与「全部任务」并排
- tasks/create 支持 back=home(白名单),新建完回首页

三、其它
- assets/js/task_panel.js:事件委托;动作后整块换面板;
  顺手同步首页那张卡的状态徽标与复选框(关掉浮层不能还是旧状态);
  传附件时保住用户写了一半的留言与子任务标题;取不到面板时退回只读信息
- app.css 追加面板样式(含 color-mix 的 @supports 兜底)与卡片窄到宽 2 时允许折行

验证:本地 e2e 1558/0、几何 428/0 + 660/0、v78 反向验证 11/0、
片段作用域体检 35/0、php -l 与 node --check 全过
This commit is contained in:
2026-10-22 12:40:14 +08:00
parent 036e208809
commit 40fd6c501c
13 changed files with 1216 additions and 178 deletions
+15 -177
View File
@@ -57,7 +57,7 @@ $lastColIdx = count($colList) - 1;
</span>
<div class="actions">
<?php if ($canWrite): ?>
<button class="btn" type="button" id="taskAddBtn">新建任务</button>
<button class="btn" type="button" data-tk-add-open>新建任务</button>
<?php else: ?>
<span class="link-quiet">只读账号</span>
<?php endif; ?>
@@ -65,158 +65,17 @@ $lastColIdx = count($colList) - 1;
</div>
</div>
<?php if ($canWrite): ?>
<?php /* 🔴🔴 v37 用户第 6 条第 3 点:「新建任务也和新建日程一样,新建时弹出一个框,
新建完之后刷新新建任务的列表、看板页面。」
⇒ 从"页面上展开的一张卡片"改成**浮层弹框**(与日程弹框同一套形态 / 类名),
提交后照旧 302 回看板刷新 —— 用户要的"刷新看板页面"就是这样。
⚠️ 表单字段本身**一个字没动**(除了新加的附件框),服务端契约照旧。 */ ?>
<div class="cal-modal" id="taskAddCard" role="dialog" aria-modal="true"
aria-label="新建任务" hidden>
<div class="cal-modal-panel">
<div class="cal-modal-head">
<b>新建任务</b>
<button type="button" class="cal-modal-x" id="taskAddX" aria-label="关闭">✕</button>
</div>
<?php /* ⚠️ enctype 必须有:本批新增了**附件**上传(用户第 6 条第 2 点
「新建任务时,竟然没有添加附件,这个应该加上」)。
没有它的话文件根本不会随表单发出去,而且 $_FILES 是空的。 */ ?>
<form method="post" action="<?= e(url('tasks/create')) ?>"
enctype="multipart/form-data" id="taskAddForm">
<?= csrf_field() ?>
<?php /* ⚠️ 新建后回**看板**(用户:「新建完之后刷新…看板页面」)。
原来服务端一律 redirect('tasks/show'),新建完会跳去详情页。
`back=tasks` 走白名单。 */ ?>
<input type="hidden" name="back" value="tasks">
<div class="cal-modal-body">
<?php /* 🔴 v44(用户 2026-10-12 第 5 条):「新建任务页面的排版还没有统一,
应该把标题放在前面,树框放在后面。」
⇒ 用观影/行程/日程那套 **`.mv-grid`**(标签 62px 右对齐 + CSS `::after` 冒号 +
控件占满剩余宽度),标题那一行在最前、其余字段依次跟上 ——
"标题在前、后面的框跟上"就是这套排版的自然顺序。 */ ?>
<div class="mv-grid">
<div class="mv-row">
<span class="mv-lb">任务名称<i>*</i></span>
<input class="input" id="tk-title" name="title" required
placeholder="如:周末大扫除">
</div>
<?php /* v24 第 5 条:截止时刻也走全局统一的「日期 + 时分秒」控件。
秒这一档保留(与其它模块同一口径),默认 23:59:59 —— "这天之前做完"
比"这天 00:00 之前"更符合直觉。
🔴 v44(用户第 5 条):「截止时间也用我们最新的日历控件,
记得把现在这个控件删掉。」
⇒ 换成 AntD 的**单点** DatePicker(带时刻):
`$dtPlugin='antd-single'`(见 app.js 的 mountAntdSingleDT)。
原生那几档下拉由 CSS **真删掉**(不是 clip 藏起来)。
标签去掉那个括号(见下一条)。 */ ?>
<div class="mv-row">
<span class="mv-lb">截止时间</span>
<?php $dtName = 'due_at'; $dtId = 'tk-due'; $dtValue = null;
$dtRequired = false; $dtPlugin = 'antd-single';
require __DIR__ . '/../_datetime.php'; ?>
</div>
<?php /* 🔴 v44(用户第 5 条):「负责人默认是自己。」
⇒ 默认选中当前登录的人(原来 `$ppSelected = []`,空着)。
⚠️ 用 `Auth::personId()` 取"我",与后端"负责人为空"的兜底一致。 */ ?>
<div class="mv-row">
<span class="mv-lb">负责人</span>
<?php
$ppName = 'assignee_id';
$ppPeople = $people;
$ppSelected = [(int)\App\Auth::personId()];
$ppMode = 'single';
$ppOpenBtn = '更换负责人';
$ppTitle = '谁负责这件事';
$ppEmptyText = '不指定';
$ppAllowEmpty = true;
require __DIR__ . '/../_person_picker.php';
?>
</div>
<?php /* 🔴 v44(用户第 5 条):「状态下面选"已完成"的话,它只在"已完成"里出现,
看板上不再显示,这句话也去掉。」
⇒ 删掉那句 `.tip`(状态语义由状态徽标本身表达)。
⚠️ 用户同条要求「任务页面,任务下面有一行字…这个提示不要了」——
那是**看板顶部**那块「状态说明」(见上方 legend 那段)。 */ ?>
<div class="mv-row">
<span class="mv-lb">状态</span>
<select class="select" id="tk-status" name="status">
<?php foreach (\App\Task::STATUS as $sk => $sv): ?>
<option value="<?= e($sk) ?>" <?= $sk === 'todo' ? 'selected' : '' ?>><?= e($sv) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mv-row">
<span class="mv-lb">循环</span>
<select class="select" id="tk-repeat" name="repeat_rule">
<?php foreach (\App\Task::REPEAT as $rk => $rv): ?>
<option value="<?= e($rk) ?>"><?= e($rv) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mv-row">
<span class="mv-lb">分类</span>
<select class="select" id="tk-cat" name="category_id">
<option value="0">未分类</option>
<?php foreach ($categories as $c): ?>
<option value="<?= (int)$c['id'] ?>"><?= e((string)$c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<?php /* 用户第 7 条:看板 = 任务"归属哪摊事",新建时直接选(不再是顶上的切换条) */ ?>
<div class="mv-row">
<span class="mv-lb">看板</span>
<select class="select" id="tk-board" name="board_id">
<?php foreach ($boards as $b): ?>
<option value="<?= (int)$b['id'] ?>"><?= e((string)$b['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<?php /* 🔴 v44(用户第 5 条):「"共享给"后面有个括号,可多选,任何人完成都算完成,
这个括号里面的内容也去掉。」⇒ 标签只留「共享给」。
第 8 条的同一诉求:人多时不要下拉里瞪眼找。换成弹窗 + 首字母联想。
⚠️ 候选里去掉**自己**——「共享给自己」是空操作;组件没有"排除项",
所以在传进去之前先过滤掉。 */ ?>
<div class="mv-row">
<span class="mv-lb">共享给</span>
<?php
$ppName = 'shares[]';
$ppPeople = array_values(array_filter($people,
fn($p) => (int)$p['id'] !== (int)\App\Auth::personId()));
$ppSelected = [];
$ppMode = 'multi';
$ppOpenBtn = '+ 添加人';
$ppTitle = '共享给谁';
require __DIR__ . '/../_person_picker.php';
?>
</div>
<div class="mv-row">
<span class="mv-lb">备注</span>
<textarea class="input" id="tk-note" name="note" rows="2"></textarea>
</div>
<?php /* 🔴 v37 用户第 6 条第 2 点:「新建任务时,竟然没有添加附件,
这个应该加上。」
⚠️ 走全站统一的「点击或拖动进行上传」框(templates/_stage_drop.php);
字段名 `attach[]` 与详情页的附件上传**一致** ——
服务端 attach_save_files(..., 'attach', ...) 零改动就能收。
⚠️ 拖进来的走暂存(stage_token[]),随表单一起认领;
直接点选走 $_FILES 路径。两条路服务端都接(见 attach_save_files)。 */ ?>
<div class="mv-row">
<span class="mv-lb">附件</span>
<?php $sdName = 'attach[]'; $sdId = 'sd-tk-attach'; $sdSlot = 'photo';
require __DIR__ . '/../_stage_drop.php'; ?>
</div>
</div><?php /* /.mv-grid */ ?>
</div><?php /* /.cal-modal-body */ ?>
<div class="cal-modal-foot">
<span class="cal-modal-tip" id="taskAddTip"></span>
<button class="btn btn-sm" type="submit" form="taskAddForm" id="taskAddSubmit">新建</button>
<button class="btn btn-ghost btn-sm" type="button" id="taskAddCancel">取消</button>
</div>
</form>
</div>
</div>
<?php endif; ?>
<?php /* 🔴 v78(用户 2026-10-22):新建任务弹框与**首页**共用同一份
`templates/tasks/_add_modal.php`(不许各写一张 —— 字段迟早不同步)。
⚠️ 打开/关闭的脚本也搬到了 `assets/js/task_add.js`(两页共用一份)。
⚠️ 这里把本页已有的 `$boards` / `$categories` / `$people` 原样传进去;
新建完回看板(`back=tasks`)。 */
$tkAddBack = 'tasks';
$tkAddBoards = $boards;
$tkAddCats = $categories;
$tkAddPeople = $people;
require __DIR__ . '/_add_modal.php';
unset($tkAddBack, $tkAddBoards, $tkAddCats, $tkAddPeople); ?>
<?php /* 拖动的提交目标:JS 把 id / 目标**看板** / 参照顺序填进来再 submit。
放在看板**外面**,避免被任何一张卡片的 form 套住。 */ ?>
@@ -375,30 +234,9 @@ $lastColIdx = count($colList) - 1;
<script>
(function () {
/* ---- "新建任务"弹框(v37:从内联卡片改成浮层)----
⚠️ 不再 scrollIntoView —— 那是"卡片长在页面上"时代才需要的动作;
浮层固定在视口中央,底下的看板一动不动。 */
var addBtn = document.getElementById('taskAddBtn');
var addCard = document.getElementById('taskAddCard');
var addX = document.getElementById('taskAddX');
var addCancel = document.getElementById('taskAddCancel');
function openAdd() {
if (!addCard) { return; }
addCard.hidden = false;
var t = document.getElementById('tk-title');
if (t) { t.focus(); }
}
function closeAdd() { if (addCard) { addCard.hidden = true; } }
if (addBtn && addCard) { addBtn.addEventListener('click', openAdd); }
if (addX) { addX.addEventListener('click', closeAdd); }
if (addCancel) { addCancel.addEventListener('click', closeAdd); }
/* 点遮罩关(点面板内不关)/ Esc 关 —— 与站内其它浮层同一套行为。 */
if (addCard) {
addCard.addEventListener('click', function (e) { if (e.target === addCard) { closeAdd(); } });
}
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && addCard && !addCard.hidden) { closeAdd(); }
});
/* 🔴 v78:「新建任务」弹框的开关搬到了 `assets/js/task_add.js`
—— 首页那张卡也要开同一张弹框,两处各写一份必然有一处忘了 `fbBootAntd`。
本文件从这一行往下只剩**看板**自己的交互(搬卡 / 状态 / 拖动)。 */
/* ---- 左右搬(不需要拖也能用)----
⚠️ 列的集合**从 DOM 现取**,不要写死数组:看板是用户自己建的,