docs: 记录文档库多选批量分享设计(#25)。

明确整份覆盖、交集预选、batch-shares 事务与 Web 弹窗扩展范围,供实现对照。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-11-12 17:36:51 +08:00
1 parent 391e5f66f5
commit bf89a1f301
1 file changed
+115
@@ -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,请继续」批准落盘)。待用户审阅本文件后进入实现计划。