# File Delete Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use compose:subagent (recommended) or compose:execute to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add a permanent file delete API endpoint and frontend delete button with confirmation dialog. **Architecture:** Backend adds `POST api/files/delete` that deletes physical file + thumbnail + DB record. Frontend adds delete button to FileCard and FileListItem, with `window.confirm()` guard. **Tech Stack:** ASP.NET Core 10, EF Core 10, Vue 3.5, TypeScript 6 ## Global Constraints - Do not edit `FileShare-EFCore/Migrations/**` — schema changes require user to run migration scripts - All endpoints use POST (project convention) - Frontend response interceptor auto-unwraps `{ success, data }` envelope - Follow existing code patterns (sealed record DTOs, `ResponseHelper`, `defineProps`/`defineEmits`) --- ## File Map | File | Action | Purpose | |------|--------|---------| | `FileShare-Services/Services/FileLibrary/FileLibraryContracts.cs` | Modify | Add `DeleteFileRequest` DTO | | `FileShare-Services/Services/FileLibrary/IFileLibraryService.cs` | Modify | Add `DeleteFileAsync` method | | `FileShare-Services/Services/FileLibrary/FileLibraryService.cs` | Modify | Implement delete logic | | `FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs` | Modify | Add `DeleteFileAsync` endpoint method | | `FileShare-Services/Services/FileLibrary/FileLibraryEndpointService.cs` | Modify | Implement endpoint adapter | | `FileShare-Services/Endpoints/AppEndpoints.cs` | Modify | Register route | | `FileShare-Web-VUE/src/api/index.ts` | Modify | Add `deleteFile` API method | | `FileShare-Web-VUE/src/components/client/FileCard.vue` | Modify | Add delete button | | `FileShare-Web-VUE/src/components/client/FileListItem.vue` | Modify | Add delete button | | `FileShare-Web-VUE/src/components/ClientPage.vue` | Modify | Handle delete event | --- ## Task 1: Backend — Contract, Service Interface, Service Implementation **Covers:** S2 (backend delete logic) **Files:** - Modify: `FileShare-Services/Services/FileLibrary/FileLibraryContracts.cs:35` - Modify: `FileShare-Services/Services/FileLibrary/IFileLibraryService.cs:123` - Modify: `FileShare-Services/Services/FileLibrary/FileLibraryService.cs:546` - [ ] **Step 1: Add DeleteFileRequest to FileLibraryContracts.cs** After line 35 (the `DeleteLibraryRootRequest` record), add: ```csharp /// /// 永久删除文件的请求。 /// public sealed record DeleteFileRequest( [property: JsonPropertyName("id")] int Id); ``` - [ ] **Step 2: Add DeleteFileAsync to IFileLibraryService.cs** After line 123 (`SaveFileProgressAsync`), add: ```csharp /// /// 永久删除指定文件,同时删除物理文件和缩略图。 /// /// 文件记录 ID。 /// 取消令牌。 Task DeleteFileAsync(int id, CancellationToken cancellationToken = default); ``` - [ ] **Step 3: Implement DeleteFileAsync in FileLibraryService.cs** After line 546 (end of `SaveFileProgressAsync`), add: ```csharp /// public async Task DeleteFileAsync(int id, CancellationToken cancellationToken = default) { var record = await db.ManagedFileRecords .Include(f => f.Thumbnail) .FirstOrDefaultAsync(f => f.Id == id, cancellationToken); if (record is null) { return; } // 删除物理文件 try { if (File.Exists(record.AbsolutePath)) { File.Delete(record.AbsolutePath); } } catch (Exception ex) { Serilog.Log.Warning(ex, "删除物理文件失败 FilePath={FilePath}", record.AbsolutePath); } // 删除缩略图文件 if (record.Thumbnail is not null) { try { var thumbnailPath = thumbnailService.GetAbsolutePath(record.Thumbnail.RelativePath); if (File.Exists(thumbnailPath)) { File.Delete(thumbnailPath); } } catch (Exception ex) { Serilog.Log.Warning(ex, "删除缩略图文件失败 ThumbnailId={ThumbnailId}", record.ThumbnailId); } db.ManagedThumbnailMaps.Remove(record.Thumbnail); } db.ManagedFileRecords.Remove(record); await db.SaveChangesAsync(cancellationToken); } ``` --- ## Task 2: Backend — Endpoint Service + Route Registration **Covers:** S2 (endpoint and routing) **Files:** - Modify: `FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs:107` - Modify: `FileShare-Services/Services/FileLibrary/FileLibraryEndpointService.cs:114` - Modify: `FileShare-Services/Endpoints/AppEndpoints.cs:92` - [ ] **Step 1: Add DeleteFileAsync to IFileLibraryEndpointService.cs** After line 107 (`SaveFileProgressAsync`), add: ```csharp /// /// 永久删除指定文件。 /// /// 包含文件 ID 的请求。 /// API 响应。 Task DeleteFileAsync(DeleteFileRequest request); ``` - [ ] **Step 2: Implement DeleteFileAsync in FileLibraryEndpointService.cs** After line 114 (end of `SaveFileProgressAsync`), add: ```csharp /// public async Task DeleteFileAsync(DeleteFileRequest request) { ValidateFileId(request.Id); await fileLibrary.DeleteFileAsync(request.Id); return ResponseHelper.Succeed("文件已永久删除。"); } ``` - [ ] **Step 3: Register route in AppEndpoints.cs** After line 92 (the `SaveFileProgress` endpoint), add: ```csharp endpoints.MapPost("api/files/delete", (service, request, _) => service.DeleteFileAsync(request)) .WithOpenApi("FileLibrary", "永久删除文件(物理文件+数据库记录)。") .WithName("DeleteFile"); ``` --- ## Task 3: Frontend — API Method + Components **Covers:** S3 (frontend delete button and API) **Files:** - Modify: `FileShare-Web-VUE/src/api/index.ts:117` - Modify: `FileShare-Web-VUE/src/components/client/FileCard.vue` - Modify: `FileShare-Web-VUE/src/components/client/FileListItem.vue` - Modify: `FileShare-Web-VUE/src/components/ClientPage.vue` - [ ] **Step 1: Add deleteFile API method in index.ts** After line 117 (`saveFileProgress`), add: ```typescript deleteFile: (id: number) => request('files/delete', { method: 'POST', body: { id } }), ``` - [ ] **Step 2: Add delete button to FileCard.vue** Replace the entire `FileCard.vue` content with: ```vue × {{ file.mediaType }} {{ file.fileName }} {{ formatSize(file.sizeBytes) }} · {{ formatDuration(file.videoDuration) }} {{ formatCreatedTime(file) }} ``` - [ ] **Step 3: Add delete button to FileListItem.vue** Replace the entire `FileListItem.vue` content with: ```vue {{ file.mediaType }} {{ file.fileName }} {{ formatSize(file.sizeBytes) }} · {{ formatDuration(file.videoDuration) }} · {{ formatDate(file.lastPlayedAt) }} {{ formatCreatedTime(file) }} × ``` - [ ] **Step 4: Add delete handler and wire events in ClientPage.vue** In `