# 模块06:文件删除功能 --- ## 🎨 UI设计系统概览 > **完整设计系统文档请参考:** `UI设计系统.md` ### 核心设计原则 - **现代简约**:界面清晰,层次分明 - **专业高效**:减少操作步骤,提升工作效率 - **一致性**:统一的视觉语言和交互模式 - **可访问性**:符合WCAG 2.1 AA标准 ### 关键设计令牌 **颜色系统:** - 主色:`#0d6efd`(操作按钮、选中状态) - 成功:`#198754`(连接成功状态) - 危险:`#dc3545`(删除操作、错误提示) - 深灰:`#212529`(导航栏背景) - 浅灰:`#e9ecef`(工具栏背景) **字体系统:** - 字体族:系统字体栈(-apple-system, Segoe UI, Roboto等) - 正文:14px,行高1.5 - 标题:20-32px,行高1.2-1.4 - 小号文字:12px(文件大小、日期等) **间距系统:** - 基础单位:8px - 标准间距:16px(1rem) - 组件内边距:8px-16px **组件规范:** - 导航栏:高度48px,深色背景 - 工具栏:浅灰背景,按钮间距8px - 文件项:最小高度44px,悬停效果150ms - 按钮:圆角4px,过渡150ms **交互规范:** - 悬停效果:150ms过渡 - 触摸目标:最小44x44px - 键盘导航:Tab、Enter、Delete、F2、F5、Esc - 焦点状态:2px蓝色轮廓 **响应式断点:** - 移动端:< 768px(双面板垂直排列) - 平板:768px - 1024px - 桌面:> 1024px(标准布局) --- ## 6.1 功能概述 实现删除本地文件和SFTP服务器上文件的功能,支持单个文件删除和批量删除,包含删除确认机制。 ## 6.2 后端设计 ### 6.2.1 LocalFileService删除方法 ```java // 删除单个文件或目录 public boolean deleteFile(String path) throws Exception { File file = new File(path); if (!file.exists()) { throw new Exception("文件不存在: " + path); } if (file.isDirectory()) { return deleteDirectory(file); } else { return file.delete(); } } // 递归删除目录 private boolean deleteDirectory(File directory) throws Exception { File[] files = directory.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { deleteDirectory(file); } else { if (!file.delete()) { throw new Exception("删除文件失败: " + file.getPath()); } } } } return directory.delete(); } // 批量删除 public BatchDeleteResult batchDelete(List paths) { BatchDeleteResult result = new BatchDeleteResult(); int successCount = 0; int failCount = 0; List failedFiles = new ArrayList<>(); for (String path : paths) { try { deleteFile(path); successCount++; } catch (Exception e) { failCount++; failedFiles.add(path + " - " + e.getMessage()); } } result.setSuccessCount(successCount); result.setFailCount(failCount); result.setFailedFiles(failedFiles); return result; } // 批量删除结果类 public static class BatchDeleteResult { private int successCount; private int failCount; private List failedFiles; // getters and setters } ``` ### 6.2.2 SftpService删除方法 ```java // 删除单个文件或目录 public boolean deleteFile(String sessionId, String path) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { FileInfo fileInfo = getFileInfo(sessionId, path); if (fileInfo.isDirectory()) { deleteDirectoryRecursive(sessionId, path); } else { channel.rm(path); } return true; } catch (SftpException e) { throw new Exception("删除失败: " + e.getMessage(), e); } } // 递归删除SFTP目录 private void deleteDirectoryRecursive(String sessionId, String path) throws Exception { ChannelSftp channel = sessionManager.getSession(sessionId); if (channel == null) { throw new Exception("会话不存在或已断开"); } try { // 列出目录内容 Vector entries = channel.ls(path); for (ChannelSftp.LsEntry entry : entries) { String fileName = entry.getFilename(); if (".".equals(fileName) || "..".equals(fileName)) { continue; } String fullPath = path.endsWith("/") ? path + fileName : path + "/" + fileName; if (entry.getAttrs().isDir()) { // 递归删除子目录 deleteDirectoryRecursive(sessionId, fullPath); } else { // 删除文件 channel.rm(fullPath); } } // 删除空目录 channel.rmdir(path); } catch (SftpException e) { throw new Exception("删除目录失败: " + e.getMessage(), e); } } // 批量删除 public BatchDeleteResult batchDelete(String sessionId, List paths) { BatchDeleteResult result = new BatchDeleteResult(); int successCount = 0; int failCount = 0; List failedFiles = new ArrayList<>(); for (String path : paths) { try { deleteFile(sessionId, path); successCount++; } catch (Exception e) { failCount++; failedFiles.add(path + " - " + e.getMessage()); } } result.setSuccessCount(successCount); result.setFailCount(failCount); result.setFailedFiles(failedFiles); return result; } ``` ### 6.2.3 FileController删除接口 ```java // 删除单个文件 @DeleteMapping("/delete") public ApiResponse deleteFile(@RequestParam String sessionId, @RequestParam String path) { try { if ("local".equals(sessionId)) { localFileService.deleteFile(path); } else { sftpService.deleteFile(sessionId, path); } return ApiResponse.success("删除成功", null); } catch (Exception e) { return ApiResponse.error("删除失败: " + e.getMessage()); } } // 批量删除 @PostMapping("/batch-delete") public ApiResponse batchDelete(@RequestBody Map request) { try { String sessionId = (String) request.get("sessionId"); @SuppressWarnings("unchecked") List paths = (List) request.get("paths"); LocalFileService.BatchDeleteResult result; if ("local".equals(sessionId)) { result = localFileService.batchDelete(paths); } else { result = sftpService.batchDelete(sessionId, paths); } return ApiResponse.success("删除完成", result); } catch (Exception e) { return ApiResponse.error("批量删除失败: " + e.getMessage()); } } ``` ## 6.3 前端设计 ### 6.3.1 删除交互 ```javascript // 删除选中的文件 function deleteSelectedFiles(panelId) { const selectedFiles = panelState[panelId].selectedFiles; if (selectedFiles.length === 0) { alert('请先选择要删除的文件'); return; } const sessionId = panelState[panelId].sessionId; // 确认对话框 let message; if (selectedFiles.length === 1) { const fileName = getFileNameFromPath(selectedFiles[0]); message = `确定要删除 "${fileName}" 吗?`; } else { message = `确定要删除选中的 ${selectedFiles.length} 个文件吗?`; } if (confirm(message)) { deleteFiles(sessionId, selectedFiles); } } // 删除文件 function deleteFiles(sessionId, paths) { if (paths.length === 1) { // 单个删除 $.ajax({ url: '/api/files/delete', method: 'DELETE', data: { sessionId: sessionId, path: paths[0] }, success: function(response) { if (response.success) { alert('删除成功'); refreshCurrentPanel(); } else { alert('删除失败: ' + response.message); } }, error: handleError }); } else { // 批量删除 $.ajax({ url: '/api/files/batch-delete', method: 'POST', contentType: 'application/json', data: JSON.stringify({ sessionId: sessionId, paths: paths }), success: function(response) { if (response.success) { const result = response.data; let message = `成功删除 ${result.successCount} 个文件`; if (result.failCount > 0) { message += `,失败 ${result.failCount} 个\n`; message += '失败详情:\n' + result.failedFiles.join('\n'); } alert(message); refreshCurrentPanel(); } else { alert('批量删除失败: ' + response.message); } }, error: handleError }); } } // 刷新当前面板 function refreshCurrentPanel() { const activePanelId = getActivePanelId(); loadFiles(activePanelId); } // 从路径获取文件名 function getFileNameFromPath(path) { const index = path.lastIndexOf('/'); if (index === -1) { return path; } return path.substring(index + 1); } ``` ### 6.3.2 删除按钮 ```html
``` ### 6.3.3 右键菜单删除 ```javascript // 文件列表右键菜单 function showContextMenu(event, panelId, path) { event.preventDefault(); // 创建右键菜单 const menu = $('
'); menu.css({ position: 'absolute', left: event.pageX + 'px', top: event.pageY + 'px' }); menu.append(''); menu.append(''); $('body').append(menu); // 点击其他地方关闭菜单 $(document).one('click', function() { menu.remove(); }); } // 删除单个文件(通过右键菜单) function deleteFile(panelId, path) { const sessionId = panelState[panelId].sessionId; const fileName = getFileNameFromPath(path); if (confirm(`确定要删除 "${fileName}" 吗?`)) { deleteFiles(sessionId, [path]); } } ``` ## 6.4 安全措施 ### 6.4.1 删除确认 - 必须用户确认后才能执行删除 - 显示将被删除的文件数量和名称 - 防止误操作 ### 6.4.2 权限检查 ```java // 检查文件删除权限 private void checkDeletePermission(File file) throws Exception { if (!file.canWrite()) { throw new Exception("没有删除权限: " + file.getPath()); } // 检查是否为系统文件 String systemPaths = "C:\\Windows,C:\\Program Files,C:\\System32"; String[] paths = systemPaths.split(","); for (String systemPath : paths) { if (file.getPath().toLowerCase().startsWith(systemPath.toLowerCase())) { throw new Exception("系统文件,禁止删除: " + file.getPath()); } } } ``` ### 6.4.3 操作日志 ```java // 记录删除操作 @Autowired private OperationLogService logService; public boolean deleteFile(String path) throws Exception { File file = new File(path); try { checkDeletePermission(file); boolean result = deleteFileInternal(file); // 记录操作日志 logService.logOperation("delete", "local", path, null, result, null); return result; } catch (Exception e) { // 记录失败日志 logService.logOperation("delete", "local", path, null, false, e.getMessage()); throw e; } } ``` ### 6.4.4 回收站机制(可选) ```java // 移动到回收站而不是直接删除 public boolean moveToRecycleBin(String path) throws Exception { File file = new File(path); if (!file.exists()) { throw new Exception("文件不存在: " + path); } // 创建回收站目录 File recycleBin = new File(getRecycleBinPath()); if (!recycleBin.exists()) { recycleBin.mkdirs(); } // 生成唯一文件名(避免重名) String newPath = recycleBin.getPath() + File.separator + file.getName() + "_" + System.currentTimeMillis(); return file.renameTo(new File(newPath)); } private String getRecycleBinPath() { return System.getProperty("user.home") + File.separator + ".sftp-manager-recycle"; } ``` ## 实施步骤 1. **更新LocalFileService**:添加删除方法 2. **更新SftpService**:添加删除方法 3. **更新FileController**:添加删除接口 4. **添加前端删除功能** 5. **编译测试** ``` mvn clean compile ``` 6. **启动服务** ``` mvn spring-boot:run ``` ## 测试验证 ### 1. 删除单个文件 ```bash curl -X DELETE "http://localhost:8080/sftp-manager/api/files/delete?sessionId=local&path=C:/test/file.txt" ``` ### 2. 批量删除 ```bash curl -X POST http://localhost:8080/sftp-manager/api/files/batch-delete \ -H "Content-Type: application/json" \ -d '{ "sessionId": "local", "paths": ["C:/test/file1.txt", "C:/test/file2.txt"] }' ``` ### 3. 删除目录 ```bash curl -X DELETE "http://localhost:8080/sftp-manager/api/files/delete?sessionId=local&path=C:/test/folder" ``` ## 注意事项 1. **递归删除**:删除目录时需要递归删除所有子文件和子目录 2. **权限检查**:确保有删除权限,避免删除系统文件 3. **错误处理**:部分文件删除失败时,继续删除其他文件,最后返回结果 4. **确认机制**:必须用户确认后才能执行删除 5. **日志记录**:记录所有删除操作,便于审计 ## 下一步 完成模块06后,继续模块07:文件重命名功能