From bf89a1f3016b56356662640186e9ffe643cba729 Mon Sep 17 00:00:00 2001 From: wuzilong <704541015@qq.com> Date: Thu, 12 Nov 2026 17:36:51 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=AE=B0=E5=BD=95=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=BA=93=E5=A4=9A=E9=80=89=E6=89=B9=E9=87=8F=E5=88=86=E4=BA=AB?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=EF=BC=88#25=EF=BC=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 明确整份覆盖、交集预选、batch-shares 事务与 Web 弹窗扩展范围,供实现对照。 Co-authored-by: Cursor --- .../2026-11-12-doclib-batch-share-design.md | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 docs/superpowers/specs/2026-11-12-doclib-batch-share-design.md diff --git a/docs/superpowers/specs/2026-11-12-doclib-batch-share-design.md b/docs/superpowers/specs/2026-11-12-doclib-batch-share-design.md new file mode 100644 index 000000000..858e2875a --- /dev/null +++ b/docs/superpowers/specs/2026-11-12-doclib-batch-share-design.md @@ -0,0 +1,115 @@ +# 文档库多选批量设置分享(工单 #25) + +工单:https://gitea.aichengyan.com/cykj/shanxia2.0/issues/25 + +## 背景与问题 + +Web 文档库管理(`/#/doclib/enter`)工具栏「设置分享」当前仅在恰好选中 1 份且可管理的文档时可用(`actionsFor.share`),弹窗与接口均为单文档。用户需要多选文档后统一设置分享人员。 + +## 已确认决策 + +| 项 | 结论 | +| --- | --- | +| 保存语义 | 整份覆盖:弹窗所选 `userIds` 写入每一份所选文档,替换原分享人;空列表表示全部收回 | +| 弹窗初始勾选 | 所选文档现有分享人的交集;无共同人员则为空 | +| 修改端 | Web 管理端 + 后端批量 API | +| 失败策略 | 整批事务:全部成功或全部回滚 | +| 实现方案 | 新增 `batch-shares` + 扩展现有 `ShareDialog`(方案 A) | + +## 范围 + +**包含** + +- `web-admin`:`doclib/enter` 列表工具栏、`ShareDialog`、`actionsFor`、API 封装与单测 +- `backend`:批量分享路由、schema、服务层事务与单文件写入逻辑复用 + +**不包含** + +- 小程序 / 移动 H5 +- 「只追加不收回」、部分成功返回清单 +- 文件夹级分享 + +## 后端设计 + +### 接口 + +`POST /web/doclib/files/batch-shares` + +请求体(`extra=forbid`): + +```json +{ + "ids": ["file-id-1", "file-id-2"], + "userIds": ["user-id-a", "user-id-b"] +} +``` + +约束: + +- `ids`:`min_length=1`,`max_length=100`(对齐 `DeleteFilesBody`) +- `userIds`:`max_length=500`(对齐 `ShareBody`) +- 去重后的 `ids` 均须当前用户可管理;任一份不可管理则 4xx,整批不写 +- 人员须同租户且启用;自动排除各文档上传人(与现 `set_shares` 一致:`requested - {create_by}`) +- 非法 / 停用 / 跨租户人员 → 400,整批回滚 + +响应:成功后返回更新后的文档列表(字段与现有单文档分享成功返回一致的序列化)。 + +### 事务与复用 + +- 现有 `DoclibService.set_shares` 在单文件路径末尾 `commit`。批量路径须在**全部文件写入完成前不 commit**。 +- 抽取内部方法(名称示例:`_apply_shares(file_id, user_ids)`)完成单文件差量更新(含 lock);`set_shares` = `_apply_shares` + `commit` + 返回序列化;`set_shares_batch` = 校验全部可管理 → 校验人员 → 对每个 id 调用 `_apply_shares` → 一次 `commit` → 返回列表。 +- 任一步抛错依赖会话回滚,不留下部分文档已改的状态。 + +路由注册位置:与现有 `PUT /web/doclib/files/{file_id}/shares`、`POST .../batch-delete` 同模块(`backend/app/api/doclib.py`)。 + +## 前端设计 + +### 工具栏与权限 + +- `actionsFor.share`:`rows.length > 0 && rows.every((row) => row.canManage)`(与删除多选门槛一致,取消「仅 1 条」限制)。 +- 工具栏「设置分享」对 `selected` 全量打开弹窗,不再只取 `selected[0]`。 +- 详情抽屉单文档「设置分享」仍传入单元素数组或单文档,行为不变。 + +### ShareDialog + +- Props 改为支持多文档(例如 `documents: DoclibDocument[]`);打开时: + - 摘要列出所选文档名称(数量较多时可截断并注明总数) + - `userIds` 初始 = 分享人 id **交集** + - 候选人员:交集中的人员 ∪ 远程搜索结果;排除各文档上传人(多文档时排除所有相关 `ownerId`) +- 保存:统一调用批量接口 `saveDoclibBatchShares(ids, userIds)`(单文档也走批量,少分支);成功提示后关闭并刷新列表 / 更新详情。 + +### 纯函数(便于单测) + +- `shareUserIntersection(documents): string[]` — 交集;空文档列表返回 `[]` +- 可选:`canShareSelection(rows)` 与 `actionsFor` 共用语义 + +### API + +在 `web-admin/src/api/doclib.ts` 增加: + +`saveDoclibBatchShares(ids: string[], userIds: string[])` → `POST .../files/batch-shares` + +单文档 `saveDoclibShares` 可保留供兼容/测试,页面路径以批量为准。 + +## UI 规范 + +- 沿用现有 `doclib-dialog dialog-form-layout`、Fresh Mint、P12 类弹窗;不新造布局与主色。 +- 按钮图标规则与现页一致(Close / Check 等已有用法)。 + +## 验收 + +1. 多选可管理文档后可打开「设置分享」;含不可管理项时按钮禁用。 +2. 打开时勾选为交集;无共同分享人时为空。 +3. 保存后所选文档分享人员均等于弹窗所选集合(含清空)。 +4. 故意混入无权限文档(若后端被直接调用)→ 整批不改。 +5. 详情页单文档分享仍可用。 +6. 单测覆盖交集函数与 `actionsFor.share`;后端/服务层覆盖批量成功与权限失败回滚(按项目既有测试方式)。 + +## 风险 + +- `set_shares` 重构时须保证单文档路径回归(commit 时机、上传人排除、软删恢复历史 share 行)。 +- 批量上限 100 与删除一致;超大选区依赖现有表格多选,不另做分片 UI。 + +## 状态 + +设计已获用户确认(会话中「按照推荐」方案 A,「OK,请继续」批准落盘)。待用户审阅本文件后进入实现计划。