From 60878f9b6eaa1f17b8a011a520ac9ed8f982e886 Mon Sep 17 00:00:00 2001 From: luoqian <2769838458@qq.com> Date: Thu, 9 Jul 2026 17:42:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=B0=B8=E4=B9=85?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=E6=8E=A5=E5=8F=A3=E5=92=8C?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E5=88=A0=E9=99=A4=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端新增 POST api/files/delete 接口,同时删除物理文件、缩略图和数据库记录。 前端在 FileCard 和 FileListItem 中增加 hover 可见的删除按钮,点击后弹出确认提示。 --- FileShare-Services/Endpoints/AppEndpoints.cs | 4 + .../FileLibrary/FileLibraryContracts.cs | 6 + .../FileLibrary/FileLibraryEndpointService.cs | 8 + .../FileLibrary/FileLibraryService.cs | 48 ++ .../IFileLibraryEndpointService.cs | 7 + .../FileLibrary/IFileLibraryService.cs | 7 + FileShare-Web-VUE/src/api/index.ts | 2 + .../src/components/ClientPage.vue | 27 +- .../src/components/client/FileCard.vue | 25 + .../src/components/client/FileListItem.vue | 25 + docs/compose/plans/2026-07-09-file-delete.md | 431 ++++++++++++++++++ 11 files changed, 587 insertions(+), 3 deletions(-) create mode 100644 docs/compose/plans/2026-07-09-file-delete.md diff --git a/FileShare-Services/Endpoints/AppEndpoints.cs b/FileShare-Services/Endpoints/AppEndpoints.cs index 0726cf3..782539b 100644 --- a/FileShare-Services/Endpoints/AppEndpoints.cs +++ b/FileShare-Services/Endpoints/AppEndpoints.cs @@ -82,6 +82,10 @@ namespace FileShare_Services.Endpoints .WithOpenApi("FileLibrary", "保存文件播放进度。") .WithName("SaveFileProgress"); + endpoints.MapPost("api/files/delete", (service, request, _) => service.DeleteFileAsync(request)) + .WithOpenApi("FileLibrary", "永久删除文件(物理文件+数据库记录)。") + .WithName("DeleteFile"); + endpoints.MapGet("api/files/detail", (service, request, _) => service.GetFileAsync(request)) .WithOpenApi("FileLibrary", "查询文件详情。") .WithName("GetFileDetail"); diff --git a/FileShare-Services/Services/FileLibrary/FileLibraryContracts.cs b/FileShare-Services/Services/FileLibrary/FileLibraryContracts.cs index e71150c..b3073af 100644 --- a/FileShare-Services/Services/FileLibrary/FileLibraryContracts.cs +++ b/FileShare-Services/Services/FileLibrary/FileLibraryContracts.cs @@ -34,6 +34,12 @@ namespace FileShare_Services.Services.FileLibrary public sealed record DeleteLibraryRootRequest( [property: JsonPropertyName("id")] int Id); + /// + /// 永久删除文件的请求。 + /// + public sealed record DeleteFileRequest( + [property: JsonPropertyName("id")] int Id); + /// /// 查询服务器子目录的请求。 /// diff --git a/FileShare-Services/Services/FileLibrary/FileLibraryEndpointService.cs b/FileShare-Services/Services/FileLibrary/FileLibraryEndpointService.cs index 4bb5c60..d3e6995 100644 --- a/FileShare-Services/Services/FileLibrary/FileLibraryEndpointService.cs +++ b/FileShare-Services/Services/FileLibrary/FileLibraryEndpointService.cs @@ -113,6 +113,14 @@ namespace FileShare_Services.Services.FileLibrary return ResponseHelper.Succeed(); } + /// + public async Task DeleteFileAsync(DeleteFileRequest request) + { + ValidateFileId(request.Id); + await fileLibrary.DeleteFileAsync(request.Id); + return ResponseHelper.Succeed("文件已永久删除。"); + } + /// /// 验证文件 ID 是否有效,无效时抛出 。 /// diff --git a/FileShare-Services/Services/FileLibrary/FileLibraryService.cs b/FileShare-Services/Services/FileLibrary/FileLibraryService.cs index 728e1d1..cb43f03 100644 --- a/FileShare-Services/Services/FileLibrary/FileLibraryService.cs +++ b/FileShare-Services/Services/FileLibrary/FileLibraryService.cs @@ -545,6 +545,54 @@ namespace FileShare_Services.Services.FileLibrary } } + /// + 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); + } + /// /// 深度优先遍历目录树,枚举所有被 支持的媒体文件路径。 /// 遇到无权限的目录时跳过该分支继续遍历。 diff --git a/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs b/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs index df68761..c96b327 100644 --- a/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs +++ b/FileShare-Services/Services/FileLibrary/IFileLibraryEndpointService.cs @@ -105,5 +105,12 @@ namespace FileShare_Services.Services.FileLibrary /// 包含文件 ID 和播放位置的请求。 /// API 响应。 Task SaveFileProgressAsync(SaveFileProgressRequest request); + + /// + /// 永久删除指定文件。 + /// + /// 包含文件 ID 的请求。 + /// API 响应。 + Task DeleteFileAsync(DeleteFileRequest request); } } diff --git a/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs b/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs index 84688d6..691786c 100644 --- a/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs +++ b/FileShare-Services/Services/FileLibrary/IFileLibraryService.cs @@ -121,5 +121,12 @@ namespace FileShare_Services.Services.FileLibrary /// 播放位置(秒)。 /// 取消令牌。 Task SaveFileProgressAsync(int id, double position, CancellationToken cancellationToken = default); + + /// + /// 永久删除指定文件,同时删除物理文件和缩略图。 + /// + /// 文件记录 ID。 + /// 取消令牌。 + Task DeleteFileAsync(int id, CancellationToken cancellationToken = default); } } diff --git a/FileShare-Web-VUE/src/api/index.ts b/FileShare-Web-VUE/src/api/index.ts index 56b872d..57887f7 100644 --- a/FileShare-Web-VUE/src/api/index.ts +++ b/FileShare-Web-VUE/src/api/index.ts @@ -114,5 +114,7 @@ export const api = { request('files/played', { method: 'POST', body: { id } }), saveFileProgress: (id: number, position: number) => request('files/progress', { method: 'POST', body: { id, position } }), + deleteFile: (id: number) => + request('files/delete', { method: 'POST', body: { id } }), qrCode: () => request<{ url: string; qrCodeBase64: string }>('qrcode'), } diff --git a/FileShare-Web-VUE/src/components/ClientPage.vue b/FileShare-Web-VUE/src/components/ClientPage.vue index 133dc81..4d64568 100644 --- a/FileShare-Web-VUE/src/components/ClientPage.vue +++ b/FileShare-Web-VUE/src/components/ClientPage.vue @@ -272,6 +272,24 @@ function exitSearch() { searchTotalPages.value = 0 } +async function deleteFile(file: FileRecordDto) { + if (!window.confirm(`确定要永久删除 "${file.fileName}" 吗?\n\n删除后无法恢复!`)) { + return + } + try { + await api.deleteFile(file.id) + await browseDirectory() + if (isSearching.value) { + await doSearch(searchPage.value) + } + if (selectedFile.value?.id === file.id) { + selectedFile.value = null + } + } catch (error) { + setError(error) + } +} + function updatePlaybackPosition(id: number, position: number) { if (selectedFile.value?.id === id) { selectedFile.value.playbackPosition = position @@ -463,7 +481,7 @@ onBeforeUnmount(() => {

无匹配文件