# 批量重命名冲突误报修复与冲突弹窗优化设计 ## 背景 当前批量重命名在执行前会先进行冲突检测,并在检测到冲突时通过浏览器原生 `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` + 文件列表 + 新名称列表整理为 UI 明细数组的辅助函数,供对话框渲染使用。 ### 3. 冲突对话框 UI 新增一个 Lit 组件,例如 `conflict-resolution-dialog`,风格对齐现有 `rename-dialog` / `progress-dialog`: #### 布局 - 标题:检测到 N 个文件名冲突 - 说明:提示用户先查看冲突详情,再选择处理方式 - 可滚动详情区域: - 每项显示 - 原文件名 - 箭头/目标文件名 - 冲突类型标签 - 若为批量内部冲突,可显示“同批次重复目标名”及相关文件名 - 底部操作区: - 自动添加编号(主按钮,推荐) - 跳过冲突文件 - 取消本次执行 #### 交互 - 点击遮罩或取消本次执行:返回 `null` - 点击“自动添加编号”或“跳过冲突文件”:返回对应 `ConflictResolution` - 移除 `ConflictResolution.OVERWRITE` 及相关误导文案,避免让用户误以为扩展具备删除/覆盖网盘同名文件的能力 ### 4. 集成方式 在 `file-selector-panel.ts` 中以状态驱动方式集成: - 增加对话框开关与冲突详情状态。 - `handleExecute()` 检测到冲突时: - 先保存待执行上下文 - 打开冲突对话框 - 等用户选择后再继续执行 - 为减少侵入,可将“等待用户选择”封装成 `Promise`,由对话框按钮事件 resolve。 ### 5. 国际化 为三套语言文案新增/补充分组 key: - 对话框标题、说明、副标题 - 按钮文案 - 冲突类型标签 - 明细字段标签:原文件名、目标文件名、冲突文件 - 取消执行提示文案 ## 测试策略 ### 单元测试 1. `FileSelectorPanel.handleExecute()` 相关行为: - 当部分文件未变化时,仅变化文件参与冲突检测。 - 未变化文件不会触发误报冲突。 2. `conflict-detector`: - 批量内部重复名仍可正确生成冲突详情。 - 冲突详情整理函数输出正确。 3. 冲突对话框组件: - 渲染冲突条目 - 点击不同按钮返回对应 resolution ### 回归验证 - 真实替换场景:仅 7 个变化文件,无重复时不弹冲突框。 - 存在真实重名时:弹窗能列出冲突文件,并能按选择继续处理。 ## 风险与缓解 - **风险:** 改成异步自定义对话框后,执行流程可能被打断。 - **缓解:** 用 `Promise` 封装选择流程,保持 `handleExecute()` 线性。 - **风险:** 新增文案导致多语言缺失。 - **缓解:** 同步更新 `zh_CN` / `zh_TW` / `en`。 - **风险:** 冲突数量统计与 UI 列表不一致。 - **缓解:** 统一由冲突详情数组派生数量,而不是重复计算。 ## 成功标准 - 用户给出的样例不再出现“检测到 2 个冲突”的误报。 - 真正冲突时会显示自定义对话框,而不是浏览器原生 prompt。 - 对话框中能滚动查看冲突文件列表。 - 自动加序号 / 跳过 两种处理策略继续可用,且可以安全取消本次执行。