using Prism.Commands; using Prism.Mvvm; using Prism.Regions; using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Windows; using System.Windows.Media; using System.Windows.Threading; using TeamAAS.Core; using TeamAAS.Models; using TeamAAS.Localization; using TeamAAS.Theme; namespace TeamAAS.ViewModels { public class MainWindowViewModel : TeamAAS.BindableBase { private readonly IRegionManager _regionManager; private DispatcherTimer _timer; public ObservableCollection DeviceStatusItems { get; } = new ObservableCollection(); /// 左侧导航(首页/产品/设置) public ObservableCollection LeftNavItems { get; } = new ObservableCollection(); /// 右侧导航(标定/统计/用户) public ObservableCollection RightNavItems { get; } = new ObservableCollection(); private string _memoryUsage = ""; public string MemoryUsage { get => _memoryUsage; set => SetProperty(ref _memoryUsage, value); } private string _currentTime = ""; public string CurrentTime { get => _currentTime; set => SetProperty(ref _currentTime, value); } private string _currentView = "HomeView"; public string CurrentView { get => _currentView; set => SetProperty(ref _currentView, value); } #region 主题与主色 public bool IsDarkMode { get => string.Equals(App.SystemConfig?.Theme, "Dark", StringComparison.OrdinalIgnoreCase); set { if (IsDarkMode == value) return; App.SystemConfig.Theme = value ? "Dark" : "Light"; ThemeManager.ApplyTheme(value, App.SystemConfig.AccentColor); App.SaveSystemConfig(); RaisePropertyChanged(nameof(IsDarkMode)); RaisePropertyChanged(nameof(ThemeIcon)); RaisePropertyChanged(nameof(ThemeDisplayName)); } } public string ThemeIcon => IsDarkMode ? "☀️" : "🌙"; public string ThemeDisplayName => IsDarkMode ? "深色(点击切换)" : "浅色(点击切换)"; public ObservableCollection AccentColors { get; } = new ObservableCollection(); 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); } public DelegateCommand NavigateCommand { get; } public DelegateCommand ToggleThemeCommand { get; } public DelegateCommand SetAccentCommand { get; } public MainWindowViewModel(IRegionManager regionManager) { _regionManager = regionManager; NavigateCommand = new DelegateCommand(Navigate); ToggleThemeCommand = new DelegateCommand(() => IsDarkMode = !IsDarkMode); SetAccentCommand = new DelegateCommand(hex => { if (string.IsNullOrWhiteSpace(hex)) return; App.SystemConfig.AccentColor = hex; // 主色系画刷与文字对比色由主题覆盖层派生重建(毫秒级生效) ThemeManager.UpdateAccent(hex); App.SaveSystemConfig(); RaisePropertyChanged(nameof(AccentBrush)); }); InitAccentColors(); ThemeManager.ThemeChanged += OnAppThemeChanged; InitNavItems(); } 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)) }); } } private 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; } } private void OnAppThemeChanged() { RaisePropertyChanged(nameof(IsDarkMode)); RaisePropertyChanged(nameof(ThemeIcon)); RaisePropertyChanged(nameof(ThemeDisplayName)); RaisePropertyChanged(nameof(AccentBrush)); } #endregion public void OnLoaded() { Navigate("HomeView"); StartTimer(); } #region 导航 private void InitNavItems() { // 图标沿用 Material Design Path(与旧 XAML 资源一致) AddNav(LeftNavItems, "首页", "HomeView", "M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z"); AddNav(LeftNavItems, "产品", "ProductView", "M12,2L2,7V17L12,22L22,17V7L12,2Z M12,2V12 M2,7L12,12L22,7"); AddNav(LeftNavItems, "设置", "SettingView", "M19.14,12.94C19.18,12.64 19.2,12.33 19.2,12C19.2,11.68 19.18,11.36 19.13,11.06L21.16,9.48C21.34,9.34 21.39,9.07 21.27,8.87L19.35,5.55C19.23,5.33 18.96,5.25 18.73,5.33L16.38,6.28C15.88,5.89 15.35,5.56 14.76,5.32L14.4,2.81C14.36,2.57 14.16,2.4 13.92,2.4H10.08C9.83,2.4 9.63,2.57 9.59,2.81L9.24,5.32C8.65,5.56 8.11,5.89 7.62,6.28L5.27,5.33C5.04,5.25 4.77,5.33 4.65,5.55L2.73,8.87C2.61,9.07 2.66,9.34 2.84,9.48L4.87,11.06C4.82,11.36 4.8,11.68 4.8,12C4.8,12.33 4.82,12.64 4.87,12.94L2.84,14.52C2.66,14.66 2.61,14.93 2.73,15.13L4.65,18.45C4.77,18.67 5.04,18.75 5.27,18.67L7.62,17.72C8.11,18.11 8.65,18.44 9.24,18.68L9.6,21.19C9.64,21.43 9.84,21.6 10.08,21.6H13.92C14.17,21.6 14.37,21.43 14.41,21.19L14.76,18.68C15.35,18.44 15.89,18.11 16.38,17.72L18.73,18.67C18.96,18.75 19.23,18.67 19.35,18.45L21.27,15.13C21.39,14.93 21.34,14.66 21.16,14.52L19.14,12.94ZM12,15.6C10.02,15.6 8.4,13.98 8.4,12C8.4,10.02 10.02,8.4 12,8.4C13.98,8.4 15.6,10.02 15.6,12C15.6,13.98 13.98,15.6 12,15.6Z"); AddNav(RightNavItems, "标定", "CalibrationView", "M14.06,9L15,9.94L5.92,19H5V18.08L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z"); AddNav(RightNavItems, "统计", "StatisticsView", "M3,3H21V21H3V3Z M7,17V11 M12,17V7 M17,17V13"); AddNav(RightNavItems, "用户", "UserView", "M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"); } private void AddNav(ObservableCollection collection, string title, string viewName, string iconData) { // Path.Data 在运行时不会自动把字符串转成 Geometry, // 必须在代码里 Geometry.Parse 成对象(并 Freeze 提升渲染性能/线程安全) var geometry = Geometry.Parse(iconData); geometry.Freeze(); var item = new NavItem { Title = title, ViewName = viewName, IconData = geometry }; item.SelectRequested += nav => { if (CurrentView != nav.ViewName) Navigate(nav.ViewName); }; collection.Add(item); } private void Navigate(string viewName) { if (string.IsNullOrEmpty(viewName)) return; _regionManager.RequestNavigate("ContentRegion", viewName, r => { if (r.Result != true) { // 解包内部异常链,找到根因(Prism 只抛 ContainerResolutionException 外壳) var root = r.Error; var depth = 0; while (root?.InnerException != null && depth++ < 10) root = root.InnerException; AppLogger.Error($"导航到 {viewName} 失败,内容区保持原页面 | 根因: {root?.Message}", r.Error, "Navigation"); } }); CurrentView = viewName; // 每次导航都同步选中状态(含首次启动: // 初始 CurrentView 与目标相同,不能依赖"是否变化"判断,否则首页不会被选中) SyncNavSelection(); } private void SyncNavSelection() { foreach (var nav in LeftNavItems) nav.IsSelected = nav.ViewName == CurrentView; foreach (var nav in RightNavItems) nav.IsSelected = nav.ViewName == CurrentView; } #endregion #region 定时刷新 private void StartTimer() { // Loaded 事件在 WPF 中可能触发多次,避免重复创建定时器导致刷新回调叠加。 if (_timer != null) return; _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; _timer.Tick += OnTimerTick; _timer.Start(); } private void OnTimerTick(object sender, EventArgs e) { RefreshDeviceStatuses(); UpdateSystemInfo(); } private void RefreshDeviceStatuses() { var statuses = CoreManager.GetAllDeviceStatuses(); if (DeviceStatusItems.Count != statuses.Count) { DeviceStatusItems.Clear(); foreach (var s in statuses) DeviceStatusItems.Add(new DeviceStatusItem { Name = s.Name, Category = L10n.T(s.Category), IsConnected = s.IsConnected }); return; } // 设备数量未变时也要同步名称/类别(用户在设备管理页改名后即时反映到状态栏) for (int i = 0; i < statuses.Count; i++) { DeviceStatusItems[i].Name = statuses[i].Name; DeviceStatusItems[i].Category = L10n.T(statuses[i].Category); DeviceStatusItems[i].IsConnected = statuses[i].IsConnected; } } private void UpdateSystemInfo() { var proc = Process.GetCurrentProcess(); MemoryUsage = L10n.F("内存: {0} MB", proc.PrivateMemorySize64 / 1024 / 1024); CurrentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } #endregion } /// /// 顶部导航项(数据驱动生成导航按钮,新增页面只需在 MainWindowViewModel.InitNavItems 加一行) /// public class NavItem : TeamAAS.BindableBase { public string Title { get; set; } public string ViewName { get; set; } /// 图标 Geometry 对象(Path.Data 直接绑定,避免运行时字符串→Geometry 转换失败) public Geometry IconData { get; set; } private bool _isSelected; public bool IsSelected { get => _isSelected; set { if (SetProperty(ref _isSelected, value) && value) SelectRequested?.Invoke(this); } } public event Action SelectRequested; } public class DeviceStatusItem : TeamAAS.BindableBase { private string _name; private string _category; private bool _isConnected; public string Name { get => _name; set => SetProperty(ref _name, value); } public string Category { get => _category; set => SetProperty(ref _category, value); } public bool IsConnected { get => _isConnected; set => SetProperty(ref _isConnected, value); } } }