任务共享:面板补「共享给谁」区块 laoyang/FamilyBook#26
用户原话(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 + 反证命中。
This commit is contained in:
@@ -31,17 +31,20 @@
|
|||||||
|
|
||||||
import { FbColors, FbMetric, FB_MASK, fbAlpha, fbTint } from '../common/Theme';
|
import { FbColors, FbMetric, FB_MASK, fbAlpha, fbTint } from '../common/Theme';
|
||||||
import {
|
import {
|
||||||
FbBoardOpt, FbTaskAttach, FbTaskComment, FbTaskDetail, FbTaskItem, FbTaskKid,
|
FbBoardOpt, FbTaskAttach, FbTaskComment, FbTaskDetail, FbTaskItem, FbTaskKid, FbTaskShare,
|
||||||
} from '../model/Types';
|
} from '../model/Types';
|
||||||
import { FbSheetRing } from './FbSheetRing';
|
import { FbSheetRing } from './FbSheetRing';
|
||||||
import { FbSheetClose } from './FbSheetClose';
|
import { FbSheetClose } from './FbSheetClose';
|
||||||
import { FbCheckCircle } from './Widgets';
|
import { FbCheckCircle } from './Widgets';
|
||||||
|
import { FbPersonPickSheet } from './FbPersonPickSheet';
|
||||||
|
import { FbPersonRepo, FbRosterPerson } from '../data/FbPersonRepo';
|
||||||
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
import { FB_TOKEN_STORE } from '../data/FbTokenStore';
|
||||||
import { FbApiResult } from '../data/FbHttp';
|
import { FbApiResult } from '../data/FbHttp';
|
||||||
import { fbImageSrc } from '../data/FbAttachQueue';
|
import { fbImageSrc } from '../data/FbAttachQueue';
|
||||||
import {
|
import {
|
||||||
addTaskComment, createTask, deleteTask, delTaskAttach, getTask, moveTask,
|
addTaskComment, createTask, deleteTask, delTaskAttach, getTask, moveTask,
|
||||||
setTaskStatus, uploadTaskAttach, FbTaskDraft,
|
setTaskStatus, uploadTaskAttach, FbTaskDraft,
|
||||||
|
fetchTaskShares, shareTask, unshareTask,
|
||||||
} from '../data/FbTaskRepo';
|
} from '../data/FbTaskRepo';
|
||||||
import { parseTaskDetail } from '../data/FbParse';
|
import { parseTaskDetail } from '../data/FbParse';
|
||||||
import { picker } from '@kit.CoreFileKit';
|
import { picker } from '@kit.CoreFileKit';
|
||||||
@@ -104,6 +107,10 @@ export struct FbTaskPanel {
|
|||||||
/** 删除按钮点第一次 → 变成「确定删除?」 */
|
/** 删除按钮点第一次 → 变成「确定删除?」 */
|
||||||
@Local confirmDel: boolean = false;
|
@Local confirmDel: boolean = false;
|
||||||
@Local uploading: boolean = false;
|
@Local uploading: boolean = false;
|
||||||
|
// 共享(工单 #26)
|
||||||
|
@Local roster: FbRosterPerson[] = [];
|
||||||
|
@Local pickingShare: boolean = false;
|
||||||
|
@Local shareBusy: boolean = false;
|
||||||
|
|
||||||
aboutToAppear(): void {
|
aboutToAppear(): void {
|
||||||
this.reload();
|
this.reload();
|
||||||
@@ -231,6 +238,52 @@ export struct FbTaskPanel {
|
|||||||
return d !== null ? d.attachs : [];
|
return d !== null ? d.attachs : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 共享给谁(工单 #26) */
|
||||||
|
private shares(): FbTaskShare[] {
|
||||||
|
const d: FbTaskDetail | null = this.detail;
|
||||||
|
return d !== null ? d.shares : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async openSharePicker(): Promise<void> {
|
||||||
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
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 {
|
private categoryOf(): string {
|
||||||
const d: FbTaskDetail | null = this.detail;
|
const d: FbTaskDetail | null = this.detail;
|
||||||
return d !== null ? d.category : '';
|
return d !== null ? d.category : '';
|
||||||
@@ -515,6 +568,7 @@ export struct FbTaskPanel {
|
|||||||
this.statusSeg()
|
this.statusSeg()
|
||||||
this.boardChips()
|
this.boardChips()
|
||||||
this.infoRows()
|
this.infoRows()
|
||||||
|
this.shareSection()
|
||||||
this.kidSection()
|
this.kidSection()
|
||||||
this.attachSection()
|
this.attachSection()
|
||||||
this.commentSection()
|
this.commentSection()
|
||||||
@@ -551,6 +605,22 @@ export struct FbTaskPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('94%')
|
.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%')
|
.width('100%')
|
||||||
.height('100%')
|
.height('100%')
|
||||||
@@ -754,6 +824,70 @@ export struct FbTaskPanel {
|
|||||||
.alignItems(VerticalAlign.Top)
|
.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
|
@Builder
|
||||||
kidSection() {
|
kidSection() {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
FbLedgerBook, FbLedgerDetail, FbLedgerEntry, FbLedgerPoint, FbNoteItem, FbNoteBlock, FbNotificationItem, FbResourceItem,
|
FbLedgerBook, FbLedgerDetail, FbLedgerEntry, FbLedgerPoint, FbNoteItem, FbNoteBlock, FbNotificationItem, FbResourceItem,
|
||||||
FbTaskItem, FbTripItem, FbWeather, FbStat, FbMemoryItem, FbRecentDay,
|
FbTaskItem, FbTripItem, FbWeather, FbStat, FbMemoryItem, FbRecentDay,
|
||||||
FbTaskAttach, FbTaskComment, FbTaskDetail, FbTaskKid, FbTaskMeta, FbCatOpt, FbBoardOpt,
|
FbTaskAttach, FbTaskComment, FbTaskDetail, FbTaskKid, FbTaskMeta, FbCatOpt, FbBoardOpt,
|
||||||
|
FbTaskShare,
|
||||||
} from '../model/Types';
|
} from '../model/Types';
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -200,6 +201,13 @@ interface RawTaskDetail extends RawTask {
|
|||||||
children?: RawTaskKid[];
|
children?: RawTaskKid[];
|
||||||
comments?: RawTaskComment[];
|
comments?: RawTaskComment[];
|
||||||
attachs?: RawTaskAttach[];
|
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) */
|
/** `?view=boards` 的看板元数据(v79) */
|
||||||
@@ -1016,6 +1024,17 @@ export function parseTaskDetail(json: string): FbTaskDetail | null {
|
|||||||
attachs.push(one);
|
attachs.push(one);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 共享给谁(工单 #26;服务端 Task::find 已带 shares)
|
||||||
|
const shares: FbTaskShare[] = [];
|
||||||
|
for (const sh of arr<RawTaskShare>(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 creatorNick: string = s(d.creator_nick);
|
||||||
const detail: FbTaskDetail = {
|
const detail: FbTaskDetail = {
|
||||||
base: base,
|
base: base,
|
||||||
@@ -1028,6 +1047,7 @@ export function parseTaskDetail(json: string): FbTaskDetail | null {
|
|||||||
kids: kids,
|
kids: kids,
|
||||||
comments: comments,
|
comments: comments,
|
||||||
attachs: attachs,
|
attachs: attachs,
|
||||||
|
shares: shares,
|
||||||
};
|
};
|
||||||
return detail;
|
return detail;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -779,6 +779,13 @@ export interface FbTaskAttach {
|
|||||||
sizeWord: string;
|
sizeWord: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 任务共享给谁(工单 #26;服务端 `Task::shares()`) */
|
||||||
|
export interface FbTaskShare {
|
||||||
|
personId: number;
|
||||||
|
name: string;
|
||||||
|
perm: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 任务详情(v79 面板用):列表行 + 只有详情接口才给的那几块。
|
* 任务详情(v79 面板用):列表行 + 只有详情接口才给的那几块。
|
||||||
*
|
*
|
||||||
@@ -801,6 +808,8 @@ export interface FbTaskDetail {
|
|||||||
kids: FbTaskKid[];
|
kids: FbTaskKid[];
|
||||||
comments: FbTaskComment[];
|
comments: FbTaskComment[];
|
||||||
attachs: FbTaskAttach[];
|
attachs: FbTaskAttach[];
|
||||||
|
/** 共享给谁(工单 #26) */
|
||||||
|
shares: FbTaskShare[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user