mirror of
https://github.com/moeacgx/Telegram-Panel.git
synced 2026-05-07 22:43:30 +08:00
feat(bot-channels): 删除频道时可勾选要移除的Bot绑定
This commit is contained in:
@@ -351,15 +351,55 @@ public class BotManagementService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按 TelegramId 全局删除频道:会移除该频道本体及所有 Bot 绑定关系。
|
||||
/// 获取指定频道绑定的 Bot 列表(按名称排序)。
|
||||
/// </summary>
|
||||
public async Task DeleteChannelGloballyByTelegramIdAsync(long telegramId)
|
||||
public async Task<IReadOnlyList<Bot>> GetChannelBoundBotsAsync(long telegramId)
|
||||
{
|
||||
var ch = await _botChannelRepository.GetGlobalByTelegramIdAsync(telegramId);
|
||||
if (ch == null)
|
||||
return;
|
||||
return Array.Empty<Bot>();
|
||||
|
||||
await _botChannelRepository.DeleteAsync(ch);
|
||||
var members = await _memberRepository.FindAsync(x => x.BotChannelId == ch.Id);
|
||||
var botIdSet = members
|
||||
.Select(x => x.BotId)
|
||||
.Where(x => x > 0)
|
||||
.Distinct()
|
||||
.ToHashSet();
|
||||
|
||||
if (botIdSet.Count == 0)
|
||||
return Array.Empty<Bot>();
|
||||
|
||||
var bots = (await _botRepository.GetAllAsync())
|
||||
.Where(x => botIdSet.Contains(x.Id))
|
||||
.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
return bots;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除指定频道上选中 Bot 的绑定关系;若无剩余绑定,会自动清理频道本体。
|
||||
/// 返回实际删除的绑定条数。
|
||||
/// </summary>
|
||||
public async Task<int> DeleteChannelBindingsByBotIdsAsync(long telegramId, IReadOnlyCollection<int> botIds)
|
||||
{
|
||||
var selected = botIds
|
||||
.Where(x => x > 0)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
if (selected.Count == 0)
|
||||
return 0;
|
||||
|
||||
var ch = await _botChannelRepository.GetGlobalByTelegramIdAsync(telegramId);
|
||||
if (ch == null)
|
||||
return 0;
|
||||
|
||||
var removed = await _memberRepository.DeleteByChannelAndBotsAsync(ch.Id, selected);
|
||||
var remains = await _memberRepository.CountForChannelAsync(ch.Id);
|
||||
if (remains == 0)
|
||||
await _botChannelRepository.DeleteAsync(ch);
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
public async Task UpdateChannelStatusAsync(int botId, long telegramId, bool ok, string? error, DateTime checkedAtUtc)
|
||||
|
||||
@@ -44,9 +44,29 @@ public class BotChannelMemberRepository : Repository<BotChannelMember>, IBotChan
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<int> DeleteByChannelAndBotsAsync(int botChannelId, IReadOnlyCollection<int> botIds)
|
||||
{
|
||||
if (botChannelId <= 0 || botIds.Count == 0)
|
||||
return 0;
|
||||
|
||||
var targets = botIds.Where(x => x > 0).Distinct().ToList();
|
||||
if (targets.Count == 0)
|
||||
return 0;
|
||||
|
||||
var rows = await _dbSet
|
||||
.Where(x => x.BotChannelId == botChannelId && targets.Contains(x.BotId))
|
||||
.ToListAsync();
|
||||
|
||||
if (rows.Count == 0)
|
||||
return 0;
|
||||
|
||||
_dbSet.RemoveRange(rows);
|
||||
await _context.SaveChangesAsync();
|
||||
return rows.Count;
|
||||
}
|
||||
|
||||
public async Task<int> CountForChannelAsync(int botChannelId)
|
||||
{
|
||||
return await _dbSet.CountAsync(x => x.BotChannelId == botChannelId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@ public interface IBotChannelMemberRepository : IRepository<BotChannelMember>
|
||||
Task<BotChannelMember?> GetAsync(int botId, int botChannelId);
|
||||
Task UpsertAsync(int botId, int botChannelId, DateTime syncedAt);
|
||||
Task DeleteAsync(int botId, int botChannelId);
|
||||
Task<int> DeleteByChannelAndBotsAsync(int botChannelId, IReadOnlyCollection<int> botIds);
|
||||
Task<int> CountForChannelAsync(int botChannelId);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
@namespace TelegramPanel.Web.Components.Dialogs
|
||||
@using TelegramPanel.Data.Entities
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.body2">@Prompt</MudText>
|
||||
|
||||
@if (options.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined">
|
||||
当前没有可删除的 Bot 绑定。
|
||||
</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center" Style="flex-wrap: wrap;">
|
||||
<MudButton Variant="Variant.Outlined" Size="Size.Small" Color="Color.Primary"
|
||||
OnClick="SelectCurrentBot"
|
||||
Disabled="@(!options.Any(x => x.Id == CurrentBotId))">
|
||||
仅当前 Bot
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined" Size="Size.Small" Color="Color.Default" OnClick="SelectAll">
|
||||
全选
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined" Size="Size.Small" Color="Color.Default" OnClick="ClearAll">
|
||||
清空
|
||||
</MudButton>
|
||||
<MudSpacer />
|
||||
<MudChip T="string" Size="Size.Small" Color="Color.Info">
|
||||
已选 @selectedBotIds.Count / @options.Count
|
||||
</MudChip>
|
||||
</MudStack>
|
||||
|
||||
<MudPaper Class="pa-2" Outlined="true">
|
||||
<MudStack Spacing="1">
|
||||
@foreach (var bot in options)
|
||||
{
|
||||
<MudCheckBox T="bool"
|
||||
Label="@BuildBotLabel(bot)"
|
||||
Value="@IsChecked(bot.Id)"
|
||||
ValueChanged="@(value => SetChecked(bot.Id, value))" />
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="Cancel">取消</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Error" OnClick="Confirm"
|
||||
Disabled="@(options.Count == 0 || selectedBotIds.Count == 0)">
|
||||
删除选中绑定
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter] public string Prompt { get; set; } = "";
|
||||
[Parameter] public List<Bot> Bots { get; set; } = new();
|
||||
[Parameter] public int CurrentBotId { get; set; }
|
||||
|
||||
private List<Bot> options = new();
|
||||
private HashSet<int> selectedBotIds = new();
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
options = (Bots ?? new List<Bot>())
|
||||
.Where(x => x.Id > 0)
|
||||
.GroupBy(x => x.Id)
|
||||
.Select(x => x.First())
|
||||
.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
selectedBotIds = new HashSet<int>();
|
||||
if (options.Count == 0)
|
||||
return;
|
||||
|
||||
if (CurrentBotId > 0 && options.Any(x => x.Id == CurrentBotId))
|
||||
{
|
||||
selectedBotIds.Add(CurrentBotId);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var bot in options)
|
||||
selectedBotIds.Add(bot.Id);
|
||||
}
|
||||
|
||||
private bool IsChecked(int botId) => selectedBotIds.Contains(botId);
|
||||
|
||||
private void SetChecked(int botId, bool selected)
|
||||
{
|
||||
if (selected)
|
||||
selectedBotIds.Add(botId);
|
||||
else
|
||||
selectedBotIds.Remove(botId);
|
||||
}
|
||||
|
||||
private void SelectCurrentBot()
|
||||
{
|
||||
selectedBotIds.Clear();
|
||||
if (CurrentBotId > 0)
|
||||
selectedBotIds.Add(CurrentBotId);
|
||||
}
|
||||
|
||||
private void SelectAll()
|
||||
{
|
||||
selectedBotIds.Clear();
|
||||
foreach (var bot in options)
|
||||
selectedBotIds.Add(bot.Id);
|
||||
}
|
||||
|
||||
private void ClearAll()
|
||||
{
|
||||
selectedBotIds.Clear();
|
||||
}
|
||||
|
||||
private static string BuildBotLabel(Bot bot)
|
||||
{
|
||||
var name = string.IsNullOrWhiteSpace(bot.Name) ? $"Bot {bot.Id}" : bot.Name.Trim();
|
||||
var username = string.IsNullOrWhiteSpace(bot.Username) ? null : "@" + bot.Username.Trim().TrimStart('@');
|
||||
return string.IsNullOrWhiteSpace(username) ? name : $"{name}({username})";
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
private void Confirm()
|
||||
{
|
||||
var result = selectedBotIds
|
||||
.Where(x => x > 0)
|
||||
.OrderBy(x => x)
|
||||
.ToList();
|
||||
|
||||
if (result.Count == 0)
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
MudDialog.Close(DialogResult.Ok(result));
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@
|
||||
<MudMenuItem Disabled="@(selected.Count == 0)" OnClick="OpenSetAdmins">批量设置管理员(机器人/ID)</MudMenuItem>
|
||||
<MudMenuItem Disabled="@(selected.Count == 0)" OnClick="OpenBatchSetCategory">批量设置分类(已选)</MudMenuItem>
|
||||
<MudMenuItem Disabled="@(selected.Count == 0)" OnClick="OpenBanMember">踢出用户(已选)</MudMenuItem>
|
||||
<MudMenuItem Disabled="@(selected.Count == 0)" OnClick="DeleteSelectedChannelsAsync">全局删除频道(已选)</MudMenuItem>
|
||||
<MudMenuItem Disabled="@(selected.Count == 0)" OnClick="DeleteSelectedChannelsAsync">删除频道(已选)</MudMenuItem>
|
||||
</MudMenu>
|
||||
|
||||
@if (selected.Count > 0)
|
||||
@@ -157,7 +157,7 @@
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="Size.Small" Color="Color.Primary"
|
||||
OnClick="@(async () => await EditChannelAsync(context))" Title="编辑频道" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Small" Color="Color.Error"
|
||||
OnClick="@(async () => await DeleteSingleChannelAsync(context))" title="全局删除频道" />
|
||||
OnClick="@(async () => await DeleteSingleChannelAsync(context))" title="删除频道" />
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
@@ -484,19 +484,18 @@
|
||||
return;
|
||||
|
||||
var channelName = string.IsNullOrWhiteSpace(channel.Title) ? channel.TelegramId.ToString() : channel.Title;
|
||||
bool? ok = await DialogService.ShowMessageBox(
|
||||
"确认删除",
|
||||
$"确定全局删除频道“{channelName}”吗?该频道会从所有 Bot 的频道列表中移除。",
|
||||
yesText: "删除", cancelText: "取消");
|
||||
|
||||
if (ok != true)
|
||||
var selectedBotIds = await SelectBotsForDeleteAsync(new[] { channel.TelegramId }, channelName);
|
||||
if (selectedBotIds == null || selectedBotIds.Count == 0)
|
||||
return;
|
||||
|
||||
loading = true;
|
||||
try
|
||||
{
|
||||
await BotManagement.DeleteChannelGloballyByTelegramIdAsync(channel.TelegramId);
|
||||
Snackbar.Add($"已全局删除频道:{channelName}", Severity.Success);
|
||||
var removed = await BotManagement.DeleteChannelBindingsByBotIdsAsync(channel.TelegramId, selectedBotIds);
|
||||
if (removed > 0)
|
||||
Snackbar.Add($"已删除频道绑定:{channelName}(删除 {removed} 条绑定)", Severity.Success);
|
||||
else
|
||||
Snackbar.Add($"未删除任何绑定:{channelName}(所选 Bot 可能未绑定该频道)", Severity.Info);
|
||||
await ReloadTable();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -520,26 +519,24 @@
|
||||
return;
|
||||
}
|
||||
|
||||
bool? ok = await DialogService.ShowMessageBox(
|
||||
"确认批量删除",
|
||||
$"确定全局删除已选 {selected.Count} 个频道吗?这些频道会从所有 Bot 的频道列表中移除。",
|
||||
yesText: "删除", cancelText: "取消");
|
||||
|
||||
if (ok != true)
|
||||
var ids = selected.Select(x => x.TelegramId).Distinct().ToList();
|
||||
var selectedBotIds = await SelectBotsForDeleteAsync(ids, null);
|
||||
if (selectedBotIds == null || selectedBotIds.Count == 0)
|
||||
return;
|
||||
|
||||
loading = true;
|
||||
try
|
||||
{
|
||||
var ids = selected.Select(x => x.TelegramId).Distinct().ToList();
|
||||
var success = 0;
|
||||
var failed = 0;
|
||||
var removedTotal = 0;
|
||||
|
||||
foreach (var chatId in ids)
|
||||
{
|
||||
try
|
||||
{
|
||||
await BotManagement.DeleteChannelGloballyByTelegramIdAsync(chatId);
|
||||
var removed = await BotManagement.DeleteChannelBindingsByBotIdsAsync(chatId, selectedBotIds);
|
||||
removedTotal += removed;
|
||||
success++;
|
||||
}
|
||||
catch
|
||||
@@ -550,11 +547,11 @@
|
||||
|
||||
if (failed == 0)
|
||||
{
|
||||
Snackbar.Add($"批量删除完成:成功 {success} 个频道", Severity.Success);
|
||||
Snackbar.Add($"批量删除完成:处理 {success} 个频道,删除绑定 {removedTotal} 条", Severity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add($"批量删除完成:成功 {success} 个,失败 {failed} 个", Severity.Warning);
|
||||
Snackbar.Add($"批量删除完成:处理成功 {success} 个,失败 {failed} 个,删除绑定 {removedTotal} 条", Severity.Warning);
|
||||
}
|
||||
|
||||
await ReloadTable();
|
||||
@@ -569,6 +566,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<int>?> SelectBotsForDeleteAsync(IReadOnlyCollection<long> channelTelegramIds, string? singleChannelName)
|
||||
{
|
||||
var ids = channelTelegramIds.Where(x => x != 0).Distinct().ToList();
|
||||
if (ids.Count == 0)
|
||||
return null;
|
||||
|
||||
var botMap = new Dictionary<int, Bot>();
|
||||
foreach (var telegramId in ids)
|
||||
{
|
||||
var boundBots = await BotManagement.GetChannelBoundBotsAsync(telegramId);
|
||||
foreach (var bot in boundBots)
|
||||
{
|
||||
if (bot.Id > 0 && !botMap.ContainsKey(bot.Id))
|
||||
botMap[bot.Id] = bot;
|
||||
}
|
||||
}
|
||||
|
||||
if (botMap.Count == 0)
|
||||
{
|
||||
Snackbar.Add("未找到可删除的 Bot 绑定", Severity.Info);
|
||||
return null;
|
||||
}
|
||||
|
||||
var prompt = ids.Count == 1
|
||||
? $"频道“{singleChannelName ?? ids[0].ToString()}”当前绑定了以下 Bot,请勾选要删除的绑定:"
|
||||
: $"已选 {ids.Count} 个频道。请勾选要批量删除的 Bot 绑定(未绑定的频道会自动跳过)。";
|
||||
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["Prompt"] = prompt,
|
||||
["Bots"] = botMap.Values.OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase).ToList(),
|
||||
["CurrentBotId"] = selectedBotId
|
||||
};
|
||||
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Small, FullWidth = true };
|
||||
var dialog = DialogService.Show<BotChannelDeleteBindingsDialog>("选择要删除的 Bot 绑定", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
if (result.Canceled)
|
||||
return null;
|
||||
|
||||
if (result.Data is List<int> selectedIds)
|
||||
return selectedIds.Where(x => x > 0).Distinct().ToList();
|
||||
if (result.Data is IReadOnlyList<int> selectedReadOnlyIds)
|
||||
return selectedReadOnlyIds.Where(x => x > 0).Distinct().ToList();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task OpenCreateCategoryDialog()
|
||||
{
|
||||
if (selectedBotId <= 0)
|
||||
|
||||
Reference in New Issue
Block a user