@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();
}