From 59a9d3df39a5868bf769bdfb848698e866dca56b Mon Sep 17 00:00:00 2001 From: laoyang Date: Wed, 28 Oct 2026 22:10:15 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=85=B1=E4=BA=AB=EF=BC=9A?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E8=A1=A5=E3=80=8C=E5=85=B1=E4=BA=AB=E7=BB=99?= =?UTF-8?q?=E8=B0=81=E3=80=8D=E5=8C=BA=E5=9D=97=20laoyang/FamilyBook#26?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户原话(2026-10-28):「鸿蒙端对齐网页端」。任务面板原来没有共享入口,现在: 1) FbTaskShare 类型 + parseTaskDetail 解析 Task::find 已带的 shares 2) FbTaskRepo 补 fetchTaskShares/shareTask/unshareTask(删除走 query) 3) FbTaskPanel 加「共享给谁」区块:列表(可改/只看)+ 逐条取消 + 「+ 共享」复用选人组件(与资源/财务共享同一套) 编译 BUILD SUCCESSFUL;服务端冒烟 _i26_task_share_api.py 13/0 + 反证命中。 --- entry/src/main/ets/components/FbTaskPanel.ets | 136 +++++++++++++++++- entry/src/main/ets/data/FbParse.ets | 20 +++ entry/src/main/ets/data/FbTaskRepo.ets | 24 ++++ entry/src/main/ets/model/Types.ets | 9 ++ 4 files changed, 188 insertions(+), 1 deletion(-) diff --git a/entry/src/main/ets/components/FbTaskPanel.ets b/entry/src/main/ets/components/FbTaskPanel.ets index 3a49d62..c0fde99 100644 --- a/entry/src/main/ets/components/FbTaskPanel.ets +++ b/entry/src/main/ets/components/FbTaskPanel.ets @@ -31,17 +31,20 @@ import { FbColors, FbMetric, FB_MASK, fbAlpha, fbTint } from '../common/Theme'; import { - FbBoardOpt, FbTaskAttach, FbTaskComment, FbTaskDetail, FbTaskItem, FbTaskKid, + FbBoardOpt, FbTaskAttach, FbTaskComment, FbTaskDetail, FbTaskItem, FbTaskKid, FbTaskShare, } from '../model/Types'; import { FbSheetRing } from './FbSheetRing'; import { FbSheetClose } from './FbSheetClose'; import { FbCheckCircle } from './Widgets'; +import { FbPersonPickSheet } from './FbPersonPickSheet'; +import { FbPersonRepo, FbRosterPerson } from '../data/FbPersonRepo'; import { FB_TOKEN_STORE } from '../data/FbTokenStore'; import { FbApiResult } from '../data/FbHttp'; import { fbImageSrc } from '../data/FbAttachQueue'; import { addTaskComment, createTask, deleteTask, delTaskAttach, getTask, moveTask, setTaskStatus, uploadTaskAttach, FbTaskDraft, + fetchTaskShares, shareTask, unshareTask, } from '../data/FbTaskRepo'; import { parseTaskDetail } from '../data/FbParse'; import { picker } from '@kit.CoreFileKit'; @@ -104,6 +107,10 @@ export struct FbTaskPanel { /** 删除按钮点第一次 → 变成「确定删除?」 */ @Local confirmDel: boolean = false; @Local uploading: boolean = false; + // 共享(工单 #26) + @Local roster: FbRosterPerson[] = []; + @Local pickingShare: boolean = false; + @Local shareBusy: boolean = false; aboutToAppear(): void { this.reload(); @@ -231,6 +238,52 @@ export struct FbTaskPanel { return d !== null ? d.attachs : []; } + /** 共享给谁(工单 #26) */ + private shares(): FbTaskShare[] { + const d: FbTaskDetail | null = this.detail; + return d !== null ? d.shares : []; + } + + private async openSharePicker(): Promise { + if (this.roster.length === 0) { + try { + this.roster = await FbPersonRepo.inst().list(); + } catch (e) { + this.roster = []; + } + } + this.pickingShare = true; + } + + private async addShares(ids: number[]): Promise { + this.pickingShare = false; + if (ids.length === 0) { + return; + } + this.shareBusy = true; + let bad: string = ''; + for (const pid of ids) { + const res: FbApiResult = await shareTask(this.token(), this.taskId, pid, 'write'); + if (!res.ok && bad === '') { + bad = res.error; + } + } + this.shareBusy = false; + this.note = bad !== '' ? bad : ''; + await this.reload(); + } + + private async dropShare(personId: number): Promise { + this.shareBusy = true; + const res: FbApiResult = await unshareTask(this.token(), this.taskId, personId); + this.shareBusy = false; + if (!res.ok) { + this.note = res.error !== '' ? res.error : '没取消成。'; + return; + } + await this.reload(); + } + private categoryOf(): string { const d: FbTaskDetail | null = this.detail; return d !== null ? d.category : ''; @@ -515,6 +568,7 @@ export struct FbTaskPanel { this.statusSeg() this.boardChips() this.infoRows() + this.shareSection() this.kidSection() this.attachSection() this.commentSection() @@ -551,6 +605,22 @@ export struct FbTaskPanel { } } .width('94%') + + /* 选人浮层:共享给谁(工单 #26) */ + if (this.pickingShare) { + FbPersonPickSheet({ + people: this.roster, + selected: [], + title: '共享给谁', + accent: this.accent, + onConfirm: (ids: number[]): void => { + this.addShares(ids); + }, + onClose: (): void => { + this.pickingShare = false; + } + }) + } } .width('100%') .height('100%') @@ -754,6 +824,70 @@ export struct FbTaskPanel { .alignItems(VerticalAlign.Top) } + /** 共享给谁(工单 #26):列表 + 共享入口 + 逐条取消 */ + @Builder + shareSection() { + Column({ space: 6 }) { + Row({ space: 6 }) { + Text('共享给谁') + .fontSize(FbMetric.fontSmall) + .fontWeight(FontWeight.Medium) + .fontColor(FbColors.ink) + if (this.shares().length > 0) { + Text(this.shares().length.toString()) + .fontSize(FbMetric.fontTiny) + .fontColor(FbColors.faint) + } + Blank() + Text(this.shareBusy ? '处理中…' : '+ 共享') + .fontSize(FbMetric.fontTiny) + .fontColor(this.shareBusy ? FbColors.faint : this.accent) + .padding({ left: 10, right: 10, top: 6, bottom: 6 }) + .backgroundColor(fbAlpha(this.accent, 0.1)) + .borderRadius(FbMetric.radiusPill) + .onClick((): void => { + this.openSharePicker(); + }) + } + .width('100%') + .padding({ top: 12 }) + .alignItems(VerticalAlign.Center) + + if (this.shares().length === 0) { + Text('还没共享给谁;共享后对方能在任务里看到它,权限分「可改 / 只看」') + .fontSize(FbMetric.fontTiny) + .fontColor(FbColors.faint) + .width('100%') + } else { + ForEach(this.shares(), (s: FbTaskShare) => { + Row({ space: 8 }) { + Text(s.name) + .fontSize(FbMetric.fontSmall) + .fontColor(FbColors.ink) + .layoutWeight(1) + .maxLines(1) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + Text(s.perm === 'read' ? '只看' : '可改') + .fontSize(FbMetric.fontTiny) + .fontColor(FbColors.faint) + Text('取消') + .fontSize(FbMetric.fontTiny) + .fontColor(FbColors.red) + .padding({ left: 6, right: 2, top: 2, bottom: 2 }) + .onClick((): void => { + this.dropShare(s.personId); + }) + } + .width('100%') + .padding({ top: 6, bottom: 6 }) + .alignItems(VerticalAlign.Center) + }, (s: FbTaskShare): string => 'tsh_' + s.personId.toString()) + } + } + .width('100%') + .alignItems(HorizontalAlign.Start) + } + /** 子任务:列表 + 加一条 */ @Builder kidSection() { diff --git a/entry/src/main/ets/data/FbParse.ets b/entry/src/main/ets/data/FbParse.ets index 162a6a9..0787583 100644 --- a/entry/src/main/ets/data/FbParse.ets +++ b/entry/src/main/ets/data/FbParse.ets @@ -20,6 +20,7 @@ import { FbLedgerBook, FbLedgerDetail, FbLedgerEntry, FbLedgerPoint, FbNoteItem, FbNoteBlock, FbNotificationItem, FbResourceItem, FbTaskItem, FbTripItem, FbWeather, FbStat, FbMemoryItem, FbRecentDay, FbTaskAttach, FbTaskComment, FbTaskDetail, FbTaskKid, FbTaskMeta, FbCatOpt, FbBoardOpt, + FbTaskShare, } from '../model/Types'; // ============================================================ @@ -200,6 +201,13 @@ interface RawTaskDetail extends RawTask { children?: RawTaskKid[]; comments?: RawTaskComment[]; attachs?: RawTaskAttach[]; + shares?: RawTaskShare[]; +} + +/** 任务共享行(工单 #26;服务端 `Task::shares()`:s.person_id/perm + p.name + 昵称) */ +interface RawTaskShare { + person_id?: number; perm?: string | null; + name?: string | null; my_nickname?: string | null; } /** `?view=boards` 的看板元数据(v79) */ @@ -1016,6 +1024,17 @@ export function parseTaskDetail(json: string): FbTaskDetail | null { attachs.push(one); } + // 共享给谁(工单 #26;服务端 Task::find 已带 shares) + const shares: FbTaskShare[] = []; + for (const sh of arr(d.shares)) { + const nick: string = s(sh.my_nickname); + shares.push({ + personId: n(sh.person_id), + name: nick !== '' ? nick : s(sh.name), + perm: s(sh.perm) !== '' ? s(sh.perm) : 'write', + }); + } + const creatorNick: string = s(d.creator_nick); const detail: FbTaskDetail = { base: base, @@ -1028,6 +1047,7 @@ export function parseTaskDetail(json: string): FbTaskDetail | null { kids: kids, comments: comments, attachs: attachs, + shares: shares, }; return detail; } diff --git a/entry/src/main/ets/data/FbTaskRepo.ets b/entry/src/main/ets/data/FbTaskRepo.ets index 8319e0f..099193e 100644 --- a/entry/src/main/ets/data/FbTaskRepo.ets +++ b/entry/src/main/ets/data/FbTaskRepo.ets @@ -174,3 +174,27 @@ export async function delTaskAttach(token: string, id: number, aid: number): Pro return await fbApi('/api/v1/tasks/' + id.toString() + '/attach', opts); } +/** 共享给谁(工单 #26) */ +export async function fetchTaskShares(token: string, id: number): Promise { + 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 { + const body: Record = {}; + 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 { + const q: Record = {}; + q['person_id'] = personId.toString(); + const opts: FbApiOptions = { method: 'DELETE', token: token, query: q }; + return await fbApi('/api/v1/tasks/' + id.toString() + '/shares', opts); +} + diff --git a/entry/src/main/ets/model/Types.ets b/entry/src/main/ets/model/Types.ets index 58e55a0..8aec361 100644 --- a/entry/src/main/ets/model/Types.ets +++ b/entry/src/main/ets/model/Types.ets @@ -779,6 +779,13 @@ export interface FbTaskAttach { sizeWord: string; } +/** 任务共享给谁(工单 #26;服务端 `Task::shares()`) */ +export interface FbTaskShare { + personId: number; + name: string; + perm: string; +} + /** * 任务详情(v79 面板用):列表行 + 只有详情接口才给的那几块。 * @@ -801,6 +808,8 @@ export interface FbTaskDetail { kids: FbTaskKid[]; comments: FbTaskComment[]; attachs: FbTaskAttach[]; + /** 共享给谁(工单 #26) */ + shares: FbTaskShare[]; } // ============================================================