202 lines
7.0 KiB
Plaintext
202 lines
7.0 KiB
Plaintext
@page "/"
|
|
@inject SongCatalogService Catalog
|
|
@inject SiteSettingsService SiteSettings
|
|
@inject IJSRuntime Js
|
|
@inject ISnackbar Snackbar
|
|
|
|
<PageTitle>洛星瓷歌单</PageTitle>
|
|
|
|
<section class="song-page">
|
|
<div class="page-heading">
|
|
<div>
|
|
<MudText Typo="Typo.h1">洛星瓷歌单</MudText>
|
|
<MudText Typo="Typo.body1" Color="Color.Secondary">快速查找会唱的歌,复制点歌文案,也可以给歌曲补充标签。</MudText>
|
|
</div>
|
|
<MudPaper Class="asset-slot character-slot" Elevation="0">
|
|
<MudIcon Icon="@Icons.Material.Filled.AutoAwesome" />
|
|
<span>角色图占位</span>
|
|
</MudPaper>
|
|
</div>
|
|
|
|
<MudPaper Class="search-panel" Elevation="0">
|
|
<MudTextField T="string"
|
|
Value="_search"
|
|
ValueChanged="OnSearchChanged"
|
|
Immediate="true"
|
|
DebounceInterval="180"
|
|
Placeholder="搜索歌名或标签"
|
|
Variant="Variant.Outlined"
|
|
Adornment="Adornment.Start"
|
|
AdornmentIcon="@Icons.Material.Filled.Search"
|
|
Clearable="true" />
|
|
|
|
@if (_selectedTag is not null)
|
|
{
|
|
<div class="active-filter">
|
|
<MudChip T="string" Color="Color.Primary" Variant="Variant.Filled" OnClose="ClearTagFilter">
|
|
标签:@_selectedTag
|
|
</MudChip>
|
|
</div>
|
|
}
|
|
|
|
@if (_allTags.Count > 0)
|
|
{
|
|
<div class="tag-cloud" aria-label="标签筛选">
|
|
@foreach (var tag in _allTags.Take(32))
|
|
{
|
|
<MudChip T="string"
|
|
Color="@(_selectedTag == tag ? Color.Primary : Color.Info)"
|
|
Variant="@(_selectedTag == tag ? Variant.Filled : Variant.Outlined)"
|
|
OnClick="@(() => SelectTag(tag))">
|
|
@tag
|
|
</MudChip>
|
|
}
|
|
</div>
|
|
}
|
|
</MudPaper>
|
|
|
|
@if (_isLoading)
|
|
{
|
|
<div class="loading-row">
|
|
<MudProgressCircular Indeterminate="true" Color="Color.Primary" />
|
|
</div>
|
|
}
|
|
else if (_songs.Count == 0)
|
|
{
|
|
<MudPaper Class="empty-state" Elevation="0">
|
|
<MudIcon Icon="@Icons.Material.Filled.LibraryMusic" Size="Size.Large" />
|
|
<MudText Typo="Typo.h6">歌单还是空的</MudText>
|
|
<MudText Color="Color.Secondary">之后导入歌曲后,这里会显示可搜索的曲目。</MudText>
|
|
</MudPaper>
|
|
}
|
|
else
|
|
{
|
|
<div class="song-grid">
|
|
@foreach (var song in _songs)
|
|
{
|
|
<MudPaper Class="song-card" Elevation="0">
|
|
<div class="song-card-main">
|
|
<MudText Typo="Typo.h6">@song.Title</MudText>
|
|
<div class="song-tags">
|
|
@foreach (var tag in song.Tags)
|
|
{
|
|
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="Color.Info" OnClick="@(() => SelectTag(tag))">
|
|
@tag
|
|
</MudChip>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="song-card-actions">
|
|
<MudButton Variant="Variant.Filled"
|
|
Color="Color.Primary"
|
|
StartIcon="@Icons.Material.Filled.ContentCopy"
|
|
OnClick="@(() => CopyRequest(song.Title))">
|
|
复制点歌
|
|
</MudButton>
|
|
</div>
|
|
|
|
<div class="viewer-tag-row">
|
|
<MudTextField T="string"
|
|
Value="@song.NewTag"
|
|
ValueChanged="@((string value) => song.NewTag = value)"
|
|
Placeholder="添加标签"
|
|
Variant="Variant.Outlined"
|
|
Margin="Margin.Dense" />
|
|
<MudIconButton Icon="@Icons.Material.Filled.Add"
|
|
Color="Color.Primary"
|
|
Variant="Variant.Filled"
|
|
Disabled="@string.IsNullOrWhiteSpace(song.NewTag)"
|
|
OnClick="@(() => AddTag(song))" />
|
|
</div>
|
|
</MudPaper>
|
|
}
|
|
</div>
|
|
}
|
|
</section>
|
|
|
|
@code {
|
|
private readonly List<SongCardState> _songs = [];
|
|
private readonly List<string> _allTags = [];
|
|
private string _search = string.Empty;
|
|
private string? _selectedTag;
|
|
private string _requestTemplate = "点歌《{title}》";
|
|
private bool _isLoading = true;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
_requestTemplate = await SiteSettings.GetRequestTemplateAsync();
|
|
await LoadAsync();
|
|
}
|
|
|
|
private async Task OnSearchChanged(string value)
|
|
{
|
|
_search = value;
|
|
await LoadAsync();
|
|
}
|
|
|
|
private async Task SelectTag(string tag)
|
|
{
|
|
_selectedTag = _selectedTag == tag ? null : tag;
|
|
await LoadAsync();
|
|
}
|
|
|
|
private async Task ClearTagFilter()
|
|
{
|
|
_selectedTag = null;
|
|
await LoadAsync();
|
|
}
|
|
|
|
private async Task LoadAsync()
|
|
{
|
|
_isLoading = true;
|
|
var songs = await Catalog.SearchSongsAsync(_search, _selectedTag);
|
|
var tags = await Catalog.GetVisibleTagsAsync();
|
|
|
|
_songs.Clear();
|
|
_songs.AddRange(songs.Select(SongCardState.From));
|
|
_allTags.Clear();
|
|
_allTags.AddRange(tags);
|
|
_isLoading = false;
|
|
}
|
|
|
|
private async Task CopyRequest(string title)
|
|
{
|
|
var request = SiteSettingsService.FormatRequest(_requestTemplate, title);
|
|
await Js.InvokeVoidAsync("navigator.clipboard.writeText", request);
|
|
Snackbar.Add($"已复制:{request}", Severity.Success);
|
|
}
|
|
|
|
private async Task AddTag(SongCardState song)
|
|
{
|
|
try
|
|
{
|
|
await Catalog.AddViewerTagAsync(song.Id, song.NewTag);
|
|
Snackbar.Add("标签已添加", Severity.Success);
|
|
await LoadAsync();
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
Snackbar.Add(ex.Message, Severity.Warning);
|
|
}
|
|
}
|
|
|
|
private sealed class SongCardState
|
|
{
|
|
public int Id { get; init; }
|
|
public string Title { get; init; } = string.Empty;
|
|
public IReadOnlyList<string> Tags { get; init; } = [];
|
|
public string NewTag { get; set; } = string.Empty;
|
|
|
|
public static SongCardState From(SongListItem item)
|
|
{
|
|
return new SongCardState
|
|
{
|
|
Id = item.Id,
|
|
Title = item.Title,
|
|
Tags = item.Tags
|
|
};
|
|
}
|
|
}
|
|
}
|