7.7 KiB
7.7 KiB
文件覆盖保护功能
🎯 功能概述
为保存工作流功能添加了文件覆盖保护机制。当保存的工作流文件已存在时,系统会弹出友好的对话框,让用户选择如何处理。
更新版本: v0.2.2.1
更新日期: 2026-01-07
涉及文件: web/src/stores/useStore.ts
✨ 功能特性
保存工作流时的智能检测
当用户点击保存工作流按钮时:
- 检查文件是否存在 - 调用
GET /api/files/info检查目标路径 - 文件不存在 - 直接保存
- 文件已存在 - 弹出选择对话框
对话框选项
⚠️ 文件已存在
工作流 数据分析.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: 对话框关闭,不执行任何保存操作
🔧 技术实现
文件存在检查
// 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 组件:
const choice = await new Promise<'overwrite' | 'rename' | 'cancel'>((resolve) => {
// 创建遮罩层
const dialog = document.createElement('div')
dialog.style.cssText = '...'
// 创建模态框
const modal = document.createElement('div')
modal.innerHTML = `
<button id="btn-overwrite">🔄 覆盖现有文件</button>
<button id="btn-rename">✏️ 重命名后保存</button>
<button id="btn-cancel">❌ 取消</button>
`
// 绑定事件
modal.querySelector('#btn-overwrite').addEventListener('click', () => {
document.body.removeChild(dialog)
resolve('overwrite')
})
// ESC 键支持
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') resolve('cancel')
})
})
处理用户选择
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