diff --git a/src/TelegramPanel.Core/Services/BatchTaskManagementService.cs b/src/TelegramPanel.Core/Services/BatchTaskManagementService.cs
index 0f398c1..5d78d49 100644
--- a/src/TelegramPanel.Core/Services/BatchTaskManagementService.cs
+++ b/src/TelegramPanel.Core/Services/BatchTaskManagementService.cs
@@ -68,6 +68,18 @@ public class BatchTaskManagementService
}
}
+ public async Task UpdateTaskDraftAsync(int taskId, int total, string? config)
+ {
+ var task = await _batchTaskRepository.GetByIdAsync(taskId);
+ if (task != null)
+ {
+ if (total < 0) total = 0;
+ task.Total = total;
+ task.Config = config;
+ await _batchTaskRepository.UpdateAsync(task);
+ }
+ }
+
public async Task StartTaskAsync(int taskId)
{
var task = await _batchTaskRepository.GetByIdAsync(taskId);
diff --git a/src/TelegramPanel.Web/Components/Dialogs/EditUserChatActiveTaskDialog.razor b/src/TelegramPanel.Web/Components/Dialogs/EditUserChatActiveTaskDialog.razor
new file mode 100644
index 0000000..0248740
--- /dev/null
+++ b/src/TelegramPanel.Web/Components/Dialogs/EditUserChatActiveTaskDialog.razor
@@ -0,0 +1,133 @@
+@namespace TelegramPanel.Web.Components.Dialogs
+@inject BatchTaskManagementService TaskManagement
+@inject ISnackbar Snackbar
+@inject IDialogService DialogService
+@using TelegramPanel.Core.BatchTasks
+@using TelegramPanel.Data.Entities
+@using TelegramPanel.Modules
+@using Microsoft.AspNetCore.Components
+
+
+
+ @if (loading)
+ {
+
+
+ 正在加载任务配置...
+
+ }
+ else if (task == null)
+ {
+
+ 任务不存在或已被删除。
+
+ }
+ else if (!string.Equals(task.TaskType, BatchTaskTypes.UserChatActive, StringComparison.OrdinalIgnoreCase))
+ {
+
+ 仅支持编辑“账号持续活跃”任务。
+
+ }
+ else if (string.Equals(task.Status, "running", StringComparison.OrdinalIgnoreCase))
+ {
+
+ 该任务正在执行中,请先取消后再编辑。
+
+ }
+ else
+ {
+
+ 编辑后会更新当前任务配置。建议编辑完成后使用“重新运行”创建新任务执行。
+
+
+
+
+ @if (!string.IsNullOrWhiteSpace(draft.ValidationError))
+ {
+ @draft.ValidationError
+ }
+ }
+
+
+ 关闭
+
+ @if (saving)
+ {
+
+ 保存中...
+ }
+ else
+ {
+ 保存配置
+ }
+
+
+
+
+@code {
+ [CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
+ [Parameter] public int TaskId { get; set; }
+
+ private BatchTask? task;
+ private bool loading = true;
+ private bool saving;
+ private ModuleTaskDraft draft = new(0, null, false, null);
+
+ private bool CanSave =>
+ task != null
+ && string.Equals(task.TaskType, BatchTaskTypes.UserChatActive, StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(task.Status, "running", StringComparison.OrdinalIgnoreCase)
+ && draft.CanSubmit
+ && !string.IsNullOrWhiteSpace(draft.Config);
+
+ protected override async Task OnInitializedAsync()
+ {
+ try
+ {
+ if (TaskId > 0)
+ task = await TaskManagement.GetTaskAsync(TaskId);
+ }
+ finally
+ {
+ loading = false;
+ }
+ }
+
+ private void OnDraftChanged(ModuleTaskDraft value)
+ {
+ draft = value;
+ }
+
+ private async Task Save()
+ {
+ if (!CanSave || task == null || saving)
+ return;
+
+ bool? confirm = await DialogService.ShowMessageBox(
+ "确认保存",
+ $"将更新任务 #{task.Id} 的配置,是否继续?",
+ yesText: "保存", cancelText: "取消");
+
+ if (confirm != true)
+ return;
+
+ saving = true;
+ try
+ {
+ var config = draft.Config?.Trim();
+ await TaskManagement.UpdateTaskDraftAsync(task.Id, draft.Total < 0 ? 0 : draft.Total, config);
+ Snackbar.Add($"任务 #{task.Id} 配置已更新", Severity.Success);
+ MudDialog.Close(DialogResult.Ok(task.Id));
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"保存失败:{ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ saving = false;
+ }
+ }
+
+ private void Cancel() => MudDialog.Close();
+}
diff --git a/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor b/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor
index 0fc7502..a775373 100644
--- a/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor
+++ b/src/TelegramPanel.Web/Components/Dialogs/UserChatActiveTaskEditor.razor
@@ -85,9 +85,11 @@
@code {
[Parameter] public ModuleTaskDraft Draft { get; set; }
[Parameter] public EventCallback DraftChanged { get; set; }
+ [Parameter] public string? InitialConfigJson { get; set; }
private List categories = new();
private bool loading = true;
+ private bool initializedFromConfig;
private int selectedCategoryId;
private string targetsText = "";
@@ -102,6 +104,8 @@
protected override async Task OnInitializedAsync()
{
+ ApplyInitialConfigIfNeeded();
+
try
{
categories = (await CategoryManagement.GetAllCategoriesAsync())
@@ -119,6 +123,41 @@
}
}
+ private void ApplyInitialConfigIfNeeded()
+ {
+ if (initializedFromConfig)
+ return;
+
+ initializedFromConfig = true;
+
+ var raw = (InitialConfigJson ?? string.Empty).Trim();
+ if (raw.Length == 0)
+ return;
+
+ try
+ {
+ var cfg = JsonSerializer.Deserialize(raw);
+ if (cfg == null)
+ return;
+
+ if (cfg.CategoryId > 0)
+ selectedCategoryId = cfg.CategoryId;
+
+ targetsText = string.Join(Environment.NewLine, cfg.Targets ?? new List());
+ dictionaryText = string.Join(Environment.NewLine, cfg.Dictionary ?? new List());
+ delayMinMs = cfg.DelayMinMs;
+ delayMaxMs = cfg.DelayMaxMs;
+ maxMessages = cfg.MaxMessages;
+ accountMode = NormalizeMode(cfg.AccountMode);
+ targetMode = NormalizeMode(cfg.TargetMode);
+ messageMode = NormalizeMode(cfg.MessageMode);
+ }
+ catch
+ {
+ // 忽略旧配置解析失败,回退到默认值。
+ }
+ }
+
private async Task OnInputsChanged()
{
await PushDraftAsync();
@@ -209,6 +248,13 @@
|| string.Equals(value, UserChatActiveTaskModes.Queue, StringComparison.OrdinalIgnoreCase);
}
+ private static string NormalizeMode(string? value)
+ {
+ return string.Equals(value, UserChatActiveTaskModes.Queue, StringComparison.OrdinalIgnoreCase)
+ ? UserChatActiveTaskModes.Queue
+ : UserChatActiveTaskModes.Random;
+ }
+
private static List ParseTargetLines(string? text)
{
if (string.IsNullOrWhiteSpace(text))
diff --git a/src/TelegramPanel.Web/Components/Pages/Tasks.razor b/src/TelegramPanel.Web/Components/Pages/Tasks.razor
index f81c9aa..517ea0e 100644
--- a/src/TelegramPanel.Web/Components/Pages/Tasks.razor
+++ b/src/TelegramPanel.Web/Components/Pages/Tasks.razor
@@ -90,6 +90,11 @@
+ @if (CanEditUserChatActiveTask(context))
+ {
+
+ }
@if (IsActiveStatus(GetDisplayStatus(context)))
{
+ @if (CanEditUserChatActiveTask(context))
+ {
+
+ }
+ @if (CanRerunUserChatActiveTask(context))
+ {
+
+ }
@@ -398,6 +413,18 @@
private static bool IsUserChatActiveTask(string taskType) =>
taskType is BatchTaskTypes.UserChatActive;
+ private static bool CanEditUserChatActiveTask(BatchTask task)
+ {
+ return IsUserChatActiveTask(task.TaskType)
+ && !string.Equals(task.Status, "running", StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static bool CanRerunUserChatActiveTask(BatchTask task)
+ {
+ return IsUserChatActiveTask(task.TaskType)
+ && !string.Equals(task.Status, "running", StringComparison.OrdinalIgnoreCase);
+ }
+
private static List ExtractBotAdminFailureLines(BatchTask task)
{
var lines = new List();
@@ -632,4 +659,125 @@
if (result is { Canceled: false })
await LoadTasks();
}
+
+ private async Task OpenEditUserChatActiveTask(BatchTask task)
+ {
+ if (!CanEditUserChatActiveTask(task))
+ {
+ Snackbar.Add("该任务当前不可编辑", Severity.Warning);
+ return;
+ }
+
+ var parameters = new DialogParameters
+ {
+ ["TaskId"] = task.Id
+ };
+ var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large, FullWidth = true };
+ var dialog = DialogService.Show($"编辑持续任务 #{task.Id}", parameters, options)!;
+ var result = await dialog.Result;
+ if (result is { Canceled: false })
+ await LoadTasks();
+ }
+
+ private async Task RerunUserChatActiveTask(BatchTask task)
+ {
+ if (!CanRerunUserChatActiveTask(task))
+ {
+ Snackbar.Add("该任务当前不可重新运行", Severity.Warning);
+ return;
+ }
+
+ bool? confirm = await DialogService.ShowMessageBox(
+ "确认重新运行",
+ $"将基于任务 #{task.Id} 的配置创建一个新任务,是否继续?",
+ yesText: "继续", cancelText: "取消");
+
+ if (confirm != true)
+ return;
+
+ try
+ {
+ var rerunConfig = BuildRerunConfig(task.Config);
+ var total = rerunConfig.MaxMessages > 0 ? rerunConfig.MaxMessages : 0;
+ var configJson = JsonSerializer.Serialize(rerunConfig, new JsonSerializerOptions { WriteIndented = true });
+
+ var created = await TaskManagement.CreateTaskAsync(new BatchTask
+ {
+ TaskType = BatchTaskTypes.UserChatActive,
+ Total = total,
+ Completed = 0,
+ Failed = 0,
+ Config = configJson
+ });
+
+ Snackbar.Add($"已创建重跑任务 #{created.Id}", Severity.Success);
+ await LoadTasks();
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"重新运行失败:{ex.Message}", Severity.Error);
+ }
+ }
+
+ private static UserChatActiveTaskConfig BuildRerunConfig(string? rawConfig)
+ {
+ var raw = (rawConfig ?? string.Empty).Trim();
+ if (raw.Length == 0)
+ throw new InvalidOperationException("任务配置为空,无法重新运行");
+
+ UserChatActiveTaskConfig cfg;
+ try
+ {
+ cfg = JsonSerializer.Deserialize(raw)
+ ?? throw new InvalidOperationException("任务配置解析结果为空");
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException($"任务配置 JSON 无效:{ex.Message}");
+ }
+
+ if (cfg.CategoryId <= 0)
+ throw new InvalidOperationException("任务缺少账号分类,无法重新运行");
+
+ cfg.Targets = (cfg.Targets ?? new List())
+ .Select(x => (x ?? string.Empty).Trim())
+ .Where(x => x.Length > 0)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ cfg.Dictionary = (cfg.Dictionary ?? new List())
+ .Select(x => (x ?? string.Empty).Trim())
+ .Where(x => x.Length > 0)
+ .ToList();
+
+ if (cfg.Targets.Count == 0)
+ throw new InvalidOperationException("任务缺少目标群组/频道,无法重新运行");
+
+ if (cfg.Dictionary.Count == 0)
+ throw new InvalidOperationException("任务缺少词典消息,无法重新运行");
+
+ if (cfg.DelayMinMs < 0) cfg.DelayMinMs = 0;
+ if (cfg.DelayMaxMs < 0) cfg.DelayMaxMs = 0;
+ if (cfg.DelayMinMs > 600000) cfg.DelayMinMs = 600000;
+ if (cfg.DelayMaxMs > 600000) cfg.DelayMaxMs = 600000;
+ if (cfg.DelayMaxMs < cfg.DelayMinMs) cfg.DelayMaxMs = cfg.DelayMinMs;
+ if (cfg.MaxMessages < 0) cfg.MaxMessages = 0;
+
+ cfg.AccountMode = NormalizeModeValue(cfg.AccountMode);
+ cfg.TargetMode = NormalizeModeValue(cfg.TargetMode);
+ cfg.MessageMode = NormalizeModeValue(cfg.MessageMode);
+
+ cfg.Canceled = false;
+ cfg.Error = null;
+ cfg.RecentFailures = new List();
+
+ return cfg;
+ }
+
+ private static string NormalizeModeValue(string? mode)
+ {
+ return string.Equals((mode ?? string.Empty).Trim(), UserChatActiveTaskModes.Queue, StringComparison.OrdinalIgnoreCase)
+ ? UserChatActiveTaskModes.Queue
+ : UserChatActiveTaskModes.Random;
+ }
}