feat(app): 初始化 OmniScheduler WPF 调度器

基于 PRD 搭建 .NET 8 WPF 桌面应用,包含主控制台、任务编辑、全局设置、系统托盘和应用图标集成。

实现本地任务模型、触发器规则、JSON 状态持久化、OmniNotify HTTP 推送、执行日志记录、动态变量替换以及基础 Cron 预览能力。

补充 .gitignore,排除构建产物和本地 IDE 文件。

BREAKING CHANGE: 首次提交,建立项目初始结构
This commit is contained in:
2026-05-20 00:12:17 +08:00
commit 2a669bdfe7
15 changed files with 1733 additions and 0 deletions

View File

@@ -0,0 +1,225 @@
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();
}
}