using Prism.Commands; using System; using System.Collections.ObjectModel; using System.Linq; using System.Windows.Media; using TeamAAS.FlowEditor; using TeamAAS.FlowEditor.Execution; using TeamAAS.FlowEngine.Execution; using TeamAAS.Theme; using TeamAAS.Vision; namespace TeamAAS.ViewModels { public class HomeViewModel : TeamAAS.BindableBase { public ObservableCollection Products { get; } = new ObservableCollection(); private string _selectedProduct = ""; public string SelectedProduct { get => _selectedProduct; set => SetProperty(ref _selectedProduct, value); } public DelegateCommand LoadProductCommand { get; } public DelegateCommand ResetStatisticsCommand { get; } public DelegateCommand RunCommand { get; } public DelegateCommand StopCommand { get; } #region 运行状态(总运行/停止按钮) public bool IsRunning => FlowRunner.Instance.IsRunning; public bool CanRun => !IsRunning && !string.IsNullOrEmpty(FlowRunner.Instance.RunProductName); public bool CanStop => IsRunning; /// FlowRunner 运行状态变化后刷新按钮状态 public void RefreshRunState() { RaisePropertyChanged(nameof(IsRunning)); RaisePropertyChanged(nameof(CanRun)); RaisePropertyChanged(nameof(CanStop)); RaisePropertyChanged(nameof(RunProductName)); } private async void RunAll() { if (string.IsNullOrEmpty(FlowRunner.Instance.RunProductName)) { DialogHelper.ShowWarning("请先加载运行产品"); return; } // 循环运行当前产品所有主流程,直到点击停止 await FlowRunner.Instance.RunLoopAsync(); } private void StopAll() { FlowRunner.Instance.Stop(); } #endregion /// 首页统计数据(真实运行数据,不再是写死的假数据) public RunStatistics Stats => RunStatistics.Instance; /// 当前运行产品名(空=未加载) public string RunProductName => FlowRunner.Instance.RunProductName; #region 主题与主色(单一数据源:App.SystemConfig,切换后写入 SystemConfiguration.json) public bool IsDarkMode => string.Equals(App.SystemConfig?.Theme, "Dark", StringComparison.OrdinalIgnoreCase); public string ThemeIcon => IsDarkMode ? "☀️" : "🌙"; /// 预设主色 public ObservableCollection AccentColors { get; } = new ObservableCollection(); /// 当前主色画刷(ColorPicker 与色板联动) public Brush AccentBrush { get { try { var color = (Color)ColorConverter.ConvertFromString(App.SystemConfig?.AccentColor); // 规范化为主色不透明版本(配置里可能残留带 alpha 的历史值) var brush = new SolidColorBrush(Color.FromRgb(color.R, color.G, color.B)); brush.Freeze(); return brush; } catch { return Brushes.DodgerBlue; } } set => ApplyAccent(value); // ColorPicker TwoWay 回写:应用并持久化 } public DelegateCommand ToggleThemeCommand { get; } public DelegateCommand SetAccentCommand { get; } /// 应用主色(色板按钮 / ColorPicker 回调共用) public void ApplyAccent(Brush brush) { if (!(brush is SolidColorBrush sb)) return; // 规范化:只取 RGB、强制不透明。ColorPicker 样式重算时会回写带任意 alpha 的画刷 // (实测出现过 A=0x0A 的 96% 透明色被存进配置,表现为主色"特别淡、朦了一层"), // 统一丢弃 alpha——色相保真,主色永远纯色。 var color = Color.FromRgb(sb.Color.R, sb.Color.G, sb.Color.B); // 按 RGB 去重:同色不同 alpha 的回写(切主题时样式重算触发)完全静默忽略 var current = TryParseAccentColor(); if (current.HasValue && current.Value.R == color.R && current.Value.G == color.G && current.Value.B == color.B) { return; } App.SystemConfig.AccentColor = color.ToString(); // 主色系画刷与文字对比色由主题覆盖层派生重建(毫秒级生效,不碰合并字典) ThemeManager.UpdateAccent(color); App.SaveSystemConfig(); RaisePropertyChanged(nameof(AccentBrush)); } private static Color? TryParseAccentColor() { try { var hex = App.SystemConfig?.AccentColor; if (string.IsNullOrWhiteSpace(hex)) return null; return (Color)ColorConverter.ConvertFromString(hex); } catch { return null; } } /// 主题/主色在别处(顶部栏、设置页)变化后刷新本地状态 public void RefreshThemeState() { RaisePropertyChanged(nameof(IsDarkMode)); RaisePropertyChanged(nameof(ThemeIcon)); RaisePropertyChanged(nameof(AccentBrush)); } #endregion public HomeViewModel() { LoadProductCommand = new DelegateCommand(LoadProduct); ResetStatisticsCommand = new DelegateCommand(() => RunStatistics.Instance.Reset()); RunCommand = new DelegateCommand(RunAll); StopCommand = new DelegateCommand(StopAll); ToggleThemeCommand = new DelegateCommand(() => { App.SystemConfig.Theme = IsDarkMode ? "Light" : "Dark"; ThemeManager.ApplyTheme(!IsDarkMode, App.SystemConfig.AccentColor); App.SaveSystemConfig(); RaisePropertyChanged(nameof(IsDarkMode)); RaisePropertyChanged(nameof(ThemeIcon)); }); SetAccentCommand = new DelegateCommand(hex => { if (string.IsNullOrWhiteSpace(hex)) return; App.SystemConfig.AccentColor = hex; // 主色系画刷与文字对比色由主题覆盖层派生重建(毫秒级生效) ThemeManager.UpdateAccent(hex); App.SaveSystemConfig(); RaisePropertyChanged(nameof(AccentBrush)); }); InitAccentColors(); RefreshProducts(); // 产品管理页新增/删除/重命名产品后,同步刷新首页产品型号下拉 ProductManager.Instance.ProductListChanged += OnProductListChanged; // 产品画面配置(画面数/排列/名称,跟产品走)变化后重建布局 ProductManager.Instance.HomeFrameConfigChanged += RefreshEngineView; RefreshEngineView(); } #region 主页视觉画面(引擎多画面视图 + 画面框注册) private object _engineMainView; /// 当前视觉引擎的主页多画面视图(无可用引擎时为 null,显示占位提示) public object EngineMainView { get { return _engineMainView; } private set { SetProperty(ref _engineMainView, value); } } /// /// 按当前产品的画面配置刷新主页画面:应用当前视觉引擎的布局, /// 并把各画面框注册到 VisualFrameManager(唯一 ID + 用户可见名称), /// 其他页面/插件通过 ID 即可选择往哪个框显示结果。 /// public void RefreshEngineView() { var manager = VisualEngineManager.Instance; var engine = manager.GetOrLoad(App.SystemConfig?.Vision?.EngineName); if (engine == null) { EngineMainView = null; return; } manager.SetCurrent(engine.EngineName); // 画面配置跟产品走(ProductMeta.HomeFrame*) // 注意:必须读"运行产品"(主页面刚加载的),而不是编辑态产品—— // 主页面下拉直接加载某产品时编辑态(CurrentProductName)并未跟随, // 若读编辑态产品会导致首次加载的画面数/排列用的是别的产品的配置 var pm = ProductManager.Instance; var configProduct = string.IsNullOrEmpty(pm.RunProductName) ? pm.CurrentProductName : pm.RunProductName; var cfg = pm.GetHomeFrameConfig(configProduct); // 画面列表(可空):0 个画面 = 主页面不显示画面框 var count = cfg.HomeFrames?.Count ?? 0; engine.ApplyHomeLayout(count, (int)cfg.HomeFrameArrange); TeamAAS.Vision.VisualFrameManager.Clear(); for (int i = 0; i < count; i++) { var frameId = $"home:{i + 1}"; var frameName = cfg.GetFrameName(i); var showDot = cfg.GetFrameShowDot(i); TeamAAS.Vision.VisualFrameManager.Register(frameId, frameName, "Home"); // 通过引擎抽象接口设置画面框标题与状态圆点显隐 //(不直接引用引擎插件内部类型——引擎可替换) engine.SetFrameCaption(i, frameName); engine.SetFrameDotVisible(i, showDot); } EngineMainView = engine.GetMainView(); } #endregion private void OnProductListChanged() { RefreshProducts(); } private void InitAccentColors() { var presets = new[] { ("默认蓝", "#2D6CDF"), ("成功绿", "#4CAF50"), ("活力橙", "#FF9800"), ("警示红", "#F44336"), ("优雅紫", "#7B1FA2"), ("清新青", "#00BCD4"), ("少女粉", "#E91E63"), ("中性灰", "#607D8B"), }; foreach (var (name, hex) in presets) { AccentColors.Add(new AccentColorOption { Name = name, Hex = hex, Brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(hex)) }); } } public void RefreshProducts() { Products.Clear(); var list = ProductManager.Instance.GetProductList(); foreach (var name in list) Products.Add(name); // 当前选中的产品已被删除 → 清空或切到第一个;否则保留选中状态 if (!Products.Contains(SelectedProduct)) SelectedProduct = Products.Count > 0 ? Products.First() : ""; } private void LoadProduct() { if (string.IsNullOrEmpty(SelectedProduct)) return; // 先停掉正在运行的流程(Stop 内部已完全释放 ResultRegistry.Main 的流程数据),再切换运行产品 FlowRunner.Instance.Stop(); // 清空视觉画面框注册 TeamAAS.Vision.VisualFrameManager.Clear(); // 加载为"运行产品"并复制运行态流程(真正接上运行引擎) if (FlowRunner.Instance.LoadRunProduct(SelectedProduct)) { // 加载成功后刷新主页画面布局(应用新产品的画面配置,会重建视觉引擎布局) RefreshEngineView(); RefreshRunState(); DialogHelper.Success($"运行产品已切换为: {SelectedProduct}"); } else { DialogHelper.ShowError($"加载运行产品失败: {SelectedProduct}"); } } } /// 预设主色选项 public class AccentColorOption { public string Name { get; set; } public string Hex { get; set; } public Brush Brush { get; set; } } }