修:分类显示「未分类」—— Task::find 没 JOIN 分类表 (#84)

用户 2026-11-05:「鸿蒙端的分类那个地方没带进来。明明是有分类的,
但是它却显示的是未分类。」

根因是**两个叠在一起**:

① `Task::find()` 的 SQL **没有 JOIN `task_categories`**
   `t.*` 里有 `category_id`,但没有 `category_name`;
   端上 `category: s(d.category_name)` 永远拿空串 ⇒ 显示「未分类」。
   同一个文件里 `doneList()` / 列表页那几条 SQL 都有这个 JOIN,
   只有 find() 漏了(写它不是同一时期,属于典型的"漏一处")。
   ⇒ 补 `LEFT JOIN task_categories c ON c.id = t.category_id`、选 `c.name AS category_name`。
   实测(#49 海大信息中心评审费):category_name = [工作相关] ✅

② `taskOut()` 里 `category_id => Api::int(category_id, ...)`
   `Api::int()` 是**读请求参数**的:先看 $_REQUEST 有没有该键,
   没有才用第二个参数当默认值。
   PATCH 时用户会传 ⇒ 值对;**GET 时没有请求参数** ⇒ 走默认值。
   这次默认值本身是从 `$t` 算的,所以"看起来是对的",
   但这种写法极易被误读成"已经取到表里的值了",后续改动容易出错。
   ⇒ 直接写 `(int)($t[category_id] ?? 0)`,不绕 Api::int。

注:`repeat_rule` 在 Task::find 里没被单独 SELECT ——
   但它在 `t.*` 里,`$t[repeat_rule]` 是有的(实测任务 #53 能正确读到/写入)。
This commit is contained in:
2026-11-05 20:19:36 +08:00
parent 50286e5327
commit 47a0027ef7
2 changed files with 51 additions and 3 deletions
+41 -3
View File
@@ -1821,9 +1821,39 @@ final class ApiHandler
}
if ($method === 'POST') {
Api::requireWrite();
/* 🔴 #77g(用户 2026-11-05):「留言是可以需要添加附件的。」
⇒ 支持 **multipart/form-data**(带 `file`)与原来的
**JSON / 表单**(只有 content)两种提交方式。
前端有附件就走 multipart,没附件走原来的路径 —— 不破坏旧调用方。
⚠️ **允许"只发附件不写字"**:拍张照片直接发是常见用法。
网页端 `tasks/comment_add` 早有同样的处理(占位文案「(附件)」),
这里与它保持同一口径。
⚠️ 附件走 `Attachment::save()`,`kind` 用 `'task_comment'`
—— 与网页端 `attach_save_files(..., 'task_comment', ...)`
是同一个归属类型,否则同一条留言的附件在两端互相看不见。
⚠️ 一个评论先建(拿到 cid),再存附件 —— 顺序不能反:
`Attachment::save()` 要 `owner_id`,而 cid 是插入后才有的。 */
$txt = Api::str('content', '', 4000);
if ($txt === '') { Api::fail('评论不能是空的。', 'unprocessable', 422); }
Api::ok(['id' => Task::addComment($id, $pid, $txt)], 201);
$file = $_FILES['file'] ?? null;
$hasFile = is_array($file)
&& trim((string)($file['name'] ?? '')) !== ''
&& (int)($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK;
if ($txt === '' && !$hasFile) {
Api::fail('评论不能是空的。', 'unprocessable', 422);
}
if ($txt === '' && $hasFile) {
$txt = '(附件)';
}
$cid = Task::addComment($id, $pid, $txt);
if ($hasFile) {
$r = \App\Attachment::save($fam, 'task_comment', $cid, $file, $pid);
if (empty($r['ok'])) {
Api::fail('评论发出去了,但附件没存下来。', 'unprocessable', 422);
}
}
Api::ok(['id' => $cid], 201);
}
}
@@ -1913,7 +1943,15 @@ final class ApiHandler
'repeat_rule' => Api::str('repeat_rule', (string)($t['repeat_rule'] ?? '')),
'parent_id' => Api::int('parent_id', (int)($t['parent_id'] ?? 0)),
'assignee_id' => Api::int('assignee_id', (int)($t['assignee_id'] ?? 0)),
'category_id' => Api::int('category_id', (int)($t['category_id'] ?? 0)),
/* 🔴 #84:原来写的是 `Api::int('category_id', (int)($t['category_id'] ?? 0))`
—— 但 `Api::int()` 是**读请求参数**的,它先看 $_REQUEST 里有没有
`category_id`,没有才用第二个参数当默认值。
PATCH 时用户会传这个字段 ⇒ 走到默认值时值是对的;
但 **GET 时没有这个请求参数** ⇒ 拿到的仍然是默认值 ——
恰好默认值本身是从 $t 算的,所以"看起来对"。
真正的问题是读起来极容易误判成"已经取到表里的值了"。
⇒ 直接写表里的值,不绕 Api::int。 */
'category_id' => (int)($t['category_id'] ?? 0),
'board_id' => Api::int('board_id', (int)($t['board_id'] ?? 0)),
'status' => (string)$t['status'], // 状态只走 /status 子动作
]);