using Prism.Commands; using Prism.Mvvm; using System; using System.Collections.ObjectModel; using System.Linq; using System.Windows; using System.Windows.Input; using System.Windows.Threading; using TeamAAS.FlowEditor; using TeamAAS.FlowEditor.ProductModels; using TeamAAS.FlowEngine.Execution; using TeamAAS.Communication; using TeamAAS.Communication.Models; using TeamAAS.FlowEditor.Execution; namespace TeamAAS.ViewModels { public class ProductViewModel : TeamAAS.BindableBase { public ObservableCollection Products { get; } = new ObservableCollection(); private string _selectedProduct = ""; public string SelectedProduct { get => _selectedProduct; set { if (_selectedProduct == value) return; // 切换产品前:如果有未保存的修改,提示用户(是=保存后切换 / 否=放弃修改切换 / 取消=不切换) if (!_isInitializing && !string.IsNullOrEmpty(_selectedProduct) && !ConfirmDiscardOrSave()) { // 取消切换:把 ComboBox 显示还原为当前产品 RaisePropertyChanged(nameof(SelectedProduct)); return; } SetProperty(ref _selectedProduct, value); RaisePropertyChanged(nameof(IsProductSelected)); RaisePropertyChanged(nameof(IsRunProduct)); RaisePropertyChanged(nameof(IsEditorLocked)); RaisePropertyChanged(nameof(ShowRunBadge)); RaisePropertyChanged(nameof(FrameConfig)); // 切换产品时监控状态由 SwitchProductAsync 末尾 ApplyMonitorAvailability 统一处理 if (!_isInitializing) _ = LoadProductAsync(value); } } /// 选中产品的画面配置(画面数/排列/名称,跟产品走;在产品“属性”窗口中编辑) public ProductMeta FrameConfig => ProductManager.Instance.GetHomeFrameConfig(SelectedProduct); /// 打开产品属性编辑(通用属性编辑器:画面列表由集合编辑器添加/删除) public void ShowProductProperties(string productName) { if (string.IsNullOrEmpty(productName)) return; // 右键的产品即编辑目标(并同步选中,保证语义一致) SelectedProduct = productName; var meta = ProductManager.Instance.GetHomeFrameConfig(productName); // 直接用通用属性编辑器(PropertyGridDialog):ProductMeta 挂了 Category/DisplayName/ // CollectionEditor 特性,画面列表在编辑器里可添加/删除/排序 if (DialogHelper.EditProperties(meta, $"产品属性 - {productName}")) { // 产品管理页修改产品属性只保存到磁盘,不触发主页刷新 // (主页刷新由主页面的"加载"按钮触发) ProductManager.Instance.SaveProductMeta(meta); RaisePropertyChanged(nameof(FrameConfig)); } } private DelegateCommand _productPropertiesCommand; /// 右键菜单“属性”命令(参数=产品名;未传参数时用当前选中产品) public DelegateCommand ProductPropertiesCommand => _productPropertiesCommand ?? (_productPropertiesCommand = new DelegateCommand( name => ShowProductProperties(string.IsNullOrEmpty(name) ? SelectedProduct : name))); /// /// 打开某产品的“全局变量”弹窗,编辑该产品的 Product 作用域变量。 /// 直接从该产品快照文件读取(不切换当前产品,规避异步切换竞态); /// 确定时写回文件,若正是当前归属产品则同步刷新 live 列表让公式立即生效。 /// public void ShowProductGlobalVars(string productName) { if (string.IsNullOrEmpty(productName)) return; var gvm = GlobalVariableManager.Instance; string path = ProductManager.Instance.GetGlobalVarsPath(productName); var vm = new GlobalVarEditorViewModel(VarScope.Product, gvm.ReadSnapshotList(path)); var view = new Views.GlobalVarEditorView { DataContext = vm }; if (DialogHelper.ShowPluginView(view, $"全局变量 - {productName}")) { gvm.WriteSnapshotList(path, vm.Items); if (string.Equals(ProductManager.Instance.GlobalVarsOwnerProduct, productName, StringComparison.Ordinal)) { ProductManager.Instance.LoadProductGlobalVars(productName); ResultRegistry.RefreshGlobals(); } } } private DelegateCommand _productGlobalVarsCommand; /// 右键菜单“全局变量”命令(参数=产品名;未传时用当前选中产品)。 public DelegateCommand ProductGlobalVarsCommand => _productGlobalVarsCommand ?? (_productGlobalVarsCommand = new DelegateCommand( name => ShowProductGlobalVars(string.IsNullOrEmpty(name) ? SelectedProduct : name))); /// 当前编辑的产品是否就是"运行产品" public bool IsRunProduct => !string.IsNullOrEmpty(SelectedProduct) && string.Equals(ProductManager.Instance.RunProductName, SelectedProduct, StringComparison.Ordinal); /// 运行产品正在执行时编辑器进入只读监控模式(当前产品==运行产品 且 正在运行) public bool IsEditorLocked => IsRunProduct && FlowRunner.Instance.IsRunning; /// 运行状态提示文本(当前产品==运行产品时显示) /// 顶部运行状态角标(运行产品但未在运行时显示提示) public bool ShowRunBadge => IsRunProduct && !FlowRunner.Instance.IsRunning; /// 运行状态变化后刷新锁定状态(由 ProductView 订阅 FlowRunner.RunStateChanged 调用) public void RefreshRunState() { // 运行开始/停止 → 重新评估监控选项可用性 + 自动进入/退出监控 ShellVm.ApplyMonitorAvailability(); RaisePropertyChanged(nameof(IsRunProduct)); RaisePropertyChanged(nameof(IsEditorLocked)); RaisePropertyChanged(nameof(ShowRunBadge)); } public bool IsProductSelected => !string.IsNullOrEmpty(SelectedProduct); private bool _isSidebarCollapsed; public bool IsSidebarCollapsed { get => _isSidebarCollapsed; set => SetProperty(ref _isSidebarCollapsed, value); } private bool _isProductLoading; public bool IsProductLoading { get => _isProductLoading; set => SetProperty(ref _isProductLoading, value); } public FlowEditorShellViewModel ShellVm { get; } public DelegateCommand NewProductCommand { get; } public DelegateCommand DeleteProductCommand { get; } public DelegateCommand RenameProductCommand { get; } public DelegateCommand SaveProductCommand { get; } public DelegateCommand ToggleSidebarCommand { get; } private bool _isInitializing; public ProductViewModel() { ShellVm = new FlowEditorShellViewModel(); NewProductCommand = new DelegateCommand(NewProduct); DeleteProductCommand = new DelegateCommand(DeleteProduct); RenameProductCommand = new DelegateCommand(RenameProduct); SaveProductCommand = new DelegateCommand(SaveProduct); ToggleSidebarCommand = new DelegateCommand(() => IsSidebarCollapsed = !IsSidebarCollapsed); _isInitializing = true; RefreshProducts(); var lastProduct = ProductManager.Instance.CurrentProductName; if (!string.IsNullOrEmpty(lastProduct) && Products.Contains(lastProduct)) SelectedProduct = lastProduct; else if (Products.Count > 0) SelectedProduct = Products.First(); _isInitializing = false; if (!string.IsNullOrEmpty(SelectedProduct)) _ = LoadProductAsync(SelectedProduct); } public void RefreshProducts() { Products.Clear(); var list = ProductManager.Instance.GetProductList(); foreach (var name in list) Products.Add(name); } private async System.Threading.Tasks.Task LoadProductAsync(string productName) { if (IsProductLoading) return; IsProductLoading = true; try { // 让加载遮罩先渲染一帧,再切换产品(文件 IO 在后台,集合变更在 UI 线程) await System.Threading.Tasks.Task.Yield(); await ProductManager.Instance.SwitchProductAsync(productName, ShellVm); } finally { IsProductLoading = false; } } /// 切换产品前询问:有未保存修改才提示。true=可以继续切换。 private bool ConfirmDiscardOrSave() { if (!ShellVm.HasUnsavedChanges) return true; bool? result = DialogHelper.ConfirmYesNoCancel( $"产品 \"{SelectedProduct}\" 的流程有未保存的修改,是否保存?\n\n" + "是 = 保存后切换 否 = 不保存直接切换 取消 = 不切换", "保存更改"); if (result == null) return false; // 取消 → 不切换 if (result == true) SaveProduct(); // 保存后切换 return true; } private void NewProduct() { string name = DialogHelper.Input("产品名称", "新建产品"); if (string.IsNullOrWhiteSpace(name)) return; name = name.Trim(); if (ProductManager.Instance.CreateProduct(name)) { RefreshProducts(); SelectedProduct = name; } else { DialogHelper.ShowError("创建产品失败,名称可能已存在或无效。"); } } private void DeleteProduct() { if (string.IsNullOrEmpty(SelectedProduct)) return; if (!DialogHelper.ConfirmYesNo( $"确认删除产品 \"{SelectedProduct}\" 吗?\n该产品的所有流程配置将被删除!", "确认删除")) { return; } if (ProductManager.Instance.DeleteProduct(SelectedProduct)) { // 产品文件夹已整体删除,未保存的修改随之作废,清掉脏标记避免切换时误弹保存提示 ShellVm.MarkAllClean(); RefreshProducts(); if (Products.Count > 0) SelectedProduct = Products.First(); else SelectedProduct = ""; } } private void RenameProduct() { if (string.IsNullOrEmpty(SelectedProduct)) return; string newName = DialogHelper.Input("新名称", "重命名产品", SelectedProduct); if (string.IsNullOrWhiteSpace(newName)) return; newName = newName.Trim(); if (ProductManager.Instance.RenameProduct(SelectedProduct, newName)) { RefreshProducts(); SelectedProduct = newName; } else { DialogHelper.ShowError("重命名失败,名称可能已存在或无效。"); } } /// 点击"保存"才真正落盘;保存成功后清除脏标记并给出反馈 private void SaveProduct() { if (string.IsNullOrEmpty(SelectedProduct)) return; if (!ShellVm.HasUnsavedChanges) { DialogHelper.Info($"产品 \"{SelectedProduct}\" 没有需要保存的更改"); return; } bool ok = ProductManager.Instance.SaveAllFlows(SelectedProduct, ShellVm.FlowTabs); if (ok) { ShellVm.MarkAllClean(); DialogHelper.Success($"产品 \"{SelectedProduct}\" 已保存"); } else { DialogHelper.ShowError($"产品 \"{SelectedProduct}\" 保存失败,请查看日志"); } } } }