v79 服务端:端上任务面板需要的三个口子
配合鸿蒙端 v79(任务面板 / 任务模块功能补齐),服务端只补三处,
其余全部复用既有路由。
① GET /api/v1/tasks?view=boards —— 只回看板 + 分类元数据,**不带任务**。
端上原来是从已加载任务里反推看板,那样只能看到"有任务的看板":
用户刚建好、还没放任务的看板在端上永远选不到(表现为"网页能建,
端上挪不过去")。
② POST /api/v1/tasks/<id>/attach(multipart 字段 file)与
DELETE /api/v1/tasks/<id>/attach?attachment_id=<aid> —— 都要先
requireWrite() 再 Task::canWrite(),两者都回整条任务(端上直接重画)。
③ ApiHandler::taskOut() —— Task::find() 只 SELECT t.*,不含看板名/色
⇒ 详情与各动作回包统一补 board_name / board_color_calc(7 个返回点)。
🔴 两条踩过的坑写进注释:
· Api::dispatch() 只解析「资源/id/子动作」三段,附件 id 不能拼进路径;
· DELETE 带 JSON body 时 Api::param 读不到(实测),id 必须走 query。
另新增 Attachment::removeFrom():删附件前先确认它确实挂在这条任务上,
拿别条任务的附件 id 来删自己的 → 404(已由 _fb_v79_api.py 钉死)。
验证:php -l 全过、e2e 1558/0、_fb_v79_api.py 26/0、已部署并冒烟。
This commit is contained in:
+100
-5
@@ -1712,6 +1712,28 @@ final class ApiHandler
|
||||
任务
|
||||
═══════════════════════════════════════════════════════════════ */
|
||||
|
||||
/**
|
||||
* 一条任务 → 给 App 的形状。
|
||||
*
|
||||
* 🔴 v79 为什么要包这一层:`Task::find()` 只 SELECT `t.*`,**不含看板名/看板色**
|
||||
* (网页端是在模板里再查一次)。端上面板要显示"这条在哪个看板"、
|
||||
* 还要拿它的颜色去画那颗小圆点 —— 让端上为了两个字段再打一次
|
||||
* `/tasks?view=boards` 是多余的一跳,就在服务端补齐。
|
||||
* `board_color_calc` 与首页任务用的是**同一个**派生口径,
|
||||
* 否则同一个看板在首页和详情里是两种颜色(用户能一眼看出来)。
|
||||
*/
|
||||
private static function taskOut(int $fam, int $id, int $pid): array
|
||||
{
|
||||
$t = Task::find($fam, $id, $pid);
|
||||
if (!$t) {
|
||||
return [];
|
||||
}
|
||||
$bid = (int)($t['board_id'] ?? 0);
|
||||
$t['board_name'] = Task::boardName($fam, $bid > 0 ? $bid : null);
|
||||
$t['board_color_calc'] = Task::boardColorById($fam, $bid > 0 ? $bid : null);
|
||||
return $t;
|
||||
}
|
||||
|
||||
private function tasks(int $id, string $sub, string $method): void
|
||||
{
|
||||
[$fam, $pid] = Api::me();
|
||||
@@ -1724,12 +1746,12 @@ final class ApiHandler
|
||||
if ($sub === 'status' && ($method === 'POST' || $method === 'PATCH')) {
|
||||
Api::requireWrite();
|
||||
Task::setStatus($fam, $id, Api::str('status', 'todo'), Api::int('position'));
|
||||
Api::ok(Task::find($fam, $id, $pid));
|
||||
Api::ok(self::taskOut($fam, $id, $pid));
|
||||
}
|
||||
if ($sub === 'move' && $method === 'POST') {
|
||||
Api::requireWrite();
|
||||
Task::moveToBoard($fam, $id, Api::int('board_id'), Api::int('before'), $pid);
|
||||
Api::ok(Task::find($fam, $id, $pid));
|
||||
Api::ok(self::taskOut($fam, $id, $pid));
|
||||
}
|
||||
if ($sub === 'comments') {
|
||||
if ($method === 'GET') {
|
||||
@@ -1743,7 +1765,53 @@ final class ApiHandler
|
||||
}
|
||||
}
|
||||
|
||||
if ($method === 'GET') { Api::ok($t); }
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
v79(用户 2026-10-22):「鸿蒙端的功能,也需要同样适配……
|
||||
任务模块的功能需要完善。」
|
||||
⇒ 端上要对齐网页 v78 的「就地面板」,而面板里的**加附件**一直没有 API
|
||||
(网页端走的是 `tasks/attach_add` 这条表单路由)。
|
||||
────────────────────────────────────────────────────────────
|
||||
POST /api/v1/tasks/<id>/attach multipart,字段名 `file`
|
||||
DELETE /api/v1/tasks/<id>/attach 参数 `attachment_id`
|
||||
⚠️ 路由只解析「资源/id/子动作」**三段**(`Api::dispatch`),没有第四段
|
||||
⇒ 删附件**必须**把 id 放参数里。写成 `/attach/<aid>` 的话,
|
||||
`Api::dispatch` 只会给到 `$sub='attach'`,aid 直接丢掉(静默 404)。
|
||||
⚠️ 两个分支都回**整条任务**(`Task::find`,含 attachs):端上拿到就能
|
||||
直接重画面板,不用再补一次 GET。
|
||||
⚠️ 权限除了 `requireWrite()` 还要 `Task::canWrite()`:前者只说明
|
||||
"这个账号不是只读访客",后者才说明"这条任务归我管"。
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
if ($sub === 'attach') {
|
||||
Api::requireWrite();
|
||||
if (!Task::canWrite($t, $pid)) {
|
||||
Api::fail('这项任务不能改。', 'forbidden', 403);
|
||||
}
|
||||
if ($method === 'POST') {
|
||||
$file = $_FILES['file'] ?? null;
|
||||
if (!is_array($file)) {
|
||||
Api::fail('没有收到文件。', 'unprocessable', 422);
|
||||
}
|
||||
$r = \App\Attachment::save($fam, Task::ATTACH, $id, $file, $pid);
|
||||
if (empty($r['ok'])) {
|
||||
$msg = (string)($r['error'] ?? '');
|
||||
Api::fail($msg !== '' ? $msg : '文件没存下来', 'unprocessable', 422);
|
||||
}
|
||||
Api::ok(self::taskOut($fam, $id, $pid), 201);
|
||||
}
|
||||
if ($method === 'DELETE') {
|
||||
$aid = Api::int('attachment_id');
|
||||
if ($aid <= 0) {
|
||||
Api::fail('要指定删哪个附件。', 'bad_request', 400);
|
||||
}
|
||||
if (!\App\Attachment::removeFrom(Task::ATTACH, $id, $aid)) {
|
||||
Api::notFound('这个附件');
|
||||
}
|
||||
Api::ok(self::taskOut($fam, $id, $pid));
|
||||
}
|
||||
Api::fail('不支持的方法。', 'method_not_allowed', 405);
|
||||
}
|
||||
|
||||
if ($method === 'GET') { Api::ok(self::taskOut($fam, $id, $pid)); }
|
||||
if ($method === 'PATCH' || $method === 'PUT') {
|
||||
Api::requireWrite();
|
||||
Task::update($fam, $id, [
|
||||
@@ -1757,7 +1825,7 @@ final class ApiHandler
|
||||
'board_id' => Api::int('board_id', (int)($t['board_id'] ?? 0)),
|
||||
'status' => (string)$t['status'], // 状态只走 /status 子动作
|
||||
]);
|
||||
Api::ok(Task::find($fam, $id, $pid));
|
||||
Api::ok(self::taskOut($fam, $id, $pid));
|
||||
}
|
||||
if ($method === 'DELETE') {
|
||||
Api::requireWrite();
|
||||
@@ -1787,7 +1855,7 @@ final class ApiHandler
|
||||
foreach (Api::people(Api::ints('shares'), $fam) as $sp) {
|
||||
Task::setShare($newId, $sp, 'write');
|
||||
}
|
||||
Api::ok(Task::find($fam, $newId, $pid), 201);
|
||||
Api::ok(self::taskOut($fam, $newId, $pid), 201);
|
||||
}
|
||||
|
||||
// ---- 列表:三种视图 ----
|
||||
@@ -1801,6 +1869,33 @@ final class ApiHandler
|
||||
if ($view === 'done') {
|
||||
Api::ok(Task::doneList($fam, $pid, Api::int('category_id'), Api::str('keyword')));
|
||||
}
|
||||
/* v79:**只给元数据**(看板 + 分类),不带任务。
|
||||
🔴 为什么需要它:端上「挪看板 / 选分类」要的是"**全部**看板",
|
||||
而看板视图(下面那段)每列都塞满了任务,为了拿 5 个名字去解析几百条任务
|
||||
很浪费;更关键的是——**空看板在别的视图里根本不存在**
|
||||
(端上原来只能从已加载任务里反推看板 ⇒ 空看板永远选不到)。
|
||||
网页端同一份数据来自 `Task::boards()` / `Task::categories()`。 */
|
||||
if ($view === 'boards') {
|
||||
$bs = [];
|
||||
foreach (Task::boards($fam) as $b) {
|
||||
$bs[] = [
|
||||
'id' => (int)$b['id'],
|
||||
'name' => (string)$b['name'],
|
||||
'color' => Task::boardColor($b['color'] ?? null, (string)$b['name']),
|
||||
'open' => (int)($b['open_count'] ?? 0),
|
||||
];
|
||||
}
|
||||
$cs = [];
|
||||
foreach (Task::categories($fam) as $c) {
|
||||
$cs[] = ['id' => (int)$c['id'], 'name' => (string)$c['name']];
|
||||
}
|
||||
Api::ok([
|
||||
'boards' => $bs,
|
||||
'categories' => $cs,
|
||||
'status' => Task::STATUS,
|
||||
'repeat' => Task::REPEAT,
|
||||
]);
|
||||
}
|
||||
// 看板视图:把"列"与"任务"一起给出来(App 一次请求就够画整屏)
|
||||
$boards = Task::boards($fam);
|
||||
/* ⚠️ `Task::boardColumns()` 返回的是**包了一层的数组**
|
||||
|
||||
Reference in New Issue
Block a user