cloud-drive-renamer/docs/superpowers/specs/2026-03-18-conflict-dialog-design.md
Zain bd69762a45 chore: 更新版本号至 1.0.4 并添加冲突处理设计文档
更新 package.json、manifest.json 和版本常量文件中的版本号至 1.0.4。
新增冲突检测与对话框优化的详细设计规格文档和实现计划文档,为后续功能开发做准备。
2026-03-19 11:04:02 +08:00

144 行
6.4 KiB
Markdown

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

# 批量重命名冲突误报修复与冲突弹窗优化设计
## 背景
当前批量重命名在执行前会先进行冲突检测,并在检测到冲突时通过浏览器原生 `prompt()` 让用户选择处理方式。现有实现存在两个问题:
1. **误报冲突**:未发生改名的文件仍然参与外部重名检测,阿里云盘适配器只按文件名匹配,不排除自身,导致“与自己重名”被误判为冲突。
2. **交互能力不足**:原生 `prompt()` 无法展示结构化冲突详情,也无法承载滚动列表、说明文本、危险操作强调等 UI。
## 目标
- 修复“未改名文件被误判冲突”的问题。
- 保持真实冲突检测不变:
- 批量内部生成相同目标名仍应识别。
- 当前目录已存在其他同名文件仍应识别。
- 将原生冲突弹窗升级为扩展内自定义对话框。
- 在对话框中显示可滚动的冲突明细,至少包含:原文件名、目标文件名、冲突类型。
- 删除误导性的“强制覆盖”选项,改为用户可安全理解的“取消本次执行”。
## 非目标
- 不改动批量执行器的核心重命名流程。
- 不新增“手动编辑每个冲突项”的复杂交互。
- 不重构所有 confirm/alert,仅替换冲突处理相关弹窗。
## 方案对比
### 方案 A:仅在适配器层排除自身
- 做法:扩展 `checkNameConflict` 接口,额外传入当前文件 ID,在适配器层排除自己。
- 优点:语义最准确,冲突判断在数据源层完成。
- 缺点:需要修改平台接口和多个适配器,改动面更大;对“未改名文件直接应跳过检测”这一业务规则表达不够直接。
### 方案 B:执行前仅对“实际会改名”的任务做冲突检测(推荐)
- 做法:在 `handleExecute()` 中先根据 `newName !== file.name` 过滤出真正需要改名的文件,再对这些任务执行冲突检测和后续执行计划构建。
- 优点:
- 直接匹配产品语义:“没改名就不应参与改名冲突检测”。
- 改动范围小,平台接口无需变动。
- 与现有 `skipUnchanged` 行为一致,避免检测和执行语义不一致。
- 缺点:如果未来需要对“未改名文件”做别的预检查,需要单独扩展逻辑。
### 方案 C:保留现有检测范围,但在冲突结果合并后剔除 unchanged 自冲突
- 做法:检测后再根据 `newName === oldName` 把相关冲突结果清理掉。
- 优点:改动集中。
- 缺点:是事后修补,逻辑绕,不如方案 B 清晰。
**采用方案:B。**
## 详细设计
### 1. 冲突检测修复
在 `FileSelectorPanel.handleExecute()` 中新增“候选任务”概念:
- 从 `selectedFiles` 和 `newNames` 派生出 `renameCandidates`。
- 只有 `newName !== file.name` 的候选项才参与:
- 冲突检测
- 冲突解决
- 执行计划
- 当候选项为空时,继续沿用现有 `no_rename_needed` 提示。
这样可以保证:
- 预览、冲突检测、执行三者都只关注实际要改名的文件。
- 不再出现“预览里没显示,但执行阶段又报冲突”的割裂体验。
### 2. 冲突详情数据模型
在 `conflict-detector.ts` 中补充冲突详情结构,面向 UI 传递:
- 文件 ID
- 原文件名
- 目标文件名
- 冲突类型:
- `DUPLICATE_IN_BATCH`
- `NAME_EXISTS`
- 相关冲突文件名列表(若适用)
新增一个将 `Map<string, ConflictResult>` + 文件列表 + 新名称列表整理为 UI 明细数组的辅助函数,供对话框渲染使用。
### 3. 冲突对话框 UI
新增一个 Lit 组件,例如 `conflict-resolution-dialog`,风格对齐现有 `rename-dialog` / `progress-dialog`:
#### 布局
- 标题:检测到 N 个文件名冲突
- 说明:提示用户先查看冲突详情,再选择处理方式
- 可滚动详情区域:
- 每项显示
- 原文件名
- 箭头/目标文件名
- 冲突类型标签
- 若为批量内部冲突,可显示“同批次重复目标名”及相关文件名
- 底部操作区:
- 自动添加编号(主按钮,推荐)
- 跳过冲突文件
- 取消本次执行
#### 交互
- 点击遮罩或取消本次执行:返回 `null`
- 点击“自动添加编号”或“跳过冲突文件”:返回对应 `ConflictResolution`
- 移除 `ConflictResolution.OVERWRITE` 及相关误导文案,避免让用户误以为扩展具备删除/覆盖网盘同名文件的能力
### 4. 集成方式
在 `file-selector-panel.ts` 中以状态驱动方式集成:
- 增加对话框开关与冲突详情状态。
- `handleExecute()` 检测到冲突时:
- 先保存待执行上下文
- 打开冲突对话框
- 等用户选择后再继续执行
- 为减少侵入,可将“等待用户选择”封装成 `Promise<ConflictResolution | null>`,由对话框按钮事件 resolve。
### 5. 国际化
为三套语言文案新增/补充分组 key:
- 对话框标题、说明、副标题
- 按钮文案
- 冲突类型标签
- 明细字段标签:原文件名、目标文件名、冲突文件
- 取消执行提示文案
## 测试策略
### 单元测试
1. `FileSelectorPanel.handleExecute()` 相关行为:
- 当部分文件未变化时,仅变化文件参与冲突检测。
- 未变化文件不会触发误报冲突。
2. `conflict-detector`:
- 批量内部重复名仍可正确生成冲突详情。
- 冲突详情整理函数输出正确。
3. 冲突对话框组件:
- 渲染冲突条目
- 点击不同按钮返回对应 resolution
### 回归验证
- 真实替换场景:仅 7 个变化文件,无重复时不弹冲突框。
- 存在真实重名时:弹窗能列出冲突文件,并能按选择继续处理。
## 风险与缓解
- **风险:** 改成异步自定义对话框后,执行流程可能被打断。
- **缓解:** 用 `Promise` 封装选择流程,保持 `handleExecute()` 线性。
- **风险:** 新增文案导致多语言缺失。
- **缓解:** 同步更新 `zh_CN` / `zh_TW` / `en`。
- **风险:** 冲突数量统计与 UI 列表不一致。
- **缓解:** 统一由冲突详情数组派生数量,而不是重复计算。
## 成功标准
- 用户给出的样例不再出现“检测到 2 个冲突”的误报。
- 真正冲突时会显示自定义对话框,而不是浏览器原生 prompt。
- 对话框中能滚动查看冲突文件列表。
- 自动加序号 / 跳过 两种处理策略继续可用,且可以安全取消本次执行。