mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
新增触发器与持久化
This commit is contained in:
@@ -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>();
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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}"
|
||||
|
||||
127
BetterGenshinImpact/View/Windows/GearTask/AddTriggerDialog.xaml
Normal file
127
BetterGenshinImpact/View/Windows/GearTask/AddTriggerDialog.xaml
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user