feat: add category filters and sync action
This commit is contained in:
1 parent
da44797626
commit
7dc1eea3c0
25 files changed
+1707
-110
No files matched your search
@@ -5,11 +5,11 @@
|
||||
<DialogContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudAlert Severity="Severity.Info">
|
||||
将为 <strong>@SelectedCount</strong> 个频道设置分组
|
||||
将为 <strong>@SelectedCount</strong> 个频道设置分类
|
||||
</MudAlert>
|
||||
|
||||
<MudSelect @bind-Value="selectedGroupId" Label="选择目标分组" Variant="Variant.Outlined" Required="true">
|
||||
<MudSelectItem Value="0">未分组</MudSelectItem>
|
||||
<MudSelect @bind-Value="selectedGroupId" Label="选择目标分类" Variant="Variant.Outlined" Required="true">
|
||||
<MudSelectItem Value="0">未分类</MudSelectItem>
|
||||
@foreach (var g in Groups)
|
||||
{
|
||||
<MudSelectItem Value="@g.Id">@g.Name</MudSelectItem>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
@namespace TelegramPanel.Web.Components.Dialogs
|
||||
@using TelegramPanel.Data.Entities
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudAlert Severity="Severity.Info">
|
||||
将为 <strong>@SelectedCount</strong> 个群组设置分类
|
||||
</MudAlert>
|
||||
|
||||
<MudSelect @bind-Value="selectedCategoryId" Label="选择目标分类" Variant="Variant.Outlined" Required="true">
|
||||
<MudSelectItem Value="0">未分类</MudSelectItem>
|
||||
@foreach (var category in Categories)
|
||||
{
|
||||
<MudSelectItem Value="@category.Id">@category.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="Cancel">取消</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="Confirm">确定</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Parameter] public List<GroupCategory> Categories { get; set; } = new();
|
||||
[Parameter] public int SelectedCount { get; set; }
|
||||
|
||||
private int selectedCategoryId;
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private void Confirm()
|
||||
{
|
||||
MudDialog.Close(DialogResult.Ok(selectedCategoryId));
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,12 @@
|
||||
<MudNavGroup Title="频道管理" Icon="@Icons.Material.Filled.Campaign" Expanded="true">
|
||||
<MudNavLink Href="/channels" Icon="@Icons.Material.Filled.List">频道列表</MudNavLink>
|
||||
<MudNavLink Href="/channels/create" Icon="@Icons.Material.Filled.Add">创建频道</MudNavLink>
|
||||
<MudNavLink Href="/channels/groups" Icon="@Icons.Material.Filled.Folder">频道分组</MudNavLink>
|
||||
<MudNavLink Href="/channels/groups" Icon="@Icons.Material.Filled.Folder">频道分类</MudNavLink>
|
||||
</MudNavGroup>
|
||||
|
||||
<MudNavGroup Title="群组管理" Icon="@Icons.Material.Filled.Group">
|
||||
<MudNavLink Href="/groups" Icon="@Icons.Material.Filled.List">群组列表</MudNavLink>
|
||||
<MudNavLink Href="/groups/categories" Icon="@Icons.Material.Filled.Folder">群组分类</MudNavLink>
|
||||
</MudNavGroup>
|
||||
|
||||
<MudNavGroup Title="机器人管理" Icon="@Icons.Material.Filled.SmartToy">
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
@page "/channels/groups"
|
||||
@inject ChannelGroupManagementService GroupManagement
|
||||
@inject ChannelManagementService ChannelManagement
|
||||
@inject AccountManagementService AccountManagement
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
@using TelegramPanel.Data.Entities
|
||||
|
||||
<PageTitle>频道分组 - Telegram Panel</PageTitle>
|
||||
<PageTitle>频道分类 - Telegram Panel</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-4">频道分组管理</MudText>
|
||||
<MudText Typo="Typo.h4" Class="mb-4">频道分类管理</MudText>
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">添加分组</MudText>
|
||||
<MudText Typo="Typo.h6">@(editingGroupId > 0 ? "编辑分类" : "添加分类")</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudTextField @bind-Value="newGroupName" Label="分组名称" Variant="Variant.Outlined" />
|
||||
<MudTextField @bind-Value="newGroupName" Label="分类名称" Variant="Variant.Outlined" />
|
||||
<MudTextField @bind-Value="newGroupDesc" Label="描述" Variant="Variant.Outlined" Lines="3" Class="mt-3" />
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true"
|
||||
Disabled="@string.IsNullOrEmpty(newGroupName)" OnClick="AddGroup">
|
||||
添加分组
|
||||
@if (editingGroupId > 0)
|
||||
{
|
||||
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="CancelEdit">取消</MudButton>
|
||||
}
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="@(editingGroupId <= 0)"
|
||||
Disabled="@string.IsNullOrWhiteSpace(newGroupName)" OnClick="SaveGroup">
|
||||
@(editingGroupId > 0 ? "保存修改" : "添加分类")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
@@ -31,27 +36,31 @@
|
||||
<MudItem xs="12" md="8">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">分组列表</MudText>
|
||||
<MudText Typo="Typo.h6">分类列表</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudTable Items="@groups" Hover="true" Loading="@loading">
|
||||
<HeaderContent>
|
||||
<MudTh>分组名称</MudTh>
|
||||
<MudTh>分类名称</MudTh>
|
||||
<MudTh>描述</MudTh>
|
||||
<MudTh>频道数量</MudTh>
|
||||
<MudTh>操作</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="分组名称">@context.Name</MudTd>
|
||||
<MudTd DataLabel="分类名称">@context.Name</MudTd>
|
||||
<MudTd DataLabel="描述">@(context.Description ?? "-")</MudTd>
|
||||
<MudTd DataLabel="频道数量">@context.Channels.Count</MudTd>
|
||||
<MudTd DataLabel="操作">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Small" Color="Color.Error"
|
||||
OnClick="@(() => DeleteGroup(context))" />
|
||||
<MudStack Row="true" Spacing="1">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Small" Color="Color.Primary"
|
||||
OnClick="@(() => BeginEdit(context))" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Small" Color="Color.Error"
|
||||
OnClick="@(() => DeleteGroup(context))" />
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<NoRecordsContent>
|
||||
<MudText>暂无分组,请添加分组</MudText>
|
||||
<MudText>暂无分类,请添加分类</MudText>
|
||||
</NoRecordsContent>
|
||||
</MudTable>
|
||||
</MudCardContent>
|
||||
@@ -59,32 +68,43 @@
|
||||
|
||||
<MudCard Class="mt-4">
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">分组绑定频道</MudText>
|
||||
<MudText Typo="Typo.h6">分类绑定频道</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudSelect @bind-Value="assignGroupId" Label="选择分组" Variant="Variant.Outlined" Dense="true"
|
||||
@bind-Value:after="OnAssignGroupChanged">
|
||||
<MudSelectItem Value="0">-- 请选择分组 --</MudSelectItem>
|
||||
@foreach (var g in groups)
|
||||
{
|
||||
<MudSelectItem Value="@g.Id">@g.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudStack Row="true" Spacing="2" Style="flex-wrap: wrap;">
|
||||
<MudSelect @bind-Value="assignGroupId" Label="选择分类" Variant="Variant.Outlined" Dense="true"
|
||||
Class="tp-input-full-xs" Style="min-width: 220px;" @bind-Value:after="OnAssignGroupChanged">
|
||||
<MudSelectItem Value="0">-- 请选择分类 --</MudSelectItem>
|
||||
@foreach (var g in groups)
|
||||
{
|
||||
<MudSelectItem Value="@g.Id">@g.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudTable Items="@channels" Hover="true" Dense="true" Class="mt-3"
|
||||
<MudSelect @bind-Value="filterAccountId" Label="筛选账号" Variant="Variant.Outlined" Dense="true"
|
||||
Class="tp-input-full-xs" Style="min-width: 220px;" @bind-Value:after="OnAssignGroupChanged">
|
||||
<MudSelectItem Value="0">全部账号</MudSelectItem>
|
||||
@foreach (var account in accounts)
|
||||
{
|
||||
<MudSelectItem Value="@account.Id">@FormatAccountLabel(account)</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
|
||||
<MudTable Items="@VisibleChannels" Hover="true" Dense="true" Class="mt-3"
|
||||
MultiSelection="true" @bind-SelectedItems="selectedChannels"
|
||||
Loading="@channelsLoading">
|
||||
<HeaderContent>
|
||||
<MudTh>频道名称</MudTh>
|
||||
<MudTh>用户名</MudTh>
|
||||
<MudTh>创建账号</MudTh>
|
||||
<MudTh>当前分组</MudTh>
|
||||
<MudTh>当前分类</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="频道名称">@context.Title</MudTd>
|
||||
<MudTd DataLabel="用户名">@(string.IsNullOrWhiteSpace(context.Username) ? "-" : context.Username)</MudTd>
|
||||
<MudTd DataLabel="创建账号">@(context.CreatorAccount?.Phone ?? (context.CreatorAccountId?.ToString() ?? "(非系统创建)"))</MudTd>
|
||||
<MudTd DataLabel="当前分组">@(context.Group?.Name ?? "未分组")</MudTd>
|
||||
<MudTd DataLabel="当前分类">@(context.Group?.Name ?? "未分类")</MudTd>
|
||||
</RowTemplate>
|
||||
<NoRecordsContent>
|
||||
<MudText>暂无频道数据</MudText>
|
||||
@@ -95,7 +115,7 @@
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
Disabled="@(assignGroupId == 0 || channelsLoading)"
|
||||
OnClick="@(async (MouseEventArgs _) => await SaveGroupAssignments())">
|
||||
保存勾选到分组
|
||||
保存勾选到分类
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
@@ -105,20 +125,41 @@
|
||||
@code {
|
||||
private string newGroupName = "";
|
||||
private string newGroupDesc = "";
|
||||
private int editingGroupId = 0;
|
||||
private List<ChannelGroup> groups = new();
|
||||
private bool loading = true;
|
||||
|
||||
private List<Channel> channels = new();
|
||||
private List<Account> accounts = new();
|
||||
private bool channelsLoading = true;
|
||||
private int assignGroupId = 0;
|
||||
private int filterAccountId = 0;
|
||||
private HashSet<Channel> selectedChannels = new();
|
||||
|
||||
private IEnumerable<Channel> VisibleChannels => channels.Where(MatchAccountFilter);
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadAccounts();
|
||||
await LoadGroups();
|
||||
await LoadChannels();
|
||||
}
|
||||
|
||||
private async Task LoadAccounts()
|
||||
{
|
||||
try
|
||||
{
|
||||
var accountList = await AccountManagement.GetAllAccountsAsync();
|
||||
accounts = accountList
|
||||
.Where(a => a.Category?.ExcludeFromOperations != true)
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"加载账号失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadGroups()
|
||||
{
|
||||
loading = true;
|
||||
@@ -144,6 +185,7 @@
|
||||
{
|
||||
var list = await ChannelManagement.GetAllChannelsAsync();
|
||||
channels = list.ToList();
|
||||
await OnAssignGroupChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -155,11 +197,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
private bool MatchAccountFilter(Channel channel)
|
||||
{
|
||||
if (filterAccountId <= 0)
|
||||
return true;
|
||||
|
||||
return channel.AccountChannels.Any(x => x.AccountId == filterAccountId);
|
||||
}
|
||||
|
||||
private Task OnAssignGroupChanged()
|
||||
{
|
||||
selectedChannels = assignGroupId <= 0
|
||||
? new HashSet<Channel>()
|
||||
: channels.Where(c => c.GroupId == assignGroupId).ToHashSet();
|
||||
: VisibleChannels.Where(c => c.GroupId == assignGroupId).ToHashSet();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -167,30 +217,30 @@
|
||||
{
|
||||
if (assignGroupId <= 0)
|
||||
{
|
||||
Snackbar.Add("请选择分组", Severity.Error);
|
||||
Snackbar.Add("请选择分类", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var currentlyInGroup = channels.Where(c => c.GroupId == assignGroupId).ToList();
|
||||
var scope = VisibleChannels.ToList();
|
||||
var currentlyInGroup = scope.Where(c => c.GroupId == assignGroupId).ToList();
|
||||
var selected = selectedChannels.ToHashSet();
|
||||
|
||||
// 取消勾选的 -> 移出分组
|
||||
foreach (var channel in currentlyInGroup.Where(c => !selected.Contains(c)))
|
||||
{
|
||||
await ChannelManagement.UpdateChannelGroupAsync(channel.Id, null);
|
||||
}
|
||||
|
||||
// 新勾选的 -> 加入分组
|
||||
foreach (var channel in selected.Where(c => c.GroupId != assignGroupId))
|
||||
{
|
||||
await ChannelManagement.UpdateChannelGroupAsync(channel.Id, assignGroupId);
|
||||
}
|
||||
|
||||
await LoadGroups();
|
||||
await LoadChannels();
|
||||
selectedChannels = channels.Where(c => c.GroupId == assignGroupId).ToHashSet();
|
||||
Snackbar.Add("分组绑定已保存", Severity.Success);
|
||||
selectedChannels = VisibleChannels.Where(c => c.GroupId == assignGroupId).ToHashSet();
|
||||
Snackbar.Add("分类绑定已保存", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -198,35 +248,66 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddGroup()
|
||||
private async Task SaveGroup()
|
||||
{
|
||||
try
|
||||
{
|
||||
var group = new ChannelGroup
|
||||
if (editingGroupId > 0)
|
||||
{
|
||||
Name = newGroupName,
|
||||
Description = newGroupDesc,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
var existing = await GroupManagement.GetGroupAsync(editingGroupId);
|
||||
if (existing == null)
|
||||
{
|
||||
Snackbar.Add("分类不存在", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
await GroupManagement.CreateGroupAsync(group);
|
||||
existing.Name = newGroupName.Trim();
|
||||
existing.Description = string.IsNullOrWhiteSpace(newGroupDesc) ? null : newGroupDesc.Trim();
|
||||
await GroupManagement.UpdateGroupAsync(existing);
|
||||
Snackbar.Add($"分类 \"{newGroupName}\" 修改成功", Severity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
var group = new ChannelGroup
|
||||
{
|
||||
Name = newGroupName.Trim(),
|
||||
Description = string.IsNullOrWhiteSpace(newGroupDesc) ? null : newGroupDesc.Trim(),
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await GroupManagement.CreateGroupAsync(group);
|
||||
Snackbar.Add($"分类 \"{newGroupName}\" 添加成功", Severity.Success);
|
||||
}
|
||||
|
||||
CancelEdit();
|
||||
await LoadGroups();
|
||||
Snackbar.Add($"分组 \"{newGroupName}\" 添加成功", Severity.Success);
|
||||
|
||||
newGroupName = "";
|
||||
newGroupDesc = "";
|
||||
await LoadChannels();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"添加失败:{ex.Message}", Severity.Error);
|
||||
Snackbar.Add($"保存失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void BeginEdit(ChannelGroup group)
|
||||
{
|
||||
editingGroupId = group.Id;
|
||||
newGroupName = group.Name;
|
||||
newGroupDesc = group.Description ?? string.Empty;
|
||||
}
|
||||
|
||||
private void CancelEdit()
|
||||
{
|
||||
editingGroupId = 0;
|
||||
newGroupName = string.Empty;
|
||||
newGroupDesc = string.Empty;
|
||||
}
|
||||
|
||||
private async Task DeleteGroup(ChannelGroup group)
|
||||
{
|
||||
bool? result = await DialogService.ShowMessageBox(
|
||||
"确认删除",
|
||||
$"确定要删除分组 {group.Name} 吗?关联的频道将变为未分组。",
|
||||
$"确定要删除分类 {group.Name} 吗?关联的频道将变为未分类。",
|
||||
yesText: "删除", cancelText: "取消");
|
||||
|
||||
if (result == true)
|
||||
@@ -234,7 +315,10 @@
|
||||
try
|
||||
{
|
||||
await GroupManagement.DeleteGroupAsync(group.Id);
|
||||
groups.Remove(group);
|
||||
if (editingGroupId == group.Id)
|
||||
CancelEdit();
|
||||
await LoadGroups();
|
||||
await LoadChannels();
|
||||
Snackbar.Add("删除成功", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -243,4 +327,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatAccountLabel(Account account)
|
||||
{
|
||||
var nickname = string.IsNullOrWhiteSpace(account.Nickname) ? null : account.Nickname.Trim();
|
||||
var username = string.IsNullOrWhiteSpace(account.Username) ? null : account.Username.Trim();
|
||||
|
||||
var namePart = nickname ?? account.Phone;
|
||||
if (!string.IsNullOrWhiteSpace(username))
|
||||
return $"{namePart} (@{username})";
|
||||
return namePart;
|
||||
}
|
||||
}
|
||||
@@ -32,13 +32,21 @@
|
||||
<MudSelectItem Value='@("private")'>私密</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudSelect @bind-Value="filterMembershipRole" @bind-Value:after="OnFiltersChanged" Label="账号角色" Variant="Variant.Outlined" Dense="true"
|
||||
Disabled="@(filterAccount <= 0)"
|
||||
Class="tp-input-full-xs" Style="min-width: 140px; flex: 0 1 180px;">
|
||||
<MudSelectItem Value='@("all")'>全部角色</MudSelectItem>
|
||||
<MudSelectItem Value='@("creator")'>我创建</MudSelectItem>
|
||||
<MudSelectItem Value='@("admin")'>我管理</MudSelectItem>
|
||||
<MudSelectItem Value='@("creator")'>创建者</MudSelectItem>
|
||||
<MudSelectItem Value='@("admin")'>管理员</MudSelectItem>
|
||||
<MudSelectItem Value='@("member")'>非管理员</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudSelect @bind-Value="filterGroupId" @bind-Value:after="OnFiltersChanged" Label="频道分类" Variant="Variant.Outlined" Dense="true"
|
||||
Class="tp-input-full-xs" Style="min-width: 160px; flex: 0 1 220px;">
|
||||
<MudSelectItem Value='@(-1)'>全部分类</MudSelectItem>
|
||||
<MudSelectItem Value='@(0)'>未分类</MudSelectItem>
|
||||
@foreach (var group in groups)
|
||||
{
|
||||
<MudSelectItem Value="@group.Id">@group.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudTextField @bind-Value="searchString" Placeholder="搜索频道..." Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0 tp-input-full-xs"
|
||||
Style="min-width: 200px; flex: 1; max-width: 400px;"
|
||||
@@ -67,7 +75,7 @@
|
||||
<MudMenuItem Disabled="@(selectedChannels.Count == 0)" OnClick="BatchCopyInvitesSelected">批量复制邀请链接(已选)</MudMenuItem>
|
||||
<MudMenuItem Disabled="@(selectedChannels.Count == 0)" OnClick="BatchExportInvitesSelected">批量导出邀请链接(已选)</MudMenuItem>
|
||||
<MudMenuItem Disabled="@(selectedChannels.Count == 0)" OnClick="OpenBatchSetAdminsSelected">批量设置管理员(已选)</MudMenuItem>
|
||||
<MudMenuItem Disabled="@(selectedChannels.Count == 0)" OnClick="OpenBatchSetGroupSelected">批量修改分组(已选)</MudMenuItem>
|
||||
<MudMenuItem Disabled="@(selectedChannels.Count == 0)" OnClick="OpenBatchSetGroupSelected">批量修改分类(已选)</MudMenuItem>
|
||||
</MudMenu>
|
||||
|
||||
@if (selectedChannels.Count > 0)
|
||||
@@ -89,6 +97,7 @@
|
||||
<MudTh>频道名称</MudTh>
|
||||
<MudTh>用户名</MudTh>
|
||||
<MudTh>类型</MudTh>
|
||||
<MudTh>频道分类</MudTh>
|
||||
<MudTh>成员数</MudTh>
|
||||
@if (filterAccount > 0)
|
||||
{
|
||||
@@ -126,6 +135,11 @@
|
||||
@(string.IsNullOrEmpty(context.Username) ? "私密" : "公开")
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="频道分类">
|
||||
<MudChip T="string" Size="Size.Small" Color="@(context.GroupId.HasValue ? Color.Info : Color.Default)">
|
||||
@(context.Group?.Name ?? "未分类")
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="成员数">@context.MemberCount</MudTd>
|
||||
@if (filterAccount > 0)
|
||||
{
|
||||
@@ -189,6 +203,7 @@
|
||||
private bool loading = true;
|
||||
private string searchString = "";
|
||||
private int filterAccount = 0;
|
||||
private int filterGroupId = -1;
|
||||
private string filterType = "all";
|
||||
private string filterMembershipRole = "all";
|
||||
private HashSet<Channel> selectedChannels = new();
|
||||
@@ -241,7 +256,7 @@
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"加载分组失败:{ex.Message}", Severity.Error);
|
||||
Snackbar.Add($"加载分类失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,8 +267,9 @@
|
||||
{
|
||||
var (items, total) = await ChannelManagement.QueryChannelsForViewPagedAsync(
|
||||
accountId: filterAccount,
|
||||
groupId: filterGroupId,
|
||||
filterType: filterType,
|
||||
membershipRole: filterAccount > 0 ? filterMembershipRole : "all",
|
||||
membershipRole: filterMembershipRole,
|
||||
search: searchString,
|
||||
pageIndex: state.Page,
|
||||
pageSize: state.PageSize,
|
||||
@@ -310,9 +326,6 @@
|
||||
|
||||
private async Task OnFiltersChanged()
|
||||
{
|
||||
if (filterAccount <= 0)
|
||||
filterMembershipRole = "all";
|
||||
|
||||
await ReloadTable();
|
||||
}
|
||||
|
||||
@@ -684,7 +697,7 @@
|
||||
["SelectedCount"] = targets.Count
|
||||
};
|
||||
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true };
|
||||
var dialog = DialogService.Show<BatchSetChannelGroupDialog>("批量修改分组", parameters, options);
|
||||
var dialog = DialogService.Show<BatchSetChannelGroupDialog>("批量修改分类", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
if (result is not { Canceled: false })
|
||||
return;
|
||||
@@ -700,11 +713,11 @@
|
||||
|
||||
selectedChannels.Clear();
|
||||
await ReloadTable();
|
||||
Snackbar.Add($"分组已更新:{targets.Count} 个频道", Severity.Success);
|
||||
Snackbar.Add($"分类已更新:{targets.Count} 个频道", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"修改分组失败:{ex.Message}", Severity.Error);
|
||||
Snackbar.Add($"修改分类失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
@page "/groups/categories"
|
||||
@inject GroupCategoryManagementService CategoryManagement
|
||||
@inject GroupManagementService GroupManagement
|
||||
@inject AccountManagementService AccountManagement
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
@using TelegramPanel.Data.Entities
|
||||
|
||||
<PageTitle>群组分类 - Telegram Panel</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-4">群组分类管理</MudText>
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="4">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">@(editingCategoryId > 0 ? "编辑分类" : "添加分类")</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudTextField @bind-Value="newCategoryName" Label="分类名称" Variant="Variant.Outlined" />
|
||||
<MudTextField @bind-Value="newCategoryDesc" Label="描述" Variant="Variant.Outlined" Lines="3" Class="mt-3" />
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
@if (editingCategoryId > 0)
|
||||
{
|
||||
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="CancelEdit">取消</MudButton>
|
||||
}
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="@(editingCategoryId <= 0)"
|
||||
Disabled="@string.IsNullOrWhiteSpace(newCategoryName)" OnClick="SaveCategory">
|
||||
@(editingCategoryId > 0 ? "保存修改" : "添加分类")
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" md="8">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">分类列表</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudTable Items="@categories" Hover="true" Loading="@categoriesLoading">
|
||||
<HeaderContent>
|
||||
<MudTh>分类名称</MudTh>
|
||||
<MudTh>描述</MudTh>
|
||||
<MudTh>群组数量</MudTh>
|
||||
<MudTh>操作</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="分类名称">@context.Name</MudTd>
|
||||
<MudTd DataLabel="描述">@(context.Description ?? "-")</MudTd>
|
||||
<MudTd DataLabel="群组数量">@context.Groups.Count</MudTd>
|
||||
<MudTd DataLabel="操作">
|
||||
<MudStack Row="true" Spacing="1">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Small" Color="Color.Primary"
|
||||
OnClick="@(() => BeginEdit(context))" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Small" Color="Color.Error"
|
||||
OnClick="@(() => DeleteCategory(context))" />
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<NoRecordsContent>
|
||||
<MudText>暂无分类,请添加分类</MudText>
|
||||
</NoRecordsContent>
|
||||
</MudTable>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Class="mt-4">
|
||||
<MudCardHeader>
|
||||
<MudText Typo="Typo.h6">分类绑定群组</MudText>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudStack Row="true" Spacing="2" Style="flex-wrap: wrap;">
|
||||
<MudSelect @bind-Value="assignCategoryId" Label="选择分类" Variant="Variant.Outlined" Dense="true"
|
||||
Class="tp-input-full-xs" Style="min-width: 220px;" @bind-Value:after="OnAssignCategoryChanged">
|
||||
<MudSelectItem Value="0">-- 请选择分类 --</MudSelectItem>
|
||||
@foreach (var category in categories)
|
||||
{
|
||||
<MudSelectItem Value="@category.Id">@category.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="filterAccountId" Label="筛选账号" Variant="Variant.Outlined" Dense="true"
|
||||
Class="tp-input-full-xs" Style="min-width: 220px;" @bind-Value:after="OnAssignCategoryChanged">
|
||||
<MudSelectItem Value="0">全部账号</MudSelectItem>
|
||||
@foreach (var account in accounts)
|
||||
{
|
||||
<MudSelectItem Value="@account.Id">@FormatAccountLabel(account)</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
|
||||
<MudTable Items="@VisibleGroups" Hover="true" Dense="true" Class="mt-3"
|
||||
MultiSelection="true" @bind-SelectedItems="selectedGroups"
|
||||
Loading="@groupsLoading">
|
||||
<HeaderContent>
|
||||
<MudTh>群组名称</MudTh>
|
||||
<MudTh>用户名</MudTh>
|
||||
<MudTh>创建账号</MudTh>
|
||||
<MudTh>当前分类</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="群组名称">@context.Title</MudTd>
|
||||
<MudTd DataLabel="用户名">@(string.IsNullOrWhiteSpace(context.Username) ? "-" : context.Username)</MudTd>
|
||||
<MudTd DataLabel="创建账号">@(context.CreatorAccount?.Phone ?? (context.CreatorAccountId?.ToString() ?? "(非系统创建)"))</MudTd>
|
||||
<MudTd DataLabel="当前分类">@(context.Category?.Name ?? "未分类")</MudTd>
|
||||
</RowTemplate>
|
||||
<NoRecordsContent>
|
||||
<MudText>暂无群组数据</MudText>
|
||||
</NoRecordsContent>
|
||||
</MudTable>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
Disabled="@(assignCategoryId == 0 || groupsLoading)"
|
||||
OnClick="@(async (MouseEventArgs _) => await SaveCategoryAssignments())">
|
||||
保存勾选到分类
|
||||
</MudButton>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@code {
|
||||
private string newCategoryName = string.Empty;
|
||||
private string newCategoryDesc = string.Empty;
|
||||
private int editingCategoryId;
|
||||
private List<GroupCategory> categories = new();
|
||||
private bool categoriesLoading = true;
|
||||
|
||||
private List<Group> groups = new();
|
||||
private List<Account> accounts = new();
|
||||
private bool groupsLoading = true;
|
||||
private int assignCategoryId;
|
||||
private int filterAccountId;
|
||||
private HashSet<Group> selectedGroups = new();
|
||||
|
||||
private IEnumerable<Group> VisibleGroups => groups.Where(MatchAccountFilter);
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadAccounts();
|
||||
await LoadCategories();
|
||||
await LoadGroups();
|
||||
}
|
||||
|
||||
private async Task LoadAccounts()
|
||||
{
|
||||
try
|
||||
{
|
||||
var accountList = await AccountManagement.GetAllAccountsAsync();
|
||||
accounts = accountList
|
||||
.Where(a => a.Category?.ExcludeFromOperations != true)
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"加载账号失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadCategories()
|
||||
{
|
||||
categoriesLoading = true;
|
||||
try
|
||||
{
|
||||
var result = await CategoryManagement.GetAllCategoriesAsync();
|
||||
categories = result.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"加载分类失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
categoriesLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadGroups()
|
||||
{
|
||||
groupsLoading = true;
|
||||
try
|
||||
{
|
||||
var list = await GroupManagement.GetAllGroupsAsync();
|
||||
groups = list.ToList();
|
||||
await OnAssignCategoryChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"加载群组失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
groupsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool MatchAccountFilter(Group group)
|
||||
{
|
||||
if (filterAccountId <= 0)
|
||||
return true;
|
||||
|
||||
return group.AccountGroups.Any(x => x.AccountId == filterAccountId);
|
||||
}
|
||||
|
||||
private Task OnAssignCategoryChanged()
|
||||
{
|
||||
selectedGroups = assignCategoryId <= 0
|
||||
? new HashSet<Group>()
|
||||
: VisibleGroups.Where(g => g.CategoryId == assignCategoryId).ToHashSet();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task SaveCategoryAssignments()
|
||||
{
|
||||
if (assignCategoryId <= 0)
|
||||
{
|
||||
Snackbar.Add("请选择分类", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var scope = VisibleGroups.ToList();
|
||||
var currentlyInCategory = scope.Where(g => g.CategoryId == assignCategoryId).ToList();
|
||||
var selected = selectedGroups.ToHashSet();
|
||||
|
||||
foreach (var group in currentlyInCategory.Where(g => !selected.Contains(g)))
|
||||
{
|
||||
await GroupManagement.UpdateGroupCategoryAsync(group.Id, null);
|
||||
}
|
||||
|
||||
foreach (var group in selected.Where(g => g.CategoryId != assignCategoryId))
|
||||
{
|
||||
await GroupManagement.UpdateGroupCategoryAsync(group.Id, assignCategoryId);
|
||||
}
|
||||
|
||||
await LoadCategories();
|
||||
await LoadGroups();
|
||||
selectedGroups = VisibleGroups.Where(g => g.CategoryId == assignCategoryId).ToHashSet();
|
||||
Snackbar.Add("分类绑定已保存", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"保存失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveCategory()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (editingCategoryId > 0)
|
||||
{
|
||||
var existing = await CategoryManagement.GetCategoryAsync(editingCategoryId);
|
||||
if (existing == null)
|
||||
{
|
||||
Snackbar.Add("分类不存在", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
existing.Name = newCategoryName.Trim();
|
||||
existing.Description = string.IsNullOrWhiteSpace(newCategoryDesc) ? null : newCategoryDesc.Trim();
|
||||
await CategoryManagement.UpdateCategoryAsync(existing);
|
||||
Snackbar.Add($"分类 \"{newCategoryName}\" 修改成功", Severity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
var category = new GroupCategory
|
||||
{
|
||||
Name = newCategoryName.Trim(),
|
||||
Description = string.IsNullOrWhiteSpace(newCategoryDesc) ? null : newCategoryDesc.Trim(),
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await CategoryManagement.CreateCategoryAsync(category);
|
||||
Snackbar.Add($"分类 \"{newCategoryName}\" 添加成功", Severity.Success);
|
||||
}
|
||||
|
||||
CancelEdit();
|
||||
await LoadCategories();
|
||||
await LoadGroups();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"保存失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void BeginEdit(GroupCategory category)
|
||||
{
|
||||
editingCategoryId = category.Id;
|
||||
newCategoryName = category.Name;
|
||||
newCategoryDesc = category.Description ?? string.Empty;
|
||||
}
|
||||
|
||||
private void CancelEdit()
|
||||
{
|
||||
editingCategoryId = 0;
|
||||
newCategoryName = string.Empty;
|
||||
newCategoryDesc = string.Empty;
|
||||
}
|
||||
|
||||
private async Task DeleteCategory(GroupCategory category)
|
||||
{
|
||||
bool? result = await DialogService.ShowMessageBox(
|
||||
"确认删除",
|
||||
$"确定要删除分类 {category.Name} 吗?关联的群组将变为未分类。",
|
||||
yesText: "删除", cancelText: "取消");
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
await CategoryManagement.DeleteCategoryAsync(category.Id);
|
||||
if (editingCategoryId == category.Id)
|
||||
CancelEdit();
|
||||
await LoadCategories();
|
||||
await LoadGroups();
|
||||
Snackbar.Add("删除成功", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"删除失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatAccountLabel(Account account)
|
||||
{
|
||||
var nickname = string.IsNullOrWhiteSpace(account.Nickname) ? null : account.Nickname.Trim();
|
||||
var username = string.IsNullOrWhiteSpace(account.Username) ? null : account.Username.Trim();
|
||||
|
||||
var namePart = nickname ?? account.Phone;
|
||||
if (!string.IsNullOrWhiteSpace(username))
|
||||
return $"{namePart} (@{username})";
|
||||
return namePart;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/groups"
|
||||
@inject GroupManagementService GroupManagement
|
||||
@inject GroupCategoryManagementService GroupCategoryManagement
|
||||
@inject AccountManagementService AccountManagement
|
||||
@inject TelegramPanel.Web.Services.DataSyncService DataSync
|
||||
@inject IGroupService GroupService
|
||||
@@ -31,13 +32,21 @@
|
||||
<MudSelectItem Value='@("private")'>私密</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudSelect @bind-Value="filterMembershipRole" @bind-Value:after="OnFiltersChanged" Label="账号角色" Variant="Variant.Outlined" Dense="true"
|
||||
Disabled="@(filterAccount <= 0)"
|
||||
Class="tp-input-full-xs" Style="min-width: 140px; flex: 0 1 180px;">
|
||||
<MudSelectItem Value='@("all")'>全部角色</MudSelectItem>
|
||||
<MudSelectItem Value='@("creator")'>我创建</MudSelectItem>
|
||||
<MudSelectItem Value='@("admin")'>我管理</MudSelectItem>
|
||||
<MudSelectItem Value='@("member")'>非管理员</MudSelectItem>
|
||||
</MudSelect>
|
||||
<MudSelect @bind-Value="filterCategoryId" @bind-Value:after="OnFiltersChanged" Label="群组分类" Variant="Variant.Outlined" Dense="true"
|
||||
Class="tp-input-full-xs" Style="min-width: 160px; flex: 0 1 220px;">
|
||||
<MudSelectItem Value='@(-1)'>全部分类</MudSelectItem>
|
||||
<MudSelectItem Value='@(0)'>未分类</MudSelectItem>
|
||||
@foreach (var category in categories)
|
||||
{
|
||||
<MudSelectItem Value="@category.Id">@category.Name</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudTextField @bind-Value="searchString" Placeholder="搜索群组..." Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0 tp-input-full-xs"
|
||||
Style="min-width: 200px; flex: 1; max-width: 400px;"
|
||||
@@ -64,6 +73,7 @@
|
||||
Size="Size.Small" Disabled="@loading" Dense="true" EndIcon="@Icons.Material.Filled.KeyboardArrowDown">
|
||||
<MudMenuItem Disabled="@(selectedGroups.Count == 0)" OnClick="BatchCopyLinksSelected">批量复制加入链接(已选)</MudMenuItem>
|
||||
<MudMenuItem Disabled="@(selectedGroups.Count == 0)" OnClick="BatchExportLinksSelected">批量导出加入链接(已选)</MudMenuItem>
|
||||
<MudMenuItem Disabled="@(selectedGroups.Count == 0)" OnClick="OpenBatchSetCategorySelected">批量修改分类(已选)</MudMenuItem>
|
||||
<MudDivider />
|
||||
<MudMenuItem Disabled="@(selectedGroups.Count == 0)" Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="DeleteSelectedGroups">批量删除(已选)</MudMenuItem>
|
||||
</MudMenu>
|
||||
@@ -87,6 +97,7 @@
|
||||
<MudTh>群组名称</MudTh>
|
||||
<MudTh>用户名</MudTh>
|
||||
<MudTh>类型</MudTh>
|
||||
<MudTh>群组分类</MudTh>
|
||||
<MudTh>成员数</MudTh>
|
||||
@if (filterAccount > 0)
|
||||
{
|
||||
@@ -124,6 +135,11 @@
|
||||
@(string.IsNullOrWhiteSpace(context.Username) ? "私密" : "公开")
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="群组分类">
|
||||
<MudChip T="string" Size="Size.Small" Color="@(context.CategoryId.HasValue ? Color.Info : Color.Default)">
|
||||
@(context.Category?.Name ?? "未分类")
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="成员数">@context.MemberCount</MudTd>
|
||||
@if (filterAccount > 0)
|
||||
{
|
||||
@@ -177,10 +193,12 @@
|
||||
private MudTable<Group>? _table;
|
||||
private List<Group> groups = new();
|
||||
private List<Account> accounts = new();
|
||||
private List<GroupCategory> categories = new();
|
||||
private int totalCount;
|
||||
private bool loading = true;
|
||||
private string searchString = "";
|
||||
private int filterAccount = 0;
|
||||
private int filterCategoryId = -1;
|
||||
private string filterType = "all";
|
||||
private string filterMembershipRole = "all";
|
||||
private HashSet<Group> selectedGroups = new();
|
||||
@@ -206,6 +224,7 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadAccounts();
|
||||
await LoadCategories();
|
||||
}
|
||||
|
||||
private async Task LoadAccounts()
|
||||
@@ -223,6 +242,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadCategories()
|
||||
{
|
||||
try
|
||||
{
|
||||
var list = await GroupCategoryManagement.GetAllCategoriesAsync();
|
||||
categories = list.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"加载分类失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<TableData<Group>> LoadGroupsServerData(TableState state, CancellationToken cancellationToken)
|
||||
{
|
||||
loading = true;
|
||||
@@ -230,8 +262,9 @@
|
||||
{
|
||||
var (items, total) = await GroupManagement.QueryGroupsForViewPagedAsync(
|
||||
accountId: filterAccount,
|
||||
categoryId: filterCategoryId,
|
||||
filterType: filterType,
|
||||
membershipRole: filterAccount > 0 ? filterMembershipRole : "all",
|
||||
membershipRole: filterMembershipRole,
|
||||
search: searchString,
|
||||
pageIndex: state.Page,
|
||||
pageSize: state.PageSize,
|
||||
@@ -289,9 +322,6 @@
|
||||
|
||||
private async Task OnFiltersChanged()
|
||||
{
|
||||
if (filterAccount <= 0)
|
||||
filterMembershipRole = "all";
|
||||
|
||||
await ReloadTable();
|
||||
}
|
||||
|
||||
@@ -491,6 +521,52 @@
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task OpenBatchSetCategorySelected()
|
||||
{
|
||||
var targets = selectedGroups.ToList();
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
Snackbar.Add("未选择任何群组", Severity.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
if (categories.Count == 0)
|
||||
await LoadCategories();
|
||||
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["Categories"] = categories,
|
||||
["SelectedCount"] = targets.Count
|
||||
};
|
||||
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true };
|
||||
var dialog = DialogService.Show<BatchSetGroupCategoryDialog>("批量修改分类", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
if (result is not { Canceled: false })
|
||||
return;
|
||||
|
||||
var categoryIdRaw = result.Data is int x ? x : 0;
|
||||
int? categoryId = categoryIdRaw <= 0 ? null : categoryIdRaw;
|
||||
|
||||
loading = true;
|
||||
try
|
||||
{
|
||||
foreach (var group in targets)
|
||||
await GroupManagement.UpdateGroupCategoryAsync(group.Id, categoryId);
|
||||
|
||||
selectedGroups.Clear();
|
||||
await ReloadTable();
|
||||
Snackbar.Add($"分类已更新:{targets.Count} 个群组", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"修改分类失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SyncCurrentAccountGroups()
|
||||
{
|
||||
if (loading || filterAccount <= 0)
|
||||
|
||||
@@ -158,10 +158,20 @@
|
||||
<MudNumericField @bind-Value="syncInterval" Label="同步间隔 (小时)" Variant="Variant.Outlined" Class="mt-3"
|
||||
Min="1" Max="24" />
|
||||
}
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" FullWidth="true" Class="mt-3"
|
||||
OnClick="@(async (MouseEventArgs _) => await SaveSyncConfig())">
|
||||
保存同步设置
|
||||
</MudButton>
|
||||
<MudStack Row="true" Spacing="2" Class="mt-3" Style="flex-wrap: wrap;">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Class="tp-input-full-xs"
|
||||
Style="flex: 1; min-width: 220px;"
|
||||
OnClick="@(async (MouseEventArgs _) => await SaveSyncConfig())">
|
||||
保存同步设置
|
||||
</MudButton>
|
||||
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" Class="tp-input-full-xs"
|
||||
Style="flex: 1; min-width: 220px;"
|
||||
Disabled="@syncing"
|
||||
OnClick="@(async (MouseEventArgs _) => await SyncNow())">
|
||||
@(syncing ? "同步中..." : "立即同步频道/群组")
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
<MudDivider Class="my-4" />
|
||||
|
||||
@@ -180,10 +190,6 @@
|
||||
保存 Bot 自动同步设置
|
||||
</MudButton>
|
||||
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Info" FullWidth="true" Class="mt-4"
|
||||
OnClick="@(async (MouseEventArgs _) => await SyncNow())">
|
||||
立即同步
|
||||
</MudButton>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
|
||||
Reference in new issue
Block a user