diff --git a/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor b/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor index a690aab..9e7d4aa 100644 --- a/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor +++ b/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor @@ -1,4 +1,3 @@ -@namespace TelegramPanel.Web.Components.Dialogs @inject AccountCategoryManagementService CategoryManagement @using System.Text.Json @using TelegramPanel.Data.Entities @@ -18,8 +17,14 @@ } else { - + @foreach (var c in categories) { @c.Name @@ -91,7 +96,7 @@ private bool loading = true; private bool initializedFromConfig; - private int selectedCategoryId; + private IEnumerable selectedCategoryIds = Array.Empty(); private string targetsText = ""; private string dictionaryText = ""; private int delayMinMs = 15; @@ -113,8 +118,11 @@ .OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase) .ToList(); - if (selectedCategoryId <= 0) - selectedCategoryId = categories.FirstOrDefault()?.Id ?? 0; + if (!selectedCategoryIds.Any()) + { + var firstCategoryId = categories.FirstOrDefault()?.Id ?? 0; + selectedCategoryIds = firstCategoryId > 0 ? new[] { firstCategoryId } : Array.Empty(); + } } finally { @@ -140,8 +148,9 @@ if (cfg == null) return; - if (cfg.CategoryId > 0) - selectedCategoryId = cfg.CategoryId; + var categoryIds = NormalizeCategoryIds(cfg.CategoryIds, cfg.CategoryId); + if (categoryIds.Count > 0) + selectedCategoryIds = categoryIds; targetsText = string.Join(Environment.NewLine, cfg.Targets ?? new List()); dictionaryText = string.Join(Environment.NewLine, cfg.Dictionary ?? new List()); @@ -158,6 +167,12 @@ } } + private async Task OnSelectedCategoriesChanged(IEnumerable values) + { + selectedCategoryIds = NormalizeCategoryIds(values, 0); + await OnInputsChanged(); + } + private async Task OnInputsChanged() { await PushDraftAsync(); @@ -167,6 +182,8 @@ { var targets = ParseTargetLines(targetsText); var dictionary = ParseMessageLines(dictionaryText); + var selectedIds = NormalizeCategoryIds(selectedCategoryIds, 0); + var selectedCategories = categories.Where(x => selectedIds.Contains(x.Id)).ToList(); if (loading) { @@ -180,9 +197,9 @@ return; } - if (selectedCategoryId <= 0 || categories.All(x => x.Id != selectedCategoryId)) + if (selectedCategories.Count == 0) { - await DraftChanged.InvokeAsync(new ModuleTaskDraft(0, null, false, "请选择执行账号分类")); + await DraftChanged.InvokeAsync(new ModuleTaskDraft(0, null, false, "请至少选择一个执行账号分类")); return; } @@ -222,11 +239,13 @@ return; } - var category = categories.First(x => x.Id == selectedCategoryId); + var categoryNames = selectedCategories.Select(x => x.Name).ToList(); var cfg = new UserChatActiveTaskConfig { - CategoryId = selectedCategoryId, - CategoryName = category.Name, + CategoryId = selectedCategories[0].Id, + CategoryName = selectedCategories[0].Name, + CategoryIds = selectedCategories.Select(x => x.Id).ToList(), + CategoryNames = categoryNames, Targets = targets, Dictionary = dictionary, DelayMinMs = delayMinMs * 1000, @@ -242,6 +261,19 @@ await DraftChanged.InvokeAsync(new ModuleTaskDraft(total, json, true, null)); } + private static List NormalizeCategoryIds(IEnumerable? values, int fallback) + { + var ids = (values ?? Array.Empty()) + .Where(x => x > 0) + .Distinct() + .ToList(); + + if (ids.Count == 0 && fallback > 0) + ids.Add(fallback); + + return ids; + } + private static int ConvertMillisecondsToSeconds(int value) { if (value <= 0) @@ -288,5 +320,3 @@ .ToList(); } } - - diff --git a/src/TelegramPanel.Web/Components/Pages/Tasks.razor b/src/TelegramPanel.Web/Components/Pages/Tasks.razor index 8150326..efc9249 100644 --- a/src/TelegramPanel.Web/Components/Pages/Tasks.razor +++ b/src/TelegramPanel.Web/Components/Pages/Tasks.razor @@ -110,6 +110,8 @@ } + @@ -175,7 +177,7 @@ OnClick="@(() => RerunTask(context))" /> } + OnClick="@(() => DeleteTask(context))" /> @@ -538,8 +540,7 @@ using var doc = JsonDocument.Parse(task.Config); var root = doc.RootElement; - var categoryName = root.TryGetProperty("category_name", out var categoryNameEl) ? (categoryNameEl.GetString() ?? "").Trim() : ""; - var categoryId = root.TryGetProperty("category_id", out var categoryIdEl) && categoryIdEl.TryGetInt32(out var cid) ? cid : 0; + var categoryText = BuildSelectedCategorySummary(root); var maxMessages = root.TryGetProperty("max_messages", out var maxMessagesEl) && maxMessagesEl.TryGetInt32(out var mm) ? mm : 0; var accountMode = root.TryGetProperty("account_mode", out var accountModeEl) ? (accountModeEl.GetString() ?? "").Trim() : ""; var targetMode = root.TryGetProperty("target_mode", out var targetModeEl) ? (targetModeEl.GetString() ?? "").Trim() : ""; @@ -573,7 +574,6 @@ } } - var categoryText = categoryName.Length > 0 ? $"{categoryName} (#{categoryId})" : $"#{categoryId}"; details += "\n配置摘要:\n"; details += $"分类: {categoryText}\n"; details += $"目标数: {targetCount}\n"; @@ -607,6 +607,72 @@ return seconds.ToString("0.###"); } + private static string BuildSelectedCategorySummary(JsonElement root) + { + var ids = ExtractSelectedCategoryIds(root); + var names = ExtractSelectedCategoryNames(root); + var count = Math.Max(ids.Count, names.Count); + if (count == 0) + return "-"; + + var parts = new List(); + for (var i = 0; i < count; i++) + { + var id = i < ids.Count ? ids[i] : 0; + var name = i < names.Count ? names[i] : string.Empty; + + if (id > 0 && name.Length > 0) + parts.Add($"{name} (#{id})"); + else if (id > 0) + parts.Add($"#{id}"); + else if (name.Length > 0) + parts.Add(name); + } + + return parts.Count == 0 ? "-" : string.Join(", ", parts); + } + + private static List ExtractSelectedCategoryIds(JsonElement root) + { + var ids = new List(); + if (root.TryGetProperty("category_ids", out var idsEl) && idsEl.ValueKind == JsonValueKind.Array) + { + foreach (var item in idsEl.EnumerateArray()) + { + if (item.TryGetInt32(out var id) && id > 0) + ids.Add(id); + } + } + + if (ids.Count == 0 && root.TryGetProperty("category_id", out var categoryIdEl) && categoryIdEl.TryGetInt32(out var categoryId) && categoryId > 0) + ids.Add(categoryId); + + return ids.Distinct().ToList(); + } + + private static List ExtractSelectedCategoryNames(JsonElement root) + { + var names = new List(); + if (root.TryGetProperty("category_names", out var namesEl) && namesEl.ValueKind == JsonValueKind.Array) + { + foreach (var item in namesEl.EnumerateArray()) + { + var name = (item.GetString() ?? string.Empty).Trim(); + if (name.Length > 0) + names.Add(name); + } + } + + if (names.Count == 0 && root.TryGetProperty("category_name", out var categoryNameEl)) + { + var fallbackName = (categoryNameEl.GetString() ?? string.Empty).Trim(); + if (fallbackName.Length > 0) + names.Add(fallbackName); + } + + return names.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + } + private static string NormalizeTaskModeDisplay(string? mode) { var text = (mode ?? "").Trim(); @@ -688,20 +754,26 @@ } } - private async Task DeleteTask(int id) + private async Task DeleteTask(BatchTask task) { + var status = GetDisplayStatus(task); + var isActive = IsActiveStatus(status); + var message = isActive + ? $"确定要删除任务 #{task.Id} 吗?删除后将停止后台执行并移除记录,此操作不可恢复。" + : $"确定要删除任务 #{task.Id} 的记录吗?此操作不可恢复。"; + bool? result = await DialogService.ShowMessageBox( "确认删除", - "确定要删除这个任务记录吗?此操作不可恢复。", + message, yesText: "删除", cancelText: "取消"); if (result == true) { try { - await TaskManagement.DeleteTaskAsync(id); - tasks.RemoveAll(t => t.Id == id); - Snackbar.Add("任务已删除", Severity.Success); + await TaskManagement.DeleteTaskAsync(task.Id); + await LoadTasks(); + Snackbar.Add(isActive ? "任务已删除,后台执行将在下一次状态检查时停止" : "任务已删除", Severity.Success); } catch (Exception ex) { @@ -885,9 +957,14 @@ throw new InvalidOperationException($"任务配置 JSON 无效:{ex.Message}"); } - if (cfg.CategoryId <= 0) + cfg.CategoryIds = NormalizeCategoryIdsForRerun(cfg.CategoryIds, cfg.CategoryId); + if (cfg.CategoryIds.Count == 0) throw new InvalidOperationException("任务缺少账号分类,无法重新运行"); + cfg.CategoryId = cfg.CategoryIds[0]; + cfg.CategoryNames = NormalizeCategoryNamesForRerun(cfg.CategoryNames, cfg.CategoryName); + cfg.CategoryName = cfg.CategoryNames.FirstOrDefault() ?? cfg.CategoryName; + cfg.Targets = (cfg.Targets ?? new List()) .Select(x => (x ?? string.Empty).Trim()) .Where(x => x.Length > 0) @@ -923,6 +1000,34 @@ return cfg; } + private static List NormalizeCategoryIdsForRerun(IEnumerable? values, int fallback) + { + var ids = (values ?? Array.Empty()) + .Where(x => x > 0) + .Distinct() + .ToList(); + + if (ids.Count == 0 && fallback > 0) + ids.Add(fallback); + + return ids; + } + + private static List NormalizeCategoryNamesForRerun(IEnumerable? values, string? fallback) + { + var names = (values ?? Array.Empty()) + .Select(x => (x ?? string.Empty).Trim()) + .Where(x => x.Length > 0) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var fallbackName = (fallback ?? string.Empty).Trim(); + if (names.Count == 0 && fallbackName.Length > 0) + names.Add(fallbackName); + + return names; + } + private static string NormalizeModeValue(string? mode) { return string.Equals((mode ?? string.Empty).Trim(), UserChatActiveTaskModes.Queue, StringComparison.OrdinalIgnoreCase) diff --git a/src/TelegramPanel.Web/Services/UserChatActiveTaskHandler.cs b/src/TelegramPanel.Web/Services/UserChatActiveTaskHandler.cs index bfd1db3..53ee9fd 100644 --- a/src/TelegramPanel.Web/Services/UserChatActiveTaskHandler.cs +++ b/src/TelegramPanel.Web/Services/UserChatActiveTaskHandler.cs @@ -25,9 +25,10 @@ public sealed class UserChatActiveTaskHandler : IModuleTaskHandler var config = DeserializeConfig(host.Config); ValidateAndNormalizeConfig(config); + var selectedCategoryIds = NormalizeSelectedCategoryIds(config).ToHashSet(); var allAccounts = (await accountManagement.GetAllAccountsAsync()) .Where(x => x.IsActive && x.UserId > 0 && x.Category?.ExcludeFromOperations != true) - .Where(x => x.CategoryId == config.CategoryId) + .Where(x => x.CategoryId.HasValue && selectedCategoryIds.Contains(x.CategoryId.Value)) .OrderBy(x => x.Id) .ToList(); @@ -196,9 +197,14 @@ public sealed class UserChatActiveTaskHandler : IModuleTaskHandler private static void ValidateAndNormalizeConfig(UserChatActiveTaskConfig config) { - if (config.CategoryId <= 0) + config.CategoryIds = NormalizeSelectedCategoryIds(config); + if (config.CategoryIds.Count == 0) throw new InvalidOperationException("任务缺少账号分类"); + config.CategoryId = config.CategoryIds[0]; + config.CategoryNames = NormalizeSelectedCategoryNames(config); + config.CategoryName = config.CategoryNames.FirstOrDefault() ?? config.CategoryName; + config.Targets = config.Targets .Select(x => (x ?? string.Empty).Trim()) .Where(x => x.Length > 0) @@ -231,6 +237,34 @@ public sealed class UserChatActiveTaskHandler : IModuleTaskHandler config.RecentFailures ??= new List(); } + private static List NormalizeSelectedCategoryIds(UserChatActiveTaskConfig config) + { + var ids = (config.CategoryIds ?? new List()) + .Where(x => x > 0) + .Distinct() + .ToList(); + + if (ids.Count == 0 && config.CategoryId > 0) + ids.Add(config.CategoryId); + + return ids; + } + + private static List NormalizeSelectedCategoryNames(UserChatActiveTaskConfig config) + { + var names = (config.CategoryNames ?? new List()) + .Select(x => (x ?? string.Empty).Trim()) + .Where(x => x.Length > 0) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var fallbackName = (config.CategoryName ?? string.Empty).Trim(); + if (names.Count == 0 && fallbackName.Length > 0) + names.Add(fallbackName); + + return names; + } + private static string NormalizeMode(string? mode) { return string.Equals((mode ?? string.Empty).Trim(), UserChatActiveTaskModes.Queue, StringComparison.OrdinalIgnoreCase) diff --git a/src/TelegramPanel.Web/Services/UserChatActiveTaskModels.cs b/src/TelegramPanel.Web/Services/UserChatActiveTaskModels.cs index c8f5b46..7b7744a 100644 --- a/src/TelegramPanel.Web/Services/UserChatActiveTaskModels.cs +++ b/src/TelegramPanel.Web/Services/UserChatActiveTaskModels.cs @@ -16,6 +16,12 @@ public sealed class UserChatActiveTaskConfig [JsonPropertyName("category_name")] public string? CategoryName { get; set; } + [JsonPropertyName("category_ids")] + public List CategoryIds { get; set; } = new(); + + [JsonPropertyName("category_names")] + public List CategoryNames { get; set; } = new(); + [JsonPropertyName("targets")] public List Targets { get; set; } = new();