-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAppMainWindow.xaml.cs
More file actions
331 lines (280 loc) · 9.71 KB
/
Copy pathAppMainWindow.xaml.cs
File metadata and controls
331 lines (280 loc) · 9.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using WechatBot.Pages;
using WechatBot.ViewModels;
namespace WechatBot;
public partial class AppMainWindow : Window
{
private readonly MainViewModel _viewModel;
private readonly Settings _settings;
private readonly ImageRecognition _imageRec;
private TrayIcon? _trayIcon;
private HwndSource? _hwndSource;
private bool _forceClose;
private DispatcherTimer? _scheduleTimer;
// 页面实例(缓存)
private HomePage? _homePage;
private StepsPage? _stepsPage;
private LogsPage? _logsPage;
private SettingsPage? _settingsPage;
// 悬浮状态窗口
private FloatingStatusWindow? _floatingStatus;
private const int WM_COMMAND = 0x0111;
public AppMainWindow()
{
// 先加载主题,再初始化 UI
_settings = Settings.Load(warn => { });
ThemeManager.ApplyTheme(_settings.Theme);
InitializeComponent();
_imageRec = new ImageRecognition();
_viewModel = new MainViewModel(_settings, _imageRec);
DataContext = _viewModel;
// 初始化页面并默认显示首页
_homePage = new HomePage(_viewModel);
_stepsPage = new StepsPage(_viewModel);
_logsPage = new LogsPage(_viewModel);
_settingsPage = new SettingsPage(_viewModel);
PageContent.Content = _homePage;
CleanOldLogs();
StartScheduleTimer();
StartLogoBreathAnimation();
// 窗口启动动画
Opacity = 0;
Loaded += (_, _) =>
{
InitTrayIcon();
AnimateWindowStartup();
InitFloatingStatus();
};
Closed += (_, _) =>
{
_scheduleTimer?.Stop();
_trayIcon?.Dispose();
_floatingStatus?.Close();
_hwndSource?.RemoveHook(WndProc);
_imageRec.Dispose();
};
}
#region 窗口动画
private void AnimateWindowStartup()
{
// 淡入动画
var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(500))
{
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
};
BeginAnimation(OpacityProperty, fadeIn);
}
#endregion
#region 窗口按钮
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
BtnMaximize_Click(sender, e);
else
DragMove();
}
private void BtnMinimize_Click(object sender, RoutedEventArgs e)
=> WindowState = WindowState.Minimized;
private void BtnMaximize_Click(object sender, RoutedEventArgs e)
=> WindowState = WindowState == WindowState.Maximized
? WindowState.Normal
: WindowState.Maximized;
private void BtnClose_Click(object sender, RoutedEventArgs e)
=> Close();
#endregion
#region 导航 + 页面切换动画
private void Nav_Checked(object sender, RoutedEventArgs e)
{
if (sender is not RadioButton rb || _homePage == null) return;
UserControl? newPage = rb.Name switch
{
"NavHome" => _homePage,
"NavSteps" => _stepsPage,
"NavLogs" => _logsPage,
"NavSettings" => _settingsPage,
_ => _homePage
};
if (newPage == null || PageContent.Content == newPage) return;
AnimatePageSwitch(newPage);
}
private void AnimatePageSwitch(UserControl newPage)
{
// 淡出旧页面
var fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(120))
{ EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn } };
fadeOut.Completed += (_, _) =>
{
PageContent.Content = newPage;
// 淡入新页面
var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(250))
{ EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut } };
PageContent.BeginAnimation(OpacityProperty, fadeIn);
};
PageContent.BeginAnimation(OpacityProperty, fadeOut);
}
private void StartLogoBreathAnimation()
{
var breathAnim = new DoubleAnimation(1, 0.6, TimeSpan.FromSeconds(1.5))
{
AutoReverse = true,
EasingFunction = new SineEase { EasingMode = EasingMode.EaseInOut }
};
LogoEmoji.BeginAnimation(OpacityProperty, breathAnim);
}
#endregion
#region 系统托盘
private void InitTrayIcon()
{
var hwnd = new WindowInteropHelper(this).Handle;
_trayIcon = new TrayIcon(hwnd, "排序小助手 - 猪猪工作室");
var iconPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "app.ico");
_trayIcon.LoadIconFromFile(iconPath);
_trayIcon.OnDoubleClick += RestoreFromTray;
_trayIcon.OnShowClick += RestoreFromTray;
_trayIcon.OnExitClick += () => { _forceClose = true; Close(); };
_hwndSource = HwndSource.FromHwnd(hwnd);
_hwndSource?.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (_trayIcon != null)
{
if (_trayIcon.HandleMessage((uint)msg, wParam, lParam))
{
handled = true;
return IntPtr.Zero;
}
if (msg == WM_COMMAND && _trayIcon.HandleCommand((uint)wParam.ToInt32()))
{
handled = true;
return IntPtr.Zero;
}
}
return IntPtr.Zero;
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
if (!_forceClose)
{
e.Cancel = true;
MinimizeToTray();
return;
}
base.OnClosing(e);
}
private void MinimizeToTray()
{
Hide();
_trayIcon?.Show();
_trayIcon?.ShowBalloon("排序小助手", "已最小化到系统托盘,双击恢复");
}
private void RestoreFromTray()
{
Show();
WindowState = WindowState.Normal;
Activate();
_trayIcon?.Hide();
}
#endregion
#region 悬浮状态窗口
private void InitFloatingStatus()
{
_floatingStatus = new FloatingStatusWindow();
// 监听运行状态变化
_viewModel.PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(MainViewModel.IsRunning))
{
if (_viewModel.IsRunning)
{
_floatingStatus.UpdateStatus("⏳", "正在执行...", "");
_floatingStatus.ShowWithAnimation();
}
else
{
_floatingStatus.HideWithAnimation();
}
}
else if (e.PropertyName == nameof(MainViewModel.StatusText) && _viewModel.IsRunning)
{
_floatingStatus.UpdateStatus(
_viewModel.StatusIcon,
_viewModel.StatusText,
$"步骤 {_viewModel.CurrentStepIndex}/{_viewModel.TotalCount}"
);
}
};
}
#endregion
#region 定时
private void StartScheduleTimer()
{
_scheduleTimer = new DispatcherTimer { Interval = TimeSpan.FromMinutes(1) };
_scheduleTimer.Tick += (_, _) => CheckSchedule();
_scheduleTimer.Start();
}
private void CheckSchedule()
{
if (!_settings.Schedule.Enabled) return;
try
{
var now = DateTime.Now;
string todayStr = now.ToString("yyyy-MM-dd");
int todayDayOfWeek = (int)now.DayOfWeek;
if (todayDayOfWeek == 0) todayDayOfWeek = 7;
if (!_settings.Schedule.Days.Contains(todayDayOfWeek)) return;
if (_settings.Schedule.LastRunDate == todayStr) return;
var timeParts = _settings.Schedule.Time.Split(':');
if (timeParts.Length != 2) return;
int targetHour = int.Parse(timeParts[0]);
int targetMinute = int.Parse(timeParts[1]);
if (now.Hour == targetHour && now.Minute == targetMinute)
{
var stepKeys = AutomationEngine.Steps.Select(s => s.Key).ToArray();
if (!ImageRecognition.AllTemplatesExist(stepKeys))
{
_viewModel.AddLog("⏰ 定时任务触发但模板未全部配置,跳过");
_settings.Schedule.LastRunDate = todayStr;
_settings.Save();
return;
}
_settings.Schedule.LastRunDate = todayStr;
_settings.Save();
_viewModel.AddLog("⏰ 定时任务触发");
_ = _viewModel.RunCommand.ExecuteAsync(null);
}
}
catch (Exception ex)
{
_viewModel.AddLog($"⏰ 定时检查异常:{ex.Message}");
}
}
#endregion
#region 日志清理
private static void CleanOldLogs()
{
try
{
var logDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
if (!Directory.Exists(logDir)) return;
var cutoff = DateTime.Now.AddDays(-7);
foreach (var file in Directory.GetFiles(logDir, "*.log"))
{
if (File.GetLastWriteTime(file) < cutoff)
{
try { File.Delete(file); }
catch { /* 文件被占用,忽略 */ }
}
}
}
catch { /* 日志清理失败不应影响主流程 */ }
}
#endregion
}