ProductViewModel.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. using Prism.Commands;
  2. using Prism.Mvvm;
  3. using System;
  4. using System.Collections.ObjectModel;
  5. using System.Linq;
  6. using System.Windows;
  7. using System.Windows.Input;
  8. using System.Windows.Threading;
  9. using TeamAAS.FlowEditor;
  10. using TeamAAS.FlowEditor.ProductModels;
  11. using TeamAAS.FlowEngine.Execution;
  12. using TeamAAS.Communication;
  13. using TeamAAS.Communication.Models;
  14. using TeamAAS.FlowEditor.Execution;
  15. namespace TeamAAS.ViewModels
  16. {
  17. public class ProductViewModel : TeamAAS.BindableBase
  18. {
  19. public ObservableCollection<string> Products { get; } = new ObservableCollection<string>();
  20. private string _selectedProduct = "";
  21. public string SelectedProduct
  22. {
  23. get => _selectedProduct;
  24. set
  25. {
  26. if (_selectedProduct == value) return;
  27. // 切换产品前:如果有未保存的修改,提示用户(是=保存后切换 / 否=放弃修改切换 / 取消=不切换)
  28. if (!_isInitializing && !string.IsNullOrEmpty(_selectedProduct) && !ConfirmDiscardOrSave())
  29. {
  30. // 取消切换:把 ComboBox 显示还原为当前产品
  31. RaisePropertyChanged(nameof(SelectedProduct));
  32. return;
  33. }
  34. SetProperty(ref _selectedProduct, value);
  35. RaisePropertyChanged(nameof(IsProductSelected));
  36. RaisePropertyChanged(nameof(IsRunProduct));
  37. RaisePropertyChanged(nameof(IsEditorLocked));
  38. RaisePropertyChanged(nameof(ShowRunBadge));
  39. RaisePropertyChanged(nameof(FrameConfig));
  40. // 切换产品时监控状态由 SwitchProductAsync 末尾 ApplyMonitorAvailability 统一处理
  41. if (!_isInitializing)
  42. _ = LoadProductAsync(value);
  43. }
  44. }
  45. /// <summary>选中产品的画面配置(画面数/排列/名称,跟产品走;在产品“属性”窗口中编辑)</summary>
  46. public ProductMeta FrameConfig
  47. => ProductManager.Instance.GetHomeFrameConfig(SelectedProduct);
  48. /// <summary>打开产品属性编辑(通用属性编辑器:画面列表由集合编辑器添加/删除)</summary>
  49. public void ShowProductProperties(string productName)
  50. {
  51. if (string.IsNullOrEmpty(productName)) return;
  52. // 右键的产品即编辑目标(并同步选中,保证语义一致)
  53. SelectedProduct = productName;
  54. var meta = ProductManager.Instance.GetHomeFrameConfig(productName);
  55. // 直接用通用属性编辑器(PropertyGridDialog):ProductMeta 挂了 Category/DisplayName/
  56. // CollectionEditor 特性,画面列表在编辑器里可添加/删除/排序
  57. if (DialogHelper.EditProperties(meta, $"产品属性 - {productName}"))
  58. {
  59. // 产品管理页修改产品属性只保存到磁盘,不触发主页刷新
  60. // (主页刷新由主页面的"加载"按钮触发)
  61. ProductManager.Instance.SaveProductMeta(meta);
  62. RaisePropertyChanged(nameof(FrameConfig));
  63. }
  64. }
  65. private DelegateCommand<string> _productPropertiesCommand;
  66. /// <summary>右键菜单“属性”命令(参数=产品名;未传参数时用当前选中产品)</summary>
  67. public DelegateCommand<string> ProductPropertiesCommand
  68. => _productPropertiesCommand ?? (_productPropertiesCommand = new DelegateCommand<string>(
  69. name => ShowProductProperties(string.IsNullOrEmpty(name) ? SelectedProduct : name)));
  70. /// <summary>
  71. /// 打开某产品的“全局变量”弹窗,编辑该产品的 Product 作用域变量。
  72. /// 直接从该产品快照文件读取(不切换当前产品,规避异步切换竞态);
  73. /// 确定时写回文件,若正是当前归属产品则同步刷新 live 列表让公式立即生效。
  74. /// </summary>
  75. public void ShowProductGlobalVars(string productName)
  76. {
  77. if (string.IsNullOrEmpty(productName)) return;
  78. var gvm = GlobalVariableManager.Instance;
  79. string path = ProductManager.Instance.GetGlobalVarsPath(productName);
  80. var vm = new GlobalVarEditorViewModel(VarScope.Product, gvm.ReadSnapshotList(path));
  81. var view = new Views.GlobalVarEditorView { DataContext = vm };
  82. if (DialogHelper.ShowPluginView(view, $"全局变量 - {productName}"))
  83. {
  84. gvm.WriteSnapshotList(path, vm.Items);
  85. if (string.Equals(ProductManager.Instance.GlobalVarsOwnerProduct, productName, StringComparison.Ordinal))
  86. {
  87. ProductManager.Instance.LoadProductGlobalVars(productName);
  88. ResultRegistry.RefreshGlobals();
  89. }
  90. }
  91. }
  92. private DelegateCommand<string> _productGlobalVarsCommand;
  93. /// <summary>右键菜单“全局变量”命令(参数=产品名;未传时用当前选中产品)。</summary>
  94. public DelegateCommand<string> ProductGlobalVarsCommand
  95. => _productGlobalVarsCommand ?? (_productGlobalVarsCommand = new DelegateCommand<string>(
  96. name => ShowProductGlobalVars(string.IsNullOrEmpty(name) ? SelectedProduct : name)));
  97. /// <summary>当前编辑的产品是否就是"运行产品"</summary>
  98. public bool IsRunProduct =>
  99. !string.IsNullOrEmpty(SelectedProduct)
  100. && string.Equals(ProductManager.Instance.RunProductName, SelectedProduct, StringComparison.Ordinal);
  101. /// <summary>运行产品正在执行时编辑器进入只读监控模式(当前产品==运行产品 且 正在运行)</summary>
  102. public bool IsEditorLocked => IsRunProduct && FlowRunner.Instance.IsRunning;
  103. /// <summary>运行状态提示文本(当前产品==运行产品时显示)</summary>
  104. /// <summary>顶部运行状态角标(运行产品但未在运行时显示提示)</summary>
  105. public bool ShowRunBadge => IsRunProduct && !FlowRunner.Instance.IsRunning;
  106. /// <summary>运行状态变化后刷新锁定状态(由 ProductView 订阅 FlowRunner.RunStateChanged 调用)</summary>
  107. public void RefreshRunState()
  108. {
  109. // 运行开始/停止 → 重新评估监控选项可用性 + 自动进入/退出监控
  110. ShellVm.ApplyMonitorAvailability();
  111. RaisePropertyChanged(nameof(IsRunProduct));
  112. RaisePropertyChanged(nameof(IsEditorLocked));
  113. RaisePropertyChanged(nameof(ShowRunBadge));
  114. }
  115. public bool IsProductSelected => !string.IsNullOrEmpty(SelectedProduct);
  116. private bool _isSidebarCollapsed;
  117. public bool IsSidebarCollapsed
  118. {
  119. get => _isSidebarCollapsed;
  120. set => SetProperty(ref _isSidebarCollapsed, value);
  121. }
  122. private bool _isProductLoading;
  123. public bool IsProductLoading
  124. {
  125. get => _isProductLoading;
  126. set => SetProperty(ref _isProductLoading, value);
  127. }
  128. public FlowEditorShellViewModel ShellVm { get; }
  129. public DelegateCommand NewProductCommand { get; }
  130. public DelegateCommand DeleteProductCommand { get; }
  131. public DelegateCommand RenameProductCommand { get; }
  132. public DelegateCommand SaveProductCommand { get; }
  133. public DelegateCommand ToggleSidebarCommand { get; }
  134. private bool _isInitializing;
  135. public ProductViewModel()
  136. {
  137. ShellVm = new FlowEditorShellViewModel();
  138. NewProductCommand = new DelegateCommand(NewProduct);
  139. DeleteProductCommand = new DelegateCommand(DeleteProduct);
  140. RenameProductCommand = new DelegateCommand(RenameProduct);
  141. SaveProductCommand = new DelegateCommand(SaveProduct);
  142. ToggleSidebarCommand = new DelegateCommand(() => IsSidebarCollapsed = !IsSidebarCollapsed);
  143. _isInitializing = true;
  144. RefreshProducts();
  145. var lastProduct = ProductManager.Instance.CurrentProductName;
  146. if (!string.IsNullOrEmpty(lastProduct) && Products.Contains(lastProduct))
  147. SelectedProduct = lastProduct;
  148. else if (Products.Count > 0)
  149. SelectedProduct = Products.First();
  150. _isInitializing = false;
  151. if (!string.IsNullOrEmpty(SelectedProduct))
  152. _ = LoadProductAsync(SelectedProduct);
  153. }
  154. public void RefreshProducts()
  155. {
  156. Products.Clear();
  157. var list = ProductManager.Instance.GetProductList();
  158. foreach (var name in list)
  159. Products.Add(name);
  160. }
  161. private async System.Threading.Tasks.Task LoadProductAsync(string productName)
  162. {
  163. if (IsProductLoading) return;
  164. IsProductLoading = true;
  165. try
  166. {
  167. // 让加载遮罩先渲染一帧,再切换产品(文件 IO 在后台,集合变更在 UI 线程)
  168. await System.Threading.Tasks.Task.Yield();
  169. await ProductManager.Instance.SwitchProductAsync(productName, ShellVm);
  170. }
  171. finally
  172. {
  173. IsProductLoading = false;
  174. }
  175. }
  176. /// <summary>切换产品前询问:有未保存修改才提示。true=可以继续切换。</summary>
  177. private bool ConfirmDiscardOrSave()
  178. {
  179. if (!ShellVm.HasUnsavedChanges) return true;
  180. bool? result = DialogHelper.ConfirmYesNoCancel(
  181. $"产品 \"{SelectedProduct}\" 的流程有未保存的修改,是否保存?\n\n" +
  182. "是 = 保存后切换 否 = 不保存直接切换 取消 = 不切换",
  183. "保存更改");
  184. if (result == null) return false; // 取消 → 不切换
  185. if (result == true) SaveProduct(); // 保存后切换
  186. return true;
  187. }
  188. private void NewProduct()
  189. {
  190. string name = DialogHelper.Input("产品名称", "新建产品");
  191. if (string.IsNullOrWhiteSpace(name)) return;
  192. name = name.Trim();
  193. if (ProductManager.Instance.CreateProduct(name))
  194. {
  195. RefreshProducts();
  196. SelectedProduct = name;
  197. }
  198. else
  199. {
  200. DialogHelper.ShowError("创建产品失败,名称可能已存在或无效。");
  201. }
  202. }
  203. private void DeleteProduct()
  204. {
  205. if (string.IsNullOrEmpty(SelectedProduct)) return;
  206. if (!DialogHelper.ConfirmYesNo(
  207. $"确认删除产品 \"{SelectedProduct}\" 吗?\n该产品的所有流程配置将被删除!",
  208. "确认删除"))
  209. {
  210. return;
  211. }
  212. if (ProductManager.Instance.DeleteProduct(SelectedProduct))
  213. {
  214. // 产品文件夹已整体删除,未保存的修改随之作废,清掉脏标记避免切换时误弹保存提示
  215. ShellVm.MarkAllClean();
  216. RefreshProducts();
  217. if (Products.Count > 0)
  218. SelectedProduct = Products.First();
  219. else
  220. SelectedProduct = "";
  221. }
  222. }
  223. private void RenameProduct()
  224. {
  225. if (string.IsNullOrEmpty(SelectedProduct)) return;
  226. string newName = DialogHelper.Input("新名称", "重命名产品", SelectedProduct);
  227. if (string.IsNullOrWhiteSpace(newName)) return;
  228. newName = newName.Trim();
  229. if (ProductManager.Instance.RenameProduct(SelectedProduct, newName))
  230. {
  231. RefreshProducts();
  232. SelectedProduct = newName;
  233. }
  234. else
  235. {
  236. DialogHelper.ShowError("重命名失败,名称可能已存在或无效。");
  237. }
  238. }
  239. /// <summary>点击"保存"才真正落盘;保存成功后清除脏标记并给出反馈</summary>
  240. private void SaveProduct()
  241. {
  242. if (string.IsNullOrEmpty(SelectedProduct)) return;
  243. if (!ShellVm.HasUnsavedChanges)
  244. {
  245. DialogHelper.Info($"产品 \"{SelectedProduct}\" 没有需要保存的更改");
  246. return;
  247. }
  248. bool ok = ProductManager.Instance.SaveAllFlows(SelectedProduct, ShellVm.FlowTabs);
  249. if (ok)
  250. {
  251. ShellVm.MarkAllClean();
  252. DialogHelper.Success($"产品 \"{SelectedProduct}\" 已保存");
  253. }
  254. else
  255. {
  256. DialogHelper.ShowError($"产品 \"{SelectedProduct}\" 保存失败,请查看日志");
  257. }
  258. }
  259. }
  260. }