新增触发器与持久化

This commit is contained in:
辉鸭蛋
2025-10-02 22:49:37 +08:00
parent 95e20619d2
commit 95a1cc3dfa
8 changed files with 808 additions and 13 deletions

View File

@@ -12,6 +12,7 @@ using BetterGenshinImpact.Helpers.Extensions;
using BetterGenshinImpact.Helpers.Win32;
using BetterGenshinImpact.Hutao;
using BetterGenshinImpact.Service;
using BetterGenshinImpact.Service.GearTask;
using BetterGenshinImpact.Service.Interface;
using BetterGenshinImpact.Service.Notification;
using BetterGenshinImpact.Service.Notifier;
@@ -159,6 +160,7 @@ public partial class App : Application
services.AddSingleton<BgiOnnxFactory>();
services.AddSingleton<OcrFactory>();
services.AddSingleton<GearTaskStorageService>();
services.AddSingleton<GearTriggerStorageService>();

View File

@@ -0,0 +1,236 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using BetterGenshinImpact.Model;
using BetterGenshinImpact.Model.Gear;
using BetterGenshinImpact.ViewModel.Pages.Component;
using BetterGenshinImpact.Model.Gear.Triggers;
using BetterGenshinImpact.Service.GearTask.Model;
namespace BetterGenshinImpact.Service.GearTask;
/// <summary>
/// 齿轮触发器存储服务,负责触发器数据的 JSON 持久化
/// </summary>
public class GearTriggerStorageService
{
private readonly ILogger<GearTriggerStorageService> _logger;
private readonly string _triggerStoragePath;
private readonly string _triggerFilePath;
private readonly JsonSerializerSettings _jsonSettings;
public GearTriggerStorageService(ILogger<GearTriggerStorageService> logger)
{
_logger = logger;
_triggerStoragePath = GearTaskPaths.TaskTriggerPath;
_triggerFilePath = Path.Combine(_triggerStoragePath, "triggers.json");
// 确保目录存在
Directory.CreateDirectory(_triggerStoragePath);
// 配置 JSON 序列化设置
_jsonSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
DateFormatString = "yyyy-MM-dd HH:mm:ss",
NullValueHandling = NullValueHandling.Ignore
};
}
/// <summary>
/// 保存所有触发器到 JSON 文件
/// </summary>
/// <param name="timedTriggers">定时触发器列表</param>
/// <param name="hotkeyTriggers">快捷键触发器列表</param>
/// <returns></returns>
public async Task SaveTriggersAsync(IEnumerable<GearTriggerViewModel> timedTriggers, IEnumerable<GearTriggerViewModel> hotkeyTriggers)
{
try
{
var triggerData = new GearTriggerCollectionData
{
TimedTriggers = new List<GearTriggerData>(),
HotkeyTriggers = new List<GearTriggerData>()
};
// 转换定时触发器
foreach (var trigger in timedTriggers)
{
triggerData.TimedTriggers.Add(ConvertToData(trigger));
}
// 转换快捷键触发器
foreach (var trigger in hotkeyTriggers)
{
triggerData.HotkeyTriggers.Add(ConvertToData(trigger));
}
var json = JsonConvert.SerializeObject(triggerData, _jsonSettings);
await File.WriteAllTextAsync(_triggerFilePath, json);
_logger.LogInformation("触发器数据已保存到 {FilePath},定时触发器: {TimedCount},快捷键触发器: {HotkeyCount}",
_triggerFilePath, triggerData.TimedTriggers.Count, triggerData.HotkeyTriggers.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "保存触发器数据时发生错误");
throw;
}
}
/// <summary>
/// 从 JSON 文件加载所有触发器
/// </summary>
/// <returns></returns>
public async Task<(List<GearTriggerViewModel> TimedTriggers, List<GearTriggerViewModel> HotkeyTriggers)> LoadTriggersAsync()
{
var timedTriggers = new List<GearTriggerViewModel>();
var hotkeyTriggers = new List<GearTriggerViewModel>();
try
{
if (!File.Exists(_triggerFilePath))
{
_logger.LogInformation("触发器数据文件不存在,返回空列表: {FilePath}", _triggerFilePath);
return (timedTriggers, hotkeyTriggers);
}
var json = await File.ReadAllTextAsync(_triggerFilePath);
var triggerData = JsonConvert.DeserializeObject<GearTriggerCollectionData>(json, _jsonSettings);
if (triggerData == null)
{
_logger.LogWarning("无法反序列化触发器数据文件: {FilePath}", _triggerFilePath);
return (timedTriggers, hotkeyTriggers);
}
// 转换定时触发器
if (triggerData.TimedTriggers != null)
{
foreach (var data in triggerData.TimedTriggers)
{
var viewModel = ConvertToViewModel(data);
if (viewModel != null)
{
timedTriggers.Add(viewModel);
}
}
}
// 转换快捷键触发器
if (triggerData.HotkeyTriggers != null)
{
foreach (var data in triggerData.HotkeyTriggers)
{
var viewModel = ConvertToViewModel(data);
if (viewModel != null)
{
hotkeyTriggers.Add(viewModel);
}
}
}
_logger.LogInformation("触发器数据已从 {FilePath} 加载,定时触发器: {TimedCount},快捷键触发器: {HotkeyCount}",
_triggerFilePath, timedTriggers.Count, hotkeyTriggers.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "加载触发器数据时发生错误");
}
return (timedTriggers, hotkeyTriggers);
}
/// <summary>
/// 将 ViewModel 转换为数据模型
/// </summary>
private GearTriggerData ConvertToData(GearTriggerViewModel viewModel)
{
return new GearTriggerData
{
Name = viewModel.Name,
IsEnabled = viewModel.IsEnabled,
TriggerType = viewModel.TriggerType.ToString(),
CronExpression = viewModel.CronExpression,
Hotkey = viewModel.Hotkey?.ToString() ?? string.Empty,
TaskDefinitionName = viewModel.TaskDefinitionName,
CreatedTime = DateTime.Now,
ModifiedTime = DateTime.Now
};
}
/// <summary>
/// 将数据模型转换为 ViewModel
/// </summary>
private GearTriggerViewModel? ConvertToViewModel(GearTriggerData data)
{
try
{
if (!Enum.TryParse<TriggerType>(data.TriggerType, out var triggerType))
{
_logger.LogWarning("无效的触发器类型: {TriggerType}", data.TriggerType);
return null;
}
var viewModel = new GearTriggerViewModel(data.Name, triggerType)
{
IsEnabled = data.IsEnabled,
CronExpression = data.CronExpression,
Hotkey = string.IsNullOrEmpty(data.Hotkey) ? null : HotKey.FromString(data.Hotkey),
TaskDefinitionName = data.TaskDefinitionName
};
return viewModel;
}
catch (Exception ex)
{
_logger.LogError(ex, "转换触发器数据时发生错误: {TriggerName}", data.Name);
return null;
}
}
}
/// <summary>
/// 触发器集合数据模型,用于 JSON 序列化
/// </summary>
public class GearTriggerCollectionData
{
[JsonProperty("timed_triggers")]
public List<GearTriggerData> TimedTriggers { get; set; } = new();
[JsonProperty("hotkey_triggers")]
public List<GearTriggerData> HotkeyTriggers { get; set; } = new();
}
/// <summary>
/// 触发器数据模型,用于 JSON 序列化
/// </summary>
public class GearTriggerData
{
[JsonProperty("name")]
public string Name { get; set; } = string.Empty;
[JsonProperty("is_enabled")]
public bool IsEnabled { get; set; } = true;
[JsonProperty("trigger_type")]
public string TriggerType { get; set; } = string.Empty;
[JsonProperty("cron_expression")]
public string CronExpression { get; set; } = string.Empty;
[JsonProperty("hotkey")]
public string Hotkey { get; set; } = string.Empty;
[JsonProperty("task_definition_name")]
public string TaskDefinitionName { get; set; } = string.Empty;
[JsonProperty("created_time")]
public DateTime CreatedTime { get; set; } = DateTime.Now;
[JsonProperty("modified_time")]
public DateTime ModifiedTime { get; set; } = DateTime.Now;
}

View File

@@ -276,16 +276,36 @@
<TabItem Header="定时触发" Style="{StaticResource ConsistentTabItemStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- 工具栏 -->
<Border Grid.Row="0"
Background="{DynamicResource CardBackgroundFillColorSecondaryBrush}"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1"
Padding="12,8">
<StackPanel Orientation="Horizontal">
<ui:Button Content="新增定时触发器"
Command="{Binding AddTimedTriggerCommand}"
Icon="{ui:SymbolIcon Add24}"
Appearance="Primary"
Margin="0,0,8,0" />
<ui:Button Content="删除选中项"
Command="{Binding DeleteTriggerCommand}"
Icon="{ui:SymbolIcon Delete24}"
Appearance="Secondary" />
</StackPanel>
</Border>
<!-- 固定表格头部 -->
<ContentPresenter Grid.Row="0"
<ContentPresenter Grid.Row="1"
ContentTemplate="{StaticResource TimedTableHeaderTemplate}" />
<!-- 可滚动的列表内容 -->
<ScrollViewer Grid.Row="1"
<ScrollViewer Grid.Row="2"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<ui:ListView ItemsSource="{Binding TimedTriggers}"
@@ -300,16 +320,36 @@
<TabItem Header="快捷键触发" Style="{StaticResource ConsistentTabItemStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- 工具栏 -->
<Border Grid.Row="0"
Background="{DynamicResource CardBackgroundFillColorSecondaryBrush}"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1"
Padding="12,8">
<StackPanel Orientation="Horizontal">
<ui:Button Content="新增快捷键触发器"
Command="{Binding AddHotkeyTriggerCommand}"
Icon="{ui:SymbolIcon Add24}"
Appearance="Primary"
Margin="0,0,8,0" />
<ui:Button Content="删除选中项"
Command="{Binding DeleteTriggerCommand}"
Icon="{ui:SymbolIcon Delete24}"
Appearance="Secondary" />
</StackPanel>
</Border>
<!-- 固定表格头部 -->
<ContentPresenter Grid.Row="0"
<ContentPresenter Grid.Row="1"
ContentTemplate="{StaticResource TableHeaderTemplate}" />
<!-- 可滚动的列表内容 -->
<ScrollViewer Grid.Row="1"
<ScrollViewer Grid.Row="2"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<ui:ListView ItemsSource="{Binding HotkeyTriggers}"

View File

@@ -0,0 +1,127 @@
<ui:FluentWindow x:Class="BetterGenshinImpact.View.Windows.GearTask.AddTriggerDialog"
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:gearTask="clr-namespace:BetterGenshinImpact.ViewModel.Windows.GearTask"
xmlns:converters="clr-namespace:BetterGenshinImpact.View.Converters"
d:DataContext="{d:DesignInstance Type=gearTask:AddTriggerDialogViewModel}"
Width="500"
Height="450"
MinWidth="450"
MinHeight="400"
ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}"
ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
ExtendsContentIntoTitleBar="True"
FontFamily="{DynamicResource TextThemeFontFamily}"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
WindowStyle="SingleBorderWindow"
mc:Ignorable="d">
<ui:FluentWindow.Resources>
<converters:EnumToVisibilityConverter x:Key="EnumToVisibilityConverter" />
</ui:FluentWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- 标题栏 -->
<ui:TitleBar Grid.Row="0" Title="新增触发器">
<ui:TitleBar.Icon>
<ui:ImageIcon Source="pack://application:,,,/Resources/Images/logo.png" />
</ui:TitleBar.Icon>
</ui:TitleBar>
<!-- 主要内容 -->
<ScrollViewer Grid.Row="1" Margin="20" VerticalScrollBarVisibility="Auto">
<StackPanel>
<!-- 触发器名称 -->
<StackPanel>
<ui:TextBlock Text="触发器名称" FontWeight="SemiBold" Margin="0,0,0,8" />
<ui:TextBox x:Name="TriggerNameTextBox"
Text="{Binding TriggerName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="请输入触发器名称" />
</StackPanel>
<!-- 触发器类型 -->
<StackPanel Margin="0,16,0,0">
<ui:TextBlock Text="触发器类型" FontWeight="SemiBold" Margin="0,0,0,8" />
<ComboBox ItemsSource="{Binding TriggerTypes}"
SelectedValue="{Binding SelectedTriggerType, Mode=TwoWay}"
SelectedValuePath="Value"
DisplayMemberPath="DisplayName"
IsEnabled="{Binding IsTriggerTypeSelectionEnabled}" />
</StackPanel>
<!-- 定时触发设置 -->
<StackPanel Margin="0,16,0,0"
Visibility="{Binding SelectedTriggerType, Converter={StaticResource EnumToVisibilityConverter}, ConverterParameter=Timed}">
<ui:TextBlock Text="Cron 表达式" FontWeight="SemiBold" Margin="0,0,0,8" />
<ui:TextBox Text="{Binding CronExpression, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="例如: 0 0 8 * * ? (每天8点)"
FontFamily="Consolas" />
<ui:TextBlock Text="格式: 秒 分 时 日 月 星期 [年]"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,4,0,0" />
</StackPanel>
<!-- 热键触发设置 -->
<StackPanel Margin="0,16,0,0"
Visibility="{Binding SelectedTriggerType, Converter={StaticResource EnumToVisibilityConverter}, ConverterParameter=Hotkey}">
<ui:TextBlock Text="快捷键" FontWeight="SemiBold" Margin="0,0,0,8" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBox Grid.Column="0"
Text="{Binding SelectedHotkey, Mode=OneWay}"
IsReadOnly="True"
PlaceholderText="点击右侧按钮设置快捷键" />
<ui:Button Grid.Column="1"
Content="设置"
Command="{Binding SelectHotkeyCommand}"
Margin="8,0,0,0" />
</Grid>
</StackPanel>
<!-- 关联任务 -->
<StackPanel Margin="0,16,0,0">
<ui:TextBlock Text="关联任务定义" FontWeight="SemiBold" Margin="0,0,0,8" />
<ComboBox ItemsSource="{Binding AvailableTaskDefinitions}"
SelectedItem="{Binding SelectedTaskDefinitionName, Mode=TwoWay}"
IsEditable="True" />
</StackPanel>
</StackPanel>
</ScrollViewer>
<!-- 按钮栏 -->
<Border Grid.Row="2"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="0,1,0,0"
Padding="20,16">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right">
<ui:Button Content="取消"
Command="{Binding CancelCommand}"
Appearance="Secondary"
MinWidth="80" />
<ui:Button Content="确定"
Command="{Binding ConfirmCommand}"
Appearance="Primary"
MinWidth="80" />
</StackPanel>
</Border>
</Grid>
</ui:FluentWindow>

View File

@@ -0,0 +1,85 @@
using System;
using System.Windows;
using BetterGenshinImpact.Helpers.Ui;
using BetterGenshinImpact.ViewModel.Windows.GearTask;
using BetterGenshinImpact.ViewModel.Pages.Component;
using Microsoft.Extensions.DependencyInjection;
using BetterGenshinImpact.Service;
using Microsoft.Extensions.Logging;
namespace BetterGenshinImpact.View.Windows.GearTask;
public partial class AddTriggerDialog
{
public AddTriggerDialogViewModel ViewModel { get; }
public AddTriggerDialog(AddTriggerDialogViewModel viewModel)
{
ViewModel = viewModel;
DataContext = ViewModel;
InitializeComponent();
ViewModel.RequestClose += OnRequestClose;
this.SourceInitialized += OnSourceInitialized;
this.Loaded += OnLoaded;
}
private void OnSourceInitialized(object? sender, EventArgs e)
{
// 应用与主窗口相同的背景主题
WindowHelper.TryApplySystemBackdrop(this);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// 自动聚焦到名称输入框
TriggerNameTextBox.Focus();
TriggerNameTextBox.SelectAll();
}
private void OnRequestClose(object? sender, bool result)
{
DialogResult = result;
Close();
}
/// <summary>
/// 显示新增触发器对话框
/// </summary>
/// <returns>如果用户点击确定返回创建的触发器ViewModel否则返回null</returns>
public static AddTriggerDialogViewModel? ShowAddTriggerDialog()
{
// 使用依赖注入获取 ViewModel
var storageService = App.GetRequiredService<GearTaskStorageService>();
var logger = App.GetRequiredService<ILogger<AddTriggerDialogViewModel>>();
var viewModel = new AddTriggerDialogViewModel(storageService, logger);
var dialog = new AddTriggerDialog(viewModel)
{
Owner = Application.Current.MainWindow
};
var result = dialog.ShowDialog();
return result == true ? dialog.ViewModel : null;
}
/// <summary>
/// 显示新增触发器对话框,指定触发器类型
/// </summary>
/// <param name="predefinedType">预定义的触发器类型</param>
/// <returns>如果用户点击确定返回创建的触发器ViewModel否则返回null</returns>
public static AddTriggerDialogViewModel? ShowAddTriggerDialog(TriggerType predefinedType)
{
// 使用依赖注入获取 ViewModel
var storageService = App.GetRequiredService<GearTaskStorageService>();
var logger = App.GetRequiredService<ILogger<AddTriggerDialogViewModel>>();
var viewModel = new AddTriggerDialogViewModel(storageService, logger, predefinedType);
var dialog = new AddTriggerDialog(viewModel)
{
Owner = Application.Current.MainWindow
};
var result = dialog.ShowDialog();
return result == true ? dialog.ViewModel : null;
}
}

View File

@@ -1,9 +1,10 @@
using System;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using System.ComponentModel;
using System.Linq;
using BetterGenshinImpact.Model.Gear.Triggers;
using BetterGenshinImpact.Model.Gear.Tasks;
using BetterGenshinImpact.Model;
using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterGenshinImpact.ViewModel.Pages.Component;
@@ -107,10 +108,12 @@ public enum TriggerType
/// <summary>
/// 定时触发
/// </summary>
[Description("定时触发")]
Timed,
/// <summary>
/// 热键触发
/// </summary>
[Description("热键触发")]
Hotkey
}

View File

@@ -5,11 +5,19 @@ using BetterGenshinImpact.ViewModel.Pages.Component;
using BetterGenshinImpact.Model.Gear.Triggers;
using BetterGenshinImpact.Model;
using System.Linq;
using BetterGenshinImpact.View.Windows.GearTask;
using BetterGenshinImpact.Service.GearTask;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace BetterGenshinImpact.ViewModel.Pages;
public partial class GearTriggerPageViewModel : ViewModel
{
private readonly ILogger<GearTriggerPageViewModel> _logger;
private readonly GearTriggerStorageService _storageService;
[ObservableProperty]
private ObservableCollection<GearTriggerViewModel> _timedTriggers = new();
@@ -22,29 +30,103 @@ public partial class GearTriggerPageViewModel : ViewModel
[ObservableProperty]
private GearTaskDefinitionViewModel? _selectedTaskDefinition;
public GearTriggerPageViewModel()
public GearTriggerPageViewModel(ILogger<GearTriggerPageViewModel> logger, GearTriggerStorageService storageService)
{
_logger = logger;
_storageService = storageService;
}
public override void OnNavigatedTo()
{
_ = LoadTriggersAsync();
}
/// <summary>
/// 异步加载触发器数据
/// </summary>
private async Task LoadTriggersAsync()
{
try
{
var (timedTriggers, hotkeyTriggers) = await _storageService.LoadTriggersAsync();
TimedTriggers.Clear();
HotkeyTriggers.Clear();
foreach (var trigger in timedTriggers)
{
TimedTriggers.Add(trigger);
}
foreach (var trigger in hotkeyTriggers)
{
HotkeyTriggers.Add(trigger);
}
_logger.LogInformation("已加载 {TimedCount} 个定时触发器和 {HotkeyCount} 个快捷键触发器",
TimedTriggers.Count, HotkeyTriggers.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "加载触发器数据时发生错误");
}
}
/// <summary>
/// 保存触发器数据
/// </summary>
private async Task SaveTriggersAsync()
{
try
{
await _storageService.SaveTriggersAsync(TimedTriggers, HotkeyTriggers);
_logger.LogInformation("触发器数据已保存");
}
catch (Exception ex)
{
_logger.LogError(ex, "保存触发器数据时发生错误");
}
}
[RelayCommand]
private void AddTimedTrigger()
{
var newTrigger = new GearTriggerViewModel($"定时触发器 {TimedTriggers.Count + 1}", TriggerType.Timed);
TimedTriggers.Add(newTrigger);
SelectedTrigger = newTrigger;
var dialog = AddTriggerDialog.ShowAddTriggerDialog(TriggerType.Timed);
if (dialog != null)
{
var newTrigger = new GearTriggerViewModel(dialog.TriggerName, TriggerType.Timed)
{
CronExpression = dialog.CronExpression,
TaskDefinitionName = dialog.SelectedTaskDefinitionName,
IsEnabled = true
};
TimedTriggers.Add(newTrigger);
SelectedTrigger = newTrigger;
// 保存数据
_ = SaveTriggersAsync();
}
}
[RelayCommand]
private void AddHotkeyTrigger()
{
var newTrigger = new GearTriggerViewModel($"快捷键触发器 {HotkeyTriggers.Count + 1}", TriggerType.Hotkey);
HotkeyTriggers.Add(newTrigger);
SelectedTrigger = newTrigger;
var dialog = AddTriggerDialog.ShowAddTriggerDialog(TriggerType.Hotkey);
if (dialog != null)
{
var newTrigger = new GearTriggerViewModel(dialog.TriggerName, TriggerType.Hotkey)
{
Hotkey = dialog.SelectedHotkey,
TaskDefinitionName = dialog.SelectedTaskDefinitionName,
IsEnabled = true
};
HotkeyTriggers.Add(newTrigger);
SelectedTrigger = newTrigger;
// 保存数据
_ = SaveTriggersAsync();
}
}
[RelayCommand]
@@ -63,5 +145,8 @@ public partial class GearTriggerPageViewModel : ViewModel
}
SelectedTrigger = null;
// 保存数据
_ = SaveTriggersAsync();
}
}

View File

@@ -0,0 +1,217 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using BetterGenshinImpact.ViewModel.Pages.Component;
using BetterGenshinImpact.Model.Gear.Triggers;
using BetterGenshinImpact.Model;
using BetterGenshinImpact.Service;
using Microsoft.Extensions.Logging;
using Wpf.Ui.Violeta.Controls;
namespace BetterGenshinImpact.ViewModel.Windows.GearTask;
/// <summary>
/// 新增触发器对话框 ViewModel
/// </summary>
public partial class AddTriggerDialogViewModel : ObservableObject
{
private readonly GearTaskStorageService _storageService;
private readonly ILogger<AddTriggerDialogViewModel> _logger;
[ObservableProperty]
private string _triggerName = string.Empty;
[ObservableProperty]
private TriggerType _selectedTriggerType = TriggerType.Timed;
[ObservableProperty]
private string _cronExpression = "0 0 8 * * ?"; // 默认每天8点
[ObservableProperty]
private HotKey? _selectedHotkey;
[ObservableProperty]
private string _selectedTaskDefinitionName = string.Empty;
[ObservableProperty]
private bool _isEnabled = true;
/// <summary>
/// 触发器类型选择是否可用
/// </summary>
[ObservableProperty]
private bool _isTriggerTypeSelectionEnabled = true;
/// <summary>
/// 可用的触发器类型
/// </summary>
public ObservableCollection<EnumItem<TriggerType>> TriggerTypes { get; } = new()
{
EnumItem<TriggerType>.Create(TriggerType.Timed),
EnumItem<TriggerType>.Create(TriggerType.Hotkey)
};
/// <summary>
/// 可用的任务定义列表
/// </summary>
[ObservableProperty]
private ObservableCollection<string> _availableTaskDefinitions = new();
/// <summary>
/// 请求关闭事件
/// </summary>
public event EventHandler<bool>? RequestClose;
/// <summary>
/// 创建的触发器
/// </summary>
public GearTriggerViewModel? CreatedTrigger { get; private set; }
public AddTriggerDialogViewModel(GearTaskStorageService storageService, ILogger<AddTriggerDialogViewModel> logger)
{
_storageService = storageService;
_logger = logger;
// 生成默认名称
GenerateDefaultName();
// 加载可用的任务定义
LoadAvailableTaskDefinitions();
}
/// <summary>
/// 构造函数,用于指定触发器类型
/// </summary>
public AddTriggerDialogViewModel(GearTaskStorageService storageService, ILogger<AddTriggerDialogViewModel> logger, TriggerType? predefinedType = null)
{
_storageService = storageService;
_logger = logger;
// 如果指定了预定义类型,则设置并禁用选择
if (predefinedType.HasValue)
{
SelectedTriggerType = predefinedType.Value;
IsTriggerTypeSelectionEnabled = false;
}
// 生成默认名称
GenerateDefaultName();
// 加载可用的任务定义
LoadAvailableTaskDefinitions();
}
/// <summary>
/// 生成默认触发器名称
/// </summary>
private void GenerateDefaultName()
{
var typeName = SelectedTriggerType == TriggerType.Timed ? "定时触发器" : "热键触发器";
TriggerName = $"{typeName} {DateTime.Now:MMdd_HHmm}";
}
/// <summary>
/// 加载可用的任务定义
/// </summary>
private async void LoadAvailableTaskDefinitions()
{
try
{
AvailableTaskDefinitions.Clear();
// 从 GearTaskStorageService 加载所有任务定义
var taskDefinitions = await _storageService.LoadAllTaskDefinitionsAsync();
// 提取任务定义名称并添加到列表中
foreach (var taskDefinition in taskDefinitions)
{
if (!string.IsNullOrWhiteSpace(taskDefinition.Name))
{
AvailableTaskDefinitions.Add(taskDefinition.Name);
}
}
_logger.LogInformation("已加载 {Count} 个可用的任务定义", AvailableTaskDefinitions.Count);
// 如果有任务定义,默认选择第一个
if (AvailableTaskDefinitions.Count > 0)
{
SelectedTaskDefinitionName = AvailableTaskDefinitions[0];
}
}
catch (Exception ex)
{
_logger.LogDebug(ex, "加载可用任务定义时发生错误");
// 发生错误时添加一个提示项
AvailableTaskDefinitions.Clear();
AvailableTaskDefinitions.Add("(无可用任务定义)");
}
}
/// <summary>
/// 触发器类型改变时的处理
/// </summary>
partial void OnSelectedTriggerTypeChanged(TriggerType value)
{
GenerateDefaultName();
}
/// <summary>
/// 确认创建触发器
/// </summary>
[RelayCommand]
private void Confirm()
{
if (string.IsNullOrWhiteSpace(TriggerName))
{
Toast.Error("请输入触发器名称");
return;
}
if (SelectedTriggerType == TriggerType.Timed && string.IsNullOrWhiteSpace(CronExpression))
{
Toast.Error("请输入 Cron 表达式");
return;
}
if (SelectedTriggerType == TriggerType.Hotkey && SelectedHotkey == null)
{
Toast.Error("请选择热键");
return;
}
// 创建触发器 ViewModel
CreatedTrigger = new GearTriggerViewModel(TriggerName, SelectedTriggerType)
{
IsEnabled = IsEnabled,
TaskDefinitionName = SelectedTaskDefinitionName,
CronExpression = SelectedTriggerType == TriggerType.Timed ? CronExpression : null,
Hotkey = SelectedTriggerType == TriggerType.Hotkey ? SelectedHotkey : null
};
RequestClose?.Invoke(this, true);
}
/// <summary>
/// 取消创建
/// </summary>
[RelayCommand]
private void Cancel()
{
RequestClose?.Invoke(this, false);
}
/// <summary>
/// 选择热键
/// </summary>
[RelayCommand]
private void SelectHotkey()
{
// TODO: 打开热键选择对话框
// 这里先创建一个示例热键
SelectedHotkey = new HotKey(System.Windows.Input.Key.F1, System.Windows.Input.ModifierKeys.Control);
}
}