285 lines
11 KiB
Plaintext
285 lines
11 KiB
Plaintext
@page "/admin"
|
|
@attribute [Authorize]
|
|
@inject SongCatalogService Catalog
|
|
@inject SiteSettingsService SiteSettings
|
|
@inject ISnackbar Snackbar
|
|
|
|
<PageTitle>歌单管理</PageTitle>
|
|
|
|
<section class="admin-page">
|
|
<div class="page-heading compact">
|
|
<div>
|
|
<MudText Typo="Typo.h1">歌单管理</MudText>
|
|
<MudText Typo="Typo.body1" Color="Color.Secondary">添加歌曲、隐藏曲目、清理标签和调整点歌文案。</MudText>
|
|
</div>
|
|
<MudButton Href="/admin/logout" Variant="Variant.Outlined" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Logout">
|
|
退出
|
|
</MudButton>
|
|
</div>
|
|
|
|
<MudGrid Spacing="3">
|
|
<MudItem xs="12" md="7">
|
|
<MudPaper Class="admin-panel" Elevation="0">
|
|
<div class="panel-title-row">
|
|
<MudText Typo="Typo.h5">歌曲</MudText>
|
|
<MudChip T="string" Color="Color.Info" Variant="Variant.Outlined">@_songs.Count 首</MudChip>
|
|
</div>
|
|
|
|
<div class="admin-add-row">
|
|
<MudTextField T="string"
|
|
Value="_newSongTitle"
|
|
ValueChanged="@((string value) => _newSongTitle = value)"
|
|
Label="新增歌名"
|
|
Variant="Variant.Outlined"
|
|
Margin="Margin.Dense" />
|
|
<MudButton Variant="Variant.Filled"
|
|
Color="Color.Primary"
|
|
StartIcon="@Icons.Material.Filled.Add"
|
|
Disabled="@string.IsNullOrWhiteSpace(_newSongTitle)"
|
|
OnClick="CreateSong">
|
|
添加
|
|
</MudButton>
|
|
</div>
|
|
|
|
@if (_songs.Count == 0)
|
|
{
|
|
<MudText Class="admin-muted" Color="Color.Secondary">还没有歌曲。之后可以从这里手动添加,或直接导入数据库。</MudText>
|
|
}
|
|
else
|
|
{
|
|
<div class="admin-list">
|
|
@foreach (var song in _songs)
|
|
{
|
|
<MudPaper Class="@($"admin-list-item {(song.IsHidden ? "is-hidden" : string.Empty)}")" Elevation="0">
|
|
<MudTextField T="string"
|
|
Value="@song.Title"
|
|
ValueChanged="@((string value) => song.Title = value)"
|
|
Label="歌名"
|
|
Variant="Variant.Outlined"
|
|
Margin="Margin.Dense" />
|
|
<MudCheckBox T="bool"
|
|
Value="@song.IsHidden"
|
|
ValueChanged="@((bool value) => song.IsHidden = value)"
|
|
Label="隐藏" />
|
|
<div class="song-tags admin-song-tags">
|
|
@foreach (var tag in song.Tags)
|
|
{
|
|
<MudChip T="string" Size="Size.Small" Variant="Variant.Outlined" Color="Color.Info">@tag</MudChip>
|
|
}
|
|
</div>
|
|
<div class="admin-actions">
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Save" OnClick="@(() => SaveSong(song))">
|
|
保存
|
|
</MudButton>
|
|
<MudButton Variant="Variant.Outlined" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="@(() => DeleteSong(song))">
|
|
删除
|
|
</MudButton>
|
|
</div>
|
|
</MudPaper>
|
|
}
|
|
</div>
|
|
}
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
<MudItem xs="12" md="5">
|
|
<MudPaper Class="admin-panel" Elevation="0">
|
|
<MudText Typo="Typo.h5">站点设置</MudText>
|
|
<MudTextField T="string"
|
|
Value="_requestTemplate"
|
|
ValueChanged="@((string value) => _requestTemplate = value)"
|
|
Label="点歌文案模板"
|
|
HelperText="使用 {title} 代表歌名"
|
|
Variant="Variant.Outlined" />
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Save" OnClick="SaveRequestTemplate">
|
|
保存设置
|
|
</MudButton>
|
|
</MudPaper>
|
|
|
|
<MudPaper Class="admin-panel" Elevation="0">
|
|
<div class="panel-title-row">
|
|
<MudText Typo="Typo.h5">标签</MudText>
|
|
<MudChip T="string" Color="Color.Info" Variant="Variant.Outlined">@_tags.Count 个</MudChip>
|
|
</div>
|
|
|
|
@if (_tags.Count == 0)
|
|
{
|
|
<MudText Class="admin-muted" Color="Color.Secondary">暂无标签。</MudText>
|
|
}
|
|
else
|
|
{
|
|
<div class="tag-admin-list">
|
|
@foreach (var tag in _tags)
|
|
{
|
|
<MudPaper Class="tag-admin-item" Elevation="0">
|
|
<MudTextField T="string"
|
|
Value="@tag.Name"
|
|
ValueChanged="@((string value) => tag.Name = value)"
|
|
Label="@($"标签,{tag.SongCount} 首")"
|
|
Variant="Variant.Outlined"
|
|
Margin="Margin.Dense" />
|
|
<MudTextField T="string"
|
|
Value="@tag.MergeTarget"
|
|
ValueChanged="@((string value) => tag.MergeTarget = value)"
|
|
Label="合并到"
|
|
Variant="Variant.Outlined"
|
|
Margin="Margin.Dense" />
|
|
<div class="admin-actions">
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Save" OnClick="@(() => RenameTag(tag))">
|
|
改名
|
|
</MudButton>
|
|
<MudButton Variant="Variant.Outlined" Color="Color.Info" StartIcon="@Icons.Material.Filled.CallMerge" Disabled="@string.IsNullOrWhiteSpace(tag.MergeTarget)" OnClick="@(() => MergeTag(tag))">
|
|
合并
|
|
</MudButton>
|
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Variant="Variant.Outlined" OnClick="@(() => DeleteTag(tag))" />
|
|
</div>
|
|
</MudPaper>
|
|
}
|
|
</div>
|
|
}
|
|
</MudPaper>
|
|
</MudItem>
|
|
</MudGrid>
|
|
</section>
|
|
|
|
@code {
|
|
private readonly List<AdminSongState> _songs = [];
|
|
private readonly List<AdminTagState> _tags = [];
|
|
private string _newSongTitle = string.Empty;
|
|
private string _requestTemplate = "点歌《{title}》";
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadAsync();
|
|
}
|
|
|
|
private async Task LoadAsync()
|
|
{
|
|
var songs = await Catalog.GetAdminSongsAsync();
|
|
var tags = await Catalog.GetAdminTagsAsync();
|
|
_requestTemplate = await SiteSettings.GetRequestTemplateAsync();
|
|
|
|
_songs.Clear();
|
|
_songs.AddRange(songs.Select(AdminSongState.From));
|
|
_tags.Clear();
|
|
_tags.AddRange(tags.Select(AdminTagState.From));
|
|
}
|
|
|
|
private async Task CreateSong()
|
|
{
|
|
await RunAdminAction(async () =>
|
|
{
|
|
await Catalog.CreateSongAsync(_newSongTitle);
|
|
_newSongTitle = string.Empty;
|
|
Snackbar.Add("歌曲已添加", Severity.Success);
|
|
});
|
|
}
|
|
|
|
private async Task SaveSong(AdminSongState song)
|
|
{
|
|
await RunAdminAction(async () =>
|
|
{
|
|
await Catalog.UpdateSongAsync(song.Id, song.Title, song.IsHidden);
|
|
Snackbar.Add("歌曲已保存", Severity.Success);
|
|
});
|
|
}
|
|
|
|
private async Task DeleteSong(AdminSongState song)
|
|
{
|
|
await RunAdminAction(async () =>
|
|
{
|
|
await Catalog.DeleteSongAsync(song.Id);
|
|
Snackbar.Add("歌曲已删除", Severity.Success);
|
|
});
|
|
}
|
|
|
|
private async Task SaveRequestTemplate()
|
|
{
|
|
await RunAdminAction(async () =>
|
|
{
|
|
await SiteSettings.SaveRequestTemplateAsync(_requestTemplate);
|
|
Snackbar.Add("设置已保存", Severity.Success);
|
|
}, reload: false);
|
|
}
|
|
|
|
private async Task RenameTag(AdminTagState tag)
|
|
{
|
|
await RunAdminAction(async () =>
|
|
{
|
|
await Catalog.RenameTagAsync(tag.Id, tag.Name);
|
|
Snackbar.Add("标签已更新", Severity.Success);
|
|
});
|
|
}
|
|
|
|
private async Task MergeTag(AdminTagState tag)
|
|
{
|
|
await RunAdminAction(async () =>
|
|
{
|
|
await Catalog.MergeTagAsync(tag.Id, tag.MergeTarget);
|
|
Snackbar.Add("标签已合并", Severity.Success);
|
|
});
|
|
}
|
|
|
|
private async Task DeleteTag(AdminTagState tag)
|
|
{
|
|
await RunAdminAction(async () =>
|
|
{
|
|
await Catalog.DeleteTagAsync(tag.Id);
|
|
Snackbar.Add("标签已删除", Severity.Success);
|
|
});
|
|
}
|
|
|
|
private async Task RunAdminAction(Func<Task> action, bool reload = true)
|
|
{
|
|
try
|
|
{
|
|
await action();
|
|
if (reload)
|
|
{
|
|
await LoadAsync();
|
|
}
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
Snackbar.Add(ex.Message, Severity.Warning);
|
|
}
|
|
}
|
|
|
|
private sealed class AdminSongState
|
|
{
|
|
public int Id { get; init; }
|
|
public string Title { get; set; } = string.Empty;
|
|
public bool IsHidden { get; set; }
|
|
public IReadOnlyList<string> Tags { get; init; } = [];
|
|
|
|
public static AdminSongState From(AdminSongItem item)
|
|
{
|
|
return new AdminSongState
|
|
{
|
|
Id = item.Id,
|
|
Title = item.Title,
|
|
IsHidden = item.IsHidden,
|
|
Tags = item.Tags
|
|
};
|
|
}
|
|
}
|
|
|
|
private sealed class AdminTagState
|
|
{
|
|
public int Id { get; init; }
|
|
public string Name { get; set; } = string.Empty;
|
|
public string MergeTarget { get; set; } = string.Empty;
|
|
public int SongCount { get; init; }
|
|
|
|
public static AdminTagState From(AdminTagItem item)
|
|
{
|
|
return new AdminTagState
|
|
{
|
|
Id = item.Id,
|
|
Name = item.Name,
|
|
SongCount = item.SongCount
|
|
};
|
|
}
|
|
}
|
|
}
|