镜像自地址
https://github.com/lhly/cloud-drive-renamer.git
已同步 2026-10-20 21:57:47 +08:00
- 在平台适配器中统一暴露 getCurrentDirectoryKey() 方法,用于确定当前目录范围 - 新增 LastRenameOperation 数据模型和核心操作函数,支持单条撤销记录的创建、合并和范围校验 - 在 file-selector-panel 中集成撤销逻辑,支持执行后保存记录、retry 成功后合并记录以及撤销操作 - 在 config-panel 底部操作区新增撤销按钮,根据当前目录范围动态显示/隐藏 - 添加多语言支持,包含撤销相关文案 - 添加单元测试和集成测试,确保撤销功能的正确性和稳定性
156 行
5.8 KiB
Markdown
156 行
5.8 KiB
Markdown
# Last Batch Undo Design(仅撤销最近一次批量重命名)
|
||
|
||
**Date:** 2026-03-18
|
||
|
||
## Goal
|
||
在现有批量重命名面板中增加“撤销上一次批量重命名”能力:只回退当前平台、当前目录下最近一次点击“批量重命名”按钮触发的那次批量操作,并允许用户关闭面板查看真实结果后再返回撤销。
|
||
|
||
## Scope
|
||
- 在 `file-selector-panel` 中保存 1 条可撤销记录,而不是历史队列。
|
||
- 该记录绑定当前平台 + 当前目录。
|
||
- 关闭面板后保留记录;只要用户仍在原目录即可执行撤销。
|
||
- `retry` 不创建新历史,但 retry 成功项要并入这唯一的一条记录。
|
||
- 在 `config-panel` 中新增单一撤销按钮,不做 split-button / 下拉历史。
|
||
- 撤销部分失败时,弹窗列出失败文件并提示用户手动处理。
|
||
|
||
## Non-goals
|
||
- 不保留最近 10 次历史。
|
||
- 不支持选择任意历史回退。
|
||
- 不做跨刷新、跨页面、跨平台持久化撤销。
|
||
- 不新增独立弹窗组件;第一版沿用现有 `alert/confirm` 风格提示。
|
||
|
||
## Decisions
|
||
- 可撤销记录只有 1 条,新的批量重命名会覆盖旧记录。
|
||
- 生命周期采用“当前平台 + 当前目录 + 当前页面会话”的交集:
|
||
- 关闭面板不清空;
|
||
- 离开当前目录、刷新页面、页面卸载、跨平台访问时失效。
|
||
- “离开目录即销毁”第一版采用**懒校验**:在面板打开、点击撤销、点击批量重命名、点击 retry 时检查当前目录 scope;若 scope 不匹配,则先清空记录。
|
||
- `retry` 成功项并入当前记录,保证 undo 对应“这次批量操作最终成功改名的全部文件”。
|
||
- 部分撤销失败后,保留失败项为新的可撤销记录,方便用户在未离开目录前再次点击 undo;同时弹窗提示手动处理建议。
|
||
|
||
## Architecture
|
||
|
||
### 1) 目录 scope 抽象
|
||
为 `PlatformAdapter` 增加统一能力:
|
||
|
||
```ts
|
||
getCurrentDirectoryKey(): string;
|
||
```
|
||
|
||
实现方式:
|
||
- Quark:复用现有 `getCurrentFolderId()`。
|
||
- Aliyun:复用现有 `getParentIdFromUrl()`。
|
||
- Baidu:复用现有 `getCurrentPath()`。
|
||
|
||
`file-selector-panel` 只依赖 `adapter.platform + adapter.getCurrentDirectoryKey()` 判断当前撤销记录是否仍在有效 scope 内。
|
||
|
||
### 2) 单条撤销记录模型
|
||
新增一个轻量数据模型,仅保存 undo 必需信息:
|
||
|
||
```ts
|
||
interface UndoRenameItem {
|
||
fileId: string;
|
||
original: string;
|
||
renamed: string;
|
||
index: number;
|
||
}
|
||
|
||
interface LastRenameOperation {
|
||
platform: PlatformName;
|
||
directoryKey: string;
|
||
createdAt: number;
|
||
updatedAt: number;
|
||
items: UndoRenameItem[];
|
||
}
|
||
```
|
||
|
||
只存成功改名项;不存完整 `FileItem`,避免状态冗余。
|
||
|
||
### 3) 记录写入与合并
|
||
#### 主执行(点击“批量重命名”)
|
||
- `handleExecute()` 执行完成后,如果存在成功项:
|
||
- 用当前 `platform + directoryKey + results.success` 创建一条新的 `LastRenameOperation`;
|
||
- 覆盖旧记录。
|
||
|
||
#### Retry
|
||
- `handleRetryFailed()` 执行完成后,如果存在成功项:
|
||
- 先校验当前 scope 是否仍与现有记录一致;
|
||
- 一致则把 retry 成功项 merge 进现有记录;
|
||
- 若记录已失效,则不再创建新记录(因为 retry 只是补全原操作,不应在丢失上下文后凭空产生新 undo 历史)。
|
||
|
||
### 4) 撤销执行
|
||
新增 `handleUndoLastRename()`:
|
||
1. 校验当前 scope;不匹配则清空记录并退出。
|
||
2. 从当前记录生成反向任务:`fileId` 不变,`newName = original`。
|
||
3. 复用 `BatchExecutor` 执行这些 tasks。
|
||
4. 成功项通过现有 `applyExecutionResults()` + `syncAfterRename()` 回写页面与面板状态。
|
||
5. 若全部成功:清空记录。
|
||
6. 若部分失败:将记录收窄为失败项,并弹窗提示用户手动处理。
|
||
|
||
### 5) UI 变更
|
||
`config-panel` 新增:
|
||
- `canUndo: boolean`
|
||
- `undoBusy: boolean`
|
||
- `@undo` 事件
|
||
|
||
页脚按钮区新增:
|
||
- `撤销上一次批量重命名`
|
||
|
||
按钮行为:
|
||
- 仅当当前 scope 有有效记录时可点击;
|
||
- 正在执行批量重命名或撤销时禁用;
|
||
- 文案建议加辅助说明:仅针对当前目录最近一次批量重命名。
|
||
|
||
### 6) 错误处理
|
||
- scope 不匹配:静默清空记录,并隐藏/禁用撤销按钮。
|
||
- 撤销零任务:若记录为空或已无可回退项,直接清空记录并返回。
|
||
- 部分失败:使用 i18n 头部文案 + 文件列表 + 手动处理提示拼接成 `alert()` 文本。
|
||
- 全部失败:记录保留,便于用户在确认真实页面状态后再次尝试。
|
||
|
||
## Data Flow
|
||
|
||
### A. 主执行
|
||
```
|
||
点击 Execute
|
||
-> handleExecute()
|
||
-> BatchExecutor.execute()
|
||
-> applyExecutionResults()
|
||
-> create LastRenameOperation from results.success
|
||
```
|
||
|
||
### B. Retry
|
||
```
|
||
点击 Retry
|
||
-> handleRetryFailed()
|
||
-> BatchExecutor.execute(tasks=failed only)
|
||
-> applyExecutionResults()
|
||
-> merge retry success into LastRenameOperation
|
||
```
|
||
|
||
### C. Undo
|
||
```
|
||
点击 Undo
|
||
-> validate current scope
|
||
-> build reverse tasks from LastRenameOperation.items
|
||
-> BatchExecutor.execute(tasks=undo)
|
||
-> applyExecutionResults()
|
||
-> clear record OR keep only failed items
|
||
```
|
||
|
||
## Testing Strategy (TDD)
|
||
- 单元测试新建纯函数/帮助模块:
|
||
- 从 `BatchResults.success` 创建记录;
|
||
- retry 成功项 merge;
|
||
- scope 校验;
|
||
- 部分 undo 失败后保留失败项。
|
||
- 适配器测试覆盖 `getCurrentDirectoryKey()`。
|
||
- 现有依赖 `PlatformAdapter` 的 mock 全部补上 `getCurrentDirectoryKey()` 以通过 typecheck。
|
||
- 回归验证:
|
||
- 普通批量重命名流程不受影响;
|
||
- retry 后 undo 能覆盖首次成功项 + retry 成功项;
|
||
- 离开目录后 undo 按钮消失/失效。
|
||
|
||
## Rollout
|
||
- 第一版优先采用懒校验而不是复杂的实时路由监听。
|
||
- 若后续观察到“切目录后按钮残留到下一次交互前”影响体验,再补目录变化监听器。
|