using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using BetterGenshinImpact.Core.Config; using BetterGenshinImpact.Core.Script; using BetterGenshinImpact.GameTask; using BetterGenshinImpact.Helpers.Ui; using BetterGenshinImpact.Service; using BetterGenshinImpact.Service.GearTask; using BetterGenshinImpact.Service.GearTask.Execution; using BetterGenshinImpact.View.Pages.View; using BetterGenshinImpact.View.Windows; using BetterGenshinImpact.View.Windows.GearTask; using BetterGenshinImpact.ViewModel.Pages.Component; using BetterGenshinImpact.ViewModel.Pages.View; using BetterGenshinImpact.ViewModel.Windows; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.Logging; using Wpf.Ui; using Wpf.Ui.Violeta.Controls; namespace BetterGenshinImpact.ViewModel.Pages; /// /// 任务列表页面 ViewModel。 /// public partial class GearTaskListPageViewModel : ViewModel { private readonly ILogger _logger; private readonly GearTaskStorageService _storageService; private readonly IGearTaskHistoryStore _historyStore; private readonly GearTaskExecutor _taskExecutor; /// /// 任务定义列表(左侧)。 /// [ObservableProperty] private ObservableCollection _taskDefinitions = new(); /// /// 当前选中的任务定义。 /// [ObservableProperty] private GearTaskDefinitionViewModel? _selectedTaskDefinition; /// /// 当前任务树根节点(右侧定义区)。 /// [ObservableProperty] private GearTaskViewModel _currentTaskTreeRoot = new(); /// /// 当前选中的任务节点。 /// [ObservableProperty] private GearTaskViewModel? _selectedTaskNode; [ObservableProperty] private int _selectedDetailTabIndex; [ObservableProperty] private string _executionRecordTabTitle = "执行记录 (0)"; [ObservableProperty] private string _latestExecutionStatusText = "暂无执行记录"; [ObservableProperty] private string _latestResumeNodeText = "-"; [ObservableProperty] private string _executionSuccessRateText = "-"; [ObservableProperty] private string _executionAverageDurationText = "-"; [ObservableProperty] private ObservableCollection _recentExecutionRecords = []; [ObservableProperty] private GearTaskExecutionRecordItemViewModel? _selectedExecutionRecord; [ObservableProperty] private ObservableCollection _selectedExecutionNodes = []; [ObservableProperty] private bool _canResumeSelectedExecutionRecord; public GearTaskExecutor Executor => _taskExecutor; public GearTaskListPageViewModel( ILogger logger, GearTaskStorageService storageService, IGearTaskHistoryStore historyStore, GearTaskExecutor taskExecutor) { _logger = logger; _storageService = storageService; _historyStore = historyStore; _taskExecutor = taskExecutor; InitializeData(); TaskDefinitions.CollectionChanged += OnTaskDefinitionsChanged; CurrentTaskTreeRoot.Children.CollectionChanged += OnCurrentTaskTreeChanged; } partial void OnSelectedTaskDefinitionChanged(GearTaskDefinitionViewModel? value) { foreach (var task in TaskDefinitions) { task.IsSelected = false; } if (value != null) { value.IsSelected = true; } CurrentTaskTreeRoot.Children.CollectionChanged -= OnCurrentTaskTreeChanged; CurrentTaskTreeRoot = value?.RootTask ?? new GearTaskViewModel(); CurrentTaskTreeRoot.Children.CollectionChanged += OnCurrentTaskTreeChanged; _ = LoadExecutionRecordsForSelectedTaskDefinitionAsync(value); } partial void OnSelectedExecutionRecordChanged(GearTaskExecutionRecordItemViewModel? value) { SelectedExecutionNodes.Clear(); CanResumeSelectedExecutionRecord = value?.Record.CanResume == true; if (value == null) { return; } foreach (var node in value.Record.Nodes.OrderBy(n => n.NodeId, Comparer.Create(GearTaskExecutionDisplayHelper.CompareNodeId))) { SelectedExecutionNodes.Add(new GearTaskExecutionNodeItemViewModel(node)); } } /// /// 任务定义集合变化时,更新顺序并保存。 /// private async void OnTaskDefinitionsChanged(object? sender, NotifyCollectionChangedEventArgs e) { try { for (int i = 0; i < TaskDefinitions.Count; i++) { if (TaskDefinitions[i].Order != i) { TaskDefinitions[i].Order = i; TaskDefinitions[i].ModifiedTime = DateTime.Now; } } foreach (var taskDef in TaskDefinitions) { await _storageService.SaveTaskDefinitionAsync(taskDef); } } catch (Exception ex) { _logger.LogError(ex, "保存任务定义顺序失败"); } } /// /// 根级树结构变化后自动保存。 /// private async void OnCurrentTaskTreeChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (SelectedTaskDefinition == null) { return; } try { SelectedTaskDefinition.ModifiedTime = DateTime.Now; await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition); } catch (Exception ex) { _logger.LogError(ex, "自动保存任务定义失败: {TaskName}", SelectedTaskDefinition.Name); } } /// /// 初始化任务定义和历史记录摘要。 /// private async void InitializeData() { try { var loadedTasks = await _storageService.LoadAllTaskDefinitionsAsync(); foreach (var task in loadedTasks.OrderBy(t => t.Order)) { TaskDefinitions.Add(task); SetupTaskDefinitionPropertyChanged(task); } if (TaskDefinitions.Count == 0) { await CreateSampleTaskAsync(); } await RefreshTaskDefinitionExecutionSummariesAsync(); SelectedTaskDefinition ??= TaskDefinitions.FirstOrDefault(); } catch (Exception ex) { _logger.LogError(ex, "初始化任务列表失败"); await CreateSampleTaskAsync(); await RefreshTaskDefinitionExecutionSummariesAsync(); SelectedTaskDefinition ??= TaskDefinitions.FirstOrDefault(); } } /// /// 创建示例任务。 /// private async Task CreateSampleTaskAsync() { var sampleTask = new GearTaskDefinitionViewModel("示例任务组", "这是一个示例任务组"); if (sampleTask.RootTask != null) { sampleTask.RootTask.AddChild(new GearTaskViewModel("采集任务1") { TaskType = "采集任务" }); sampleTask.RootTask.AddChild(new GearTaskViewModel("战斗任务1") { TaskType = "战斗任务" }); var subGroup = new GearTaskViewModel("子任务组", true); subGroup.AddChild(new GearTaskViewModel("传送任务") { TaskType = "传送任务" }); subGroup.AddChild(new GearTaskViewModel("交互任务1") { TaskType = "交互任务" }); sampleTask.RootTask.AddChild(subGroup); } TaskDefinitions.Add(sampleTask); SetupTaskDefinitionPropertyChanged(sampleTask); await _storageService.SaveTaskDefinitionAsync(sampleTask); await RefreshTaskDefinitionExecutionSummaryAsync(sampleTask); } [RelayCommand] private void SelectTaskDefinition(GearTaskDefinitionViewModel? taskDefinition) { SelectedTaskDefinition = taskDefinition; } [RelayCommand] private async Task AddTaskDefinition() { var editViewModel = App.GetService(); var editWindow = App.GetService(); if (editViewModel == null || editWindow == null) { return; } editViewModel.Name = $"新任务组{TaskDefinitions.Count + 1}"; editViewModel.Description = string.Empty; editWindow.ViewModel.Name = editViewModel.Name; editWindow.ViewModel.Description = editViewModel.Description; editWindow.Owner = Application.Current.MainWindow; if (editWindow.ShowDialog() != true) { return; } var newTask = new GearTaskDefinitionViewModel(editWindow.ViewModel.Name, editWindow.ViewModel.Description) { Order = TaskDefinitions.Count > 0 ? TaskDefinitions.Max(t => t.Order) + 1 : 0, }; TaskDefinitions.Add(newTask); SetupTaskDefinitionPropertyChanged(newTask); SelectedTaskDefinition = newTask; await _storageService.SaveTaskDefinitionAsync(newTask); await RefreshTaskDefinitionExecutionSummaryAsync(newTask); } [RelayCommand] private async Task DeleteTaskDefinition(GearTaskDefinitionViewModel? taskDefinition) { if (taskDefinition == null) { return; } var result = MessageBox.Show( $"确定要删除任务定义 '{taskDefinition.Name}' 吗?", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result != MessageBoxResult.Yes) { return; } var taskName = taskDefinition.Name; TaskDefinitions.Remove(taskDefinition); if (SelectedTaskDefinition == taskDefinition) { SelectedTaskDefinition = TaskDefinitions.FirstOrDefault(); } await _storageService.DeleteTaskDefinitionAsync(taskName); } [RelayCommand] private async Task EditSelectedTaskDefinition() { if (SelectedTaskDefinition == null) { return; } var editViewModel = App.GetService(); var editWindow = App.GetService(); if (editViewModel == null || editWindow == null) { return; } editViewModel.Name = SelectedTaskDefinition.Name; editViewModel.Description = SelectedTaskDefinition.Description; editWindow.ViewModel.Name = editViewModel.Name; editWindow.ViewModel.Description = editViewModel.Description; editWindow.Owner = Application.Current.MainWindow; if (editWindow.ShowDialog() != true) { return; } SelectedTaskDefinition.Name = editWindow.ViewModel.Name; SelectedTaskDefinition.Description = editWindow.ViewModel.Description; SelectedTaskDefinition.ModifiedTime = DateTime.Now; await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition); await RefreshTaskDefinitionExecutionSummaryAsync(SelectedTaskDefinition); } [RelayCommand] private async Task DeleteSelectedTaskDefinition() { if (SelectedTaskDefinition == null) { return; } await DeleteTaskDefinition(SelectedTaskDefinition); } [RelayCommand] private async Task ExecuteSelectedTaskDefinition() { await ExecuteTaskDefinition(SelectedTaskDefinition); } [RelayCommand] private async Task ExecuteTaskDefinition(GearTaskDefinitionViewModel? taskDefinition) { taskDefinition ??= SelectedTaskDefinition; if (taskDefinition == null) { Toast.Warning("请先选择要执行的任务组"); return; } if (_taskExecutor.IsExecuting) { Toast.Warning("已有任务组正在执行,请稍后再试"); return; } try { await _taskExecutor.ExecuteTaskDefinitionAsync(taskDefinition.Name); } catch (Exception ex) { _logger.LogError(ex, "执行任务定义失败: {TaskName}", taskDefinition.Name); Toast.Error($"执行任务组失败:{ex.Message}"); } finally { await RefreshTaskDefinitionExecutionSummaryAsync(taskDefinition); if (SelectedTaskDefinition?.Name == taskDefinition.Name) { await LoadExecutionRecordsForSelectedTaskDefinitionAsync(SelectedTaskDefinition); } } } [RelayCommand] private async Task ResumeSelectedExecutionRecord() { if (SelectedTaskDefinition == null || SelectedExecutionRecord == null) { Toast.Warning("请先选择一条执行记录"); return; } if (!SelectedExecutionRecord.Record.CanResume) { Toast.Warning("当前记录没有可恢复的断点"); return; } if (_taskExecutor.IsExecuting) { Toast.Warning("已有任务组正在执行,请稍后再试"); return; } try { await _taskExecutor.ResumeTaskDefinitionAsync(SelectedTaskDefinition.Name, SelectedExecutionRecord.RecordId); } catch (Exception ex) { _logger.LogError(ex, "恢复任务记录失败: {TaskName} {RecordId}", SelectedTaskDefinition.Name, SelectedExecutionRecord.RecordId); Toast.Error($"继续执行失败:{ex.Message}"); } finally { await RefreshTaskDefinitionExecutionSummaryAsync(SelectedTaskDefinition); await LoadExecutionRecordsForSelectedTaskDefinitionAsync(SelectedTaskDefinition); SelectedDetailTabIndex = 1; } } [RelayCommand] private void StopTaskExecution() { _taskExecutor.StopExecution(); } [RelayCommand] private async Task OpenSelectedTaskDefinitionSettings() { if (SelectedTaskDefinition == null) { Toast.Warning("请先选择一个任务定义"); return; } var dialogWindow = new Wpf.Ui.Controls.FluentWindow { Title = "任务组设置", Content = new ScriptGroupConfigView(new ScriptGroupConfigViewModel(TaskContext.Instance().Config, SelectedTaskDefinition.GroupConfig)), Width = 800, Height = 600, MinWidth = 800, MaxWidth = 800, MinHeight = 600, Owner = Application.Current.MainWindow, WindowStartupLocation = WindowStartupLocation.CenterOwner, ExtendsContentIntoTitleBar = true, WindowBackdropType = Wpf.Ui.Controls.WindowBackdropType.Auto, }; dialogWindow.SourceInitialized += (_, _) => WindowHelper.TryApplySystemBackdrop(dialogWindow); dialogWindow.ShowDialog(); SelectedTaskDefinition.ModifiedTime = DateTime.Now; await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition); } [RelayCommand] private async Task AddTaskNode(string? taskType = null) { if (SelectedTaskDefinition?.RootTask == null) { Toast.Warning("请先选择一个任务定义"); return; } if (SelectedTaskNode != null && !SelectedTaskNode.IsDirectory) { Toast.Warning("只有任务组下才能继续添加任务"); return; } taskType ??= "Javascript"; GearTaskViewModel newTask; if (taskType == "Javascript") { var jsSelectionWindow = new JsScriptSelectionWindow { Owner = Application.Current.MainWindow, }; jsSelectionWindow.ShowDialog(); if (!jsSelectionWindow.DialogResult || jsSelectionWindow.ViewModel.SelectedScript == null) { return; } var selectedScript = jsSelectionWindow.ViewModel.SelectedScript; newTask = new GearTaskViewModel(string.IsNullOrWhiteSpace(selectedScript.Name) ? selectedScript.FolderName : selectedScript.Name) { TaskType = "Javascript", Path = $@"{{jsUserFolder}}\{selectedScript.FolderName}\", }; } else if (taskType == "Pathing") { var pathingSelectionWindow = new PathingTaskSelectionWindow { Owner = Application.Current.MainWindow, }; pathingSelectionWindow.ShowDialog(); if (!pathingSelectionWindow.DialogResult || pathingSelectionWindow.SelectedGearTask == null) { return; } newTask = pathingSelectionWindow.SelectedGearTask; } else if (taskType == "KeyMouse") { var list = LoadAllKmScripts(); var folder = KeyMouseRecordPageViewModel.ScriptPath; var combobox = new ComboBox { VerticalAlignment = VerticalAlignment.Top, }; foreach (var fileInfo in list) { var relativePath = Path.GetRelativePath(folder, fileInfo.FullName); combobox.Items.Add(relativePath); } var selected = PromptDialog.Prompt("请选择需要添加的键鼠脚本", "请选择需要添加的键鼠脚本", combobox); if (string.IsNullOrEmpty(selected)) { return; } newTask = new GearTaskViewModel(selected) { TaskType = "Javascript", Path = $@"{{kmUserFolder}}\{selected}\", }; } else if (taskType == "Shell") { var command = PromptDialog.Prompt( "执行 shell 操作存在风险,请确认命令内容正确后再执行。", "请输入需要执行的 shell 命令"); if (string.IsNullOrEmpty(command)) { return; } newTask = new GearTaskViewModel(command) { TaskType = "Shell", Parameters = command, }; } else { var dialogResult = AddTaskNodeDialog.ShowDialog(taskType, Application.Current.MainWindow); if (dialogResult == null) { return; } newTask = new GearTaskViewModel(dialogResult.TaskName) { TaskType = dialogResult.TaskType, }; } var targetParent = SelectedTaskNode ?? SelectedTaskDefinition.RootTask; targetParent.AddChild(newTask); targetParent.IsExpanded = true; SelectedTaskDefinition.ModifiedTime = DateTime.Now; await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition); } private List LoadAllKmScripts() { var folder = KeyMouseRecordPageViewModel.ScriptPath; var files = Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories); return files.Select(file => new FileInfo(file)).ToList(); } [RelayCommand] private async Task AddTaskGroup() { if (SelectedTaskDefinition?.RootTask == null) { Toast.Warning("请先选择一个任务定义"); return; } if (SelectedTaskNode != null && !SelectedTaskNode.IsDirectory) { Toast.Warning("只有任务组下才能继续添加任务组"); return; } var groupName = PromptDialog.Prompt("请输入任务组名称:", "添加任务组", $"新任务组{DateTime.Now:HHmmss}"); if (string.IsNullOrWhiteSpace(groupName)) { return; } var newGroup = new GearTaskViewModel(groupName, true); var targetParent = SelectedTaskNode ?? SelectedTaskDefinition.RootTask; targetParent.AddChild(newGroup); targetParent.IsExpanded = true; SelectedTaskDefinition.ModifiedTime = DateTime.Now; await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition); } [RelayCommand] private async Task DeleteTaskNode(GearTaskViewModel? taskNode) { if (taskNode == null || SelectedTaskDefinition?.RootTask == null) { return; } var result = MessageBox.Show( $"确定要删除任务 '{taskNode.Name}' 吗?", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Question); if (result != MessageBoxResult.Yes) { return; } RemoveTaskFromTree(SelectedTaskDefinition.RootTask, taskNode); SelectedTaskDefinition.ModifiedTime = DateTime.Now; await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition); } private bool RemoveTaskFromTree(GearTaskViewModel parent, GearTaskViewModel target) { if (parent.Children.Contains(target)) { parent.RemoveChild(target); return true; } foreach (var child in parent.Children) { if (RemoveTaskFromTree(child, target)) { return true; } } return false; } /// /// 从 JSON 文件重新加载所有任务定义(内部使用)。 /// private async Task LoadFromJsonInternal() { try { TaskDefinitions.Clear(); var loadedTasks = await _storageService.LoadAllTaskDefinitionsAsync(); foreach (var task in loadedTasks.OrderBy(t => t.Order)) { TaskDefinitions.Add(task); SetupTaskDefinitionPropertyChanged(task); } await RefreshTaskDefinitionExecutionSummariesAsync(); SelectedTaskDefinition ??= TaskDefinitions.FirstOrDefault(); } catch (Exception ex) { _logger.LogError(ex, "重新加载任务定义失败"); } } /// /// 监听任务定义属性变化,实现自动保存。 /// private void SetupTaskDefinitionPropertyChanged(GearTaskDefinitionViewModel taskDefinition) { taskDefinition.PropertyChanged += async (sender, _) => { if (sender is not GearTaskDefinitionViewModel task) { return; } try { await _storageService.SaveTaskDefinitionAsync(task); } catch (Exception ex) { _logger.LogError(ex, "自动保存任务定义失败: {TaskName}", task.Name); } }; if (taskDefinition.RootTask != null) { SetupTaskPropertyChangeListener(taskDefinition.RootTask, taskDefinition); } if (taskDefinition.GroupConfig != null) { SetupTaskGroupConfigChangeListener(taskDefinition.GroupConfig, taskDefinition); } } /// /// 递归监听任务节点和子节点变化。 /// private void SetupTaskPropertyChangeListener(GearTaskViewModel task, GearTaskDefinitionViewModel parentDefinition) { task.PropertyChanged += async (_, _) => { try { parentDefinition.ModifiedTime = DateTime.Now; await _storageService.SaveTaskDefinitionAsync(parentDefinition); } catch (Exception ex) { _logger.LogError(ex, "自动保存任务定义失败: {TaskName}", parentDefinition.Name); } }; foreach (var child in task.Children) { SetupTaskPropertyChangeListener(child, parentDefinition); } task.Children.CollectionChanged += async (_, e) => { if (e.NewItems != null) { foreach (GearTaskViewModel newTask in e.NewItems) { SetupTaskPropertyChangeListener(newTask, parentDefinition); } } try { parentDefinition.ModifiedTime = DateTime.Now; await _storageService.SaveTaskDefinitionAsync(parentDefinition); } catch (Exception ex) { _logger.LogError(ex, "自动保存任务定义失败: {TaskName}", parentDefinition.Name); } }; } /// /// 递归监听任务组配置及其子配置变化,实现自动保存。 /// private void SetupTaskGroupConfigChangeListener(object configObject, GearTaskDefinitionViewModel parentDefinition) { var visited = new HashSet(); SubscribeTaskGroupConfigChanges(configObject, parentDefinition, visited); } private void SubscribeTaskGroupConfigChanges(object? current, GearTaskDefinitionViewModel parentDefinition, HashSet visited) { if (current == null || !visited.Add(current)) { return; } if (current is INotifyPropertyChanged notifyPropertyChanged) { notifyPropertyChanged.PropertyChanged += async (_, _) => { try { parentDefinition.ModifiedTime = DateTime.Now; await _storageService.SaveTaskDefinitionAsync(parentDefinition); } catch (Exception ex) { _logger.LogError(ex, "任务组配置自动保存失败: {TaskName}", parentDefinition.Name); } }; } if (current is INotifyCollectionChanged notifyCollectionChanged) { notifyCollectionChanged.CollectionChanged += async (_, e) => { if (e.NewItems != null) { foreach (var item in e.NewItems) { SubscribeTaskGroupConfigChanges(item, parentDefinition, visited); } } try { parentDefinition.ModifiedTime = DateTime.Now; await _storageService.SaveTaskDefinitionAsync(parentDefinition); } catch (Exception ex) { _logger.LogError(ex, "任务组配置自动保存失败: {TaskName}", parentDefinition.Name); } }; } foreach (var child in EnumerateConfigChildren(current)) { SubscribeTaskGroupConfigChanges(child, parentDefinition, visited); } } private static IEnumerable EnumerateConfigChildren(object current) { var properties = current.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var property in properties) { if (!property.CanRead || property.GetIndexParameters().Length > 0) { continue; } if (property.PropertyType == typeof(string) || property.PropertyType.IsValueType) { continue; } object? value; try { value = property.GetValue(current); } catch { continue; } if (value == null || value is string) { continue; } if (value is IEnumerable enumerable) { foreach (var item in enumerable) { if (item != null && item is not string) { yield return item; } } continue; } yield return value; } } private async Task RefreshTaskDefinitionExecutionSummariesAsync() { foreach (var taskDefinition in TaskDefinitions) { await RefreshTaskDefinitionExecutionSummaryAsync(taskDefinition); } } private async Task RefreshTaskDefinitionExecutionSummaryAsync(GearTaskDefinitionViewModel taskDefinition) { try { var records = await _historyStore.LoadLatestAsync(GetTaskDefinitionFileKey(taskDefinition.Name), 30); ApplyTaskDefinitionExecutionSummary(taskDefinition, records); } catch (Exception ex) { _logger.LogError(ex, "读取任务历史摘要失败: {TaskName}", taskDefinition.Name); ApplyTaskDefinitionExecutionSummary(taskDefinition, []); } } private async Task LoadExecutionRecordsForSelectedTaskDefinitionAsync(GearTaskDefinitionViewModel? taskDefinition) { if (taskDefinition == null) { RecentExecutionRecords.Clear(); SelectedExecutionRecord = null; SelectedExecutionNodes.Clear(); ExecutionRecordTabTitle = "执行记录 (0)"; UpdateExecutionOverview([]); ApplyLatestExecutionProjection(null); return; } try { var taskName = taskDefinition.Name; var records = await _historyStore.LoadLatestAsync(GetTaskDefinitionFileKey(taskName), 30); if (!string.Equals(SelectedTaskDefinition?.Name, taskName, StringComparison.Ordinal)) { return; } RecentExecutionRecords.Clear(); foreach (var record in records) { RecentExecutionRecords.Add(new GearTaskExecutionRecordItemViewModel(record)); } ExecutionRecordTabTitle = $"执行记录 ({RecentExecutionRecords.Count})"; SelectedExecutionRecord = RecentExecutionRecords.FirstOrDefault(); UpdateExecutionOverview(records); ApplyLatestExecutionProjection(records.FirstOrDefault()); } catch (Exception ex) { _logger.LogError(ex, "加载任务执行记录失败: {TaskName}", taskDefinition.Name); RecentExecutionRecords.Clear(); SelectedExecutionRecord = null; SelectedExecutionNodes.Clear(); ExecutionRecordTabTitle = "执行记录 (0)"; UpdateExecutionOverview([]); ApplyLatestExecutionProjection(null); } } private void ApplyTaskDefinitionExecutionSummary(GearTaskDefinitionViewModel taskDefinition, IReadOnlyList records) { if (records.Count == 0) { taskDefinition.LatestExecutionSummary = "最近:暂无执行记录"; taskDefinition.ExecutionStatisticsSummary = "统计:最近 30 次暂无记录"; taskDefinition.ExecutionBadgeText = string.Empty; taskDefinition.HasExecutionBadge = false; return; } var latestRecord = records[0]; taskDefinition.LatestExecutionSummary = $"最近:{latestRecord.StartTime:MM-dd HH:mm} {GearTaskExecutionDisplayHelper.GetRecordStatusText(latestRecord.Status)}"; var successCount = records.Count(r => r.Status == GearTaskExecutionRecordStatus.Succeeded); var interruptedCount = records.Count(r => r.Status == GearTaskExecutionRecordStatus.Interrupted || r.Status == GearTaskExecutionRecordStatus.Cancelled); var failedCount = records.Count(r => r.Status == GearTaskExecutionRecordStatus.Failed); taskDefinition.ExecutionStatisticsSummary = $"统计:{records.Count} 次内 成功 {successCount} / 中断 {interruptedCount} / 失败 {failedCount}"; var resumableRecord = records.FirstOrDefault(r => r.CanResume); taskDefinition.HasExecutionBadge = resumableRecord != null; taskDefinition.ExecutionBadgeText = resumableRecord == null ? string.Empty : "最近有中断记录"; } private void UpdateExecutionOverview(IReadOnlyList records) { if (records.Count == 0) { LatestExecutionStatusText = "暂无执行记录"; LatestResumeNodeText = "-"; ExecutionSuccessRateText = "-"; ExecutionAverageDurationText = "-"; return; } var latestRecord = records[0]; LatestExecutionStatusText = $"{latestRecord.StartTime:MM-dd HH:mm} · {GearTaskExecutionDisplayHelper.GetRecordStatusText(latestRecord.Status)}"; LatestResumeNodeText = latestRecord.CanResume ? GearTaskExecutionDisplayHelper.BuildResumeNodeText(latestRecord) : "-"; var successCount = records.Count(r => r.Status == GearTaskExecutionRecordStatus.Succeeded); ExecutionSuccessRateText = $"{Math.Round(successCount * 100d / records.Count):0}%"; var durations = records .Where(r => r.EndTime.HasValue) .Select(r => r.EndTime!.Value - r.StartTime) .ToList(); ExecutionAverageDurationText = durations.Count == 0 ? "-" : GearTaskExecutionDisplayHelper.FormatDuration(TimeSpan.FromSeconds(durations.Average(d => d.TotalSeconds))); } private void ApplyLatestExecutionProjection(GearTaskExecutionRecord? latestRecord) { ResetTaskNodeExecutionProjection(CurrentTaskTreeRoot); if (latestRecord == null) { return; } ApplyNodeExecutionProjection(CurrentTaskTreeRoot, "0", latestRecord); } private void ResetTaskNodeExecutionProjection(GearTaskViewModel node) { node.LatestExecutionResult = string.Empty; foreach (var child in node.Children) { ResetTaskNodeExecutionProjection(child); } } private void ApplyNodeExecutionProjection(GearTaskViewModel node, string nodeId, GearTaskExecutionRecord latestRecord) { var record = latestRecord.Nodes.FirstOrDefault(n => n.NodeId == nodeId); if (record != null) { node.LatestExecutionResult = BuildNodeExecutionResultText(record, latestRecord); } for (var i = 0; i < node.Children.Count; i++) { ApplyNodeExecutionProjection(node.Children[i], $"{nodeId}/{i}", latestRecord); } } private static string BuildNodeExecutionResultText(GearTaskExecutionNodeRecord record, GearTaskExecutionRecord latestRecord) { if (latestRecord.CanResume && string.Equals(latestRecord.ResumeNodeId, record.NodeId, StringComparison.Ordinal)) { return "最近中断点"; } return record.Status switch { GearTaskExecutionNodeStatus.Succeeded => "最近成功", GearTaskExecutionNodeStatus.Failed => "最近失败", GearTaskExecutionNodeStatus.Interrupted => "最近中断", GearTaskExecutionNodeStatus.Cancelled => "最近取消", GearTaskExecutionNodeStatus.Running => "最近执行中", GearTaskExecutionNodeStatus.Skipped => "最近跳过", GearTaskExecutionNodeStatus.Pending => "未执行到", _ => record.Status.ToString(), }; } private static string GetTaskDefinitionFileKey(string name) { var invalidChars = Path.GetInvalidFileNameChars(); var safeName = string.Join("_", name.Split(invalidChars, StringSplitOptions.RemoveEmptyEntries)); return string.IsNullOrWhiteSpace(safeName) ? "unnamed_task" : safeName; } }