# 文件覆盖保护功能 ## 🎯 功能概述 为保存工作流功能添加了文件覆盖保护机制。当保存的工作流文件已存在时,系统会弹出友好的对话框,让用户选择如何处理。 **更新版本**: v0.2.2.1 **更新日期**: 2026-01-07 **涉及文件**: `web/src/stores/useStore.ts` --- ## ✨ 功能特性 ### 保存工作流时的智能检测 当用户点击保存工作流按钮时: 1. **检查文件是否存在** - 调用 `GET /api/files/info` 检查目标路径 2. **文件不存在** - 直接保存 3. **文件已存在** - 弹出选择对话框 ### 对话框选项 ``` ⚠️ 文件已存在 工作流 数据分析.utrace 已存在。 请选择操作: ┌─────────────────────────┐ │ 🔄 覆盖现有文件 │ → 直接覆盖,不改变文件名 ├─────────────────────────┤ │ ✏️ 重命名后保存 │ → 提示输入新名称 ├─────────────────────────┤ │ ❌ 取消 │ → 取消保存操作 └─────────────────────────┘ ``` --- ## 🎨 UI 设计 ### 对话框样式 - **背景遮罩**: 半透明黑色 `rgba(0, 0, 0, 0.7)` - **模态框**: 深色主题 `#1e293b`,圆角 12px - **按钮**: 不同颜色区分操作 - 覆盖: 红色 `#ef4444` - 重命名: 绿色 `#22c55e` - 取消: 灰色 `#94a3b8` - **交互**: 悬停时按钮上移,带阴影 ### 键盘支持 - **ESC 键**: 取消操作,关闭对话框 --- ## 📖 使用流程 ### 场景 1: 覆盖现有文件 ``` 步骤 1: 编辑工作流,标题为"数据分析" 步骤 2: 点击 💾 保存按钮 步骤 3: 检测到 数据分析.utrace 已存在 步骤 4: 弹出对话框,用户选择"🔄 覆盖现有文件" 步骤 5: 文件被覆盖,显示 "✅ 工作流已保存到: users/guest/workflows/数据分析.utrace" ``` ### 场景 2: 重命名后保存 ``` 步骤 1: 编辑工作流,标题为"数据分析" 步骤 2: 点击 💾 保存按钮 步骤 3: 检测到 数据分析.utrace 已存在 步骤 4: 弹出对话框,用户选择"✏️ 重命名后保存" 步骤 5: 弹出输入框: "请输入新的工作流名称: 数据分析" 步骤 6: 用户输入 "数据分析_v2" 步骤 7: 工作流标题自动更新为 "数据分析_v2" 步骤 8: 保存为 数据分析_v2.utrace ``` ### 场景 3: 取消保存 ``` 步骤 1: 点击 💾 保存按钮 步骤 2: 检测到文件已存在 步骤 3: 弹出对话框,用户选择"❌ 取消"或按 ESC 键 步骤 4: 对话框关闭,不执行任何保存操作 ``` --- ## 🔧 技术实现 ### 文件存在检查 ```typescript // 1. 构建文件完整路径 const filename = `${workflowTitle.replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, '_')}.utrace` const fullPath = `users/${currentUser}/workflows/${filename}` // 2. 调用 API 检查文件是否存在 const checkResponse = await fetch( `http://127.0.0.1:8000/api/files/info?path=${encodeURIComponent(fullPath)}` ) const fileExists = checkResponse.ok ``` ### 动态对话框创建 使用原生 DOM API 创建模态对话框,避免依赖 React 组件: ```typescript const choice = await new Promise<'overwrite' | 'rename' | 'cancel'>((resolve) => { // 创建遮罩层 const dialog = document.createElement('div') dialog.style.cssText = '...' // 创建模态框 const modal = document.createElement('div') modal.innerHTML = ` ` // 绑定事件 modal.querySelector('#btn-overwrite').addEventListener('click', () => { document.body.removeChild(dialog) resolve('overwrite') }) // ESC 键支持 document.addEventListener('keydown', (e) => { if (e.key === 'Escape') resolve('cancel') }) }) ``` ### 处理用户选择 ```typescript if (choice === 'cancel') { return // 直接返回,不保存 } if (choice === 'rename') { const newName = prompt('请输入新的工作流名称:', currentTitle) if (newName) { filename = `${newName.replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, '_')}.utrace` setWorkflowTitle(newName) // 更新标题 } } // choice === 'overwrite' 时,继续使用原文件名保存 ``` --- ## 🚀 优势 ### 用户体验 - ✅ 防止意外覆盖重要工作流 - ✅ 友好的视觉提示和清晰的选项 - ✅ 支持快速重命名,无需重新编辑标题 - ✅ 键盘快捷键支持(ESC 取消) ### 技术优势 - ✅ 使用 `GET /api/files/info` 检查,轻量高效 - ✅ Promise 封装,代码逻辑清晰 - ✅ 纯 JavaScript 实现,无额外依赖 - ✅ 自动清理 DOM,避免内存泄漏 ### 兼容性 - ✅ 向后兼容,不影响现有功能 - ✅ 在后端 API 不可用时优雅降级(直接保存) --- ## 📊 对比其他方案 | 方案 | 优点 | 缺点 | |------|------|------| | **直接覆盖** | 实现简单 | ❌ 可能丢失数据 | | **自动重命名** | 避免覆盖 | ❌ 文件名混乱(如 file_1, file_2) | | **询问用户** ✅ | 用户可控 | 需要额外交互 | --- ## 🛠️ 扩展建议 ### 短期优化 - [ ] 显示文件最后修改时间,帮助用户决策 - [ ] 添加"查看现有文件"选项,预览内容 - [ ] 保存历史记录(自动备份) ### 长期规划 - [ ] 版本管理系统(Git 式管理) - [ ] 文件差异对比(Diff 显示) - [ ] 自动保存功能(定时保存草稿) --- ## 🐛 已知限制 ### 1. 依赖后端 API - **问题**: 需要 `GET /api/files/info` 接口支持 - **降级方案**: API 不可用时跳过检查,直接保存 ### 2. 文件名冲突 - **问题**: 如果重命名后的文件名仍然存在,会再次弹出对话框 - **解决**: 可以在重命名时检查并提示 ### 3. 并发保存 - **问题**: 多个用户同时保存同名文件可能冲突 - **解决**: 后期可添加文件锁机制 --- ## 📝 测试用例 ### 测试 1: 新文件保存 ``` 前置条件: 工作流标题为 "新工作流",该文件不存在 操作: 点击保存按钮 预期结果: - 不显示对话框 - 直接保存成功 - 显示提示 "✅ 工作流已保存到: users/guest/workflows/新工作流.utrace" ``` ### 测试 2: 覆盖现有文件 ``` 前置条件: 工作流标题为 "测试工作流",该文件已存在 操作: 1. 点击保存按钮 2. 选择 "🔄 覆盖现有文件" 预期结果: - 显示对话框 - 文件被覆盖 - 显示保存成功提示 ``` ### 测试 3: 重命名保存 ``` 前置条件: 工作流标题为 "测试工作流",该文件已存在 操作: 1. 点击保存按钮 2. 选择 "✏️ 重命名后保存" 3. 输入 "测试工作流_备份" 预期结果: - 弹出输入框 - 工作流标题更新为 "测试工作流_备份" - 保存为 测试工作流_备份.utrace ``` ### 测试 4: 取消保存 ``` 前置条件: 工作流标题为 "测试工作流",该文件已存在 操作: 1. 点击保存按钮 2. 选择 "❌ 取消" 或按 ESC 键 预期结果: - 对话框关闭 - 不执行保存操作 - 不显示任何提示 ``` ### 测试 5: API 不可用 ``` 前置条件: 后端服务未启动 操作: 点击保存按钮 预期结果: - 跳过文件存在检查 - 直接尝试保存 - 显示错误提示 "❌ 保存失败,请检查后端服务是否正常运行" ``` --- ## 🔄 版本历史 | 版本 | 日期 | 更新内容 | |------|------|---------| | v0.2.2.1 | 2026-01-07 | 新增文件覆盖保护对话框 | --- ## 📧 反馈 如有问题或建议,请通过以下方式反馈: - 提交 Issue: GitHub Issues - 功能建议: GitHub Discussions