Files
FamilyBook_harmonyos/entry/src/main/ets/data/FbTaskRepo.ets
T

201 lines
9.3 KiB
Plaintext
Raw Normal View History

/**
* 家书 FamilyBook · 任务的**写操作**(鸿蒙端)
*
* 对应服务端 `app/ApiHandler.php` 的 `tasks()`:
* POST /api/v1/tasks 新建(title / note / due_at / board_id / status)
* PATCH /api/v1/tasks/<id> 改(标题/备注/截止/看板/负责人/分类)
* POST /api/v1/tasks/<id>/status 改状态(todo / doing / done)
* DELETE /api/v1/tasks/<id> 删
*
* 🔴 用户 2026-10-20:「任务也是一样啊,任务也点不进去,也没法那个完成,
* 新建任务、设置这些东西都这都没有。」
* ⇒ 端上原来这三件事**一件都没接**(按钮没 onTap、列表项没 onClick)。
*
* ⚠️ **状态只走 `/status` 子动作**:服务端 `PATCH /tasks/<id>` 里
* `'status' => (string)$t['status']` 是**原样回填**,显式忽略请求里的 status
* (见 ApiHandler 那行注释「状态只走 /status 子动作」)。
* 所以端上 toggle 完成必须打 `/status`,打 PATCH 会"看起来成功但状态没变"。
*/
import { fbApi, FbApiResult, FbApiOptions } from './FbHttp';
import { fbUploadOne } from './FbUpload';
/** 新建/编辑任务提交的字段 */
export interface FbTaskDraft {
title: string;
note: string;
/** `YYYY-MM-DD HH:MM`;空串 = 没有期限 */
dueAt: string;
/** 看板 id(0 = 不归指定看板 → 服务端落到「其他」那一列) */
boardId: number;
/** todo / doing / done(只在**新建**时生效;改状态走 `setTaskStatus`) */
status: string;
/**
* 父任务 id(v79;0 = 顶层任务)。
* 面板里「加一条子任务」就是 `parentId = 当前任务` 的新建请求。
*/
parentId: number;
/**
* 负责人 id。
*
* `> 0` = 指定这个人;`0` = 新建时"用默认值(我自己)"、编辑时"清空负责人";
* **`-1` = 这次不改**(编辑表单目前没有负责人这一项 ⇒ 不能传 0 把它抹掉)。
*/
assigneeId: number;
/** 分类 id(0 = 不分类) */
categoryId: number;
/** 循环规则:空 / day / week / month / year */
repeat: string;
}
/** 新建任务 */
export async function createTask(token: string, d: FbTaskDraft): Promise<FbApiResult> {
/* ⚠️ ArkTS 的 `arkts-no-untyped-obj-literals`:请求体必须
`Record<string, Object> = {}` 逐键赋值(见 FbTripRepo 同一说明)。 */
const body: Record<string, Object> = {};
body['title'] = d.title;
body['note'] = d.note;
body['due_at'] = d.dueAt;
body['board_id'] = d.boardId;
body['status'] = d.status;
/* v79:这三个原来没传 —— 端上因此**建不出子任务**、也指定不了负责人/分类。
⚠️ `assignee_id` 只在 >0 时才传:服务端 `Api::int('assignee_id', $pid)`
的默认值是"我自己",传 0 会把它覆盖成"没人负责"。 */
if (d.parentId > 0) {
body['parent_id'] = d.parentId;
}
if (d.assigneeId > 0) {
body['assignee_id'] = d.assigneeId;
}
if (d.categoryId > 0) {
body['category_id'] = d.categoryId;
}
if (d.repeat !== '') {
body['repeat_rule'] = d.repeat;
}
const opts: FbApiOptions = { method: 'POST', token: token, body: body };
return await fbApi('/api/v1/tasks', opts);
}
/**
* 改任务(标题 / 备注 / 截止 / 看板 / 负责人 / 分类 / 循环)。
*
* ⚠️ **不带 status**:服务端 PATCH 会忽略它(见文件头说明),
* 带上只会让人以为"改成功了",实际状态没动。
*/
export async function updateTask(token: string, id: number, d: FbTaskDraft): Promise<FbApiResult> {
const body: Record<string, Object> = {};
body['title'] = d.title;
body['note'] = d.note;
body['due_at'] = d.dueAt;
body['board_id'] = d.boardId;
/* v79:负责人/分类/循环也走 PATCH(服务端这三个字段都认)。
⚠️ `assignee_id` 只在**明确要改**(>=0)时才传:编辑表单里没有负责人这一项,
传 0 会把原来指派的负责人**静默清空**(与 v77「没传 ≠ 清空」同一类 bug)。
⚠️ 分类和循环则**必须**照传(0 / 空串就是"清空",那是合法操作)。 */
if (d.assigneeId >= 0) {
body['assignee_id'] = d.assigneeId;
}
body['category_id'] = d.categoryId;
body['repeat_rule'] = d.repeat;
const opts: FbApiOptions = { method: 'PATCH', token: token, body: body };
return await fbApi('/api/v1/tasks/' + id.toString(), opts);
}
/** 改状态(勾完成 / 取消完成 / 标记进行中) */
export async function setTaskStatus(token: string, id: number, status: string): Promise<FbApiResult> {
const body: Record<string, Object> = {};
body['status'] = status;
const opts: FbApiOptions = { method: 'POST', token: token, body: body };
return await fbApi('/api/v1/tasks/' + id.toString() + '/status', opts);
}
/** 删任务 */
export async function deleteTask(token: string, id: number): Promise<FbApiResult> {
const opts: FbApiOptions = { method: 'DELETE', token: token };
return await fbApi('/api/v1/tasks/' + id.toString(), opts);
}
/* ═══════════════════════════════════════════════════════════════
v79(用户 2026-10-22):「鸿蒙端的功能,也需要同样适配……
任务模块的功能需要完善。」
⇒ 下面这些是网页 v78「就地面板」在端上的对等物。
⚠️ 除 `getTask` 外全部返回 `FbApiResult`,且**服务端都会回整条任务** ——
调用方拿到 `res.data` 直接重画面板,不用再补一次 GET。
═══════════════════════════════════════════════════════════════ */
/** 取一条任务的详情(含子任务 / 留言 / 附件 / 共享人) */
export async function getTask(token: string, id: number): Promise<FbApiResult> {
const opts: FbApiOptions = { method: 'GET', token: token };
return await fbApi('/api/v1/tasks/' + id.toString(), opts);
}
/**
* 挪到另一个看板(网页版「分组」的等价物)。
*
* ⚠️ 走 `/move` 而不是 `PATCH`:`/move` 会顺手把这一列重排 position;
* PATCH 只改 board_id,同列里会留下重复的 position(网页端拖拽用的是 /move)。
*/
export async function moveTask(token: string, id: number, boardId: number): Promise<FbApiResult> {
const body: Record<string, Object> = {};
body['board_id'] = boardId;
const opts: FbApiOptions = { method: 'POST', token: token, body: body };
return await fbApi('/api/v1/tasks/' + id.toString() + '/move', opts);
}
/** 发一条留言(服务端只回 `{id}`,要整块内容就再 `getTask` 一次) */
export async function addTaskComment(token: string, id: number, content: string): Promise<FbApiResult> {
const body: Record<string, Object> = {};
body['content'] = content;
const opts: FbApiOptions = { method: 'POST', token: token, body: body };
return await fbApi('/api/v1/tasks/' + id.toString() + '/comments', opts);
}
/**
* 传一个附件(图片/文档都行,服务端按 mime 自己分)。
*
* @return 空串 = 成功;否则是给人看的错误说明(与 `fbUploadOne` 的约定一致)
*/
export async function uploadTaskAttach(token: string, id: number, uri: string): Promise<string> {
return await fbUploadOne('/api/v1/tasks/' + id.toString() + '/attach', 'file', uri, token);
}
/** 删一个附件(列表里点 ✕) */
export async function delTaskAttach(token: string, id: number, aid: number): Promise<FbApiResult> {
/* ⚠️ 两处都踩过,别改回去:
① 路由只解析「资源/id/子动作」**三段** ⇒ 不能写成 `/attach/<aid>`,
那样服务端只会拿到 `$sub='attach'`,附件 id 被静默丢掉;
② 附件 id 走 **query**,不要塞 JSON body —— 实测 `DELETE` 带 JSON 体时
服务端读到的是空(`Api::param` 拿不到),症状是
「要指定删哪个附件」400,而请求看起来完全正常。 */
const q: Record<string, string> = {};
q['attachment_id'] = aid.toString();
const opts: FbApiOptions = { method: 'DELETE', token: token, query: q };
return await fbApi('/api/v1/tasks/' + id.toString() + '/attach', opts);
}
/** 共享给谁(工单 #26) */
export async function fetchTaskShares(token: string, id: number): Promise<FbApiResult> {
const opts: FbApiOptions = { token: token, authProbe: true };
return await fbApi('/api/v1/tasks/' + id.toString() + '/shares', opts);
}
/** 共享给某人(perm = read / write;任务默认 write,与 Web 端 tasks/share 一致) */
export async function shareTask(token: string, id: number, personId: number,
perm: string): Promise<FbApiResult> {
const body: Record<string, Object> = {};
body['person_id'] = personId;
body['perm'] = perm;
const opts: FbApiOptions = { method: 'POST', token: token, body: body };
return await fbApi('/api/v1/tasks/' + id.toString() + '/shares', opts);
}
/** 取消某人的共享(person_id 走 query,DELETE 带 body 服务端读不到) */
export async function unshareTask(token: string, id: number, personId: number): Promise<FbApiResult> {
const q: Record<string, string> = {};
q['person_id'] = personId.toString();
const opts: FbApiOptions = { method: 'DELETE', token: token, query: q };
return await fbApi('/api/v1/tasks/' + id.toString() + '/shares', opts);
}