资源附件+共享:浮层补「附件」「共享」两个真功能 laoyang/FamilyBook#25

用户原话(2026-10-28):「鸿蒙端对齐网页端」。资源浮层原来「附件与共享
还在网页端,App 暂未开放」,现在服务端补齐 API 后:

1) FbResourceRepo 补 attach/share 写操作(上传走 fbUploadOne 字段 file;
   删除走 query 传 attachment_id / person_id —— DELETE 带 JSON body 服务端读不到)
2) FbResourceSheet:附件区块(列表 + 缩略图/图标 + 「+传附件」+ 逐条删)+
   共享区块(列表 + 「共享给…」复用选人组件 + 逐条取消)
   · 只读共享来的不摆入口(与 canWrite 对齐)

编译 BUILD SUCCESSFUL;服务端冒烟 _i25_resource_api.py 21/0 + 反证命中。
This commit is contained in:
2026-10-28 22:00:15 +08:00
parent 6f360fd6a1
commit f93dd92cdb
2 changed files with 372 additions and 23 deletions
+53 -13
View File
@@ -2,22 +2,24 @@
* 家书 FamilyBook · 资源的**写操作**(鸿蒙端)
*
* 对应服务端 `app/ApiHandler.php` 的 `resources()`:
* POST /api/v1/resources 新建(title / content)
* PATCH /api/v1/resources/<id> 改(title / content / category)
* DELETE /api/v1/resources/<id> 删
* POST /api/v1/resources 新建(title / content)
* PATCH /api/v1/resources/<id> 改(title / content / category)
* DELETE /api/v1/resources/<id> 删
* GET /api/v1/resources/<id>/attach 附件列表(工单 #25)
* POST /api/v1/resources/<id>/attach 传附件(multipart,字段 `file`)
* DELETE /api/v1/resources/<id>/attach 删附件(attachment_id)
* GET /api/v1/resources/<id>/shares 共享给了谁
* POST /api/v1/resources/<id>/shares 共享给某人(person_id / perm)
* DELETE /api/v1/resources/<id>/shares 取消共享(person_id)
*
* 🔴 用户 2026-10-20:「资源也是啊,新增加资源,资源也点不进去。
* 设置也没有任何的东西。」
* ⇒ 端上「添加」按钮**没有 `onTap`**、列表项**没有 `onClick`** —— 两件都没接。
* 设置也没有任何的东西。」⇒ 端上「添加」按钮**没有 onTap**、列表项**没有 onClick**。
*
* ⚠️⚠️ **服务端只认 title / content 两个字段**(其它字段会被静默忽略):
* · `Resource::create()` 的 INSERT 只有 `title` / `content`;
* · `resources` 表**没有 category 列**(见 `app/Schema.php`),
* 接口里那个 `category` 参数是历史遗留,写下去不会落库;
* · 归属人(`owner_id`)由接口固定成**当前登录的人**,没法从端上指定;
* · **附件与共享没有 API**(`Attachment::list/save` 只有网页在用)。
* ⇒ 端上就只做「标题 + 正文」,别在界面上摆出家能选归属人/传附件的假入口 ——
* 用户填了、保存了、却没落库,比没做更糟。
* 🔴 工单 #25(2026-10-28):原来这里只有 title/content(附件与共享没 API,
* 端上「故意不摆假入口」)。现在服务端补齐了 attach + shares 子端点,
* 这里也把三组写操作补上,端上终于能管附件与共享。
*
* ⚠️ 归属人(owner_id)仍由接口固定成当前登录的人,端上不选。
*/
import { fbApi, FbApiResult, FbApiOptions } from './FbHttp';
@@ -48,3 +50,41 @@ export async function deleteResource(token: string, id: number): Promise<FbApiRe
const opts: FbApiOptions = { method: 'DELETE', token: token };
return await fbApi('/api/v1/resources/' + id.toString(), opts);
}
/** 附件列表(工单 #25) */
export async function fetchResourceAttach(token: string, id: number): Promise<FbApiResult> {
const opts: FbApiOptions = { token: token, authProbe: true };
return await fbApi('/api/v1/resources/' + id.toString() + '/attach', opts);
}
/** 删附件(⚠️ 附件 id 走 query,DELET 带 JSON body 服务端读不到 —— 见任务删附件同款注释) */
export async function delResourceAttach(token: string, id: number, aid: number): Promise<FbApiResult> {
const q: Record<string, string> = {};
q['attachment_id'] = aid.toString();
const opts: FbApiOptions = { method: 'DELETE', token: token, query: q };
return await fbApi('/api/v1/resources/' + id.toString() + '/attach', opts);
}
/** 共享给了谁(工单 #25) */
export async function fetchResourceShares(token: string, id: number): Promise<FbApiResult> {
const opts: FbApiOptions = { token: token, authProbe: true };
return await fbApi('/api/v1/resources/' + id.toString() + '/shares', opts);
}
/** 共享给某人(perm = read / write) */
export async function shareResource(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/resources/' + id.toString() + '/shares', opts);
}
/** 取消某人的共享(person_id 走 query,DELETE 带 body 服务端读不到) */
export async function unshareResource(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/resources/' + id.toString() + '/shares', opts);
}