395 lines
15 KiB
Plaintext
395 lines
15 KiB
Plaintext
@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
|
||
|
||
<MudDialog>
|
||
<DialogContent>
|
||
<MudStack Spacing="2">
|
||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
|
||
任务会提交到后台执行,可在【任务中心】查看进度与结果。
|
||
</MudAlert>
|
||
|
||
<MudGrid>
|
||
<MudItem xs="12" md="6">
|
||
<MudSelect @bind-Value="selectedCategory" Label="任务分类" Variant="Variant.Outlined" Dense="true">
|
||
@foreach (var c in AvailableCategories)
|
||
{
|
||
<MudSelectItem Value="@c">@GetCategoryDisplayName(c)</MudSelectItem>
|
||
}
|
||
</MudSelect>
|
||
</MudItem>
|
||
<MudItem xs="12" md="6">
|
||
<MudSelect @bind-Value="selectedTaskType" Label="任务类型" Variant="Variant.Outlined" Dense="true">
|
||
@foreach (var opt in AvailableOptions)
|
||
{
|
||
<MudSelectItem Value="@opt.TaskType">@opt.DisplayName</MudSelectItem>
|
||
}
|
||
</MudSelect>
|
||
</MudItem>
|
||
</MudGrid>
|
||
|
||
@if (CurrentOption != null && !string.IsNullOrWhiteSpace(CurrentOption.Description))
|
||
{
|
||
<MudText Typo="Typo.caption" Class="mud-text-secondary">@CurrentOption.Description</MudText>
|
||
}
|
||
|
||
@if (CurrentOption != null && !string.IsNullOrWhiteSpace(CurrentOption.CreateRoute))
|
||
{
|
||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
|
||
该任务建议使用配置窗口创建/编辑:<b>@CurrentOption.CreateRoute</b>
|
||
</MudAlert>
|
||
<MudStack Row="true" Spacing="2">
|
||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(() => OpenCreateDialog(CurrentOption.CreateRoute!))">
|
||
打开窗口
|
||
</MudButton>
|
||
<MudButton Variant="Variant.Outlined" Color="Color.Default" OnClick="@(() => NavigateTo(CurrentOption.CreateRoute!))">
|
||
前往页面
|
||
</MudButton>
|
||
</MudStack>
|
||
}
|
||
else if (CurrentOption != null && !string.IsNullOrWhiteSpace(CurrentOption.EditorComponentType) && editorComponentType != null)
|
||
{
|
||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
|
||
该任务由模块提供自定义创建器。
|
||
</MudAlert>
|
||
|
||
<DynamicComponent Type="editorComponentType" Parameters="editorParameters" />
|
||
|
||
@if (!string.IsNullOrWhiteSpace(editorDraft.ValidationError))
|
||
{
|
||
<MudText Typo="Typo.caption" Color="Color.Error">@editorDraft.ValidationError</MudText>
|
||
}
|
||
}
|
||
else
|
||
{
|
||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
|
||
该任务类型暂无专用创建表单。你可以填写通用配置(JSON 字符串)提交任务;对应模块需要在后台实现该 TaskType 的执行器。
|
||
</MudAlert>
|
||
|
||
<MudTextField @bind-Value="genericConfigJson" Label="任务配置(JSON)" Variant="Variant.Outlined" Lines="8"
|
||
HelperText="模块可自行解析该 JSON。若无需配置可留空。" />
|
||
|
||
<MudNumericField @bind-Value="genericTotal" Label="总数(Total,可选)" Variant="Variant.Outlined" Min="0" Max="1000000"
|
||
HelperText="用于进度条展示;若不确定可填 0,由执行器在运行时持续更新进度。" />
|
||
}
|
||
</MudStack>
|
||
</DialogContent>
|
||
<DialogActions>
|
||
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="Cancel" Disabled="@creating">关闭</MudButton>
|
||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="Create" Disabled="@creating">
|
||
@if (creating)
|
||
{
|
||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||
<span>提交中...</span>
|
||
}
|
||
else
|
||
{
|
||
<span>提交任务</span>
|
||
}
|
||
</MudButton>
|
||
</DialogActions>
|
||
</MudDialog>
|
||
|
||
@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<string, object?> editorParameters = new();
|
||
|
||
private IEnumerable<string> 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<TelegramPanel.Modules.ModuleTaskDefinition> 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<string, object?>();
|
||
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<string, object?>
|
||
{
|
||
["Draft"] = editorDraft,
|
||
["DraftChanged"] = EventCallback.Factory.Create<TelegramPanel.Modules.ModuleTaskDraft>(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<TelegramPanel.Web.Components.Dialogs.ModulePageDialog>("任务配置", 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();
|
||
}
|