HomeViewModel.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. using MahApps.Metro.Controls;
  2. using MaterialDesignThemes.Wpf;
  3. using NPOI.SS.Formula.Functions;
  4. using OxyPlot;
  5. using OxyPlot.Axes;
  6. using OxyPlot.Legends;
  7. using OxyPlot.Series;
  8. using OxyPlot.Wpf;
  9. using Prism.Commands;
  10. using Prism.Events;
  11. using Prism.Ioc;
  12. using Prism.Mvvm;
  13. using Prism.Regions;
  14. using System;
  15. using System.Collections.Generic;
  16. using System.Collections.ObjectModel;
  17. using System.IO;
  18. using System.Linq;
  19. using System.Threading.Tasks;
  20. using System.Windows;
  21. using System.Windows.Media;
  22. using TeamAAS_VP;
  23. using TeamAAS_VP.Controls;
  24. using TeamAAS_VP.Core;
  25. using TeamAAS_VP.Data;
  26. using TeamAAS_VP.Enums;
  27. using TeamAAS_VP.Events;
  28. using TeamAAS_VP.Interfaces;
  29. using TeamAAS_VP.Models;
  30. using TeamAAS_VP.Resources.Languages;
  31. using TeamAAS_VP.Services;
  32. using TeamAAS_VP.ViewModels.DebugMod;
  33. using TeamAAS_VP.Views.DebugMod;
  34. using static TeamAAS_VP.Models.SystemConfiguration;
  35. namespace TeamAAS_VP.ViewModels
  36. {
  37. public class HomeViewModel : BindableBase, IConfirmNavigationRequest
  38. {
  39. IEventAggregator _eventAggregator;
  40. IContainerProvider _container;
  41. IProductService _productService;
  42. IConfigService _configService;
  43. IRemoteCommandService _remoteCommandService;
  44. ISystemDatabaseService _systemDatabaseService;
  45. #region 属性
  46. private ObservableCollection<TaskMessage> messageQueue = new ObservableCollection<TaskMessage>();
  47. /// <summary>
  48. /// 运行信息队列
  49. /// </summary>
  50. public ObservableCollection<TaskMessage> MessageQueue
  51. {
  52. get { return messageQueue; }
  53. set { SetProperty(ref messageQueue, value); }
  54. }
  55. private ObservableCollection<ProductModel> _ProductList;
  56. public ObservableCollection<ProductModel> ProductList
  57. {
  58. get { return _ProductList; }
  59. set { SetProperty(ref _ProductList, value); }
  60. }
  61. private Management _management;
  62. public Management management
  63. {
  64. get { return _management; }
  65. set { SetProperty(ref _management, value); }
  66. }
  67. private double _CTTotalSeconds;
  68. public double CTTotalSeconds
  69. {
  70. get { return _CTTotalSeconds; }
  71. set { SetProperty(ref _CTTotalSeconds, value); }
  72. }
  73. private ProductModel _SelectProduct;
  74. public ProductModel SelectProduct
  75. {
  76. get { return _SelectProduct; }
  77. set { SetProperty(ref _SelectProduct, value); }
  78. }
  79. private bool CanLoad
  80. {
  81. get
  82. {
  83. if (SelectProduct != null && IsLoadProduct)
  84. {
  85. return true;
  86. }
  87. else
  88. {
  89. return false;
  90. }
  91. }
  92. }
  93. private bool _IsLoadProduct = true;
  94. public bool IsLoadProduct
  95. {
  96. get { return _IsLoadProduct; }
  97. set { SetProperty(ref _IsLoadProduct, value); }
  98. }
  99. private bool _isCanExecute = true;
  100. public bool isCanExecute
  101. {
  102. get { return _isCanExecute; }
  103. set { SetProperty(ref _isCanExecute, value); }
  104. }
  105. private ProductModel _CurrentProduct;
  106. public ProductModel CurrentProduct
  107. {
  108. get { return _CurrentProduct; }
  109. set { SetProperty(ref _CurrentProduct, value); }
  110. }
  111. private ObservableCollection<PlotModel> _PressurePlotModels = new ObservableCollection<PlotModel>();
  112. /// <summary>
  113. /// 每个元素是一个 PlotModel,对应一个压力传感器
  114. /// </summary>
  115. public ObservableCollection<PlotModel> PressurePlotModels
  116. {
  117. get { return _PressurePlotModels; }
  118. set { SetProperty(ref _PressurePlotModels, value); }
  119. }
  120. private PlotModel _LockCurvePlotModel;
  121. /// <summary>
  122. /// 锁付曲线
  123. /// </summary>
  124. public PlotModel LockCurvePlotModel
  125. {
  126. get { return _LockCurvePlotModel; }
  127. set { SetProperty(ref _LockCurvePlotModel, value); }
  128. }
  129. private ObservableCollection<LockResult> _LockResults = new ObservableCollection<LockResult>();
  130. /// <summary>
  131. /// 锁付结果列表
  132. /// </summary>
  133. public ObservableCollection<LockResult> LockResults
  134. {
  135. get { return _LockResults; }
  136. set { SetProperty(ref _LockResults, value); }
  137. }
  138. private ObservableCollection<AssemblyRecord> _AssemblyResults=new ObservableCollection<AssemblyRecord>();
  139. /// <summary>
  140. /// 组装结果列表
  141. /// </summary>
  142. public ObservableCollection<AssemblyRecord> AssemblyResults
  143. {
  144. get { return _AssemblyResults; }
  145. set { SetProperty(ref _AssemblyResults, value); }
  146. }
  147. // 可选:记录传感器 Id -> 索引 的映射,便于外部(如 Management)按 Id 更新
  148. private readonly Dictionary<string, int> _sensorIdToIndex = new Dictionary<string, int>();
  149. private bool _IsAllowEdit = false;
  150. public bool IsAllowEdit
  151. {
  152. get { return _IsAllowEdit; }
  153. set { SetProperty(ref _IsAllowEdit, value); }
  154. }
  155. #endregion
  156. #region 命令
  157. public DelegateCommand LoadedCommand { get; set; }
  158. private DelegateCommand<ProductModel> _LoadProductCommand;
  159. public DelegateCommand<ProductModel> LoadProductCommand =>
  160. _LoadProductCommand ?? (_LoadProductCommand = new DelegateCommand<ProductModel>(ExecuteLoadProductCommand).ObservesCanExecute(() => CanLoad).ObservesProperty(() => SelectProduct).ObservesProperty(() => IsLoadProduct));
  161. private DelegateCommand _ResetCounterCommand;
  162. public DelegateCommand ResetCounterCommand =>
  163. _ResetCounterCommand ?? (_ResetCounterCommand = new DelegateCommand(ExecuteResetCounterCommand).ObservesCanExecute(() => DoCondition).ObservesProperty(() => isCanExecute));
  164. private bool DoCondition
  165. {
  166. get { return isCanExecute; }
  167. }
  168. private DelegateCommand<string> _CopyCommand;
  169. public DelegateCommand<string> CopyCommand =>
  170. _CopyCommand ?? (_CopyCommand = new DelegateCommand<string>(ExecuteCopyCommand));
  171. #endregion
  172. #region 事件
  173. #endregion
  174. public HomeViewModel(IEventAggregator ea, IContainerProvider container, IProductService productService, IConfigService configService, IRemoteCommandService remoteCommandService
  175. , ISystemDatabaseService systemDatabaseService)
  176. {
  177. _eventAggregator = ea;
  178. _container = container;
  179. _productService = productService;
  180. LoadedCommand = new DelegateCommand(OnLoad);
  181. _eventAggregator.GetEvent<TaskMessageNotification>().Subscribe(AddMessageInvoke);
  182. //订阅权限登录事件
  183. _eventAggregator.GetEvent<UserLoginNotification>().Subscribe(LoginChange);
  184. management = _container.Resolve<Management>();
  185. _configService = configService;
  186. _productService.OnProductChanged += _productService_OnProductChanged;
  187. _remoteCommandService = remoteCommandService;
  188. _remoteCommandService.CycleTimingTicked += _remoteCommandService_CycleTimingTicked;
  189. _systemDatabaseService = systemDatabaseService;
  190. _eventAggregator.GetEvent<LockFinishNotification>().Subscribe(OnLockFinish);
  191. _eventAggregator.GetEvent<PressureFinishNotification>().Subscribe(OnPressureFinish);
  192. _eventAggregator.GetEvent<AssemblyFinishNotification>().Subscribe(OnAssemblyFinish);
  193. //获取所有的压力传感器配置,初始化曲线图
  194. var systemConfig = _configService.GetSystemConfiguration();
  195. if (systemConfig.PressureSensorConfig != null)
  196. {
  197. InitPressurePlots(systemConfig.PressureSensorConfig.ToArray());
  198. }
  199. }
  200. private void _remoteCommandService_CycleTimingTicked(object sender, TimeSpan e)
  201. {
  202. try
  203. {
  204. if (App.Current != null)
  205. {
  206. App.Current.Invoke(() =>
  207. {
  208. CTTotalSeconds = e.TotalSeconds;
  209. });
  210. }
  211. }
  212. catch (Exception)
  213. {
  214. }
  215. }
  216. #region 方法
  217. bool isFirst = true;
  218. private void OnLoad()
  219. {
  220. if (!isFirst)
  221. {
  222. return;
  223. }
  224. InitLockCurvePlotModel();
  225. isFirst = false;
  226. }
  227. async void ExecuteLoadProductCommand(ProductModel product)
  228. {
  229. if (product == null) return;
  230. var view = new ShowMessage(Lang.是否加载产品, $"{Lang.加载产品}:{product.Name}?");
  231. //show the dialog
  232. var result = await DialogHost.Show(view, "RootDialog", null, null, null);
  233. if (result != null && result is bool)
  234. {
  235. if (((bool)result))
  236. {
  237. IsLoadProduct = false;
  238. var waiting = new WaitingControl();
  239. //show the dialog
  240. var task = DialogHost.Show(waiting, "RootDialog", null, null, null);
  241. await _productService.LoadProductAsync(product.ID);
  242. _productService.SetCurrentProduct(product.ID);
  243. await management.WriteParameterToPlcExAsync();
  244. if (DialogHost.IsDialogOpen("RootDialog"))
  245. {
  246. DialogHost.Close("RootDialog");
  247. }
  248. await task;
  249. //DatabaseHelper.AddLoadProductRecord(product.ID, product.Name, false);
  250. IsLoadProduct = true;
  251. }
  252. }
  253. }
  254. void ExecuteCopyCommand(string textToCopy)
  255. {
  256. Clipboard.SetText(textToCopy);
  257. }
  258. /// <summary>
  259. /// 添加消息至消息队列
  260. /// </summary>
  261. /// <param name="msg"></param>
  262. /// <param name="color"></param>
  263. public void AddMessage(string msg, Color color)
  264. {
  265. App.Current.Dispatcher.BeginInvoke(new Action(() => {
  266. try
  267. {
  268. MessageQueue.Add(new TaskMessage()
  269. {
  270. DT = DateTime.Now,
  271. Message = msg,
  272. FontColol = new SolidColorBrush(color)
  273. });
  274. LogHelper.WriteLogInfo(msg);
  275. }
  276. catch (Exception)
  277. {
  278. }
  279. }));
  280. }
  281. /// <summary>
  282. /// 添加消息至消息队列
  283. /// </summary>
  284. /// <param name="msg"></param>
  285. /// <param name="color"></param>
  286. public void AddMessage(string msg, MessageLevel level)
  287. {
  288. MessageStruct messageStruct = new MessageStruct() { Message = msg, level = level };
  289. App.Current.Dispatcher.BeginInvoke(new Action(() => {
  290. AddMessage(messageStruct);
  291. }));
  292. }
  293. /// <summary>
  294. /// 添加消息至消息队列
  295. /// </summary>
  296. /// <param name="messageStruct"></param>
  297. public void AddMessage(MessageStruct messageStruct)
  298. {
  299. try
  300. {
  301. if (MessageQueue.Count >= 200)
  302. {
  303. MessageQueue.RemoveAt(0);// 从队列头部移除最早的消息
  304. }
  305. Color color = Colors.Black;
  306. switch (messageStruct.level)
  307. {
  308. case MessageLevel.Info:
  309. color = Colors.Black;
  310. break;
  311. case MessageLevel.Debug:
  312. color = Colors.Green;
  313. break;
  314. case MessageLevel.Alarm:
  315. color = Colors.Orange;
  316. break;
  317. case MessageLevel.Error:
  318. color = Colors.Red;
  319. break;
  320. }
  321. MessageQueue.Add(new TaskMessage()
  322. {
  323. DT = DateTime.Now,
  324. Message = messageStruct.Message,
  325. FontColol = new SolidColorBrush(color)
  326. });
  327. LogHelper.WriteLogInfo(messageStruct.Message);
  328. }
  329. catch (Exception)
  330. {
  331. }
  332. }
  333. public void AddMessageInvoke(MessageStruct messageStruct)
  334. {
  335. App.Current.Dispatcher.BeginInvoke(new Action(() => {
  336. AddMessage(messageStruct);
  337. }));
  338. }
  339. private void LoginChange(Models.User user)
  340. {
  341. }
  342. /// <summary>
  343. /// 重置计数
  344. /// </summary>
  345. async void ExecuteResetCounterCommand()
  346. {
  347. var currentProduct = _productService.GetCurrentProduct();
  348. if (currentProduct == null) return;
  349. var view = new ShowMessage(Lang.计数清零, Lang.是否重置当前产品的计数);
  350. //show the dialog
  351. var result = await DialogHost.Show(view, "RootDialog", null, null, null);
  352. if (result != null && result is bool)
  353. {
  354. if (((bool)result))
  355. {
  356. isCanExecute = false;
  357. await _systemDatabaseService.ResetProductionAsync(currentProduct.Name);
  358. await _systemDatabaseService.ResetThrowNumberAsync();
  359. isCanExecute = true;
  360. }
  361. }
  362. }
  363. //初始化曲线图
  364. private void InitLockCurvePlotModel()
  365. {
  366. var model = new PlotModel { Title = Lang.锁付曲线, TitleFontSize = 16 };
  367. // 设置图例
  368. var legend = new Legend
  369. {
  370. LegendPosition = LegendPosition.TopRight,
  371. LegendPlacement = LegendPlacement.Outside,
  372. LegendOrientation = LegendOrientation.Vertical,
  373. LegendBorderThickness = 0
  374. };
  375. model.Legends.Add(legend);
  376. // 设置X轴
  377. var xAxis = new LinearAxis
  378. {
  379. Key = "X",
  380. Position = AxisPosition.Bottom,
  381. Title = Lang.角度数,
  382. Minimum = 0,
  383. MajorGridlineStyle = LineStyle.Solid,
  384. MinorGridlineStyle = LineStyle.Dot
  385. };
  386. model.Axes.Add(xAxis);
  387. // 设置Y轴
  388. var yAxis = new LinearAxis
  389. {
  390. Key = "Y",
  391. Position = AxisPosition.Left,
  392. Title = Lang.力矩kgf,
  393. MajorGridlineStyle = LineStyle.Solid,
  394. MinorGridlineStyle = LineStyle.Dot
  395. };
  396. model.Axes.Add(yAxis);
  397. // 添加示例数据系列
  398. var series = new LineSeries
  399. {
  400. Title = Lang.锁付力矩曲线,
  401. StrokeThickness = 2,
  402. MarkerSize = 3,
  403. MarkerType = MarkerType.Circle,
  404. CanTrackerInterpolatePoints = false,
  405. };
  406. model.Series.Add(series);
  407. LockCurvePlotModel = model;
  408. }
  409. /// <summary>
  410. /// 螺丝锁付完成时调用
  411. /// </summary>
  412. /// <param name="result"></param>
  413. /// <exception cref="NotImplementedException"></exception>
  414. private void OnLockFinish(LockResult result)
  415. {
  416. //如果当前螺丝编号为1,则清除之前的结果
  417. if (result.Number == 1)
  418. {
  419. LockResults.Clear();
  420. }
  421. LockResults.Add(result);
  422. //更新曲线图
  423. App.Current.Dispatcher.BeginInvoke(new Action(() => {
  424. var series = LockCurvePlotModel.Series[0] as LineSeries;
  425. //var yaxis = LockCurvePlotModel.Axes.FirstOrDefault(p=>p.Key=="Y");
  426. //yaxis.Maximum = result.TargetTorque;
  427. series.Points.Clear();
  428. foreach (var waveData in result.WaveDatas)
  429. {
  430. series.Points.Add(new OxyPlot.DataPoint(waveData.LockAngle, waveData.Torque));
  431. }
  432. LockCurvePlotModel.InvalidatePlot(true);
  433. }));
  434. }
  435. /// <summary>
  436. /// 锁付完成时调用
  437. /// </summary>
  438. /// <param name="record"></param>
  439. /// <exception cref="NotImplementedException"></exception>
  440. private void OnAssemblyFinish(AssemblyRecord record)
  441. {
  442. AssemblyResults.Add(record);
  443. //如果记录数超过1000,则删除最早的记录
  444. if (AssemblyResults.Count > 50)
  445. {
  446. AssemblyResults.RemoveAt(0);
  447. }
  448. }
  449. /// <summary>
  450. /// 保压完成时调用
  451. /// </summary>
  452. /// <param name="tuple"></param>
  453. private void OnPressureFinish((bool IsSuccess, int Index, (DateTime Timestamp, float Value)[] values, string SavePath) tuple)
  454. {
  455. //更新对应传感器的曲线图
  456. if (_sensorIdToIndex.TryGetValue(tuple.Index.ToString(), out int index))
  457. {
  458. App.Current.Dispatcher.BeginInvoke(new Action(() => {
  459. var model = PressurePlotModels[index];
  460. var series = model.Series[0] as LineSeries;
  461. series.Points.Clear();
  462. //第一个点的时间作为X轴起点
  463. DateTime xAxisStart = tuple.values[0].Timestamp;
  464. foreach (var dataPoint in tuple.values)
  465. {
  466. // 计算相对于起点的秒数作为X轴坐标
  467. double xValue = (dataPoint.Timestamp - xAxisStart).TotalSeconds;
  468. series.Points.Add(new OxyPlot.DataPoint(xValue, dataPoint.Value));
  469. }
  470. // 重置所有轴的范围以适应新数据
  471. model.ResetAllAxes();
  472. model.InvalidatePlot(true);
  473. if (!string.IsNullOrEmpty(tuple.SavePath))
  474. {
  475. if (!Directory.Exists(Path.GetDirectoryName(tuple.SavePath)))
  476. {
  477. Directory.CreateDirectory(Path.GetDirectoryName(tuple.SavePath));
  478. }
  479. PngExporter.Export(model, tuple.SavePath, (int)model.Width, (int)model.Height);
  480. }
  481. }));
  482. }
  483. }
  484. // 初始化由配置决定的曲线数量(在读取 SystemConfiguration 后调用)
  485. public void InitPressurePlots(IEnumerable<PressureSensorSettings> sensors)
  486. {
  487. PressurePlotModels.Clear();
  488. _sensorIdToIndex.Clear();
  489. int idx = 0;
  490. foreach (var s in sensors)
  491. {
  492. var model = CreatePlotModelForSensor(s);
  493. PressurePlotModels.Add(model);
  494. // 假设 PressureSensorConfig 有唯一 Id(string 或 int)
  495. _sensorIdToIndex[s.SensorId.ToString()] = idx++;
  496. }
  497. }
  498. private PlotModel CreatePlotModelForSensor(PressureSensorSettings sensor)
  499. {
  500. var model = new PlotModel { Title = sensor.SensorName ?? $"Sensor {sensor.SensorId}", TitleFontSize = 16 };
  501. // 设置图例
  502. var legend = new Legend
  503. {
  504. LegendPosition = LegendPosition.TopRight,
  505. LegendPlacement = LegendPlacement.Outside,
  506. LegendOrientation = LegendOrientation.Vertical,
  507. LegendBorderThickness = 0
  508. };
  509. model.Legends.Add(legend);
  510. // 设置X轴
  511. var xAxis = new LinearAxis
  512. {
  513. Key = "X",
  514. Position = AxisPosition.Bottom,
  515. Title = "Time (s)",
  516. Minimum = 0,
  517. MajorGridlineStyle = LineStyle.Solid,
  518. MinorGridlineStyle = LineStyle.Dot
  519. };
  520. model.Axes.Add(xAxis);
  521. // 设置Y轴
  522. var yAxis = new LinearAxis
  523. {
  524. Key = "Y",
  525. Position = AxisPosition.Left,
  526. Title = "Pressure",
  527. MajorGridlineStyle = LineStyle.Solid,
  528. MinorGridlineStyle = LineStyle.Dot
  529. };
  530. model.Axes.Add(yAxis);
  531. // 添加示例数据系列
  532. var series = new LineSeries
  533. {
  534. Title = sensor.SensorName ?? $"Sensor {sensor.SensorId}",
  535. StrokeThickness = 2,
  536. MarkerSize = 3,
  537. MarkerType = MarkerType.Circle,
  538. CanTrackerInterpolatePoints = false,
  539. };
  540. model.Series.Add(series);
  541. return model;
  542. }
  543. #endregion
  544. #region 继承
  545. /// <summary>
  546. /// 确认导航请求时调用。此方法允许您在导航之前执行一些操作。
  547. /// </summary>
  548. /// <param name="navigationContext"></param>
  549. /// <param name="continuationCallback"></param>
  550. public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
  551. {
  552. continuationCallback(true);
  553. }
  554. /// <summary>
  555. /// 接收导航请求时调用。传入导航参数,包含有关导航目标的信息。
  556. /// </summary>
  557. /// <param name="navigationContext"></param>
  558. /// <exception cref="NotImplementedException"></exception>
  559. public async void OnNavigatedTo(NavigationContext navigationContext)
  560. {
  561. if (_systemDatabaseService.GetCurrentUser().userPart == Enums.UserPart.Operator || _systemDatabaseService.GetCurrentUser().userPart == Enums.UserPart.Engineer)
  562. {
  563. IsAllowEdit = false;
  564. }
  565. else
  566. {
  567. IsAllowEdit = true;
  568. }
  569. try
  570. {
  571. ProductList = new ObservableCollection<ProductModel>(_productService.GetAllProducts());
  572. }
  573. catch (Exception ex)
  574. {
  575. LogHelper.WriteLogError("主界面界面进入时出错!", ex);
  576. }
  577. }
  578. /// <summary>
  579. /// 是否允许导航到此视图模型。
  580. /// </summary>
  581. /// <param name="navigationContext"></param>
  582. /// <returns></returns>
  583. public bool IsNavigationTarget(NavigationContext navigationContext)
  584. {
  585. return true;
  586. }
  587. /// <summary>
  588. /// 导航离开此视图模型时调用。您可以在此处执行清理操作或保存状态。
  589. /// </summary>
  590. /// <param name="navigationContext"></param>
  591. public void OnNavigatedFrom(NavigationContext navigationContext)
  592. {
  593. }
  594. #endregion
  595. private void _productService_OnProductChanged(ProductModel obj)
  596. {
  597. App.Current.Dispatcher.BeginInvoke(new Action(() => {
  598. CurrentProduct = obj;
  599. }));
  600. }
  601. }
  602. }