@namespace TelegramPanel.Web.Components.Dialogs @inject BatchTaskManagementService TaskManagement @inject TelegramPanel.Web.Modules.ModuleContributionRegistry Contributions @inject ISnackbar Snackbar @inject IDialogService DialogService @inject NavigationManager Navigation @using System.Text.Json @using Microsoft.AspNetCore.Components @using TelegramPanel.Data.Entities 任务会提交到后台执行,可在【任务中心】查看进度与结果。 @foreach (var c in AvailableCategories) { @GetCategoryDisplayName(c) } @foreach (var opt in AvailableOptions) { @opt.DisplayName } @if (CurrentOption != null && !string.IsNullOrWhiteSpace(CurrentOption.Description)) { @CurrentOption.Description } @if (CurrentOption != null && !string.IsNullOrWhiteSpace(CurrentOption.CreateRoute)) { 该任务建议使用配置窗口创建/编辑:@CurrentOption.CreateRoute 打开窗口 前往页面 } else if (CurrentOption != null && !string.IsNullOrWhiteSpace(CurrentOption.EditorComponentType) && editorComponentType != null) { 该任务由模块提供自定义创建器。 @if (!string.IsNullOrWhiteSpace(editorDraft.ValidationError)) { @editorDraft.ValidationError } } else { 该任务类型暂无专用创建表单。你可以填写通用配置(JSON 字符串)提交任务;对应模块需要在后台实现该 TaskType 的执行器。 } 关闭 @if (creating) { 提交中... } else { 提交任务 } @code { [CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!; private string _selectedCategory = ""; private string selectedCategory { get => _selectedCategory; set { value = (value ?? "").Trim(); if (string.Equals(_selectedCategory, value, StringComparison.OrdinalIgnoreCase)) return; _selectedCategory = value; EnsureTaskTypeValid(); } } private string _selectedTaskType = ""; private string selectedTaskType { get => _selectedTaskType; set { value = (value ?? "").Trim(); if (string.Equals(_selectedTaskType, value, StringComparison.OrdinalIgnoreCase)) return; _selectedTaskType = value; ResolveEditorComponent(); } } private bool creating; // generic task config private string genericConfigJson = ""; private int genericTotal = 0; // module editor draft private TelegramPanel.Modules.ModuleTaskDraft editorDraft = new(0, null, false, null); private Type? editorComponentType; private Dictionary editorParameters = new(); private IEnumerable AvailableCategories => Contributions.Tasks .Select(x => (x.Definition.Category ?? "").Trim()) .Where(x => x.Length > 0) .Where(x => !IsSystemCategory(x)) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(x => x, StringComparer.OrdinalIgnoreCase); private IEnumerable AvailableOptions => Contributions.Tasks .Select(x => x.Definition) .Where(o => !IsSystemCategory(o.Category)) .Where(o => string.Equals(o.Category, selectedCategory, StringComparison.OrdinalIgnoreCase)) .OrderBy(o => o.Order) .ThenBy(o => o.DisplayName); private TelegramPanel.Modules.ModuleTaskDefinition? CurrentOption => !string.IsNullOrWhiteSpace(selectedTaskType) && Contributions.TaskTypeToDefinition.TryGetValue(selectedTaskType, out var reg) ? reg.Definition : null; protected override async Task OnInitializedAsync() { if (string.IsNullOrWhiteSpace(selectedCategory)) selectedCategory = AvailableCategories.FirstOrDefault() ?? "user"; EnsureTaskTypeValid(); await Task.CompletedTask; } private void EnsureTaskTypeValid() { if (AvailableOptions.All(o => !string.Equals(o.TaskType, selectedTaskType, StringComparison.OrdinalIgnoreCase))) { selectedTaskType = AvailableOptions.FirstOrDefault()?.TaskType ?? Contributions.Tasks.FirstOrDefault()?.Definition.TaskType ?? ""; } } private static string GetCategoryDisplayName(string category) => category switch { "user" => "用户任务", "bot" => "Bot 任务", "system" => "系统任务", _ => string.IsNullOrWhiteSpace(category) ? "-" : category }; private static bool IsSystemCategory(string? category) => string.Equals((category ?? "").Trim(), "system", StringComparison.OrdinalIgnoreCase); private void ResolveEditorComponent() { editorComponentType = null; editorParameters = new Dictionary(); editorDraft = new TelegramPanel.Modules.ModuleTaskDraft(0, null, false, null); var opt = CurrentOption; if (opt == null || string.IsNullOrWhiteSpace(opt.EditorComponentType)) return; if (!Contributions.TaskTypeToDefinition.TryGetValue(opt.TaskType, out var reg)) return; editorComponentType = ModuleTaskEditorComponentResolver.ResolveCreateEditor(reg); if (editorComponentType == null) return; editorParameters = new Dictionary { ["Draft"] = editorDraft, ["DraftChanged"] = EventCallback.Factory.Create(this, OnEditorDraftChanged) }; } private void OnEditorDraftChanged(TelegramPanel.Modules.ModuleTaskDraft draft) { editorDraft = draft; } private async Task Create() { if (creating) return; if (CurrentOption != null && !string.IsNullOrWhiteSpace(CurrentOption.CreateRoute)) { Snackbar.Add("该任务建议使用独立页面创建,请点击“前往创建”。", Severity.Info); return; } if (CurrentOption != null && !string.IsNullOrWhiteSpace(CurrentOption.EditorComponentType) && editorComponentType != null) { if (!editorDraft.CanSubmit) { Snackbar.Add(string.IsNullOrWhiteSpace(editorDraft.ValidationError) ? "请先完善任务配置" : editorDraft.ValidationError!, Severity.Warning); return; } var config = string.IsNullOrWhiteSpace(editorDraft.Config) ? null : editorDraft.Config.Trim(); if (!string.IsNullOrWhiteSpace(config)) { try { JsonDocument.Parse(config); } catch (Exception ex) { Snackbar.Add($"配置 JSON 无效:{ex.Message}", Severity.Warning); return; } } var total = editorDraft.Total < 0 ? 0 : editorDraft.Total; bool? confirm = await DialogService.ShowMessageBox( "确认提交任务", $"将提交任务类型:{selectedTaskType},总数:{total}。是否继续?", yesText: "继续", cancelText: "取消"); if (confirm != true) return; creating = true; try { var task = new BatchTask { TaskType = selectedTaskType, Total = total, Completed = 0, Failed = 0, Config = config }; var created = await TaskManagement.CreateTaskAsync(task); Snackbar.Add($"已提交后台任务 #{created.Id}({selectedTaskType})。请到【任务中心】查看进度。", Severity.Success); MudDialog.Close(DialogResult.Ok(created.Id)); } catch (Exception ex) { Snackbar.Add($"提交失败:{ex.Message}", Severity.Error); } finally { creating = false; } return; } // Generic task create if (!string.IsNullOrWhiteSpace(genericConfigJson)) { try { JsonDocument.Parse(genericConfigJson); } catch (Exception ex) { Snackbar.Add($"配置 JSON 无效:{ex.Message}", Severity.Warning); return; } } bool? genericConfirm = await DialogService.ShowMessageBox( "确认提交任务", $"将提交任务类型:{selectedTaskType}。是否继续?", yesText: "继续", cancelText: "取消"); if (genericConfirm != true) return; creating = true; try { var task = new BatchTask { TaskType = selectedTaskType, Total = genericTotal, Completed = 0, Failed = 0, Config = string.IsNullOrWhiteSpace(genericConfigJson) ? null : genericConfigJson.Trim() }; var created = await TaskManagement.CreateTaskAsync(task); Snackbar.Add($"已提交后台任务 #{created.Id}({selectedTaskType})。请到【任务中心】查看进度。", Severity.Success); MudDialog.Close(DialogResult.Ok(created.Id)); } catch (Exception ex) { Snackbar.Add($"提交失败:{ex.Message}", Severity.Error); } finally { creating = false; } } private void NavigateTo(string url) { MudDialog.Close(); Navigation.NavigateTo(url); } private async Task OpenCreateDialog(string route) { route = (route ?? "").Trim(); if (route.Length == 0) return; // 仅对模块扩展页面提供“窗口打开”:/ext/{ModuleId}/{PageKey} if (!TryParseExtRoute(route, out var moduleId, out var pageKey)) { Snackbar.Add("该任务未提供可窗口打开的模块页面,请使用“前往页面”。", Severity.Info); return; } var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.ExtraLarge, FullWidth = true }; var parameters = new DialogParameters { ["ModuleId"] = moduleId, ["PageKey"] = pageKey }; DialogService.Show("任务配置", parameters, options); await Task.CompletedTask; } private static bool TryParseExtRoute(string route, out string moduleId, out string pageKey) { moduleId = ""; pageKey = ""; route = (route ?? "").Trim(); if (!route.StartsWith("/ext/", StringComparison.OrdinalIgnoreCase)) return false; // /ext/{moduleId}/{pageKey} var parts = route.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (parts.Length < 3) return false; if (!string.Equals(parts[0], "ext", StringComparison.OrdinalIgnoreCase)) return false; moduleId = parts[1]; pageKey = parts[2]; return moduleId.Length > 0 && pageKey.Length > 0; } private void Cancel() => MudDialog.Close(); }