mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
新增任务节点对话框,支持选择JS脚本类型任务,优化用户操作体验
This commit is contained in:
92
BetterGenshinImpact/View/Windows/AddTaskNodeDialog.xaml
Normal file
92
BetterGenshinImpact/View/Windows/AddTaskNodeDialog.xaml
Normal 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>
|
||||
52
BetterGenshinImpact/View/Windows/AddTaskNodeDialog.xaml.cs
Normal file
52
BetterGenshinImpact/View/Windows/AddTaskNodeDialog.xaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
219
BetterGenshinImpact/View/Windows/JsScriptSelectionWindow.xaml
Normal file
219
BetterGenshinImpact/View/Windows/JsScriptSelectionWindow.xaml
Normal 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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user