新增任务节点对话框,支持选择JS脚本类型任务,优化用户操作体验

This commit is contained in:
辉鸭蛋
2025-09-07 01:08:42 +08:00
parent 7d8b02a1a0
commit 549fc03009
7 changed files with 753 additions and 62 deletions

View File

@@ -0,0 +1,92 @@
<ui:FluentWindow x:Class="BetterGenshinImpact.View.Windows.AddTaskNodeDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:windows="clr-namespace:BetterGenshinImpact.ViewModel.Windows"
d:DataContext="{d:DesignInstance Type=windows:AddTaskNodeDialogViewModel}"
Width="450"
Height="300"
MinWidth="400"
MinHeight="250"
ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}"
ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
ExtendsContentIntoTitleBar="True"
FontFamily="{DynamicResource TextThemeFontFamily}"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
WindowStyle="SingleBorderWindow"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 标题栏 -->
<ui:TitleBar Name="MyTitleBar" Grid.Row="0" Title="添加任务">
<ui:TitleBar.Icon>
<ui:ImageIcon Source="pack://application:,,,/Resources/Images/logo.png" />
</ui:TitleBar.Icon>
</ui:TitleBar>
<!-- 内容区域 -->
<Grid Grid.Row="1" Margin="24">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- 任务类型显示 -->
<ui:TextBlock Grid.Row="0"
Margin="0,0,0,8"
FontSize="14"
FontWeight="SemiBold"
Text="{Binding TaskType, StringFormat='任务类型: {0}'}" />
<!-- 任务名称 -->
<ui:TextBlock Grid.Row="1"
Margin="0,0,0,4"
Text="任务名称 *" />
<ui:TextBox Grid.Row="2"
Margin="0,0,0,16"
Text="{Binding TaskName, UpdateSourceTrigger=PropertyChanged}" />
<!-- 任务描述 -->
<ui:TextBlock Grid.Row="3"
Margin="0,0,0,4"
Text="任务描述" />
<ui:TextBox Grid.Row="4"
Margin="0,0,0,0"
AcceptsReturn="True"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"
Text="{Binding TaskDescription, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
<!-- 按钮区域 -->
<Grid Grid.Row="2" Margin="24,0,24,24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<ui:Button Margin="0,0,8,0"
Appearance="Primary"
Command="{Binding ConfirmCommand}"
Content="确定"
IsDefault="True" />
<ui:Button Command="{Binding CancelCommand}"
Content="取消"
IsCancel="True" />
</StackPanel>
</Grid>
</Grid>
</ui:FluentWindow>

View File

@@ -0,0 +1,52 @@
using System;
using System.Windows;
using BetterGenshinImpact.Helpers.Ui;
using BetterGenshinImpact.ViewModel.Windows;
namespace BetterGenshinImpact.View.Windows;
public partial class AddTaskNodeDialog
{
public AddTaskNodeDialogViewModel ViewModel { get; }
public AddTaskNodeDialog(string taskType)
{
ViewModel = new AddTaskNodeDialogViewModel(taskType);
DataContext = ViewModel;
InitializeComponent();
ViewModel.RequestClose += OnRequestClose;
this.SourceInitialized += OnSourceInitialized;
}
private void OnSourceInitialized(object? sender, EventArgs e)
{
// 应用与主窗口相同的背景主题
WindowHelper.TryApplySystemBackdrop(this);
}
private void OnRequestClose(object? sender, bool result)
{
DialogResult = result;
Close();
}
/// <summary>
/// 显示添加任务对话框
/// </summary>
/// <param name="taskType">任务类型</param>
/// <param name="owner">父窗口</param>
/// <returns>如果用户点击确定返回ViewModel否则返回null</returns>
public static AddTaskNodeDialogViewModel? ShowDialog(string taskType, Window? owner = null)
{
var dialog = new AddTaskNodeDialog(taskType);
if (owner != null)
{
dialog.Owner = owner;
}
var result = dialog.ShowDialog();
return result == true ? dialog.ViewModel : null;
}
}

View File

@@ -0,0 +1,219 @@
<ui:FluentWindow x:Class="BetterGenshinImpact.View.Windows.JsScriptSelectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BetterGenshinImpact.View.Windows"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:vio="http://schemas.lepo.co/wpfui/2022/xaml/violeta"
Title="选择JS脚本"
Width="900"
Height="600"
MinWidth="800"
MinHeight="500"
Background="#202020"
ExtendsContentIntoTitleBar="True"
FontFamily="{DynamicResource TextThemeFontFamily}"
WindowBackdropType="None"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 标题栏 -->
<ui:TitleBar Grid.Row="0"
Title="选择JS脚本"
ShowMaximize="False"
ShowMinimize="False">
<ui:TitleBar.Icon>
<ui:ImageIcon Source="pack://application:,,,/Resources/Images/logo.png" />
</ui:TitleBar.Icon>
</ui:TitleBar>
<!-- 主内容区域 -->
<Grid Grid.Row="1" Margin="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- 左侧脚本列表 -->
<Border Grid.Column="0"
Background="{ui:ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ui:ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 标题和刷新按钮 -->
<Grid Grid.Row="0" Margin="12,12,12,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="JS脚本列表"
FontSize="16"
FontWeight="SemiBold"
VerticalAlignment="Center" />
<ui:Button Grid.Column="1"
Command="{Binding RefreshScriptsCommand}"
Content="刷新"
Appearance="Secondary"
Padding="8,4" />
</Grid>
<!-- 脚本列表 -->
<ScrollViewer Grid.Row="1"
Margin="8,0,8,8"
VerticalScrollBarVisibility="Auto">
<ListBox ItemsSource="{Binding JsScripts}"
SelectedItem="{Binding SelectedScript}"
Background="Transparent"
BorderThickness="0">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Padding="8"
Margin="2"
Background="Transparent"
BorderBrush="{ui:ThemeResource ControlStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="4">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding Manifest.Name}"
FontWeight="SemiBold"
TextWrapping="Wrap" />
<TextBlock Grid.Row="1"
Text="{Binding FolderName}"
FontSize="12"
Foreground="{ui:ThemeResource TextFillColorSecondaryBrush}"
Margin="0,2,0,4" />
<TextBlock Grid.Row="2"
Text="{Binding Description}"
FontSize="11"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap"
MaxHeight="60" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
</Grid>
</Border>
<!-- 分隔线 -->
<GridSplitter Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Transparent" />
<!-- 右侧详情区域 -->
<Border Grid.Column="2"
Background="{ui:ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ui:ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- 脚本信息标题 -->
<Border Grid.Row="0"
Background="{ui:ThemeResource CardBackgroundFillColorSecondaryBrush}"
BorderBrush="{ui:ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1"
CornerRadius="8,8,0,0"
Padding="12,8">
<TextBlock Text="{Binding SelectedScript.DisplayName, FallbackValue='请选择一个脚本'}"
FontSize="16"
FontWeight="SemiBold" />
</Border>
<!-- TabControl -->
<TabControl Grid.Row="1"
Margin="8"
Background="Transparent"
BorderThickness="0"
SelectedIndex="{Binding SelectedTabIndex}">
<TabItem Header="README.md">
<ScrollViewer VerticalScrollBarVisibility="Auto"
Padding="8">
<TextBox Text="{Binding ReadmeContent, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Disabled"
HorizontalScrollBarVisibility="Disabled"
Background="Transparent"
BorderThickness="0"
FontFamily="Consolas, 'Courier New', monospace" />
</ScrollViewer>
</TabItem>
<TabItem Header="main.js">
<ScrollViewer VerticalScrollBarVisibility="Auto"
Padding="8">
<TextBox Text="{Binding MainJsContent, Mode=OneWay}"
IsReadOnly="True"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Disabled"
HorizontalScrollBarVisibility="Disabled"
Background="Transparent"
BorderThickness="0"
FontFamily="Consolas, 'Courier New', monospace" />
</ScrollViewer>
</TabItem>
</TabControl>
</Grid>
</Border>
</Grid>
<!-- 底部按钮区域 -->
<Border Grid.Row="2"
Background="{ui:ThemeResource CardBackgroundFillColorSecondaryBrush}"
BorderBrush="{ui:ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="0,1,0,0"
Padding="12">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right">
<ui:Button Content="确定"
Appearance="Primary"
Margin="0,0,8,0"
Padding="16,8"
IsDefault="True"
Click="OnOkClick" />
<ui:Button Content="取消"
Appearance="Secondary"
Padding="16,8"
IsCancel="True"
Click="OnCancelClick" />
</StackPanel>
</Border>
</Grid>
</ui:FluentWindow>

View File

@@ -0,0 +1,40 @@
using System.Windows;
using BetterGenshinImpact.ViewModel.Windows;
using Wpf.Ui.Controls;
namespace BetterGenshinImpact.View.Windows;
public partial class JsScriptSelectionWindow : FluentWindow
{
public JsScriptSelectionViewModel ViewModel { get; }
public JsScriptInfo? SelectedScript => ViewModel.SelectedScript;
public bool DialogResult { get; private set; }
public JsScriptSelectionWindow()
{
ViewModel = new JsScriptSelectionViewModel();
DataContext = ViewModel;
InitializeComponent();
}
private void OnOkClick(object sender, RoutedEventArgs e)
{
if (ViewModel.SelectedScript != null)
{
DialogResult = true;
Close();
}
else
{
Wpf.Ui.Violeta.Controls.Toast.Warning("请选择一个JS脚本");
}
}
private void OnCancelClick(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
}

View File

@@ -2,13 +2,13 @@ using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using System.Windows;
using System.Linq;
using System;
using BetterGenshinImpact.ViewModel.Pages.Component;
using BetterGenshinImpact.Service;
using System.Threading.Tasks;
using System.Collections.Specialized;
using System.Windows;
using BetterGenshinImpact.View.Windows;
using BetterGenshinImpact.ViewModel.Windows;
using Wpf.Ui.Violeta.Controls;
@@ -26,36 +26,32 @@ public partial class GearTaskListPageViewModel : ViewModel
/// <summary>
/// 任务定义列表(左侧)
/// </summary>
[ObservableProperty]
private ObservableCollection<GearTaskDefinitionViewModel> _taskDefinitions = new();
[ObservableProperty] private ObservableCollection<GearTaskDefinitionViewModel> _taskDefinitions = new();
/// <summary>
/// 当前选中的任务定义
/// </summary>
[ObservableProperty]
private GearTaskDefinitionViewModel? _selectedTaskDefinition;
[ObservableProperty] private GearTaskDefinitionViewModel? _selectedTaskDefinition;
/// <summary>
/// 当前任务树根节点(右侧)
/// </summary>
[ObservableProperty]
private GearTaskViewModel _currentTaskTreeRoot = new();
[ObservableProperty] private GearTaskViewModel _currentTaskTreeRoot = new();
/// <summary>
/// 当前选中的任务节点
/// </summary>
[ObservableProperty]
private GearTaskViewModel? _selectedTaskNode;
[ObservableProperty] private GearTaskViewModel? _selectedTaskNode;
public GearTaskListPageViewModel(ILogger<GearTaskListPageViewModel> logger, GearTaskStorageService storageService)
{
_logger = logger;
_storageService = storageService;
InitializeData();
// 监听集合变化,实现自动保存
TaskDefinitions.CollectionChanged += OnTaskDefinitionsChanged;
// 监听当前任务树根节点的子集合变化,用于拖拽后自动保存
CurrentTaskTreeRoot.Children.CollectionChanged += OnCurrentTaskTreeChanged;
}
@@ -77,13 +73,13 @@ public partial class GearTaskListPageViewModel : ViewModel
TaskDefinitions[i].ModifiedTime = DateTime.Now;
}
}
// 保存所有受影响的任务定义
foreach (var taskDef in TaskDefinitions)
{
await _storageService.SaveTaskDefinitionAsync(taskDef);
}
_logger.LogInformation("任务定义列表顺序已更新并保存");
}
catch (Exception ex)
@@ -91,7 +87,7 @@ public partial class GearTaskListPageViewModel : ViewModel
_logger.LogError(ex, "保存任务定义列表顺序时发生错误");
}
}
/// <summary>
/// 当前任务树集合变化时的处理(用于拖拽后自动保存)
/// </summary>
@@ -121,17 +117,17 @@ public partial class GearTaskListPageViewModel : ViewModel
{
// 从 JSON 文件加载任务定义
var loadedTasks = await _storageService.LoadAllTaskDefinitionsAsync();
// 按order字段排序
var sortedTasks = loadedTasks.OrderBy(t => t.Order).ToList();
foreach (var task in sortedTasks)
{
TaskDefinitions.Add(task);
// 为每个任务定义设置属性变化监听
SetupTaskDefinitionPropertyChanged(task);
}
// 如果没有加载到任何任务,创建一个示例任务
if (TaskDefinitions.Count == 0)
{
@@ -145,7 +141,7 @@ public partial class GearTaskListPageViewModel : ViewModel
await CreateSampleTaskAsync();
}
}
/// <summary>
/// 创建示例任务
/// </summary>
@@ -156,16 +152,16 @@ public partial class GearTaskListPageViewModel : ViewModel
{
sampleTask.RootTask.AddChild(new GearTaskViewModel("采集任务1") { TaskType = "采集任务", Description = "采集莲花" });
sampleTask.RootTask.AddChild(new GearTaskViewModel("战斗任务1") { TaskType = "战斗任务", Description = "击败史莱姆" });
var subGroup = new GearTaskViewModel("子任务组", true);
subGroup.AddChild(new GearTaskViewModel("传送任务1") { TaskType = "传送任务", Description = "传送到蒙德" });
subGroup.AddChild(new GearTaskViewModel("交互任务1") { TaskType = "交互任务", Description = "与NPC对话" });
sampleTask.RootTask.AddChild(subGroup);
}
TaskDefinitions.Add(sampleTask);
SetupTaskDefinitionPropertyChanged(sampleTask);
// 保存示例任务到文件
await _storageService.SaveTaskDefinitionAsync(sampleTask);
}
@@ -180,16 +176,16 @@ public partial class GearTaskListPageViewModel : ViewModel
{
task.IsSelected = false;
}
// 设置当前选中项
if (value != null)
{
value.IsSelected = true;
}
// 先解除之前的事件绑定
CurrentTaskTreeRoot.Children.CollectionChanged -= OnCurrentTaskTreeChanged;
// 设置当前任务树根节点
if (value?.RootTask != null)
{
@@ -199,7 +195,7 @@ public partial class GearTaskListPageViewModel : ViewModel
{
CurrentTaskTreeRoot = new GearTaskViewModel();
}
// 重新绑定事件
CurrentTaskTreeRoot.Children.CollectionChanged += OnCurrentTaskTreeChanged;
}
@@ -221,17 +217,17 @@ public partial class GearTaskListPageViewModel : ViewModel
{
var editViewModel = App.GetService<TaskDefinitionEditWindowViewModel>();
if (editViewModel == null) return;
editViewModel.Name = $"新任务组{TaskDefinitions.Count + 1}";
editViewModel.Description = "";
var editWindow = App.GetService<TaskDefinitionEditWindow>();
if (editWindow == null) return;
editWindow.ViewModel.Name = editViewModel.Name;
editWindow.ViewModel.Description = editViewModel.Description;
editWindow.Owner = Application.Current.MainWindow;
if (editWindow.ShowDialog() == true)
{
var newTask = new GearTaskDefinitionViewModel(editWindow.ViewModel.Name, editWindow.ViewModel.Description);
@@ -240,7 +236,7 @@ public partial class GearTaskListPageViewModel : ViewModel
TaskDefinitions.Add(newTask);
SetupTaskDefinitionPropertyChanged(newTask);
SelectedTaskDefinition = newTask;
// 自动保存到文件
await _storageService.SaveTaskDefinitionAsync(newTask);
}
@@ -253,10 +249,10 @@ public partial class GearTaskListPageViewModel : ViewModel
private async Task DeleteTaskDefinition(GearTaskDefinitionViewModel? taskDefinition)
{
if (taskDefinition == null) return;
var result = MessageBox.Show($"确定要删除任务定义 '{taskDefinition.Name}' 吗?", "确认删除",
var result = MessageBox.Show($"确定要删除任务定义 '{taskDefinition.Name}' 吗?", "确认删除",
MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
var taskName = taskDefinition.Name;
@@ -265,7 +261,7 @@ public partial class GearTaskListPageViewModel : ViewModel
{
SelectedTaskDefinition = TaskDefinitions.FirstOrDefault();
}
// 删除对应的 JSON 文件
await _storageService.DeleteTaskDefinitionAsync(taskName);
}
@@ -278,29 +274,29 @@ public partial class GearTaskListPageViewModel : ViewModel
private async Task EditSelectedTaskDefinition()
{
if (SelectedTaskDefinition == null) return;
var editViewModel = App.GetService<TaskDefinitionEditWindowViewModel>();
if (editViewModel == null) return;
editViewModel.Name = SelectedTaskDefinition.Name;
editViewModel.Description = SelectedTaskDefinition.Description;
var editWindow = App.GetService<TaskDefinitionEditWindow>();
if (editWindow == null) return;
editWindow.ViewModel.Name = editViewModel.Name;
editWindow.ViewModel.Description = editViewModel.Description;
editWindow.Owner = Application.Current.MainWindow;
if (editWindow.ShowDialog() == true)
{
SelectedTaskDefinition.Name = editWindow.ViewModel.Name;
SelectedTaskDefinition.Description = editWindow.ViewModel.Description;
SelectedTaskDefinition.ModifiedTime = DateTime.Now;
// 自动保存到文件
await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition);
_logger.LogInformation("编辑了任务定义: {Name}", SelectedTaskDefinition.Name);
}
}
@@ -312,12 +308,11 @@ public partial class GearTaskListPageViewModel : ViewModel
private async Task DeleteSelectedTaskDefinition()
{
if (SelectedTaskDefinition == null) return;
await DeleteTaskDefinition(SelectedTaskDefinition);
}
/// <summary>
/// 添加任务节点
/// </summary>
@@ -330,22 +325,59 @@ public partial class GearTaskListPageViewModel : ViewModel
return;
}
var newTask = new GearTaskViewModel($"新任务 {DateTime.Now:HHmmss}")
// 如果没有指定任务类型默认为Javascript
taskType ??= "Javascript";
GearTaskViewModel newTask;
// 如果是JS脚本类型使用JS脚本选择窗口
if (taskType == "Javascript")
{
TaskType = "任务类型",
Description = "新创建的任务"
};
var jsSelectionWindow = new JsScriptSelectionWindow
{
Owner = Application.Current.MainWindow
};
if (jsSelectionWindow.ShowDialog() == true && jsSelectionWindow.ViewModel.SelectedScript != null)
{
var selectedScript = jsSelectionWindow.ViewModel.SelectedScript;
newTask = new GearTaskViewModel(selectedScript.DisplayName)
{
TaskType = "Javascript",
Description = selectedScript.Description ?? "JS脚本任务"
};
}
else
{
return; // 用户取消了操作
}
}
else
{
// 其他类型使用原有的对话框
var dialogResult = AddTaskNodeDialog.ShowDialog(taskType, Application.Current.MainWindow);
if (dialogResult == null)
{
return; // 用户取消了操作
}
newTask = new GearTaskViewModel(dialogResult.TaskName)
{
TaskType = dialogResult.TaskType,
Description = dialogResult.TaskDescription
};
}
// 如果有选中的节点,则在选中节点下新增
// 如果未选择节点,则在根节点下直接新增
var targetParent = SelectedTaskNode ?? SelectedTaskDefinition.RootTask;
targetParent.AddChild(newTask);
// 展开父节点
targetParent.IsExpanded = true;
SelectedTaskDefinition.ModifiedTime = DateTime.Now;
// 自动保存到文件
await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition);
}
@@ -371,12 +403,12 @@ public partial class GearTaskListPageViewModel : ViewModel
// 如果未选择节点,则在根节点下直接新增
var targetParent = SelectedTaskNode ?? SelectedTaskDefinition.RootTask;
targetParent.AddChild(newGroup);
// 展开父节点
targetParent.IsExpanded = true;
SelectedTaskDefinition.ModifiedTime = DateTime.Now;
// 自动保存到文件
await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition);
}
@@ -389,14 +421,14 @@ public partial class GearTaskListPageViewModel : ViewModel
{
if (taskNode == null || SelectedTaskDefinition?.RootTask == null) return;
var result = MessageBox.Show($"确定要删除任务 '{taskNode.Name}' 吗?", "确认删除",
var result = MessageBox.Show($"确定要删除任务 '{taskNode.Name}' 吗?", "确认删除",
MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
RemoveTaskFromTree(SelectedTaskDefinition.RootTask, taskNode);
SelectedTaskDefinition.ModifiedTime = DateTime.Now;
// 自动保存到文件
await _storageService.SaveTaskDefinitionAsync(SelectedTaskDefinition);
}
@@ -433,13 +465,13 @@ public partial class GearTaskListPageViewModel : ViewModel
{
TaskDefinitions.Clear();
var loadedTasks = await _storageService.LoadAllTaskDefinitionsAsync();
foreach (var task in loadedTasks)
{
TaskDefinitions.Add(task);
SetupTaskDefinitionPropertyChanged(task);
}
_logger.LogInformation("从JSON文件重新加载了 {Count} 个任务定义", loadedTasks.Count);
}
catch (Exception ex)
@@ -467,7 +499,7 @@ public partial class GearTaskListPageViewModel : ViewModel
}
}
};
// 为根任务及其所有子任务设置监听器
if (taskDefinition.RootTask != null)
{
@@ -492,13 +524,13 @@ public partial class GearTaskListPageViewModel : ViewModel
_logger.LogError(ex, "自动保存任务定义 {TaskName} 时发生错误", parentDefinition.Name);
}
};
// 为子任务设置监听器
foreach (var child in task.Children)
{
SetupTaskPropertyChangeListener(child, parentDefinition);
}
// 监听子任务集合变化
task.Children.CollectionChanged += async (sender, e) =>
{
@@ -509,7 +541,7 @@ public partial class GearTaskListPageViewModel : ViewModel
SetupTaskPropertyChangeListener(newTask, parentDefinition);
}
}
// 任何集合变化都触发保存(包括拖拽重排序)
try
{
@@ -527,5 +559,4 @@ public partial class GearTaskListPageViewModel : ViewModel
/// <summary>
/// 刷新当前任务树显示
/// </summary>
}

View File

@@ -0,0 +1,67 @@
using System;
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace BetterGenshinImpact.ViewModel.Windows;
public partial class AddTaskNodeDialogViewModel : ObservableValidator
{
[ObservableProperty]
[Required(ErrorMessage = "任务名称不能为空")]
private string _taskName = "";
[ObservableProperty]
private string _taskDescription = "";
[ObservableProperty]
private string _taskType = "";
public event EventHandler<bool>? RequestClose;
public AddTaskNodeDialogViewModel()
{
}
public AddTaskNodeDialogViewModel(string taskType)
{
TaskType = taskType;
TaskName = GetDefaultTaskName(taskType);
}
private string GetDefaultTaskName(string taskType)
{
return taskType switch
{
"Javascript" => "新建JS脚本",
"Pathing" => "新建地图追踪任务",
"KeyMouse" => "新建键鼠脚本",
"Shell" => "新建Shell脚本",
"CSharp" => "新建C#方法",
_ => "新建任务"
};
}
[RelayCommand]
private void Confirm()
{
ValidateAllProperties();
if (HasErrors)
{
return;
}
if (string.IsNullOrWhiteSpace(TaskName))
{
return;
}
RequestClose?.Invoke(this, true);
}
[RelayCommand]
private void Cancel()
{
RequestClose?.Invoke(this, false);
}
}

View File

@@ -0,0 +1,190 @@
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BetterGenshinImpact.Core.Script;
using BetterGenshinImpact.Core.Script.Project;
using BetterGenshinImpact.ViewModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
namespace BetterGenshinImpact.ViewModel.Windows;
public partial class JsScriptSelectionViewModel : ViewModel
{
private readonly ILogger<JsScriptSelectionViewModel> _logger = App.GetLogger<JsScriptSelectionViewModel>();
[ObservableProperty]
private ObservableCollection<JsScriptInfo> _jsScripts = [];
[ObservableProperty]
private JsScriptInfo? _selectedScript;
[ObservableProperty]
private string _readmeContent = string.Empty;
[ObservableProperty]
private string _mainJsContent = string.Empty;
[ObservableProperty]
private int _selectedTabIndex = 0;
public JsScriptSelectionViewModel()
{
LoadJsScripts();
}
private void LoadJsScripts()
{
try
{
var jsPath = Path.Combine(ScriptRepoUpdater.CenterRepoPath, "repo", "js");
if (!Directory.Exists(jsPath))
{
_logger.LogWarning($"JS脚本目录不存在: {jsPath}");
return;
}
var scriptDirectories = Directory.GetDirectories(jsPath);
var scripts = new ObservableCollection<JsScriptInfo>();
foreach (var scriptDir in scriptDirectories)
{
try
{
var manifestPath = Path.Combine(scriptDir, "manifest.json");
if (File.Exists(manifestPath))
{
var manifestContent = File.ReadAllText(manifestPath);
var manifest = Manifest.FromJson(manifestContent);
var scriptInfo = new JsScriptInfo
{
FolderName = Path.GetFileName(scriptDir),
FolderPath = scriptDir,
Manifest = manifest
};
scripts.Add(scriptInfo);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"加载JS脚本失败: {scriptDir}");
}
}
JsScripts = scripts;
}
catch (Exception ex)
{
_logger.LogError(ex, "加载JS脚本列表失败");
}
}
partial void OnSelectedScriptChanged(JsScriptInfo? value)
{
if (value != null)
{
// 清空之前的内容
ReadmeContent = string.Empty;
MainJsContent = string.Empty;
// 根据当前选中的标签页加载内容
LoadCurrentTabContent();
}
}
partial void OnSelectedTabIndexChanged(int value)
{
if (SelectedScript != null)
{
LoadCurrentTabContent();
}
}
private async void LoadCurrentTabContent()
{
if (SelectedScript == null) return;
switch (SelectedTabIndex)
{
case 0: // README.md
if (string.IsNullOrEmpty(ReadmeContent))
{
await Task.Run(LoadReadmeContent);
}
break;
case 1: // main.js
if (string.IsNullOrEmpty(MainJsContent))
{
await Task.Run(LoadMainJsContent);
}
break;
}
}
private void LoadReadmeContent()
{
if (SelectedScript == null) return;
try
{
var readmePath = Path.Combine(SelectedScript.FolderPath, "README.md");
if (File.Exists(readmePath))
{
ReadmeContent = File.ReadAllText(readmePath);
}
else
{
ReadmeContent = "README.md 文件不存在";
}
}
catch (Exception ex)
{
_logger.LogError(ex, "加载README.md失败");
ReadmeContent = $"加载README.md失败: {ex.Message}";
}
}
private void LoadMainJsContent()
{
if (SelectedScript == null) return;
try
{
var mainJsPath = Path.Combine(SelectedScript.FolderPath, SelectedScript.Manifest.Main);
if (File.Exists(mainJsPath))
{
MainJsContent = File.ReadAllText(mainJsPath);
}
else
{
MainJsContent = "main.js 文件不存在";
}
}
catch (Exception ex)
{
_logger.LogError(ex, "加载main.js失败");
MainJsContent = $"加载main.js失败: {ex.Message}";
}
}
[RelayCommand]
private void RefreshScripts()
{
LoadJsScripts();
}
}
public class JsScriptInfo
{
public string FolderName { get; set; } = string.Empty;
public string FolderPath { get; set; } = string.Empty;
public Manifest Manifest { get; set; } = new();
public string DisplayName => $"{FolderName} - {Manifest.Name}";
public string Description => Manifest.ShortDescription;
}