基于 PRD 搭建 .NET 8 WPF 桌面应用,包含主控制台、任务编辑、全局设置、系统托盘和应用图标集成。 实现本地任务模型、触发器规则、JSON 状态持久化、OmniNotify HTTP 推送、执行日志记录、动态变量替换以及基础 Cron 预览能力。 补充 .gitignore,排除构建产物和本地 IDE 文件。 BREAKING CHANGE: 首次提交,建立项目初始结构
226 lines
6.3 KiB
C#
226 lines
6.3 KiB
C#
using System.ComponentModel;
|
|
using System.Windows;
|
|
using Forms = System.Windows.Forms;
|
|
|
|
namespace OmniScheduler;
|
|
|
|
public partial class MainWindow : Window, INotifyPropertyChanged
|
|
{
|
|
private readonly StateStore _store = new();
|
|
private readonly NotifyClient _client = new();
|
|
private readonly SchedulerService _scheduler;
|
|
private readonly Forms.NotifyIcon _trayIcon;
|
|
private bool _allowExit;
|
|
private ScheduledTask? _selectedTask;
|
|
private string _statusText = "就绪";
|
|
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
State = _store.Load();
|
|
_scheduler = new SchedulerService(State, _client, AddLog);
|
|
_trayIcon = BuildTrayIcon();
|
|
DataContext = this;
|
|
_scheduler.Start();
|
|
|
|
if (Environment.GetCommandLineArgs().Any(a => a.Equals("--tray", StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
Hide();
|
|
}
|
|
}
|
|
|
|
public SchedulerState State { get; }
|
|
|
|
public ScheduledTask? SelectedTask
|
|
{
|
|
get => _selectedTask;
|
|
set
|
|
{
|
|
_selectedTask = value;
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedTask)));
|
|
}
|
|
}
|
|
|
|
public string StatusText
|
|
{
|
|
get => _statusText;
|
|
set
|
|
{
|
|
_statusText = value;
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StatusText)));
|
|
}
|
|
}
|
|
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
|
private Forms.NotifyIcon BuildTrayIcon()
|
|
{
|
|
var menu = new Forms.ContextMenuStrip();
|
|
menu.Items.Add("打开主界面", null, (_, _) => ShowDashboard());
|
|
menu.Items.Add("暂停全部任务", null, (_, _) =>
|
|
{
|
|
_scheduler.IsPaused = !_scheduler.IsPaused;
|
|
PauseToggle.IsChecked = _scheduler.IsPaused;
|
|
});
|
|
menu.Items.Add("退出", null, (_, _) => ExitApplication());
|
|
|
|
var iconPath = System.IO.Path.Combine(AppContext.BaseDirectory, "app.ico");
|
|
var icon = new Forms.NotifyIcon
|
|
{
|
|
Icon = new System.Drawing.Icon(iconPath),
|
|
Text = "OmniScheduler",
|
|
Visible = true,
|
|
ContextMenuStrip = menu
|
|
};
|
|
icon.DoubleClick += (_, _) => ShowDashboard();
|
|
return icon;
|
|
}
|
|
|
|
private void AddTask_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var task = new ScheduledTask();
|
|
task.Triggers.Add(new TaskTrigger());
|
|
if (TaskEditorWindow.Edit(this, task, State.Settings, _client))
|
|
{
|
|
State.Tasks.Add(task);
|
|
SaveAndRefresh("已创建任务");
|
|
}
|
|
}
|
|
|
|
private void EditTask_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (SelectedTask is null)
|
|
{
|
|
StatusText = "请先选择一个任务";
|
|
return;
|
|
}
|
|
|
|
if (TaskEditorWindow.Edit(this, SelectedTask, State.Settings, _client))
|
|
{
|
|
SaveAndRefresh("任务已保存");
|
|
}
|
|
}
|
|
|
|
private void TasksGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
=> EditTask_Click(sender, e);
|
|
|
|
private void DeleteTask_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (SelectedTask is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (System.Windows.MessageBox.Show(this, $"删除任务“{SelectedTask.Name}”?", "确认删除", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
|
|
{
|
|
State.Tasks.Remove(SelectedTask);
|
|
SaveAndRefresh("任务已删除");
|
|
}
|
|
}
|
|
|
|
private void CloneTask_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (SelectedTask is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var clone = SelectedTask.Clone();
|
|
State.Tasks.Add(clone);
|
|
SelectedTask = clone;
|
|
SaveAndRefresh("任务已克隆");
|
|
}
|
|
|
|
private async void FireTask_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (SelectedTask is null)
|
|
{
|
|
StatusText = "请先选择一个任务";
|
|
return;
|
|
}
|
|
|
|
StatusText = "正在手动触发...";
|
|
await _scheduler.FireNowAsync(SelectedTask);
|
|
SaveAndRefresh("手动触发完成");
|
|
}
|
|
|
|
private void Settings_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (SettingsWindow.Edit(this, State.Settings))
|
|
{
|
|
StartupRegistration.Apply(State.Settings.StartWithWindows);
|
|
SaveAndRefresh("设置已保存");
|
|
}
|
|
}
|
|
|
|
private void PauseToggle_Changed(object sender, RoutedEventArgs e)
|
|
{
|
|
_scheduler.IsPaused = PauseToggle.IsChecked == true;
|
|
StatusText = _scheduler.IsPaused ? "全部任务已暂停" : "调度已恢复";
|
|
}
|
|
|
|
private void ClearLogs_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
State.Logs.Clear();
|
|
SaveAndRefresh("日志已清空");
|
|
}
|
|
|
|
private void LogsGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
{
|
|
if (LogsGrid.SelectedItem is ExecutionLog log)
|
|
{
|
|
System.Windows.MessageBox.Show(this, $"请求 JSON:\n{log.RequestJson}\n\n响应:\n{log.Response}", "日志详情", MessageBoxButton.OK, log.Level == "ERROR" ? MessageBoxImage.Error : MessageBoxImage.Information);
|
|
}
|
|
}
|
|
|
|
private void AddLog(ExecutionLog log)
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
State.Logs.Insert(0, log);
|
|
StatusText = $"{log.Timestamp:HH:mm:ss} {log.TaskName}: {log.Message}";
|
|
_store.Save(State);
|
|
});
|
|
}
|
|
|
|
private void SaveAndRefresh(string status)
|
|
{
|
|
_scheduler.RecalculateNextRuns();
|
|
foreach (var task in State.Tasks)
|
|
{
|
|
task.Notify(nameof(ScheduledTask.TriggerSummary));
|
|
}
|
|
TasksGrid.Items.Refresh();
|
|
_store.Save(State);
|
|
StatusText = status;
|
|
}
|
|
|
|
private void Window_Closing(object? sender, CancelEventArgs e)
|
|
{
|
|
if (_allowExit)
|
|
{
|
|
_trayIcon.Visible = false;
|
|
_trayIcon.Dispose();
|
|
_store.Save(State);
|
|
return;
|
|
}
|
|
|
|
e.Cancel = true;
|
|
Hide();
|
|
StatusText = "已隐藏到系统托盘";
|
|
}
|
|
|
|
private void ShowDashboard()
|
|
{
|
|
Show();
|
|
WindowState = WindowState.Normal;
|
|
Activate();
|
|
}
|
|
|
|
private void ExitApplication()
|
|
{
|
|
_allowExit = true;
|
|
Close();
|
|
}
|
|
}
|