ShiftWatcherService.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. using Prism.Events;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using TeamAAS_VP.Events;
  9. namespace TeamAAS_VP.Services
  10. {
  11. public class ShiftWatcherService
  12. {
  13. private readonly IEventAggregator _eventAggregator;
  14. private Timer _timer;
  15. private string _lastShift;
  16. public ShiftWatcherService(IEventAggregator eventAggregator)
  17. {
  18. this._eventAggregator = eventAggregator;
  19. }
  20. public void Start()
  21. {
  22. // 中文注释:每秒检测一次
  23. _timer = new Timer(_ => CheckShift(), null, 0, 1000);
  24. }
  25. private void CheckShift()
  26. {
  27. string currentShift =
  28. (DateTime.Now.Hour >= 8 && DateTime.Now.Hour < 20) ? "Day" : "Night";
  29. if (_lastShift == null)
  30. {
  31. _lastShift = currentShift;
  32. return;
  33. }
  34. if (_lastShift != currentShift)
  35. {
  36. _lastShift = currentShift;
  37. // 中文注释:只负责“通知”,不要弹窗
  38. _eventAggregator.GetEvent<WorkShiftChangedNotification>().Publish(new ShiftChangedPayload
  39. {
  40. NewShiftName = currentShift,
  41. SwitchTime = DateTime.Now
  42. });
  43. }
  44. }
  45. }
  46. }
  47. /*
  48. ```csharp
  49. // ===========================
  50. // 文件1:ShiftChangedEvent.cs
  51. // ===========================
  52. using Prism.Events;
  53. using System;
  54. namespace YourApp.Shift
  55. {
  56. // 中文注释:班次切换事件(发布/订阅用)
  57. public class ShiftChangedEvent : PubSubEvent<ShiftChangedPayload> { }
  58. // 中文注释:班次切换携带的数据
  59. public class ShiftChangedPayload
  60. {
  61. public string NewShiftName { get; set; } // 中文注释:新班次名称
  62. public DateTime SwitchTime { get; set; } // 中文注释:切换时间
  63. }
  64. }
  65. ```
  66. ```csharp
  67. // ===========================
  68. // 文件2:IShiftWatcher.cs / ShiftWatcher.cs
  69. // ===========================
  70. using Prism.Events;
  71. using System;
  72. using System.Threading;
  73. namespace YourApp.Shift
  74. {
  75. public interface IShiftWatcher
  76. {
  77. void Start();
  78. void Stop();
  79. }
  80. public class ShiftWatcher : IShiftWatcher
  81. {
  82. private readonly IEventAggregator _ea;
  83. private Timer _timer;
  84. private string _lastShiftKey;
  85. public ShiftWatcher(IEventAggregator ea)
  86. {
  87. _ea = ea;
  88. }
  89. public void Start()
  90. {
  91. // 中文注释:每秒检查一次(你可以改成 5s/10s)
  92. _timer = new Timer(_ => CheckShift(), null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
  93. }
  94. public void Stop()
  95. {
  96. _timer?.Dispose();
  97. _timer = null;
  98. }
  99. private void CheckShift()
  100. {
  101. // 中文注释:示例班次规则:白班 08:00-20:00,夜班 20:00-08:00
  102. var now = DateTime.Now;
  103. string currentShift = (now.Hour >= 8 && now.Hour < 20) ? "Day" : "Night";
  104. if (_lastShiftKey == null)
  105. {
  106. _lastShiftKey = currentShift;
  107. return;
  108. }
  109. if (_lastShiftKey != currentShift)
  110. {
  111. _lastShiftKey = currentShift;
  112. // 中文注释:只发布事件,不在定时器线程里弹窗
  113. _ea.GetEvent<ShiftChangedEvent>().Publish(new ShiftChangedPayload
  114. {
  115. NewShiftName = currentShift,
  116. SwitchTime = now
  117. });
  118. }
  119. }
  120. }
  121. }
  122. ```
  123. ```csharp
  124. // ===========================
  125. // 文件3:IMainProcess.cs / MainProcess.cs
  126. // ===========================
  127. using System.Threading;
  128. using System.Threading.Tasks;
  129. namespace YourApp.Process
  130. {
  131. public interface IMainProcess
  132. {
  133. Task RunAsync(CancellationToken token);
  134. }
  135. public class MainProcess : IMainProcess
  136. {
  137. public async Task RunAsync(CancellationToken token)
  138. {
  139. // 中文注释:示例主流程循环(把你的采图/检测/运动控制逻辑放这里)
  140. while (!token.IsCancellationRequested)
  141. {
  142. // TODO: 在这里写你的主流程逻辑(注意尊重 token)
  143. await Task.Delay(100, token); // 中文注释:模拟工作
  144. }
  145. }
  146. }
  147. }
  148. ```
  149. ```csharp
  150. // ===========================
  151. // 文件4:MainWindowViewModel.cs(整合:切班→停流程→隐藏主窗口→ShowDialog登录→恢复)
  152. // ===========================
  153. using Prism.Events;
  154. using Prism.Mvvm;
  155. using System;
  156. using System.Text;
  157. using System.Threading;
  158. using System.Threading.Tasks;
  159. using System.Windows;
  160. using YourApp.Process;
  161. using YourApp.Shift;
  162. namespace YourApp.ViewModels
  163. {
  164. public class MainWindowViewModel : BindableBase
  165. {
  166. private readonly IEventAggregator _ea;
  167. private readonly IMainProcess _mainProcess;
  168. private CancellationTokenSource _mainFlowCts;
  169. private bool _loginShowing;
  170. public MainWindowViewModel(IEventAggregator ea, IMainProcess mainProcess)
  171. {
  172. _ea = ea;
  173. _mainProcess = mainProcess;
  174. // 中文注释:订阅班次切换事件,强制在UI线程执行(这样可以安全Hide/Show主窗口和弹窗)
  175. _ea.GetEvent<ShiftChangedEvent>()
  176. .Subscribe(OnShiftChanged, ThreadOption.UIThread);
  177. }
  178. // 中文注释:你可以在程序启动后调用一次,启动主流程
  179. public void StartMainFlow()
  180. {
  181. _mainFlowCts?.Cancel();
  182. _mainFlowCts = new CancellationTokenSource();
  183. // 中文注释:后台线程运行主流程,不阻塞UI
  184. Task.Run(async () =>
  185. {
  186. try
  187. {
  188. await _mainProcess.RunAsync(_mainFlowCts.Token);
  189. }
  190. catch (OperationCanceledException)
  191. {
  192. // 中文注释:正常取消
  193. }
  194. catch (Exception ex)
  195. {
  196. // 中文注释:实际项目里这里写日志
  197. Application.Current.Dispatcher.Invoke(() =>
  198. {
  199. MessageBox.Show(ex.Message, "主流程异常");
  200. });
  201. }
  202. });
  203. }
  204. // 中文注释:班次切换时触发:停流程→隐藏主窗口→弹登录→登录成功恢复
  205. private void OnShiftChanged(ShiftChangedPayload payload)
  206. {
  207. if (_loginShowing)
  208. return;
  209. _loginShowing = true;
  210. // 1️⃣ 中断主流程(取消后台任务,不动UI线程)
  211. _mainFlowCts?.Cancel();
  212. // 2️⃣ 隐藏主窗口
  213. var mainWindow = Application.Current.MainWindow;
  214. mainWindow?.Hide();
  215. // 3️⃣ 弹出重新登录窗口(你当前是 Window.ShowDialog)
  216. var loginWindow = new LoginWindow
  217. {
  218. Owner = mainWindow // 中文注释:设置Owner,防止窗口跑到后面
  219. };
  220. // 中文注释:可选,把提示原因传给登录窗口(你需要在LoginWindow里定义这个属性)
  221. loginWindow.LoginReason = $"班次切换到 {payload.NewShiftName},请重新登录";
  222. bool? result = loginWindow.ShowDialog();
  223. // 4️⃣ 根据登录结果处理
  224. if (result == true)
  225. {
  226. // 中文注释:登录成功 → 显示主窗口 → 重新启动主流程
  227. mainWindow?.Show();
  228. StartMainFlow();
  229. }
  230. else
  231. {
  232. // 中文注释:登录失败/取消:按需求决定(示例:仍显示主窗口但不启动流程)
  233. mainWindow?.Show();
  234. // 如果你想强制退出可以用:
  235. // Application.Current.Shutdown();
  236. }
  237. _loginShowing = false;
  238. }
  239. }
  240. }
  241. ```
  242. ```csharp
  243. // ===========================
  244. // 文件5:App.xaml.cs(Prism启动:注册服务 + 启动初始化 + 启动班次监控)
  245. // ===========================
  246. using Prism.Ioc;
  247. using Prism.Unity; // 如果你用 DryIoc/Autofac,这里换对应的PrismApplication基类命名空间
  248. using System.Text;
  249. using System.Windows;
  250. using YourApp.Process;
  251. using YourApp.Shift;
  252. namespace YourApp
  253. {
  254. public partial class App : PrismApplication
  255. {
  256. protected override Window CreateShell()
  257. {
  258. // 中文注释:支持GBK等编码(如果你有CSV/老设备编码需求)
  259. Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
  260. // 中文注释:创建主窗口(MainWindowView)
  261. return Container.Resolve<MainWindowView>();
  262. }
  263. protected override void RegisterTypes(IContainerRegistry containerRegistry)
  264. {
  265. // 中文注释:注册主流程服务
  266. containerRegistry.RegisterSingleton<IMainProcess, MainProcess>();
  267. // 中文注释:注册班次监控(定时器)
  268. containerRegistry.RegisterSingleton<IShiftWatcher, ShiftWatcher>();
  269. // 你原来这些也可以在这里注册或在别处注册
  270. // containerRegistry.RegisterSingleton<IConfigService, ConfigService>();
  271. // containerRegistry.RegisterSingleton<IDatabaseInitializer, DatabaseInitializer>();
  272. }
  273. protected override void OnInitialized()
  274. {
  275. base.OnInitialized();
  276. // 中文注释:启动时初始化(你原来Resolve的配置、数据库初始化可以放这里)
  277. var configService = Container.Resolve<IConfigService>();
  278. var dbInit = Container.Resolve<IDatabaseInitializer>();
  279. dbInit.Initialize();
  280. // 中文注释:启动班次监控
  281. var watcher = Container.Resolve<IShiftWatcher>();
  282. watcher.Start();
  283. // 中文注释:启动主流程(通过主窗口VM调用)
  284. // 注意:主窗口已经显示后再启动更合理
  285. if (Current.MainWindow?.DataContext is YourApp.ViewModels.MainWindowViewModel vm)
  286. {
  287. vm.StartMainFlow();
  288. }
  289. }
  290. }
  291. }
  292. ```
  293. ```csharp
  294. // ===========================
  295. // 文件6:LoginWindow.xaml.cs(示例:让 MainWindowViewModel 能设置 LoginReason)
  296. // 说明:你已有LoginWindow就按需合并,不需要照搬UI
  297. // ===========================
  298. using System.Windows;
  299. namespace YourApp
  300. {
  301. public partial class LoginWindow : Window
  302. {
  303. // 中文注释:给外部传入提示原因(可选)
  304. public string LoginReason
  305. {
  306. get { return (string)GetValue(LoginReasonProperty); }
  307. set { SetValue(LoginReasonProperty, value); }
  308. }
  309. public static readonly DependencyProperty LoginReasonProperty =
  310. DependencyProperty.Register(nameof(LoginReason), typeof(string), typeof(LoginWindow), new PropertyMetadata(""));
  311. public LoginWindow()
  312. {
  313. InitializeComponent();
  314. }
  315. // 中文注释:登录成功按钮
  316. private void BtnOk_Click(object sender, RoutedEventArgs e)
  317. {
  318. // TODO:这里做你的账号密码校验
  319. // 校验成功:
  320. this.DialogResult = true;
  321. this.Close();
  322. }
  323. // 中文注释:取消/关闭按钮
  324. private void BtnCancel_Click(object sender, RoutedEventArgs e)
  325. {
  326. this.DialogResult = false;
  327. this.Close();
  328. }
  329. }
  330. }
  331. ```
  332. */
  333. /*
  334. ///// <summary>
  335. ///// 判断在班次时间内是否是登录过点控
  336. ///// </summary>
  337. ///// <param name="DayShhift"></param>
  338. ///// <param name="NightShift"></param>
  339. ///// <returns></returns>
  340. //public bool JudgeIsSpotCheck(string DayShhift, string NightShift)
  341. //{
  342. // DateTime dateTimeDayShift = DateTime.Now;
  343. // DateTime dateTimeNightShift = DateTime.Now;
  344. // try
  345. // {
  346. // dateTimeDayShift = DateTime.Parse(DayShhift);
  347. // dateTimeNightShift = DateTime.Parse(NightShift);
  348. // }
  349. // catch (Exception)
  350. // {
  351. // AddLog(2, "Check Time格式输入错误,请参照 7:50 该格式进行填写");
  352. // MessageBox.Show("Check Time格式输入错误,请参照 7:50 该格式进行填写");
  353. // return false;
  354. // }
  355. // DateTime LoginTime = DateTime.Parse(IniConfigHelper.ReadIniData("CheckTime", "LoginTime", ""));
  356. // //在原有的基础上增加一天
  357. // DateTime endOfDateTimeDayShift = dateTimeDayShift.AddDays(1);
  358. // DateTime NowDataTime = DateTime.Now;
  359. // if (NowDataTime >= dateTimeDayShift && NowDataTime <= dateTimeNightShift)
  360. // {
  361. // if (!GlobalVariable.CheckTime.DayShiftSpotCheck && LoginTime >= dateTimeDayShift)
  362. // {
  363. // frmSpotCheck = new FrmSpotCheck(IniConfigHelper.ReadIniData("CheckTime", "LoginTime", ""), dateTimeDayShift, dateTimeNightShift);
  364. // if (frmSpotCheck.ShowDialog() == DialogResult.OK)
  365. // {
  366. // return true;
  367. // }
  368. // else
  369. // {
  370. // return false;
  371. // }
  372. // }
  373. // }
  374. // if (NowDataTime >= dateTimeNightShift && NowDataTime <= endOfDateTimeDayShift)
  375. // {
  376. // if (!GlobalVariable.CheckTime.NightShiftSpotCheck && LoginTime >= dateTimeNightShift)
  377. // {
  378. // frmSpotCheck = new FrmSpotCheck(IniConfigHelper.ReadIniData("CheckTime", "LoginTime", ""), dateTimeDayShift, dateTimeNightShift);
  379. // if (frmSpotCheck.ShowDialog() == DialogResult.OK)
  380. // {
  381. // return true;
  382. // }
  383. // else
  384. // {
  385. // return false;
  386. // }
  387. // }
  388. // }
  389. // return true;
  390. //}
  391. //[CheckTime]
  392. //DayShift=7:50
  393. //NightShift=19:50
  394. //NtcTempSelectStore=NTC.xlsx
  395. //LoginTime = 2025年8月14日 13:28
  396. //DayShiftSpotCheck=1
  397. //NightShiftSpotCheck=0
  398. #endregion
  399. */