feat: 前端组件拆分与文件库目录浏览功能
- 将 App.vue 拆分为 AdminPage、ClientPage、QrCodeModal 三个独立组件 - 新增 BrowseDirectory 接口,基于 RelativePath 实现层级目录浏览 - 前端新增面包屑导航、文件夹网格、文件列表等目录浏览 UI - 新增对应 CSS 样式(breadcrumb、folder-grid、file-list 等) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,15 @@ namespace Avalonia_Services.Services.FileLibrary
|
||||
string? TextUrl,
|
||||
bool BrowserPlayable);
|
||||
|
||||
public sealed record BrowseDirectoryRequest(
|
||||
[property: JsonPropertyName("rootId")] int RootId = 0,
|
||||
[property: JsonPropertyName("path")] string? Path = null);
|
||||
|
||||
public sealed record BrowseDirectoryResponse(
|
||||
string CurrentPath,
|
||||
List<string> Subdirectories,
|
||||
List<FileRecordDto> Files);
|
||||
|
||||
public sealed record TextPreviewDto(
|
||||
int Id,
|
||||
string FileName,
|
||||
|
||||
@@ -64,6 +64,15 @@ namespace Avalonia_Services.Services.FileLibrary
|
||||
: ResponseHelper.Ok(preview);
|
||||
}
|
||||
|
||||
public async Task<IApiResponse> BrowseDirectoryAsync(BrowseDirectoryRequest request)
|
||||
{
|
||||
if (request.RootId <= 0)
|
||||
return ResponseHelper.Failure(400, "rootId 参数无效。");
|
||||
|
||||
var result = await fileLibrary.BrowseDirectoryAsync(request);
|
||||
return ResponseHelper.Ok(result);
|
||||
}
|
||||
|
||||
private static void ValidateFileId(int id)
|
||||
{
|
||||
if (id > 0)
|
||||
|
||||
@@ -261,6 +261,54 @@ namespace Avalonia_Services.Services.FileLibrary
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<BrowseDirectoryResponse> BrowseDirectoryAsync(BrowseDirectoryRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var rootId = request.RootId;
|
||||
// URL 友好的正斜杠,用于响应和内存处理
|
||||
var prefix = (request.Path ?? "").Trim().Replace('\\', '/').Trim('/');
|
||||
// Windows 反斜杠,用于数据库查询
|
||||
var dbPrefix = prefix.Replace('/', '\\');
|
||||
|
||||
var query = db.ManagedFileRecords
|
||||
.AsNoTracking()
|
||||
.Where(f => f.LibraryRootId == rootId && f.Exists
|
||||
&& f.LibraryRoot != null && f.LibraryRoot.IsAvailable);
|
||||
|
||||
if (!string.IsNullOrEmpty(dbPrefix))
|
||||
{
|
||||
var dbPrefixWithSlash = dbPrefix + "\\";
|
||||
query = query.Where(f => f.RelativePath.StartsWith(dbPrefixWithSlash));
|
||||
}
|
||||
|
||||
var allFiles = await query.ToListAsync(cancellationToken);
|
||||
|
||||
var subdirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var currentFiles = new List<FileRecordDto>();
|
||||
|
||||
foreach (var file in allFiles)
|
||||
{
|
||||
var relativePath = file.RelativePath.Replace('\\', '/');
|
||||
var remaining = string.IsNullOrEmpty(prefix)
|
||||
? relativePath
|
||||
: relativePath[(prefix.Length + 1)..];
|
||||
|
||||
var slashIndex = remaining.IndexOf('/');
|
||||
if (slashIndex < 0)
|
||||
{
|
||||
currentFiles.Add(ToFileDto(file));
|
||||
}
|
||||
else
|
||||
{
|
||||
subdirs.Add(remaining[..slashIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
return new BrowseDirectoryResponse(
|
||||
prefix,
|
||||
subdirs.OrderBy(d => d, StringComparer.OrdinalIgnoreCase).ToList(),
|
||||
currentFiles);
|
||||
}
|
||||
|
||||
public async Task<TextPreviewDto?> GetTextPreviewAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var file = await db.ManagedFileRecords
|
||||
|
||||
@@ -24,5 +24,7 @@ namespace Avalonia_Services.Services.FileLibrary
|
||||
Task<IApiResponse> GetFileAsync(FileQueryRequest request);
|
||||
|
||||
Task<IApiResponse> GetTextPreviewAsync(FileQueryRequest request);
|
||||
|
||||
Task<IApiResponse> BrowseDirectoryAsync(BrowseDirectoryRequest request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,5 +25,7 @@ namespace Avalonia_Services.Services.FileLibrary
|
||||
Task<FileRecordDto?> GetFileAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<TextPreviewDto?> GetTextPreviewAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<BrowseDirectoryResponse> BrowseDirectoryAsync(BrowseDirectoryRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user