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

6.4 KiB

批量重命名冲突误报修复与冲突弹窗优化设计

背景

当前批量重命名在执行前会先进行冲突检测,并在检测到冲突时通过浏览器原生 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。
  • 对话框中能滚动查看冲突文件列表。
  • 自动加序号 / 跳过 两种处理策略继续可用,且可以安全取消本次执行。