diff --git a/src/TelegramPanel.Core/Services/BotManagementService.cs b/src/TelegramPanel.Core/Services/BotManagementService.cs
index 7458880..4ae6576 100644
--- a/src/TelegramPanel.Core/Services/BotManagementService.cs
+++ b/src/TelegramPanel.Core/Services/BotManagementService.cs
@@ -351,15 +351,55 @@ public class BotManagementService
}
///
- /// 按 TelegramId 全局删除频道:会移除该频道本体及所有 Bot 绑定关系。
+ /// 获取指定频道绑定的 Bot 列表(按名称排序)。
///
- public async Task DeleteChannelGloballyByTelegramIdAsync(long telegramId)
+ public async Task> GetChannelBoundBotsAsync(long telegramId)
{
var ch = await _botChannelRepository.GetGlobalByTelegramIdAsync(telegramId);
if (ch == null)
- return;
+ return Array.Empty();
- 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();
+
+ var bots = (await _botRepository.GetAllAsync())
+ .Where(x => botIdSet.Contains(x.Id))
+ .OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ return bots;
+ }
+
+ ///
+ /// 删除指定频道上选中 Bot 的绑定关系;若无剩余绑定,会自动清理频道本体。
+ /// 返回实际删除的绑定条数。
+ ///
+ public async Task DeleteChannelBindingsByBotIdsAsync(long telegramId, IReadOnlyCollection 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)
diff --git a/src/TelegramPanel.Data/Repositories/BotChannelMemberRepository.cs b/src/TelegramPanel.Data/Repositories/BotChannelMemberRepository.cs
index 33911ec..186090b 100644
--- a/src/TelegramPanel.Data/Repositories/BotChannelMemberRepository.cs
+++ b/src/TelegramPanel.Data/Repositories/BotChannelMemberRepository.cs
@@ -44,9 +44,29 @@ public class BotChannelMemberRepository : Repository, IBotChan
await _context.SaveChangesAsync();
}
+ public async Task DeleteByChannelAndBotsAsync(int botChannelId, IReadOnlyCollection 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 CountForChannelAsync(int botChannelId)
{
return await _dbSet.CountAsync(x => x.BotChannelId == botChannelId);
}
}
-
diff --git a/src/TelegramPanel.Data/Repositories/IBotChannelMemberRepository.cs b/src/TelegramPanel.Data/Repositories/IBotChannelMemberRepository.cs
index e6ace96..d5af219 100644
--- a/src/TelegramPanel.Data/Repositories/IBotChannelMemberRepository.cs
+++ b/src/TelegramPanel.Data/Repositories/IBotChannelMemberRepository.cs
@@ -7,6 +7,6 @@ public interface IBotChannelMemberRepository : IRepository
Task GetAsync(int botId, int botChannelId);
Task UpsertAsync(int botId, int botChannelId, DateTime syncedAt);
Task DeleteAsync(int botId, int botChannelId);
+ Task DeleteByChannelAndBotsAsync(int botChannelId, IReadOnlyCollection botIds);
Task CountForChannelAsync(int botChannelId);
}
-
diff --git a/src/TelegramPanel.Web/Components/Dialogs/BotChannelDeleteBindingsDialog.razor b/src/TelegramPanel.Web/Components/Dialogs/BotChannelDeleteBindingsDialog.razor
new file mode 100644
index 0000000..b0bd50a
--- /dev/null
+++ b/src/TelegramPanel.Web/Components/Dialogs/BotChannelDeleteBindingsDialog.razor
@@ -0,0 +1,147 @@
+@namespace TelegramPanel.Web.Components.Dialogs
+@using TelegramPanel.Data.Entities
+
+
+
+
+ @Prompt
+
+ @if (options.Count == 0)
+ {
+
+ 当前没有可删除的 Bot 绑定。
+
+ }
+ else
+ {
+
+
+ 仅当前 Bot
+
+
+ 全选
+
+
+ 清空
+
+
+
+ 已选 @selectedBotIds.Count / @options.Count
+
+
+
+
+
+ @foreach (var bot in options)
+ {
+
+ }
+
+
+ }
+
+
+
+ 取消
+
+ 删除选中绑定
+
+
+
+
+@code {
+ [CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
+
+ [Parameter] public string Prompt { get; set; } = "";
+ [Parameter] public List Bots { get; set; } = new();
+ [Parameter] public int CurrentBotId { get; set; }
+
+ private List options = new();
+ private HashSet selectedBotIds = new();
+
+ protected override void OnParametersSet()
+ {
+ options = (Bots ?? new List())
+ .Where(x => x.Id > 0)
+ .GroupBy(x => x.Id)
+ .Select(x => x.First())
+ .OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ selectedBotIds = new HashSet();
+ 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));
+ }
+}
diff --git a/src/TelegramPanel.Web/Components/Pages/BotChannelsHome.razor b/src/TelegramPanel.Web/Components/Pages/BotChannelsHome.razor
index d24eca5..602a6af 100644
--- a/src/TelegramPanel.Web/Components/Pages/BotChannelsHome.razor
+++ b/src/TelegramPanel.Web/Components/Pages/BotChannelsHome.razor
@@ -88,7 +88,7 @@
批量设置管理员(机器人/ID)
批量设置分类(已选)
踢出用户(已选)
- 全局删除频道(已选)
+ 删除频道(已选)
@if (selected.Count > 0)
@@ -157,7 +157,7 @@
+ OnClick="@(async () => await DeleteSingleChannelAsync(context))" title="删除频道" />
@@ -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?> SelectBotsForDeleteAsync(IReadOnlyCollection channelTelegramIds, string? singleChannelName)
+ {
+ var ids = channelTelegramIds.Where(x => x != 0).Distinct().ToList();
+ if (ids.Count == 0)
+ return null;
+
+ var botMap = new Dictionary();
+ 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("选择要删除的 Bot 绑定", parameters, options);
+ var result = await dialog.Result;
+ if (result.Canceled)
+ return null;
+
+ if (result.Data is List selectedIds)
+ return selectedIds.Where(x => x > 0).Distinct().ToList();
+ if (result.Data is IReadOnlyList selectedReadOnlyIds)
+ return selectedReadOnlyIds.Where(x => x > 0).Distinct().ToList();
+
+ return null;
+ }
+
private async Task OpenCreateCategoryDialog()
{
if (selectedBotId <= 0)